@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,159 +0,0 @@
1
- // src/experiment/cli.js
2
- //
3
- // CLI surface for the `experiment` subcommand. Mirrors src/atp/cli.js:
4
- // parseExperimentArgs(args) -> { ok, opts? , error? }
5
- // runExperiment(opts, deps) -> { ok, data?, error?, exitCode }
6
- // Dependency-injected (comparison / agentRunner / geneLoader / sandbox)
7
- // so the runner is unit-testable without a real agent CLI.
8
- 'use strict';
9
-
10
- const fs = require('fs');
11
- const path = require('path');
12
-
13
- const MAX_REQUEST_FILE_BYTES = 1024 * 1024; // 1 MB — a request JSON is tiny
14
-
15
- // Minimal `--key=value` / `--flag` parser (no shell, no globbing).
16
- function parseFlags(args) {
17
- const out = {};
18
- for (const a of args || []) {
19
- if (typeof a !== 'string') continue;
20
- const eq = a.indexOf('=');
21
- if (a.startsWith('--') && eq > 2) {
22
- out[a.slice(2, eq)] = a.slice(eq + 1);
23
- } else if (a.startsWith('--')) {
24
- out[a.slice(2)] = true;
25
- }
26
- }
27
- return out;
28
- }
29
-
30
- /**
31
- * Parse experiment subcommand args.
32
- *
33
- * Flags: --task= --metric= [--baseline=] [--variant=] [--gene=]
34
- * [--validation="cmd1;;cmd2"] [--timeout=ms] [--request-file=<json>]
35
- *
36
- * --request-file supplies a JSON base ({task,baseline,variant,metric,geneId,
37
- * validationCommands,timeoutMs}); explicit flags override it. This is the only
38
- * filesystem read here and lets the desktop pass complex params without argv
39
- * escaping headaches.
40
- *
41
- * @returns {{ ok: true, opts: object } | { ok: false, error: string }}
42
- */
43
- function parseExperimentArgs(args) {
44
- const f = parseFlags(args);
45
-
46
- let base = {};
47
- if (f['request-file']) {
48
- try {
49
- // Resolve + stat the path and bound its size before reading: don't open
50
- // an arbitrary non-file (device/FIFO) or slurp an unbounded file.
51
- const rfPath = path.resolve(String(f['request-file']));
52
- const st = fs.statSync(rfPath);
53
- if (!st.isFile()) return { ok: false, error: '--request-file must be a regular file' };
54
- if (st.size > MAX_REQUEST_FILE_BYTES) return { ok: false, error: '--request-file too large (> 1 MB)' };
55
- base = JSON.parse(fs.readFileSync(rfPath, 'utf8'));
56
- } catch (e) {
57
- return { ok: false, error: 'failed to read --request-file: ' + (e && e.message ? e.message : String(e)) };
58
- }
59
- if (!base || typeof base !== 'object') {
60
- return { ok: false, error: '--request-file must contain a JSON object' };
61
- }
62
- }
63
-
64
- const pick = (flagVal, baseVal, dflt) => {
65
- if (flagVal !== undefined && flagVal !== true) return String(flagVal);
66
- if (baseVal !== undefined && baseVal !== null) return String(baseVal);
67
- return dflt;
68
- };
69
-
70
- const opts = {
71
- // trim() so a whitespace-only value fails the required-field check below
72
- // (parse-time exit 2 + usage) instead of crashing later in runComparison
73
- // (exit 1 with no JSON on stdout for the Go caller).
74
- task: pick(f.task, base.task, '').trim(),
75
- baseline: pick(f.baseline, base.baseline, 'baseline'),
76
- variant: pick(f.variant, base.variant, 'variant'),
77
- metric: pick(f.metric, base.metric, '').trim(),
78
- geneId: pick(f.gene, base.geneId !== undefined ? base.geneId : base.gene, '') || null,
79
- validationCommands: null,
80
- timeoutMs: undefined,
81
- };
82
-
83
- if (f.validation !== undefined && f.validation !== true) {
84
- opts.validationCommands = String(f.validation).split(';;').map((s) => s.trim()).filter(Boolean);
85
- } else if (Array.isArray(base.validationCommands)) {
86
- opts.validationCommands = base.validationCommands.map(String);
87
- }
88
-
89
- const timeoutRaw = f.timeout !== undefined && f.timeout !== true ? Number(f.timeout)
90
- : (Number.isFinite(Number(base.timeoutMs)) ? Number(base.timeoutMs) : NaN);
91
- if (Number.isFinite(timeoutRaw)) opts.timeoutMs = timeoutRaw;
92
-
93
- if (!opts.task) return { ok: false, error: 'missing required --task (or "task" in --request-file)' };
94
- if (!opts.metric) return { ok: false, error: 'missing required --metric (or "metric" in --request-file)' };
95
-
96
- return { ok: true, opts };
97
- }
98
-
99
- /**
100
- * Run the comparison. Returns the result object (also written to stdout by the
101
- * index.js wrapper) plus an exit code. exitCode 3 == inconclusive (a real,
102
- * structured outcome, not a crash).
103
- *
104
- * @param {object} opts from parseExperimentArgs
105
- * @param {object} [deps] { comparison, agentRunner, geneLoader, sandbox, err }
106
- */
107
- async function runExperiment(opts, deps) {
108
- deps = deps || {};
109
- const comparison = deps.comparison || require('./comparison');
110
- const err = typeof deps.err === 'function' ? deps.err : ((...a) => console.error(...a));
111
-
112
- const params = Object.assign({}, opts);
113
- if (deps.agentRunner) params.agentRunner = deps.agentRunner;
114
- if (deps.geneLoader) params.geneLoader = deps.geneLoader;
115
- if (deps.sandbox) params.sandbox = deps.sandbox;
116
-
117
- try {
118
- const raw = await comparison.runComparison(params);
119
- // Redact any secrets / API keys an agent's resultText (or a parse-failure
120
- // snippet) may carry before the result crosses the process boundary into
121
- // the desktop consumer / is persisted to disk.
122
- const data = require('../gep/sanitize').sanitizePayload(raw);
123
- let exitCode = 0;
124
- if (data && data.winner === 'inconclusive') {
125
- exitCode = 3;
126
- // Two distinct causes share the inconclusive verdict; say which one
127
- // happened so operators don't go debugging arm failures that never
128
- // occurred (both arms can be ok when pass-rate was simply unmeasured).
129
- const bothArmsOk = !!(data.arms && data.arms.baseline && data.arms.baseline.ok
130
- && data.arms.variant && data.arms.variant.ok);
131
- err(bothArmsOk
132
- ? '[Experiment] inconclusive: metric resolved to pass-rate but no validation commands were given, so pass-rate was never measured — pass --validation (or pick another --metric)'
133
- : '[Experiment] inconclusive: one or both arms failed — see arms.*.error');
134
- }
135
- return { ok: true, data, exitCode };
136
- } catch (e) {
137
- err('[Experiment] error: ' + (e && e.message ? e.message : String(e)));
138
- return { ok: false, error: e && e.message ? e.message : String(e), exitCode: 1 };
139
- }
140
- }
141
-
142
- function printExperimentUsage() {
143
- return [
144
- 'Usage: node index.js experiment --task="..." --metric="..." [flags]',
145
- ' --baseline="..." 对照臂标签 (default: baseline)',
146
- ' --variant="..." 实验臂标签 (default: variant)',
147
- ' --gene=<geneId> 变体臂复用的基因 id (注入其 strategy)',
148
- ' --validation="c1;;c2" 自包含 node 校验命令 (通过率评分; ;; 分隔)',
149
- ' --timeout=<ms> 单臂超时',
150
- ' --request-file=<path> JSON 基底 (显式 flag 覆盖之)',
151
- '',
152
- 'Runs the same task twice (baseline vs variant-with-gene) via a headless',
153
- 'agent CLI and prints a comparison JSON to stdout. Logs go to stderr.',
154
- 'Env: EVOLVER_EXPERIMENT_AGENT_CMD (default claude),',
155
- ' EVOLVER_EXPERIMENT_AGENT_ARGS, EVOLVER_EXPERIMENT_TIMEOUT_MS (300000).',
156
- ].join('\n');
157
- }
158
-
159
- module.exports = { parseExperimentArgs, runExperiment, printExperimentUsage, parseFlags };
@@ -1,233 +0,0 @@
1
- // src/experiment/comparison.js
2
- //
3
- // Thin orchestrator for a comparative experiment: run the SAME task twice --
4
- // a baseline arm (plain task) and a variant arm (task + the reused gene's
5
- // strategy injected) -- through a pluggable agent runner, collect real
6
- // metrics (duration / rounds / tokens / pass-rate), and emit a versioned
7
- // comparison result.
8
- //
9
- // Design notes:
10
- // - This module NEVER requires child_process. The agent runner, gene loader,
11
- // and sandbox runner are all injectable, so unit tests stay deterministic
12
- // (no LLM, no network, no subprocess). Production defaults are lazy-loaded.
13
- // - A failed arm never fabricates a score: if either arm is !ok the winner is
14
- // 'inconclusive' and improvement is null, while still recording whatever
15
- // partial metrics were captured.
16
- 'use strict';
17
-
18
- const { deriveMetric, scoreArm, num, round } = require('./metrics');
19
-
20
- const SCHEMA = 'evolver.experiment.comparison.v1';
21
- const RESULT_TEXT_CAP = 2000;
22
- const EPS = 1e-9;
23
-
24
- // Build the variant prompt by appending the reused gene's strategy, mirroring
25
- // the numbered-list format used in src/gep/prompt.js (`${i+1}. ${s}`).
26
- function buildVariantPrompt(task, gene) {
27
- if (!gene || !Array.isArray(gene.strategy) || gene.strategy.length === 0) return task;
28
- const steps = gene.strategy.map((s, i) => `${i + 1}. ${s}`).join('\n');
29
- return (
30
- task +
31
- '\n\n## Reuse the following proven strategy\n' +
32
- steps +
33
- '\n\nApply the strategy above while completing the task.'
34
- );
35
- }
36
-
37
- // Coerce whatever the agent runner returned into the canonical arm shape.
38
- function normalizeArm(label, raw) {
39
- raw = raw || {};
40
- const tokensIn = num(raw.tokensIn);
41
- const tokensOut = num(raw.tokensOut);
42
- const tokensTotal = Number.isFinite(Number(raw.tokensTotal)) ? num(raw.tokensTotal) : tokensIn + tokensOut;
43
- return {
44
- label: String(label == null ? '' : label),
45
- ok: !!raw.ok,
46
- error: raw.error != null ? String(raw.error) : null,
47
- durationMs: num(raw.durationMs),
48
- rounds: num(raw.rounds),
49
- tokensIn,
50
- tokensOut,
51
- tokensTotal,
52
- costUsd: num(raw.costUsd),
53
- passRate: Number.isFinite(Number(raw.passRate)) ? num(raw.passRate) : (raw.ok ? 1 : 0),
54
- resultText: typeof raw.resultText === 'string' ? raw.resultText.slice(0, RESULT_TEXT_CAP) : '',
55
- exitCode: Number.isFinite(Number(raw.exitCode)) ? num(raw.exitCode) : null,
56
- timedOut: !!raw.timedOut,
57
- };
58
- }
59
-
60
- // Pass-rate for ONE arm: run its `node <script>` validation commands INSIDE that
61
- // arm's own workspace (where its agent just ran), so two arms whose agents
62
- // produced different output get different pass-rates -- the metric is linked to
63
- // the arm, not to a shared empty sandbox. Each command is a `node <script>`
64
- // vetted by sandboxExecutor's allowlist (runSingleCommand rejects anything else).
65
- async function passRateInDir(commands, cwd, runSingleCommand, timeoutMs, warnings) {
66
- let passed = 0;
67
- let total = 0;
68
- for (const cmd of commands) {
69
- total += 1;
70
- try {
71
- const r = await runSingleCommand(cmd, { cwd, timeoutMs });
72
- if (r && r.ok) passed += 1;
73
- } catch (e) {
74
- warnings.push('passrate_command_error: ' + (e && e.message ? e.message : String(e)));
75
- }
76
- }
77
- return total > 0 ? round(passed / total, 4) : 0;
78
- }
79
-
80
- /**
81
- * Run a two-arm comparison.
82
- *
83
- * @param {object} params
84
- * @param {string} params.task 自然语言任务(必填)
85
- * @param {string} [params.baseline='baseline'] 对照臂标签
86
- * @param {string} [params.variant='variant'] 实验臂标签
87
- * @param {string} params.metric 评估指标(必填)
88
- * @param {string} [params.geneId] 变体臂复用的基因 id
89
- * @param {string[]}[params.validationCommands] 自包含 `node <script>` 校验命令
90
- * @param {number} [params.timeoutMs] 单臂超时
91
- * @param {function}[params.agentRunner] (prompt, opts) => Promise<AgentResult>
92
- * @param {function}[params.geneLoader] () => Gene[]
93
- * @param {object} [params.sandbox] { createSandboxDir, cleanupDir, runSingleCommand } (default: sandboxExecutor)
94
- * @returns {Promise<object>} versioned ComparisonResult (see SCHEMA)
95
- */
96
- async function runComparison(params) {
97
- const p = params || {};
98
- const task = String(p.task == null ? '' : p.task).trim();
99
- const baseline = p.baseline ? String(p.baseline) : 'baseline';
100
- const variant = p.variant ? String(p.variant) : 'variant';
101
- const metric = String(p.metric == null ? '' : p.metric);
102
- const geneId = p.geneId ? String(p.geneId) : null;
103
- const validationCommands = Array.isArray(p.validationCommands)
104
- ? p.validationCommands.filter((c) => typeof c === 'string' && c.trim())
105
- : null;
106
- const timeoutMs = Number.isFinite(Number(p.timeoutMs)) ? Number(p.timeoutMs) : undefined;
107
-
108
- if (!task) throw new Error('task is required');
109
- if (!metric) throw new Error('metric is required');
110
-
111
- const agentRunner = typeof p.agentRunner === 'function'
112
- ? p.agentRunner
113
- : require('./agentRunner').runAgentTask;
114
- const geneLoader = typeof p.geneLoader === 'function'
115
- ? p.geneLoader
116
- : require('../gep/assetStore').loadGenes;
117
- const sandbox = p.sandbox && typeof p.sandbox === 'object'
118
- ? p.sandbox
119
- : require('../gep/validator/sandboxExecutor');
120
-
121
- const startedAt = new Date().toISOString();
122
- const t0 = Date.now();
123
- const warnings = [];
124
-
125
- const metricInfo = deriveMetric(metric);
126
- if (!metricInfo.recognized) warnings.push('metric_unrecognized: ' + metric);
127
-
128
- // Look up the reused gene (variant arm). Without a resolved gene the variant
129
- // prompt is identical to the baseline task, so the two arms are NOT a strategy
130
- // comparison -- record an explicit warning so identical arms aren't mistaken
131
- // for one.
132
- let gene = null;
133
- if (geneId) {
134
- let genes = [];
135
- try {
136
- genes = geneLoader() || [];
137
- } catch (e) {
138
- warnings.push('gene_load_error: ' + (e && e.message ? e.message : String(e)));
139
- }
140
- gene = genes.find((g) => g && String(g.id) === geneId) || null;
141
- if (!gene) warnings.push('gene_not_found: ' + geneId + ' (variant arm equals baseline)');
142
- } else {
143
- warnings.push('no_gene: variant arm equals baseline (no strategy injected)');
144
- }
145
-
146
- const hasValidation = !!(validationCommands && validationCommands.length);
147
- if (!hasValidation) warnings.push('passrate_degraded_no_validation');
148
-
149
- let metaRunner = null;
150
- let metaCommand = null;
151
-
152
- const runArm = async (label, prompt) => {
153
- // Each arm runs in its OWN fresh sandbox dir, so the agent works in
154
- // isolation (never the evolver repo / process.cwd()) and its pass-rate
155
- // validation reads that arm's own output, not a shared empty directory.
156
- const workdir = sandbox.createSandboxDir();
157
- let raw;
158
- try {
159
- raw = await agentRunner(prompt, { timeoutMs, cwd: workdir });
160
- } catch (e) {
161
- raw = { ok: false, error: 'agent_runner_threw: ' + (e && e.message ? e.message : String(e)) };
162
- }
163
- if (raw) {
164
- if (metaRunner == null && raw.runnerName) metaRunner = String(raw.runnerName);
165
- if (metaCommand == null && raw.agentCommand) metaCommand = String(raw.agentCommand);
166
- }
167
- const arm = normalizeArm(label, raw);
168
- if (hasValidation) {
169
- arm.passRate = await passRateInDir(validationCommands, workdir, sandbox.runSingleCommand, timeoutMs, warnings);
170
- }
171
- try { sandbox.cleanupDir(workdir); } catch (_) { /* best-effort cleanup */ }
172
- return arm;
173
- };
174
-
175
- // Arms run sequentially: two real agent CLIs in parallel would contend for
176
- // local resources / provider rate limits and muddy the duration metric.
177
- const armBaseline = await runArm(baseline, task);
178
- const armVariant = await runArm(variant, buildVariantPrompt(task, gene));
179
-
180
- const baselineScore = scoreArm(armBaseline, metricInfo.metricField);
181
- const variantScore = scoreArm(armVariant, metricInfo.metricField);
182
-
183
- // Pass-rate is only a real measurement when validation commands ran. Without
184
- // them it's a synthetic ok?1:0, so a pass-rate comparison would falsely tie
185
- // (both arms 1.0) — report it as inconclusive instead of a fake tie.
186
- const passRateNotMeasured = metricInfo.metricField === 'passRate' && !hasValidation;
187
- let winner;
188
- let improvement;
189
- if (!armBaseline.ok || !armVariant.ok || passRateNotMeasured) {
190
- winner = 'inconclusive';
191
- improvement = null;
192
- } else if (Math.abs(baselineScore - variantScore) <= EPS) {
193
- winner = 'tie';
194
- improvement = 0;
195
- } else {
196
- const variantBetter = metricInfo.lowerIsBetter
197
- ? variantScore < baselineScore
198
- : variantScore > baselineScore;
199
- winner = variantBetter ? 'variant' : 'baseline';
200
- if (baselineScore === 0) {
201
- improvement = null;
202
- } else {
203
- const ratio = metricInfo.lowerIsBetter
204
- ? (baselineScore - variantScore) / Math.abs(baselineScore)
205
- : (variantScore - baselineScore) / Math.abs(baselineScore);
206
- improvement = round(ratio, 4);
207
- }
208
- }
209
-
210
- return {
211
- schema: SCHEMA,
212
- task,
213
- metric,
214
- metricField: metricInfo.metricField,
215
- lowerIsBetter: metricInfo.lowerIsBetter,
216
- scoreUnit: metricInfo.scoreUnit,
217
- geneId,
218
- baselineScore,
219
- variantScore,
220
- winner,
221
- improvement,
222
- arms: { baseline: armBaseline, variant: armVariant },
223
- meta: {
224
- runner: metaRunner || 'unknown',
225
- agentCommand: metaCommand || null,
226
- startedAt,
227
- durationMs: Date.now() - t0,
228
- warnings,
229
- },
230
- };
231
- }
232
-
233
- module.exports = { runComparison, buildVariantPrompt, normalizeArm, SCHEMA };
@@ -1,75 +0,0 @@
1
- // src/experiment/metrics.js
2
- //
3
- // Pure, table-driven mapping from a human metric label (e.g. "完成耗时 (s)",
4
- // "轮次", "token", "通过率") onto a per-arm field + comparison direction.
5
- // No I/O, no side effects -- safe to unit-test in isolation.
6
- 'use strict';
7
-
8
- function num(v, fallback) {
9
- const n = Number(v);
10
- return Number.isFinite(n) ? n : (fallback === undefined ? 0 : fallback);
11
- }
12
-
13
- function round(n, digits) {
14
- const f = Math.pow(10, digits);
15
- return Math.round((num(n) + Number.EPSILON) * f) / f;
16
- }
17
-
18
- // Ordered rules. The FIRST rule whose any keyword is a (case-insensitive)
19
- // substring of the metric label wins. Order matters: pass-rate / rounds /
20
- // tokens / cost are checked before duration so a label like "通过率" is not
21
- // swallowed by a looser rule.
22
- const METRIC_RULES = [
23
- { keys: ['通过率', 'pass', 'success', 'accuracy', '准确', '正确率'], field: 'passRate', lowerIsBetter: false },
24
- { keys: ['轮次', 'turn', 'round', 'step', 'iteration', '迭代'], field: 'rounds', lowerIsBetter: true },
25
- { keys: ['token', '令牌'], field: 'tokensTotal', lowerIsBetter: true },
26
- { keys: ['成本', 'cost', 'usd', '价格', '费用'], field: 'costUsd', lowerIsBetter: true },
27
- { keys: ['耗时', 'duration', 'latency', '延迟', '秒', 'second', '(s)', 'time'], field: 'durationMs', lowerIsBetter: true },
28
- ];
29
-
30
- /**
31
- * Resolve a metric label to the per-arm field used for scoring, the
32
- * comparison direction, and the display unit.
33
- *
34
- * @param {string} metricStr
35
- * @returns {{ metricField: string, lowerIsBetter: boolean, scoreUnit: string, recognized: boolean }}
36
- */
37
- function deriveMetric(metricStr) {
38
- const m = String(metricStr || '').toLowerCase();
39
- for (const rule of METRIC_RULES) {
40
- if (rule.keys.some((k) => m.includes(String(k).toLowerCase()))) {
41
- if (rule.field === 'durationMs') {
42
- // Seconds-flavoured labels ("(s)", "秒", "seconds") -> report in seconds.
43
- if (/\(s\)|秒|second/.test(m)) {
44
- return { metricField: 'durationSec', lowerIsBetter: true, scoreUnit: 'seconds', recognized: true };
45
- }
46
- return { metricField: 'durationMs', lowerIsBetter: true, scoreUnit: 'ms', recognized: true };
47
- }
48
- return { metricField: rule.field, lowerIsBetter: rule.lowerIsBetter, scoreUnit: 'raw', recognized: true };
49
- }
50
- }
51
- // Unrecognized -> degrade to pass-rate (higher is better). Caller records a warning.
52
- return { metricField: 'passRate', lowerIsBetter: false, scoreUnit: 'raw', recognized: false };
53
- }
54
-
55
- /**
56
- * Pull the scalar score for one arm given the resolved metric field.
57
- *
58
- * @param {object} arm a normalized arm (see comparison.normalizeArm)
59
- * @param {string} metricField
60
- * @returns {number}
61
- */
62
- function scoreArm(arm, metricField) {
63
- if (!arm) return 0;
64
- switch (metricField) {
65
- case 'durationSec': return round(num(arm.durationMs) / 1000, 2);
66
- case 'durationMs': return num(arm.durationMs);
67
- case 'rounds': return num(arm.rounds);
68
- case 'tokensTotal': return num(arm.tokensTotal);
69
- case 'costUsd': return round(num(arm.costUsd), 4);
70
- case 'passRate': return round(num(arm.passRate), 4);
71
- default: return round(num(arm.passRate), 4);
72
- }
73
- }
74
-
75
- module.exports = { deriveMetric, scoreArm, METRIC_RULES, round, num };