@evomap/evolver-core 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 (373) hide show
  1. package/dist/algo/antiDistill.d.ts +35 -0
  2. package/dist/algo/antiDistill.js +167 -0
  3. package/dist/algo/bans.d.ts +6 -0
  4. package/dist/algo/bans.js +33 -0
  5. package/dist/algo/candidateAssembly.d.ts +65 -0
  6. package/dist/algo/candidateAssembly.js +196 -0
  7. package/dist/algo/capabilityCandidates.d.ts +38 -0
  8. package/dist/algo/capabilityCandidates.js +131 -0
  9. package/dist/algo/confidence.d.ts +106 -0
  10. package/dist/algo/confidence.js +196 -0
  11. package/dist/algo/cycleEngine.d.ts +143 -0
  12. package/dist/algo/cycleEngine.js +417 -0
  13. package/dist/algo/cycleFailureClassifier.d.ts +52 -0
  14. package/dist/algo/cycleFailureClassifier.js +104 -0
  15. package/dist/algo/epigenetics.d.ts +14 -0
  16. package/dist/algo/epigenetics.js +41 -0
  17. package/dist/algo/evolutionEvent.d.ts +23 -0
  18. package/dist/algo/evolutionEvent.js +24 -0
  19. package/dist/algo/exploration.d.ts +34 -0
  20. package/dist/algo/exploration.js +55 -0
  21. package/dist/algo/geneHealth.d.ts +24 -0
  22. package/dist/algo/geneHealth.js +16 -0
  23. package/dist/algo/geneIntake.d.ts +37 -0
  24. package/dist/algo/geneIntake.js +100 -0
  25. package/dist/algo/genePromotion.d.ts +48 -0
  26. package/dist/algo/genePromotion.js +53 -0
  27. package/dist/algo/geneSelection.d.ts +128 -0
  28. package/dist/algo/geneSelection.js +222 -0
  29. package/dist/algo/index.d.ts +18 -0
  30. package/dist/algo/index.js +18 -0
  31. package/dist/algo/mutation.d.ts +12 -0
  32. package/dist/algo/mutation.js +22 -0
  33. package/dist/algo/orchestrator.d.ts +65 -0
  34. package/dist/algo/orchestrator.js +63 -0
  35. package/dist/algo/solidify.d.ts +43 -0
  36. package/dist/algo/solidify.js +63 -0
  37. package/dist/algo/strategyPresets.d.ts +32 -0
  38. package/dist/algo/strategyPresets.js +61 -0
  39. package/dist/assetstore/index.d.ts +8 -0
  40. package/dist/assetstore/index.js +8 -0
  41. package/dist/assetstore/learningHistory.d.ts +25 -0
  42. package/dist/assetstore/learningHistory.js +38 -0
  43. package/dist/assetstore/localJsonl.d.ts +34 -0
  44. package/dist/assetstore/localJsonl.js +182 -0
  45. package/dist/assetstore/pendingSignals.d.ts +22 -0
  46. package/dist/assetstore/pendingSignals.js +171 -0
  47. package/dist/assetstore/provenance.d.ts +39 -0
  48. package/dist/assetstore/provenance.js +109 -0
  49. package/dist/assetstore/provider.d.ts +48 -0
  50. package/dist/assetstore/provider.js +36 -0
  51. package/dist/assetstore/remoteStub.d.ts +32 -0
  52. package/dist/assetstore/remoteStub.js +36 -0
  53. package/dist/assetstore/reviewFilter.d.ts +20 -0
  54. package/dist/assetstore/reviewFilter.js +44 -0
  55. package/dist/assetstore/reviewLedger.d.ts +55 -0
  56. package/dist/assetstore/reviewLedger.js +121 -0
  57. package/dist/benchmark/antiGeneBenchmark.d.ts +79 -0
  58. package/dist/benchmark/antiGeneBenchmark.js +241 -0
  59. package/dist/benchmark/antiGeneRollout.d.ts +80 -0
  60. package/dist/benchmark/antiGeneRollout.js +256 -0
  61. package/dist/benchmark/benchmark.d.ts +32 -0
  62. package/dist/benchmark/benchmark.js +26 -0
  63. package/dist/benchmark/evolutionThesisSolver.d.ts +25 -0
  64. package/dist/benchmark/evolutionThesisSolver.js +41 -0
  65. package/dist/benchmark/index.d.ts +5 -0
  66. package/dist/benchmark/index.js +5 -0
  67. package/dist/benchmark/thesis.d.ts +85 -0
  68. package/dist/benchmark/thesis.js +150 -0
  69. package/dist/bootstrap/envFingerprint.d.ts +45 -0
  70. package/dist/bootstrap/envFingerprint.js +72 -0
  71. package/dist/bootstrap/index.d.ts +2 -0
  72. package/dist/bootstrap/index.js +2 -0
  73. package/dist/bootstrap/selfEvolve.d.ts +36 -0
  74. package/dist/bootstrap/selfEvolve.js +26 -0
  75. package/dist/cycle/cycleTimeline.d.ts +35 -0
  76. package/dist/cycle/cycleTimeline.js +74 -0
  77. package/dist/cycle/index.d.ts +2 -0
  78. package/dist/cycle/index.js +2 -0
  79. package/dist/cycle/stateMachine.d.ts +6 -0
  80. package/dist/cycle/stateMachine.js +25 -0
  81. package/dist/daemon/cycleContext.d.ts +12 -0
  82. package/dist/daemon/cycleContext.js +13 -0
  83. package/dist/daemon/drain.d.ts +11 -0
  84. package/dist/daemon/drain.js +16 -0
  85. package/dist/daemon/guards.d.ts +87 -0
  86. package/dist/daemon/guards.js +142 -0
  87. package/dist/daemon/idempotency.d.ts +11 -0
  88. package/dist/daemon/idempotency.js +36 -0
  89. package/dist/daemon/idleScheduler.d.ts +55 -0
  90. package/dist/daemon/idleScheduler.js +181 -0
  91. package/dist/daemon/index.d.ts +8 -0
  92. package/dist/daemon/index.js +8 -0
  93. package/dist/daemon/stepRunner.d.ts +12 -0
  94. package/dist/daemon/stepRunner.js +31 -0
  95. package/dist/daemon/unhandledRejectionWindow.d.ts +21 -0
  96. package/dist/daemon/unhandledRejectionWindow.js +52 -0
  97. package/dist/daemon/watchdog.d.ts +11 -0
  98. package/dist/daemon/watchdog.js +14 -0
  99. package/dist/events/eventSchema.d.ts +132 -0
  100. package/dist/events/eventSchema.js +30 -0
  101. package/dist/events/eventStore.d.ts +32 -0
  102. package/dist/events/eventStore.js +137 -0
  103. package/dist/events/ingest.d.ts +34 -0
  104. package/dist/events/ingest.js +61 -0
  105. package/dist/events/paths.d.ts +22 -0
  106. package/dist/events/paths.js +44 -0
  107. package/dist/events/projectors.d.ts +9 -0
  108. package/dist/events/projectors.js +11 -0
  109. package/dist/events/public.d.ts +14 -0
  110. package/dist/events/public.js +8 -0
  111. package/dist/events/replayer.d.ts +26 -0
  112. package/dist/events/replayer.js +67 -0
  113. package/dist/events/reports.d.ts +90 -0
  114. package/dist/events/reports.js +244 -0
  115. package/dist/events/retention.d.ts +70 -0
  116. package/dist/events/retention.js +177 -0
  117. package/dist/events/sink.d.ts +5 -0
  118. package/dist/events/sink.js +1 -0
  119. package/dist/exec/autoExec.d.ts +137 -0
  120. package/dist/exec/autoExec.js +221 -0
  121. package/dist/exec/autonomousCycle.d.ts +48 -0
  122. package/dist/exec/autonomousCycle.js +78 -0
  123. package/dist/exec/claudeBridge.d.ts +112 -0
  124. package/dist/exec/claudeBridge.js +219 -0
  125. package/dist/exec/index.d.ts +9 -0
  126. package/dist/exec/index.js +11 -0
  127. package/dist/exec/openPrRegistry.d.ts +60 -0
  128. package/dist/exec/openPrRegistry.js +130 -0
  129. package/dist/exec/policy/blastRadius.d.ts +31 -0
  130. package/dist/exec/policy/blastRadius.js +67 -0
  131. package/dist/exec/policy/constraints.d.ts +26 -0
  132. package/dist/exec/policy/constraints.js +62 -0
  133. package/dist/exec/policy/destructive.d.ts +45 -0
  134. package/dist/exec/policy/destructive.js +108 -0
  135. package/dist/exec/policy/failureMode.d.ts +33 -0
  136. package/dist/exec/policy/failureMode.js +49 -0
  137. package/dist/exec/policy/index.d.ts +24 -0
  138. package/dist/exec/policy/index.js +30 -0
  139. package/dist/exec/policy/protectedPaths.d.ts +17 -0
  140. package/dist/exec/policy/protectedPaths.js +65 -0
  141. package/dist/exec/policyCheck.d.ts +2 -0
  142. package/dist/exec/policyCheck.js +7 -0
  143. package/dist/exec/prompt.d.ts +34 -0
  144. package/dist/exec/prompt.js +108 -0
  145. package/dist/exec/proofOfWork.d.ts +9 -0
  146. package/dist/exec/proofOfWork.js +11 -0
  147. package/dist/exec/runnerRegistry.d.ts +123 -0
  148. package/dist/exec/runnerRegistry.js +237 -0
  149. package/dist/exec/selfPr.d.ts +83 -0
  150. package/dist/exec/selfPr.js +104 -0
  151. package/dist/exec/selfPrObfuscation.d.ts +21 -0
  152. package/dist/exec/selfPrObfuscation.js +85 -0
  153. package/dist/hooks/hooks.d.ts +117 -0
  154. package/dist/hooks/hooks.js +89 -0
  155. package/dist/hooks/index.d.ts +1 -0
  156. package/dist/hooks/index.js +1 -0
  157. package/dist/hub/assetCallLog.d.ts +43 -0
  158. package/dist/hub/assetCallLog.js +72 -0
  159. package/dist/hub/bindings.d.ts +28 -0
  160. package/dist/hub/bindings.js +90 -0
  161. package/dist/hub/capability.d.ts +278 -0
  162. package/dist/hub/capability.js +1 -0
  163. package/dist/hub/conversationDistiller.d.ts +88 -0
  164. package/dist/hub/conversationDistiller.js +264 -0
  165. package/dist/hub/fake.d.ts +45 -0
  166. package/dist/hub/fake.js +69 -0
  167. package/dist/hub/hubReview.d.ts +91 -0
  168. package/dist/hub/hubReview.js +106 -0
  169. package/dist/hub/index.d.ts +10 -0
  170. package/dist/hub/index.js +10 -0
  171. package/dist/hub/ingest.d.ts +15 -0
  172. package/dist/hub/ingest.js +15 -0
  173. package/dist/hub/questionGenerator.d.ts +59 -0
  174. package/dist/hub/questionGenerator.js +406 -0
  175. package/dist/hub/reuseDecision.d.ts +87 -0
  176. package/dist/hub/reuseDecision.js +127 -0
  177. package/dist/hub/sanitize.d.ts +52 -0
  178. package/dist/hub/sanitize.js +321 -0
  179. package/dist/index.d.ts +27 -0
  180. package/dist/index.js +27 -0
  181. package/dist/mailbox/catalog.d.ts +17 -0
  182. package/dist/mailbox/catalog.js +53 -0
  183. package/dist/mailbox/daemon.d.ts +48 -0
  184. package/dist/mailbox/daemon.js +61 -0
  185. package/dist/mailbox/dispatch.d.ts +46 -0
  186. package/dist/mailbox/dispatch.js +62 -0
  187. package/dist/mailbox/envelope.d.ts +43 -0
  188. package/dist/mailbox/envelope.js +52 -0
  189. package/dist/mailbox/index.d.ts +8 -0
  190. package/dist/mailbox/index.js +8 -0
  191. package/dist/mailbox/ipcServer.d.ts +46 -0
  192. package/dist/mailbox/ipcServer.js +168 -0
  193. package/dist/mailbox/requestReply.d.ts +40 -0
  194. package/dist/mailbox/requestReply.js +77 -0
  195. package/dist/mailbox/store.d.ts +74 -0
  196. package/dist/mailbox/store.js +308 -0
  197. package/dist/mailbox/wake.d.ts +45 -0
  198. package/dist/mailbox/wake.js +60 -0
  199. package/dist/material/boundary.d.ts +7 -0
  200. package/dist/material/boundary.js +14 -0
  201. package/dist/material/consumer.d.ts +21 -0
  202. package/dist/material/consumer.js +52 -0
  203. package/dist/material/emit.d.ts +55 -0
  204. package/dist/material/emit.js +52 -0
  205. package/dist/material/factory.d.ts +27 -0
  206. package/dist/material/factory.js +25 -0
  207. package/dist/material/index.d.ts +8 -0
  208. package/dist/material/index.js +8 -0
  209. package/dist/material/materialStore.d.ts +24 -0
  210. package/dist/material/materialStore.js +76 -0
  211. package/dist/material/sampling.d.ts +21 -0
  212. package/dist/material/sampling.js +39 -0
  213. package/dist/material/sources.d.ts +25 -0
  214. package/dist/material/sources.js +33 -0
  215. package/dist/material/watermark.d.ts +21 -0
  216. package/dist/material/watermark.js +76 -0
  217. package/dist/observers/cursorRewriteObserver.d.ts +46 -0
  218. package/dist/observers/cursorRewriteObserver.js +103 -0
  219. package/dist/observers/distillObserver.d.ts +31 -0
  220. package/dist/observers/distillObserver.js +93 -0
  221. package/dist/observers/index.d.ts +5 -0
  222. package/dist/observers/index.js +5 -0
  223. package/dist/observers/observerBus.d.ts +44 -0
  224. package/dist/observers/observerBus.js +73 -0
  225. package/dist/observers/reflectionObserver.d.ts +31 -0
  226. package/dist/observers/reflectionObserver.js +124 -0
  227. package/dist/observers/valueDigestObserver.d.ts +50 -0
  228. package/dist/observers/valueDigestObserver.js +70 -0
  229. package/dist/ops/cleanup.d.ts +23 -0
  230. package/dist/ops/cleanup.js +68 -0
  231. package/dist/ops/health.d.ts +37 -0
  232. package/dist/ops/health.js +96 -0
  233. package/dist/ops/index.d.ts +7 -0
  234. package/dist/ops/index.js +7 -0
  235. package/dist/ops/recall.d.ts +65 -0
  236. package/dist/ops/recall.js +152 -0
  237. package/dist/ops/reuseOutcomes.d.ts +68 -0
  238. package/dist/ops/reuseOutcomes.js +126 -0
  239. package/dist/ops/savingsCore.d.ts +47 -0
  240. package/dist/ops/savingsCore.js +112 -0
  241. package/dist/ops/selfUpdate.d.ts +106 -0
  242. package/dist/ops/selfUpdate.js +294 -0
  243. package/dist/ops/valueLedger.d.ts +205 -0
  244. package/dist/ops/valueLedger.js +238 -0
  245. package/dist/ops/valueOutreach.d.ts +70 -0
  246. package/dist/ops/valueOutreach.js +244 -0
  247. package/dist/personality/drift.d.ts +45 -0
  248. package/dist/personality/drift.js +106 -0
  249. package/dist/personality/events.d.ts +25 -0
  250. package/dist/personality/events.js +37 -0
  251. package/dist/personality/evolveOps.d.ts +20 -0
  252. package/dist/personality/evolveOps.js +93 -0
  253. package/dist/personality/index.d.ts +11 -0
  254. package/dist/personality/index.js +11 -0
  255. package/dist/personality/mutate.d.ts +21 -0
  256. package/dist/personality/mutate.js +39 -0
  257. package/dist/personality/pivot.d.ts +23 -0
  258. package/dist/personality/pivot.js +22 -0
  259. package/dist/personality/prompt.d.ts +6 -0
  260. package/dist/personality/prompt.js +62 -0
  261. package/dist/personality/riskGate.d.ts +42 -0
  262. package/dist/personality/riskGate.js +80 -0
  263. package/dist/personality/schema.d.ts +255 -0
  264. package/dist/personality/schema.js +119 -0
  265. package/dist/personality/select.d.ts +46 -0
  266. package/dist/personality/select.js +71 -0
  267. package/dist/personality/stats.d.ts +46 -0
  268. package/dist/personality/stats.js +82 -0
  269. package/dist/personality/store.d.ts +35 -0
  270. package/dist/personality/store.js +92 -0
  271. package/dist/schema/common.d.ts +28 -0
  272. package/dist/schema/common.js +17 -0
  273. package/dist/schema/index.d.ts +5 -0
  274. package/dist/schema/index.js +5 -0
  275. package/dist/schema/material.d.ts +201 -0
  276. package/dist/schema/material.js +50 -0
  277. package/dist/schema/problem.d.ts +160 -0
  278. package/dist/schema/problem.js +45 -0
  279. package/dist/schema/proofOfWork.d.ts +97 -0
  280. package/dist/schema/proofOfWork.js +9 -0
  281. package/dist/schema/signal.d.ts +41 -0
  282. package/dist/schema/signal.js +19 -0
  283. package/dist/shadow/index.d.ts +6 -0
  284. package/dist/shadow/index.js +6 -0
  285. package/dist/shadow/jsonlSink.d.ts +17 -0
  286. package/dist/shadow/jsonlSink.js +27 -0
  287. package/dist/shadow/redact.d.ts +11 -0
  288. package/dist/shadow/redact.js +18 -0
  289. package/dist/shadow/shadowHub.d.ts +12 -0
  290. package/dist/shadow/shadowHub.js +42 -0
  291. package/dist/shadow/shadowMailbox.d.ts +17 -0
  292. package/dist/shadow/shadowMailbox.js +25 -0
  293. package/dist/shadow/shadowStore.d.ts +18 -0
  294. package/dist/shadow/shadowStore.js +55 -0
  295. package/dist/shadow/sink.d.ts +37 -0
  296. package/dist/shadow/sink.js +18 -0
  297. package/dist/signals/cycleHistoryFromEvents.d.ts +11 -0
  298. package/dist/signals/cycleHistoryFromEvents.js +96 -0
  299. package/dist/signals/expand.d.ts +34 -0
  300. package/dist/signals/expand.js +125 -0
  301. package/dist/signals/extractor.d.ts +25 -0
  302. package/dist/signals/extractor.js +57 -0
  303. package/dist/signals/index.d.ts +6 -0
  304. package/dist/signals/index.js +6 -0
  305. package/dist/signals/metaSignals.d.ts +57 -0
  306. package/dist/signals/metaSignals.js +158 -0
  307. package/dist/signals/signalGate.d.ts +15 -0
  308. package/dist/signals/signalGate.js +40 -0
  309. package/dist/signals/traceSignals.d.ts +44 -0
  310. package/dist/signals/traceSignals.js +123 -0
  311. package/dist/signatures/classifier.d.ts +16 -0
  312. package/dist/signatures/classifier.js +29 -0
  313. package/dist/signatures/index.d.ts +2 -0
  314. package/dist/signatures/index.js +2 -0
  315. package/dist/signatures/signatures.d.ts +22 -0
  316. package/dist/signatures/signatures.js +42 -0
  317. package/dist/strategy/experiment.d.ts +48 -0
  318. package/dist/strategy/experiment.js +63 -0
  319. package/dist/strategy/index.d.ts +2 -0
  320. package/dist/strategy/index.js +2 -0
  321. package/dist/strategy/strategyPoint.d.ts +30 -0
  322. package/dist/strategy/strategyPoint.js +23 -0
  323. package/dist/trace/index.d.ts +2 -0
  324. package/dist/trace/index.js +2 -0
  325. package/dist/trace/trajectory.d.ts +82 -0
  326. package/dist/trace/trajectory.js +117 -0
  327. package/dist/trace/trajectoryExport.d.ts +237 -0
  328. package/dist/trace/trajectoryExport.js +1818 -0
  329. package/dist/trigger/budget.d.ts +16 -0
  330. package/dist/trigger/budget.js +20 -0
  331. package/dist/trigger/engine.d.ts +12 -0
  332. package/dist/trigger/engine.js +23 -0
  333. package/dist/trigger/index.d.ts +6 -0
  334. package/dist/trigger/index.js +6 -0
  335. package/dist/trigger/problemBuilder.d.ts +24 -0
  336. package/dist/trigger/problemBuilder.js +32 -0
  337. package/dist/trigger/trigger.d.ts +22 -0
  338. package/dist/trigger/trigger.js +27 -0
  339. package/dist/trigger/valueModel.d.ts +26 -0
  340. package/dist/trigger/valueModel.js +25 -0
  341. package/dist/trigger/wfq.d.ts +13 -0
  342. package/dist/trigger/wfq.js +25 -0
  343. package/dist/util/fileLock.d.ts +24 -0
  344. package/dist/util/fileLock.js +227 -0
  345. package/dist/util/index.d.ts +2 -0
  346. package/dist/util/index.js +2 -0
  347. package/dist/util/version.d.ts +12 -0
  348. package/dist/util/version.js +51 -0
  349. package/dist/verify/index.d.ts +4 -0
  350. package/dist/verify/index.js +4 -0
  351. package/dist/verify/rollback.d.ts +22 -0
  352. package/dist/verify/rollback.js +33 -0
  353. package/dist/verify/sandboxRunner.d.ts +39 -0
  354. package/dist/verify/sandboxRunner.js +116 -0
  355. package/dist/verify/sandboxedValidation.d.ts +35 -0
  356. package/dist/verify/sandboxedValidation.js +56 -0
  357. package/dist/verify/validation.d.ts +41 -0
  358. package/dist/verify/validation.js +85 -0
  359. package/dist/wire/canonicalize.golden.d.ts +23 -0
  360. package/dist/wire/canonicalize.golden.js +46 -0
  361. package/dist/wire/geneHints.d.ts +64 -0
  362. package/dist/wire/geneHints.js +100 -0
  363. package/dist/wire/index.d.ts +91 -0
  364. package/dist/wire/index.js +3 -0
  365. package/dist/wire/schemaGate.d.ts +11 -0
  366. package/dist/wire/schemaGate.js +49 -0
  367. package/dist/workflow/dsl.d.ts +54 -0
  368. package/dist/workflow/dsl.js +15 -0
  369. package/dist/workflow/engine.d.ts +30 -0
  370. package/dist/workflow/engine.js +94 -0
  371. package/dist/workflow/index.d.ts +2 -0
  372. package/dist/workflow/index.js +2 -0
  373. package/package.json +29 -0
@@ -0,0 +1,238 @@
1
+ // Value ledger (#112) — "how many tokens did evolver save you" as a PURE, event-sourced, append-only ledger.
2
+ // The raw material already exists but no code turns it into savings: the proxy captures per-turn LLM usage
3
+ // (route decisions: original_model vs chosen_model) and reuse-before-solve hits land a replayable root_event.
4
+ // This module DERIVES value entries from that material — it never measures, mutates, pays, or settles anything.
5
+ //
6
+ // HARD BOUNDARY (core purity, #112): zero hub / zero economic decision / zero governance. The ledger is a
7
+ // read-only accounting view. Prices are INJECTED as a PriceTable (a data interface) — core hardcodes no model
8
+ // price, so a price update is a data-file change, never a core code change. This file does not import the
9
+ // adapter (the adapter reverse-injects prices) and emits no money movement.
10
+ //
11
+ // HONESTY INVARIANT (the user-trust line,呼应 money-safety): `measured` and `estimated` confidence are tracked
12
+ // and reported on SEPARATE rails. There is no code path that folds an estimated number into a measured total.
13
+ // `valueSummary` exposes them as two distinct buckets so a UI can never present an inflated single figure.
14
+ //
15
+ // Determinism: every function here is pure. No Date.now / Math.random — all timestamps come from the event or
16
+ // trace records themselves, so a golden fixture replays byte-for-byte.
17
+ import { ENTROPY_EVENT_TOKENS_EST } from './savingsCore.js';
18
+ /** Build a PriceTable from a plain {model: ModelPrice} map (the shape a JSON data file deserializes to). */
19
+ export function priceTableFromMap(map) {
20
+ return { priceOf: (model) => map[model] };
21
+ }
22
+ // ── the reuse root_event the ledger derives source=reuse entries from ─────────────────────────────────────
23
+ /** root_events type appended on a reuse-before-solve HIT — the replayable source for source=reuse entries. */
24
+ export const VALUE_REUSE_HIT_EVENT = 'value.reuse_hit';
25
+ // ── the injected gene-injection record (inject source — recorded, NEVER given a savings number) ───────────
26
+ /** root_events type appended when SessionStart injects genes — recorded for outcome attribution only. */
27
+ export const VALUE_INJECT_EVENT = 'value.inject';
28
+ /** Legacy savings-core E1 coefficient for "one cross-node asset reuse". The value ledger no longer uses this
29
+ * as a fallback baseline for live `value.reuse_hit` events: without a measured baseline, reuse savings stay
30
+ * zero/unknown. Kept exported so savings-core conformance and older consumers can detect spec drift. */
31
+ export const ESTIMATED_SOLVE_FRESH_TOKENS = ENTROPY_EVENT_TOKENS_EST.fetch_reuse;
32
+ function n(v) {
33
+ const x = Number(v);
34
+ return Number.isFinite(x) ? x : 0;
35
+ }
36
+ /** USD cost of a usage block under a price (absent price class → 0, never invented). */
37
+ function costOf(price, usage) {
38
+ if (!price || !usage)
39
+ return 0;
40
+ return (n(usage.input_tokens) * n(price.input) +
41
+ n(usage.output_tokens) * n(price.output) +
42
+ n(usage.cache_creation_input_tokens) * n(price.cacheCreation) +
43
+ n(usage.cache_read_input_tokens) * n(price.cacheRead));
44
+ }
45
+ // ── derivation: route (measured) ──────────────────────────────────────────────────────────────────────────
46
+ /**
47
+ * Derive route-downgrade savings from trace records. A turn saves COST (not tokens) only when the router
48
+ * actually rewrote the model (`original_model !== chosen_model`, both present): the saving is the price delta
49
+ * of the SAME usage between the model the client asked for and the cheaper model that ran. Always `measured`
50
+ * (the usage is real). A turn whose price delta is ≤0 (no cheaper, or model unpriced) contributes nothing.
51
+ */
52
+ export function deriveRouteEntries(traces, prices) {
53
+ const out = [];
54
+ for (const t of traces) {
55
+ const orig = t.original_model;
56
+ const chosen = t.chosen_model;
57
+ if (!orig || !chosen || orig === chosen)
58
+ continue;
59
+ const origCost = costOf(prices.priceOf(orig), t.usage);
60
+ const chosenCost = costOf(prices.priceOf(chosen), t.usage);
61
+ const saved = origCost - chosenCost;
62
+ if (!(saved > 0))
63
+ continue; // routing to an equal/pricier model saves nothing — never record a negative
64
+ out.push({
65
+ at: t.ts,
66
+ source: 'route',
67
+ tokensSaved: 0, // routing runs the SAME tokens on a cheaper model — it saves money, not tokens
68
+ costSavedUsd: saved,
69
+ confidence: 'measured',
70
+ refs: { originalModel: orig, chosenModel: chosen },
71
+ });
72
+ }
73
+ return out;
74
+ }
75
+ // ── derivation: reuse (measured when the event carries a baseline, else zero/unknown) ─────────────────────
76
+ /**
77
+ * Derive reuse savings from `value.reuse_hit` root_events. Saving = (baseline solve-fresh tokens) - (actual
78
+ * fetch tokens), but only when the event carries `baselineTokens` (a real historical average for the signal
79
+ * class). Without that measured baseline, the entry still records the reuse but keeps tokensSaved/costSavedUsd
80
+ * at zero so a small-step reuse inside a large task cannot be promoted into fabricated whole-task ROI.
81
+ * Each entry's refs point at the real `assetId` + `cycleId` from the hit - the audit anchor.
82
+ */
83
+ export function deriveReuseEntries(events, prices) {
84
+ const out = [];
85
+ for (const e of events) {
86
+ if (e.type !== VALUE_REUSE_HIT_EVENT)
87
+ continue;
88
+ const p = (e.payload ?? {});
89
+ const assetId = typeof p.assetId === 'string' ? p.assetId : '';
90
+ const cycleId = typeof p.cycleId === 'string' ? p.cycleId : '';
91
+ if (!assetId || !cycleId)
92
+ continue; // a reuse entry without its audit anchors is not accountable
93
+ const fetchTokens = Math.max(0, n(p.fetchTokens));
94
+ const hasMeasuredBaseline = typeof p.baselineTokens === 'number' && Number.isFinite(p.baselineTokens) && p.baselineTokens > 0;
95
+ const tokensSaved = hasMeasuredBaseline ? Math.max(0, n(p.baselineTokens) - fetchTokens) : 0;
96
+ const price = typeof p.model === 'string' ? prices.priceOf(p.model) : undefined;
97
+ // Value the saved tokens as input-side cost (a conservative single-rail price for a token bundle). When the
98
+ // model is unpriced/absent, or when there is no measured baseline, cost is 0.
99
+ const perToken = price ? n(price.input) : 0;
100
+ const costSavedUsd = tokensSaved * perToken;
101
+ out.push({
102
+ at: e.ts,
103
+ source: 'reuse',
104
+ tokensSaved,
105
+ costSavedUsd,
106
+ confidence: hasMeasuredBaseline ? 'measured' : 'estimated',
107
+ refs: { assetId, cycleId },
108
+ });
109
+ }
110
+ return out;
111
+ }
112
+ // ── derivation: inject (recorded, NEVER a savings number) ─────────────────────────────────────────────────
113
+ /**
114
+ * Derive inject records from `value.inject` root_events. Attribution-only: which genes were injected and the
115
+ * session outcome. tokensSaved / costSavedUsd are ALWAYS 0 (the issue: inject is a weak signal — record it,
116
+ * never report a savings number). Marked `estimated` so it can never land on the measured rail, and the
117
+ * summary excludes inject from both savings totals regardless.
118
+ */
119
+ export function deriveInjectEntries(events) {
120
+ const out = [];
121
+ for (const e of events) {
122
+ if (e.type !== VALUE_INJECT_EVENT)
123
+ continue;
124
+ const p = (e.payload ?? {});
125
+ const geneIds = Array.isArray(p.geneIds) ? p.geneIds.filter((g) => typeof g === 'string') : [];
126
+ if (geneIds.length === 0)
127
+ continue;
128
+ out.push({
129
+ at: e.ts,
130
+ source: 'inject',
131
+ tokensSaved: 0,
132
+ costSavedUsd: 0,
133
+ confidence: 'estimated',
134
+ refs: {
135
+ geneIds,
136
+ ...(typeof p.cycleId === 'string' ? { cycleId: p.cycleId } : {}),
137
+ ...(typeof p.outcome === 'string' ? { outcome: p.outcome } : {}),
138
+ },
139
+ });
140
+ }
141
+ return out;
142
+ }
143
+ /**
144
+ * Derive the full set of value entries from trace + root_events, sorted by `at` (stable, ties broken by source
145
+ * then assetId/models) so the ledger is a deterministic, replayable timeline. Pure: same input → same output,
146
+ * byte-for-byte. This is the function a `replay` re-runs after discarding the materialized ledger.
147
+ */
148
+ export function deriveValueEntries(input) {
149
+ const traces = input.traces ?? [];
150
+ const events = input.events ?? [];
151
+ const entries = [
152
+ ...deriveRouteEntries(traces, input.prices),
153
+ ...deriveReuseEntries(events, input.prices),
154
+ ...deriveInjectEntries(events),
155
+ ];
156
+ entries.sort((a, b) => {
157
+ if (a.at !== b.at)
158
+ return a.at < b.at ? -1 : 1;
159
+ if (a.source !== b.source)
160
+ return a.source < b.source ? -1 : 1;
161
+ const ka = a.refs.assetId ?? a.refs.chosenModel ?? '';
162
+ const kb = b.refs.assetId ?? b.refs.chosenModel ?? '';
163
+ return ka < kb ? -1 : ka > kb ? 1 : 0;
164
+ });
165
+ return entries;
166
+ }
167
+ // ── serialization (append-only JSONL, replayable) ─────────────────────────────────────────────────────────
168
+ /** Serialize entries to append-only JSONL (one entry per line). Deterministic given the entries. */
169
+ export function serializeLedger(entries) {
170
+ return entries.map((e) => JSON.stringify(e)).join('\n') + (entries.length ? '\n' : '');
171
+ }
172
+ /** Parse a ledger JSONL back into entries (the materialized view; the source of truth is still trace+events). */
173
+ export function parseLedger(jsonl) {
174
+ const out = [];
175
+ for (const line of jsonl.split('\n')) {
176
+ const s = line.trim();
177
+ if (!s)
178
+ continue;
179
+ try {
180
+ out.push(JSON.parse(s));
181
+ }
182
+ catch { /* skip a corrupt tail line — replay rebuilds it */ }
183
+ }
184
+ return out;
185
+ }
186
+ function emptyBucket() { return { tokensSaved: 0, costSavedUsd: 0, entries: 0 }; }
187
+ /**
188
+ * Aggregate entries into a ValueSummary over an optional time window. Pure. Measured and estimated are summed
189
+ * on separate rails by construction — there is deliberately NO branch that adds an estimated entry into the
190
+ * measured totals (the honesty invariant; a test asserts no such path exists).
191
+ */
192
+ export function valueSummary(entries, window = {}) {
193
+ const inWindow = (at) => (window.since === undefined || at >= window.since) && (window.until === undefined || at < window.until);
194
+ const scoped = entries.filter((e) => inWindow(e.at));
195
+ const bySource = { route: emptyBucket(), reuse: emptyBucket(), inject: emptyBucket() };
196
+ const estimated = { totalTokensSaved: 0, totalCostUsd: 0, entries: 0 };
197
+ let totalTokensSaved = 0;
198
+ let totalCostUsd = 0;
199
+ const geneAgg = new Map();
200
+ const injectAgg = new Map();
201
+ for (const e of scoped) {
202
+ const bucket = bySource[e.source];
203
+ bucket.entries += 1;
204
+ if (e.confidence === 'measured') {
205
+ // MEASURED rail — and ONLY the measured rail feeds the headline totals.
206
+ bucket.tokensSaved += e.tokensSaved;
207
+ bucket.costSavedUsd += e.costSavedUsd;
208
+ totalTokensSaved += e.tokensSaved;
209
+ totalCostUsd += e.costSavedUsd;
210
+ }
211
+ else {
212
+ // ESTIMATED rail — kept wholly separate; never touches bucket.tokensSaved or the totals above.
213
+ estimated.totalTokensSaved += e.tokensSaved;
214
+ estimated.totalCostUsd += e.costSavedUsd;
215
+ estimated.entries += 1;
216
+ }
217
+ if (e.source === 'reuse' && e.refs.assetId) {
218
+ const g = geneAgg.get(e.refs.assetId) ?? { reuses: 0, measuredTokensSaved: 0, estimatedTokensSaved: 0 };
219
+ g.reuses += 1;
220
+ if (e.confidence === 'measured')
221
+ g.measuredTokensSaved += e.tokensSaved;
222
+ else
223
+ g.estimatedTokensSaved += e.tokensSaved;
224
+ geneAgg.set(e.refs.assetId, g);
225
+ }
226
+ if (e.source === 'inject' && e.refs.geneIds) {
227
+ for (const gid of e.refs.geneIds)
228
+ injectAgg.set(gid, (injectAgg.get(gid) ?? 0) + 1);
229
+ }
230
+ }
231
+ const topGenes = [...geneAgg.entries()]
232
+ .map(([assetId, v]) => ({ assetId, ...v }))
233
+ .sort((a, b) => b.reuses - a.reuses || b.measuredTokensSaved - a.measuredTokensSaved || (a.assetId < b.assetId ? -1 : 1));
234
+ const injectedGenes = [...injectAgg.entries()]
235
+ .map(([geneId, injections]) => ({ geneId, injections }))
236
+ .sort((a, b) => b.injections - a.injections || (a.geneId < b.geneId ? -1 : 1));
237
+ return { totalTokensSaved, totalCostUsd, bySource, estimated, topGenes, injectedGenes, entries: scoped.length };
238
+ }
@@ -0,0 +1,70 @@
1
+ import { type ValueSummary, type TraceRecord, type LedgerRootEvent, type PriceTable, type SummaryWindow } from './valueLedger.js';
2
+ /**
3
+ * Read all proxy trace day-files in `dir` and return the parsed `llm_turn` records as ledger TraceRecords. A
4
+ * missing dir / unreadable file degrades to [] (the value layer must never crash a long-running daemon over a
5
+ * stat). Plain `event: 'llm_turn'` lines and encrypted envelopes with a safe `plaintext_summary` are kept; the
6
+ * structural fields the ledger reads are passed through.
7
+ */
8
+ export declare function readTraceRecords(dir: string): TraceRecord[];
9
+ export type ValueWindowSpec = '7d' | '30d' | 'all';
10
+ /** Parse a `--window` spec into a SummaryWindow against `now`. `all` → no bound. An unknown spec → 7d (the safe
11
+ * default a CLI offers first). The clock is injected so the report is deterministic under test. */
12
+ export declare function windowFromSpec(spec: string | undefined, now: number): SummaryWindow;
13
+ /** Human label for a window spec (for the report/digest header). */
14
+ export declare function windowLabel(spec: string | undefined): string;
15
+ export interface ValueSources {
16
+ /** Parsed proxy trace records (route source). Default []. */
17
+ traces?: readonly TraceRecord[];
18
+ /** Parsed root_events (reuse + inject source). Default []. */
19
+ events?: readonly LedgerRootEvent[];
20
+ prices: PriceTable;
21
+ }
22
+ /**
23
+ * Derive the full ledger then aggregate it over a window — the SINGLE aggregation seam every outreach surface
24
+ * goes through, so the CLI, the WebUI card and the digest can never disagree (and none of them re-implements the
25
+ * ledger). Pure given its inputs + window.
26
+ */
27
+ export declare function loadValueSummary(sources: ValueSources, window?: SummaryWindow): ValueSummary;
28
+ /** Compact integer with thousands separators (deterministic, locale-independent). */
29
+ export declare function fmtInt(n: number): string;
30
+ /** USD with 4 decimals (savings are often sub-cent; never round a real saving to $0.00 silently). */
31
+ export declare function fmtUsd(n: number): string;
32
+ /** Guidance shown when the ledger is empty — never an empty table (#113 acceptance). */
33
+ export declare const VALUE_EMPTY_GUIDANCE: string;
34
+ /**
35
+ * Render the three-section value report from a summary: (1) savings — measured and estimated on SEPARATE lines,
36
+ * never a merged total; (2) top reused genes with their reuse counts; (3) source breakdown. Returns the empty
37
+ * guidance text (not a blank table) when there is nothing to show. Pure: the window label is passed in.
38
+ */
39
+ export declare function formatValueReport(summary: ValueSummary, windowSpec: string | undefined): string;
40
+ export interface RecapInput {
41
+ /** Genes injected this session (N). */
42
+ injectedCount: number;
43
+ /** Past successes those genes came from (M) — e.g. distinct solidified cycles. Optional. */
44
+ successCount?: number;
45
+ /** The summary for the recap window (defaults to last 7 days at the call site). */
46
+ summary: ValueSummary;
47
+ }
48
+ /**
49
+ * Build the quiet SessionStart context line. It is intentionally metadata-shaped, non-branded, and non-second-
50
+ * person so the agent can use injected-memory/value context without reciting Evolver in routine replies. Measured
51
+ * tokens (X) stay on the trustworthy rail; estimated is emitted ONLY when there is no measured figure, under a
52
+ * separate key so the two are never conflated. Returns '' when there is nothing honest to say (no injection AND no
53
+ * savings) — the caller then injects no recap line at all rather than a hollow "saved 0".
54
+ */
55
+ export declare function buildValueRecap(input: RecapInput): string;
56
+ /** Prefix a reuse-source note onto an injected candidate so the user sees WHERE a ready-made solution came from
57
+ * (the reuse hit short-circuited a fresh solve). Pure string composition; the assetId is the audit anchor. */
58
+ export declare function reuseSourceNote(assetId: string): string;
59
+ /**
60
+ * A digest is only worth sending in a week that produced REAL (measured) value. This is the frequency/quality
61
+ * gate the issue makes a first-class requirement: "宁可少触达,不可惹人烦". Estimated-only weeks do NOT pass —
62
+ * an estimate is too weak to justify an unsolicited push.
63
+ */
64
+ export declare function digestShouldSend(summary: ValueSummary): boolean;
65
+ /**
66
+ * Build the weekly digest markdown from a summary. Returns null when the zero-measured-value gate fails (the
67
+ * caller then delivers nothing — no empty digest, ever). measured and estimated stay on separate lines. Pure:
68
+ * the window label + the period string are passed in so the digest is deterministic.
69
+ */
70
+ export declare function buildValueDigest(summary: ValueSummary, period: string): string | null;
@@ -0,0 +1,244 @@
1
+ // Value outreach (#113) — turn the value ledger's `valueSummary` into something a user actually SEES, layered
2
+ // by intrusiveness: a pull-only report (`evolver value` / WebUI card), a quiet session-start context line, and a
3
+ // frequency-capped weekly digest (the observer bus's first built-in observer).
4
+ //
5
+ // HARD BOUNDARY (core purity, mirrors the ledger): this module reads trace day-files + root_events off disk and
6
+ // FORMATS the already-derived summary — it never re-derives savings, never prices anything (prices are injected
7
+ // as a PriceTable, same as the ledger), never touches the hub / money / governance. It is a presentation layer
8
+ // over `valueSummary`, nothing more.
9
+ //
10
+ // HONESTY INVARIANT (inherited, non-negotiable): measured and estimated savings are presented on SEPARATE rails
11
+ // in every surface here. There is no code path that folds an estimated number into a measured one — the report,
12
+ // the recap and the digest each print two distinct figures. A test pins that no surface ever merges them.
13
+ //
14
+ // Determinism: the FORMATTING functions are pure (no clock / no random). Only the disk readers and the time
15
+ // window math take an injected clock, so a golden fixture of the report/recap/digest replays byte-for-byte.
16
+ import { readdirSync, readFileSync } from 'node:fs';
17
+ import { join } from 'node:path';
18
+ import { deriveValueEntries, valueSummary, } from './valueLedger.js';
19
+ // ── disk readers (the live source path: proxy trace day-files + root_events) ──────────────────────────────────
20
+ /** A `llm-trace-YYYYMMDD.jsonl` day-file written by the proxy's JsonlTraceSink. */
21
+ const TRACE_FILE_RE = /^llm-trace-.*\.jsonl$/i;
22
+ function isRecord(value) {
23
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
24
+ }
25
+ function localTraceMetrics(rec) {
26
+ if (rec['event'] === 'llm_turn')
27
+ return rec;
28
+ if (rec['event'] !== 'llm_trace_envelope' || rec['encrypted'] !== true)
29
+ return null;
30
+ const summary = rec['plaintext_summary'];
31
+ if (!isRecord(summary) || summary['event'] !== 'llm_trace_plaintext_summary')
32
+ return null;
33
+ return summary;
34
+ }
35
+ /**
36
+ * Read all proxy trace day-files in `dir` and return the parsed `llm_turn` records as ledger TraceRecords. A
37
+ * missing dir / unreadable file degrades to [] (the value layer must never crash a long-running daemon over a
38
+ * stat). Plain `event: 'llm_turn'` lines and encrypted envelopes with a safe `plaintext_summary` are kept; the
39
+ * structural fields the ledger reads are passed through.
40
+ */
41
+ export function readTraceRecords(dir) {
42
+ let files;
43
+ try {
44
+ files = readdirSync(dir).filter((f) => TRACE_FILE_RE.test(f)).sort();
45
+ }
46
+ catch {
47
+ return []; // no trace dir yet → no route savings, never an error
48
+ }
49
+ const out = [];
50
+ for (const f of files) {
51
+ let text;
52
+ try {
53
+ text = readFileSync(join(dir, f), 'utf8');
54
+ }
55
+ catch {
56
+ continue;
57
+ }
58
+ for (const line of text.split('\n')) {
59
+ const s = line.trim();
60
+ if (!s)
61
+ continue;
62
+ let rec;
63
+ try {
64
+ rec = JSON.parse(s);
65
+ }
66
+ catch {
67
+ continue;
68
+ }
69
+ const metrics = localTraceMetrics(rec);
70
+ if (!metrics)
71
+ continue;
72
+ const usage = metrics['usage'];
73
+ out.push({
74
+ ts: typeof metrics['ts'] === 'string' ? metrics['ts'] : '',
75
+ original_model: typeof metrics['original_model'] === 'string' ? metrics['original_model'] : null,
76
+ chosen_model: typeof metrics['chosen_model'] === 'string' ? metrics['chosen_model'] : null,
77
+ ...(typeof metrics['tier'] === 'string' ? { tier: metrics['tier'] } : {}),
78
+ ...(usage && typeof usage === 'object' ? { usage: usage } : {}),
79
+ });
80
+ }
81
+ }
82
+ return out;
83
+ }
84
+ /** Parse a `--window` spec into a SummaryWindow against `now`. `all` → no bound. An unknown spec → 7d (the safe
85
+ * default a CLI offers first). The clock is injected so the report is deterministic under test. */
86
+ export function windowFromSpec(spec, now) {
87
+ if (spec === 'all')
88
+ return {};
89
+ const days = spec === '30d' ? 30 : 7; // default + unknown → 7d
90
+ return { since: new Date(now - days * 24 * 60 * 60 * 1000).toISOString() };
91
+ }
92
+ /** Human label for a window spec (for the report/digest header). */
93
+ export function windowLabel(spec) {
94
+ return spec === 'all' ? 'all time' : spec === '30d' ? 'last 30 days' : 'last 7 days';
95
+ }
96
+ /**
97
+ * Derive the full ledger then aggregate it over a window — the SINGLE aggregation seam every outreach surface
98
+ * goes through, so the CLI, the WebUI card and the digest can never disagree (and none of them re-implements the
99
+ * ledger). Pure given its inputs + window.
100
+ */
101
+ export function loadValueSummary(sources, window = {}) {
102
+ const entries = deriveValueEntries({
103
+ traces: sources.traces ?? [],
104
+ events: sources.events ?? [],
105
+ prices: sources.prices,
106
+ });
107
+ return valueSummary(entries, window);
108
+ }
109
+ // ── number formatting (shared by every surface) ───────────────────────────────────────────────────────────────
110
+ /** Compact integer with thousands separators (deterministic, locale-independent). */
111
+ export function fmtInt(n) {
112
+ return Math.round(n).toLocaleString('en-US');
113
+ }
114
+ /** Plain integer for machine-shaped key=value context fields. */
115
+ function fmtContextInt(n) {
116
+ return String(Math.round(n));
117
+ }
118
+ /** USD with 4 decimals (savings are often sub-cent; never round a real saving to $0.00 silently). */
119
+ export function fmtUsd(n) {
120
+ return `$${n.toFixed(4)}`;
121
+ }
122
+ // ── layer 1: the pull report (`evolver value`) ────────────────────────────────────────────────────────────────
123
+ /** Guidance shown when the ledger is empty — never an empty table (#113 acceptance). */
124
+ export const VALUE_EMPTY_GUIDANCE = [
125
+ 'evolver value — no savings recorded yet for this window.',
126
+ '',
127
+ 'evolver records value when it actually saves you tokens or money:',
128
+ ' • route savings — run the LLM proxy so cheaper-tier routing is captured (EVOLVER_LLM_TRACE=1).',
129
+ ' • reuse savings — enable reuse-before-solve so a solved problem is fetched, not re-solved',
130
+ ' (EVOLVER_REUSE_BEFORE_SOLVE=1 with a hub credential).',
131
+ '',
132
+ 'Once either is active, this command shows measured savings, unmeasured reuse counts, and the trend.',
133
+ ].join('\n');
134
+ /**
135
+ * Render the three-section value report from a summary: (1) savings — measured and estimated on SEPARATE lines,
136
+ * never a merged total; (2) top reused genes with their reuse counts; (3) source breakdown. Returns the empty
137
+ * guidance text (not a blank table) when there is nothing to show. Pure: the window label is passed in.
138
+ */
139
+ export function formatValueReport(summary, windowSpec) {
140
+ const hasMeasured = summary.totalTokensSaved > 0 || summary.totalCostUsd > 0;
141
+ const hasEstimated = summary.estimated.totalTokensSaved > 0 || summary.estimated.totalCostUsd > 0;
142
+ if (!hasMeasured && !hasEstimated && summary.topGenes.length === 0 && summary.injectedGenes.length === 0) {
143
+ return VALUE_EMPTY_GUIDANCE;
144
+ }
145
+ const L = [];
146
+ L.push(`evolver value — ${windowLabel(windowSpec)}`);
147
+ L.push('');
148
+ // Section 1: savings, two rails, never merged.
149
+ L.push('Savings');
150
+ L.push(` measured ${fmtInt(summary.totalTokensSaved).padStart(10)} tokens ${fmtUsd(summary.totalCostUsd)}`);
151
+ L.push(` estimated ${fmtInt(summary.estimated.totalTokensSaved).padStart(10)} tokens ${fmtUsd(summary.estimated.totalCostUsd)}`);
152
+ L.push(' (measured and estimated are tracked separately and never added together)');
153
+ L.push('');
154
+ // Section 2: top reused genes.
155
+ L.push('Top reused genes');
156
+ if (summary.topGenes.length === 0) {
157
+ L.push(' (none reused in this window)');
158
+ }
159
+ else {
160
+ for (const g of summary.topGenes.slice(0, 10)) {
161
+ const saved = g.measuredTokensSaved > 0
162
+ ? `${fmtInt(g.measuredTokensSaved)} tokens measured`
163
+ : g.estimatedTokensSaved > 0
164
+ ? `${fmtInt(g.estimatedTokensSaved)} tokens estimated`
165
+ : '0 tokens unmeasured';
166
+ L.push(` ${g.assetId.padEnd(28)} ×${g.reuses} ${saved}`);
167
+ }
168
+ }
169
+ L.push('');
170
+ // Section 3: source breakdown (measured rail) — the trend / attribution.
171
+ L.push('By source (measured)');
172
+ for (const src of ['route', 'reuse', 'inject']) {
173
+ const b = summary.bySource[src];
174
+ L.push(` ${src.padEnd(7)} ${String(b.entries).padStart(4)} entries ${fmtInt(b.tokensSaved)} tokens ${fmtUsd(b.costSavedUsd)}`);
175
+ }
176
+ return L.join('\n');
177
+ }
178
+ /**
179
+ * Build the quiet SessionStart context line. It is intentionally metadata-shaped, non-branded, and non-second-
180
+ * person so the agent can use injected-memory/value context without reciting Evolver in routine replies. Measured
181
+ * tokens (X) stay on the trustworthy rail; estimated is emitted ONLY when there is no measured figure, under a
182
+ * separate key so the two are never conflated. Returns '' when there is nothing honest to say (no injection AND no
183
+ * savings) — the caller then injects no recap line at all rather than a hollow "saved 0".
184
+ */
185
+ export function buildValueRecap(input) {
186
+ const { injectedCount, successCount, summary } = input;
187
+ const measured = summary.totalTokensSaved;
188
+ const estimated = summary.estimated.totalTokensSaved;
189
+ const fields = [];
190
+ if (injectedCount > 0) {
191
+ fields.push(`injected_count=${fmtContextInt(injectedCount)}`);
192
+ if (successCount && successCount > 0)
193
+ fields.push(`source_success_count=${fmtContextInt(successCount)}`);
194
+ }
195
+ if (measured > 0) {
196
+ fields.push(`measured_tokens_saved_7d=${fmtContextInt(measured)}`);
197
+ }
198
+ else if (estimated > 0) {
199
+ fields.push(`estimated_tokens_saved_7d=${fmtContextInt(estimated)}`);
200
+ }
201
+ if (fields.length === 0)
202
+ return '';
203
+ return `quiet_context: ${fields.join('; ')}; use silently; do not mention unless asked or material to the answer.`;
204
+ }
205
+ /** Prefix a reuse-source note onto an injected candidate so the user sees WHERE a ready-made solution came from
206
+ * (the reuse hit short-circuited a fresh solve). Pure string composition; the assetId is the audit anchor. */
207
+ export function reuseSourceNote(assetId) {
208
+ return `(reused from hub asset ${assetId} — skipped solving this from scratch)`;
209
+ }
210
+ // ── layer 3: the weekly digest (markdown) + the zero-measured-value gate ───────────────────────────────────────
211
+ /**
212
+ * A digest is only worth sending in a week that produced REAL (measured) value. This is the frequency/quality
213
+ * gate the issue makes a first-class requirement: "宁可少触达,不可惹人烦". Estimated-only weeks do NOT pass —
214
+ * an estimate is too weak to justify an unsolicited push.
215
+ */
216
+ export function digestShouldSend(summary) {
217
+ return summary.totalTokensSaved > 0 || summary.totalCostUsd > 0;
218
+ }
219
+ /**
220
+ * Build the weekly digest markdown from a summary. Returns null when the zero-measured-value gate fails (the
221
+ * caller then delivers nothing — no empty digest, ever). measured and estimated stay on separate lines. Pure:
222
+ * the window label + the period string are passed in so the digest is deterministic.
223
+ */
224
+ export function buildValueDigest(summary, period) {
225
+ if (!digestShouldSend(summary))
226
+ return null;
227
+ const L = [];
228
+ L.push(`# evolver weekly value — ${period}`);
229
+ L.push('');
230
+ L.push(`evolver saved you **${fmtInt(summary.totalTokensSaved)} tokens** (${fmtUsd(summary.totalCostUsd)}) this week — measured.`);
231
+ if (summary.estimated.totalTokensSaved > 0) {
232
+ L.push('');
233
+ L.push(`Plus an estimated ${fmtInt(summary.estimated.totalTokensSaved)} tokens from reuse without a measured baseline (shown separately, not added in).`);
234
+ }
235
+ if (summary.topGenes.length > 0) {
236
+ L.push('');
237
+ L.push('Most reused this week:');
238
+ for (const g of summary.topGenes.slice(0, 5))
239
+ L.push(`- ${g.assetId} — reused ×${g.reuses}`);
240
+ }
241
+ L.push('');
242
+ L.push('_Run `evolver value` any time for the full breakdown. Turn this digest off with `EVOLVER_VALUE_DIGEST=0`._');
243
+ return L.join('\n');
244
+ }
@@ -0,0 +1,45 @@
1
+ import { type PersonalityMutation, type PersonalityStateInput } from './schema.js';
2
+ /**
3
+ * 何时/如何小步变异人格 (v1 proposeMutations / shouldTriggerPersonalityMutation + mutation.js 的信号判定端口).
4
+ * 这是"自调参"的规则引擎: 根据信号/失败连击提出对人格向量的小 nudge, 由 applyPersonalityMutations 落地.
5
+ */
6
+ /** 机会信号: 表示"有创新余地"而非只是修 bug (v1 OPPORTUNITY_SIGNALS). */
7
+ export declare const OPPORTUNITY_SIGNALS: readonly string[];
8
+ /** signals 里是否有机会信号 (精确名或 `name:` 前缀). */
9
+ export declare function hasOpportunitySignal(signals: readonly unknown[] | undefined): boolean;
10
+ /** signals 里是否有错误类信号 (但 issue_already_resolved / openclaw_self_healed 例外为 false). */
11
+ export declare function hasErrorishSignal(signals: readonly unknown[] | undefined): boolean;
12
+ export interface ProposeMutationsInput {
13
+ baseState: PersonalityStateInput;
14
+ reason?: string;
15
+ driftEnabled?: boolean;
16
+ signals?: readonly string[];
17
+ }
18
+ /**
19
+ * 依上下文提出一组人格变异 (v1 proposeMutations). 分支优先级:
20
+ * drift → 提创造性(风险回夹) / protocol_drift → 提服从&严谨 / 错误 → 提严谨降风险 /
21
+ * 机会 → 提创造性&风险 / 否则(平台期) → 微提创造性降啰嗦.
22
+ * 若 obedience 已饱和(≥0.95), 把服从类变异换成创造性.
23
+ */
24
+ export declare function proposeMutations(input: ProposeMutationsInput): PersonalityMutation[];
25
+ /** 一条 cycle 事件在触发判定里的最小形状. */
26
+ export interface TriggerCycleEvent {
27
+ outcome?: {
28
+ status?: string;
29
+ } | null;
30
+ mutationId?: string;
31
+ }
32
+ export interface TriggerDecision {
33
+ ok: boolean;
34
+ reason: string;
35
+ }
36
+ /**
37
+ * 是否该触发一次人格变异 (v1 shouldTriggerPersonalityMutation):
38
+ * - driftEnabled ⇒ 总触发
39
+ * - 最近 6 事件里, 后 4 个有 ≥3 个 failed ⇒ 长失败连击
40
+ * - 带 mutation_id 的最近 3 个事件全 failed ⇒ 变异连续失败
41
+ */
42
+ export declare function shouldTriggerPersonalityMutation(args: {
43
+ driftEnabled?: boolean;
44
+ recentEvents?: readonly TriggerCycleEvent[];
45
+ }): TriggerDecision;