@evomap/evolver 1.89.2 → 1.89.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/.cursor/BUGBOT.md +182 -0
  2. package/.env.example +68 -0
  3. package/.git-commit-guard-token +1 -0
  4. package/.github/CODEOWNERS +63 -0
  5. package/.github/ISSUE_TEMPLATE/good_first_issue.md +23 -0
  6. package/.github/pull_request_template.md +45 -0
  7. package/.github/workflows/test.yml +75 -0
  8. package/CHANGELOG.md +1237 -0
  9. package/README.ja-JP.md +1 -3
  10. package/README.ko-KR.md +1 -3
  11. package/README.md +86 -530
  12. package/README.public.md +569 -0
  13. package/README.zh-CN.md +1 -3
  14. package/SECURITY.md +108 -0
  15. package/assets/gep/events.jsonl +3 -0
  16. package/assets/gep/genes.json +496 -0
  17. package/examples/atp-consumer-quickstart.md +100 -0
  18. package/examples/hello-world.md +38 -0
  19. package/index.js +44 -48
  20. package/package.json +6 -17
  21. package/proxy-package.json +39 -0
  22. package/public.manifest.json +143 -0
  23. package/src/adapters/hookAdapter.js +2 -0
  24. package/src/adapters/scripts/_lockPaths.js +74 -0
  25. package/src/adapters/scripts/evolver-session-start.js +19 -27
  26. package/src/config.js +23 -0
  27. package/src/evolve/guards.js +721 -1
  28. package/src/evolve/pipeline/collect.js +1283 -1
  29. package/src/evolve/pipeline/dispatch.js +421 -1
  30. package/src/evolve/pipeline/enrich.js +440 -1
  31. package/src/evolve/pipeline/hub.js +319 -1
  32. package/src/evolve/pipeline/select.js +274 -1
  33. package/src/evolve/pipeline/signals.js +206 -1
  34. package/src/evolve/utils.js +264 -1
  35. package/src/evolve.js +350 -1
  36. package/src/experiment/agentRunner.js +229 -0
  37. package/src/experiment/cli.js +159 -0
  38. package/src/experiment/comparison.js +233 -0
  39. package/src/experiment/metrics.js +75 -0
  40. package/src/forceUpdate.js +311 -30
  41. package/src/gep/a2aProtocol.js +4455 -1
  42. package/src/gep/antiAbuseTelemetry.js +233 -0
  43. package/src/gep/autoDistillConv.js +205 -1
  44. package/src/gep/autoDistillLlm.js +315 -1
  45. package/src/gep/candidateEval.js +92 -1
  46. package/src/gep/candidates.js +198 -1
  47. package/src/gep/contentHash.js +30 -1
  48. package/src/gep/conversationSniffer.js +266 -1
  49. package/src/gep/crypto.js +89 -1
  50. package/src/gep/curriculum.js +163 -1
  51. package/src/gep/deviceId.js +218 -1
  52. package/src/gep/envFingerprint.js +118 -1
  53. package/src/gep/epigenetics.js +31 -1
  54. package/src/gep/execBridge.js +711 -1
  55. package/src/gep/explore.js +289 -1
  56. package/src/gep/hash.js +15 -1
  57. package/src/gep/hubFetch.js +359 -1
  58. package/src/gep/hubReview.js +207 -1
  59. package/src/gep/hubSearch.js +526 -1
  60. package/src/gep/hubVerify.js +306 -1
  61. package/src/gep/learningSignals.js +89 -1
  62. package/src/gep/memoryGraph.js +1374 -1
  63. package/src/gep/memoryGraphAdapter.js +203 -1
  64. package/src/gep/mutation.js +203 -1
  65. package/src/gep/narrativeMemory.js +108 -1
  66. package/src/gep/openPRRegistry.js +205 -1
  67. package/src/gep/personality.js +423 -1
  68. package/src/gep/policyCheck.js +599 -1
  69. package/src/gep/prompt.js +836 -1
  70. package/src/gep/recallInject.js +409 -1
  71. package/src/gep/recallVerifier.js +318 -1
  72. package/src/gep/reflection.js +177 -1
  73. package/src/gep/sanitize.js +9 -0
  74. package/src/gep/selector.js +602 -1
  75. package/src/gep/skillDistiller.js +1294 -1
  76. package/src/gep/solidify.js +1699 -1
  77. package/src/gep/strategy.js +136 -1
  78. package/src/gep/tokenSavings.js +88 -1
  79. package/src/gep/validator/sandboxExecutor.js +29 -1
  80. package/src/gep/workspaceKeychain.js +174 -1
  81. package/src/proxy/extensions/traceControl.js +99 -1
  82. package/src/proxy/index.js +14 -5
  83. package/src/proxy/inject.js +52 -1
  84. package/src/proxy/lifecycle/manager.js +30 -0
  85. package/src/proxy/mailbox/store.js +2 -1
  86. package/src/proxy/router/messages_route.js +13 -2
  87. package/src/proxy/trace/extractor.js +646 -1
  88. package/src/proxy/trace/usage.js +105 -1
  89. package/CONTRIBUTING.md +0 -19
  90. package/assets/cover.png +0 -0
  91. package/assets/gep/genes.seed.json +0 -245
  92. package/scripts/a2a_export.js +0 -63
  93. package/scripts/a2a_ingest.js +0 -79
  94. package/scripts/a2a_promote.js +0 -118
  95. package/scripts/analyze_by_skill.js +0 -121
  96. package/scripts/build_binaries.js +0 -479
  97. package/scripts/check-changelog.js +0 -166
  98. package/scripts/extract_log.js +0 -85
  99. package/scripts/generate_history.js +0 -75
  100. package/scripts/gep_append_event.js +0 -96
  101. package/scripts/gep_personality_report.js +0 -234
  102. package/scripts/human_report.js +0 -147
  103. package/scripts/recall-verify-report.js +0 -234
  104. package/scripts/recover_loop.js +0 -61
  105. package/scripts/seed-merchants.js +0 -91
  106. package/scripts/suggest_version.js +0 -89
  107. package/scripts/validate-modules.js +0 -38
  108. package/scripts/validate-suite.js +0 -78
  109. package/skills/index.json +0 -14
  110. /package/{skills → bundled-skills}/_meta/SKILL.md +0 -0
@@ -0,0 +1,100 @@
1
+ # ATP Consumer Quick Start
2
+
3
+ Three commands to place, inspect, and verify an order on the
4
+ Agent Transaction Protocol (ATP) without writing any code.
5
+
6
+ ## Prerequisites
7
+
8
+ - `@evomap/evolver` installed and registered with the Hub
9
+ (your evolver directory has a valid `.env` containing `A2A_HUB_URL` and
10
+ `A2A_NODE_SECRET`; see `README.md` for initial setup).
11
+ - Enough credits on the Hub to cover the order budget.
12
+ - A remote merchant with a matching capability active on the Hub.
13
+ (If you have `EVOLVER_ATP=auto` set the default, every evolver instance is
14
+ already advertising a generic `code_evolution` service -- this is where the
15
+ cold-start demand usually terminates.)
16
+
17
+ ## 1. Place an order and wait for settlement
18
+
19
+ ```bash
20
+ evolver buy code_review,bug_fix --budget 10 --question "Please review my latest patch for null-safety bugs"
21
+ ```
22
+
23
+ Output:
24
+
25
+ ```
26
+ [ATP] Placing order: capabilities=code_review,bug_fix budget=10 mode=fastest
27
+ [ATP-Consumer] Order placed: ord_abcd1234 -> merchant: node_xyz
28
+ [ATP] Order settled: ord_abcd1234
29
+ [ATP] Final status: { ... delivery payload ... }
30
+ ```
31
+
32
+ `buy` uses `consumerAgent.orderAndWait` internally: it places the order, polls
33
+ until the proof is settled (or the 300s timeout fires), then exits `0`.
34
+
35
+ Add `--no-wait` if you prefer to fire-and-forget and check status later with
36
+ `orders`.
37
+
38
+ ## 2. List your recent orders
39
+
40
+ ```bash
41
+ evolver orders --role consumer --status settled --limit 5
42
+ ```
43
+
44
+ ```bash
45
+ [ATP] Showing 3 order(s):
46
+ - ord_abcd1234 | status=settled | created=2026-04-22T12:00:00Z
47
+ - ord_aaaa1111 | status=settled | created=2026-04-20T08:30:00Z
48
+ - ord_bbbb2222 | status=disputed | created=2026-04-18T17:12:00Z
49
+ ```
50
+
51
+ Flip `--role merchant` to see orders you delivered. `--json` dumps the raw
52
+ payload if you want to pipe it into another tool.
53
+
54
+ ## 3. Verify delivery (bilateral mode)
55
+
56
+ If you used `--verify=bilateral` you must confirm delivery manually:
57
+
58
+ ```bash
59
+ evolver verify ord_abcd1234 --action confirm
60
+ ```
61
+
62
+ Or trigger AI judge verification:
63
+
64
+ ```bash
65
+ evolver verify ord_abcd1234 --action ai_judge
66
+ ```
67
+
68
+ ## Opt-in auto-buy (experimental, beta only)
69
+
70
+ If you run `evolver` in loop mode and want it to automatically place an ATP
71
+ order when it detects a `capability_gap` signal it cannot solve locally:
72
+
73
+ ```bash
74
+ export EVOLVER_ATP_AUTOBUY=on
75
+ export ATP_AUTOBUY_DAILY_CAP_CREDITS=50 # hard daily ceiling (default 50)
76
+ export ATP_AUTOBUY_PER_ORDER_CAP_CREDITS=10 # hard per-order ceiling (default 10)
77
+ evolver run --loop
78
+ ```
79
+
80
+ Safety properties of the auto-buyer:
81
+
82
+ - Default OFF; must be explicitly enabled.
83
+ - Cold-start grace period (first 5 minutes) halves the effective caps in case
84
+ of a restart storm or misconfiguration.
85
+ - Same question + capability pair is only bought once every 24 hours (UTC).
86
+ - Every Hub call has a hard 3s timeout race so the evolve loop never blocks.
87
+ - All budget numbers are clamped to `>= 0` on both server and client.
88
+
89
+ If something goes wrong, just `unset EVOLVER_ATP_AUTOBUY` and restart.
90
+
91
+ ## Troubleshooting
92
+
93
+ - `no_matching_services`: no merchant on the Hub currently advertises the
94
+ capabilities you asked for, or every candidate failed the reliability filter.
95
+ Try broader `caps`, raise `--budget`, or wait for new merchants to register.
96
+ - `insufficient_balance`: top up your node's credits (via faucet or validator
97
+ work) before retrying.
98
+ - `order_timeout`: the merchant never submitted delivery. The escrow cron will
99
+ refund you within 7 days; or you can dispute earlier with
100
+ `evolver verify ord_xxx --action ai_judge`.
@@ -0,0 +1,38 @@
1
+ # Hello World -- Quick Start
2
+
3
+ Try Evolver locally in 3 steps:
4
+
5
+ 1. Clone and enter:
6
+
7
+ ```bash
8
+ git clone https://github.com/EvoMap/evolver.git && cd evolver
9
+ ```
10
+
11
+ 2. Install and run a single evolution:
12
+
13
+ ```bash
14
+ npm install
15
+ node index.js
16
+ ```
17
+
18
+ 3. Review mode (human-in-the-loop):
19
+
20
+ ```bash
21
+ node index.js --review
22
+ ```
23
+
24
+ Expected: the tool prints a GEP prompt to stdout. Use `--loop` to run continuously:
25
+
26
+ ```bash
27
+ node index.js --loop
28
+ ```
29
+
30
+ ## Without the EvoMap Hub
31
+
32
+ Evolver works fully offline. The Hub connection (see `A2A_HUB_URL` / `A2A_NODE_ID` in the main README) is only needed for network features like skill sharing, worker pool, and evolution leaderboards.
33
+
34
+ ## Next steps
35
+
36
+ - Read the main [README.md](../README.md) for the full feature list and strategy presets.
37
+ - Visit [evomap.ai](https://evomap.ai) to register a node and connect to the EvoMap network.
38
+ - Explore the [GEP Protocol](https://evomap.ai/wiki) to understand Genes, Capsules, and EvolutionEvents.
package/index.js CHANGED
@@ -268,23 +268,17 @@ function getLastSignals(statePath) {
268
268
 
269
269
  // Singleton Guard - prevent multiple evolver daemon instances.
270
270
  //
271
- // Round-4: pidfile location previously defaulted to __dirname, which is a
272
- // DIFFERENT path per install mode -- /usr/local/lib/node_modules/... for a
273
- // global install, the dev-clone path for `node index.js`, a transient
274
- // $NPM_CACHE/_npx/<hash> for `npx evolver`. Two daemons launched under
275
- // different install modes never saw each other's lock and could run
276
- // concurrently against the same ~/.evomap/node_secret, ping-ponging on
277
- // secret rotation and silently entering reauth backoff -- the user-
278
- // reported "first launch ok, idle, then dead forever" pattern. Default
279
- // now lives under the per-user state dir so all install modes converge.
280
- // EVOLVER_LOCK_DIR still overrides for tests / sandboxed runs.
281
- function getLockFilePath() {
282
- if (process.env.EVOLVER_LOCK_DIR) {
283
- return path.join(process.env.EVOLVER_LOCK_DIR, 'evolver.pid');
284
- }
285
- // os.homedir() is cross-platform; process.env.HOME is unset on Windows.
286
- return path.join(os.homedir(), '.evomap', 'instance.lock');
287
- }
271
+ // Lock location + lease tunables live in src/adapters/scripts/_lockPaths.js
272
+ // (issue #176): the session-start hook's auto-restart guard needs the exact
273
+ // same resolution, and inlining it in both places drifted. The Round-4
274
+ // (per-install-mode pidfile convergence) and Round-9 (lease staleness)
275
+ // history notes moved there with the code.
276
+ const {
277
+ getLockFilePath,
278
+ lockIsStaleByLease: _lockIsStaleByLease,
279
+ STALE_LOCK_TTL_MS,
280
+ LOCK_REFRESH_MS,
281
+ } = require('./src/adapters/scripts/_lockPaths');
288
282
 
289
283
  function _writeLockAtomic(lockFile, payload) {
290
284
  // Round-6 (§19.8): the previous implementation used tmp + rename, which
@@ -372,38 +366,11 @@ function _lockPayload() {
372
366
  });
373
367
  }
374
368
 
375
- // Round-9: lease tunables for the daemon lock. A live daemon refreshes the
376
- // lock mtime every LOCK_REFRESH_MS; a lock whose mtime is older than
377
- // STALE_LOCK_TTL_MS (and that was written by a lease-aware daemon) is
378
- // treated as stale even if its PID happens to be alive -- closing the
379
- // "crash + PID reuse -> new daemon silently refuses to start" hole and the
380
- // "SIGKILL leaves a stale lock nobody reclaims" hole. The TTL is well above
381
- // the heartbeat interval (default 6min) so a healthy daemon never trips it.
382
- // On Windows, SIGTERM is implemented as TerminateProcess() (not a catchable
383
- // signal), so the shutdown() handler that calls releaseLock() never runs.
384
- // The lock file stays on disk with the dead PID. Reduce the TTL on Windows
385
- // so a subsequent start doesn't wait 15 minutes to reclaim the stale lock.
386
- // Unix dropped from 15 min -> 5 min so a wedged daemon does not block takeover
387
- // for a quarter hour. 5 min is still 2.5x the 2-min Unix refresh cadence.
388
- // Windows 3 min TTL gets a 1-min refresh (3x margin) since 2-min refresh left
389
- // only 1.5x margin against transient FS hiccups.
390
- const STALE_LOCK_TTL_MS = process.platform === 'win32' ? 3 * 60_000 : 5 * 60_000;
391
- const LOCK_REFRESH_MS = process.platform === 'win32' ? 1 * 60_000 : 2 * 60_000;
369
+ // STALE_LOCK_TTL_MS / LOCK_REFRESH_MS / _lockIsStaleByLease come from
370
+ // src/adapters/scripts/_lockPaths.js (required next to getLockFilePath
371
+ // above) see issue #176 and the Round-9 history note in that module.
392
372
  let _lockRefreshTimer = null;
393
373
 
394
- // Returns true if the lock was written by a lease-aware daemon AND its
395
- // mtime is older than the stale TTL -- i.e. no live owner is refreshing it,
396
- // so it is safe to reclaim regardless of whether the recorded PID resolves.
397
- function _lockIsStaleByLease(lockFile, payload) {
398
- if (!payload || payload.lease !== true) return false;
399
- try {
400
- const ageMs = Date.now() - fs.statSync(lockFile).mtimeMs;
401
- return ageMs > STALE_LOCK_TTL_MS;
402
- } catch (_) {
403
- return false;
404
- }
405
- }
406
-
407
374
  // Start refreshing the lock file's mtime so other processes can tell this
408
375
  // daemon is alive without trusting a (recyclable) PID. unref'd: it never
409
376
  // keeps the event loop open on its own, but fires for as long as the daemon
@@ -2955,10 +2922,39 @@ async function main() {
2955
2922
  process.exit(1);
2956
2923
  }
2957
2924
 
2925
+ } else if (command === 'experiment') {
2926
+ // Comparative experiment runner: run the SAME task twice -- a baseline arm
2927
+ // and a variant arm that reuses a gene's strategy -- via a headless agent
2928
+ // CLI, collect duration/rounds/tokens/pass-rate, and print a comparison
2929
+ // JSON to stdout. Consumed by EvoMap Desktop's ExperimentsAPI.Run, which
2930
+ // spawns `node index.js experiment --request-file=<json>` and parses stdout.
2931
+ try {
2932
+ const expCli = require('./src/experiment/cli');
2933
+ const parsed = expCli.parseExperimentArgs(args.slice(1));
2934
+ if (!parsed.ok) {
2935
+ console.error('[Experiment] ' + parsed.error);
2936
+ console.error(expCli.printExperimentUsage());
2937
+ process.exit(2);
2938
+ }
2939
+ const res = await expCli.runExperiment(parsed.opts, { err: (...a) => console.error(...a) });
2940
+ // stdout carries ONLY the structured JSON so the Go caller can JSON.parse
2941
+ // it without log contamination; all logging above went to stderr. res.data
2942
+ // is already secret-redacted by runExperiment (sanitizePayload).
2943
+ if (res && res.data) process.stdout.write(JSON.stringify(res.data) + '\n');
2944
+ process.exit(res && typeof res.exitCode === 'number' ? res.exitCode : (res && res.ok ? 0 : 1));
2945
+ } catch (expErr) {
2946
+ console.error('[Experiment] CLI error:', expErr && expErr.message || expErr);
2947
+ process.exit(1);
2948
+ }
2949
+
2958
2950
  } else {
2959
- console.log(`Usage: node index.js [run|/evolve|login|logout|solidify|review|distill|fetch|sync|asset-log|webui|setup-hooks|recipe|buy|orders|verify|atp|atp-complete] [--loop]
2951
+ console.log(`Usage: node index.js [run|/evolve|login|logout|solidify|review|distill|fetch|sync|asset-log|webui|setup-hooks|recipe|buy|orders|verify|atp|atp-complete|experiment] [--loop]
2960
2952
  - login (authorize this device via the hub, gh-auth-login style; stores an OAuth token used instead of node_secret)
2961
2953
  - logout (remove the stored OAuth token)
2954
+ - experiment flags:
2955
+ - --task="..." --metric="..." (required; same task, baseline vs variant)
2956
+ - --gene=<geneId> (variant arm reuses this gene's strategy)
2957
+ - --baseline="..." --variant="..." --validation="c1;;c2" --request-file=<json>
2962
2958
  - recipe flags:
2963
2959
  - build --title="..." --genes=<asset_id,...> [--description] [--price=N] [--publish]
2964
2960
  (builds a DRAFT DNA blueprint; --publish is opt-in)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evomap/evolver",
3
- "version": "1.89.2",
3
+ "version": "1.89.4",
4
4
  "description": "A GEP-powered self-evolution engine for AI agents. Features automated log analysis and Genome Evolution Protocol (GEP) for auditable, reusable evolution assets.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -28,10 +28,12 @@
28
28
  "run": "node index.js run",
29
29
  "solidify": "node index.js solidify",
30
30
  "review": "node index.js review",
31
+ "distill": "node index.js distill",
32
+ "webui": "node index.js webui",
33
+ "test": "node -e \"const fs=require('fs'),cp=require('child_process');const all=fs.readdirSync('test').filter(f=>f.endsWith('.test.js'));const iso=new Set(['solidifyIntegration.test.js']);const others=all.filter(f=>!iso.has(f)).map(f=>'test/'+f);const isoFiles=all.filter(f=>iso.has(f)).map(f=>'test/'+f);if(others.length)cp.execSync('node --test '+others.join(' '),{stdio:'inherit'});if(isoFiles.length)cp.execSync('node --test '+isoFiles.join(' '),{stdio:'inherit'})\"",
31
34
  "a2a:export": "node scripts/a2a_export.js",
32
35
  "a2a:ingest": "node scripts/a2a_ingest.js",
33
- "a2a:promote": "node scripts/a2a_promote.js",
34
- "test": "node -e \"const fs=require('fs'),cp=require('child_process');const all=fs.readdirSync('test').filter(f=>f.endsWith('.test.js'));const iso=new Set(['solidifyIntegration.test.js']);const others=all.filter(f=>!iso.has(f)).map(f=>'test/'+f);const isoFiles=all.filter(f=>iso.has(f)).map(f=>'test/'+f);if(others.length)cp.execSync('node --test '+others.join(' '),{stdio:'inherit'});if(isoFiles.length)cp.execSync('node --test '+isoFiles.join(' '),{stdio:'inherit'})\""
36
+ "a2a:promote": "node scripts/a2a_promote.js"
35
37
  },
36
38
  "engines": {
37
39
  "node": ">=22.12"
@@ -48,18 +50,5 @@
48
50
  },
49
51
  "optionalDependencies": {
50
52
  "@napi-rs/keyring": "^1.1.6"
51
- },
52
- "files": [
53
- "assets/",
54
- "index.js",
55
- "src/",
56
- "scripts/",
57
- "skills/",
58
- "README.md",
59
- "README.zh-CN.md",
60
- "README.ja-JP.md",
61
- "SKILL.md",
62
- "CONTRIBUTING.md",
63
- "LICENSE"
64
- ]
53
+ }
65
54
  }
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@evomap/proxy",
3
+ "version": "0.1.0",
4
+ "description": "Local mailbox proxy for agent-to-hub communication via Evomap. Decouples agents from Hub business details through an async message queue.",
5
+ "main": "src/proxy/index.js",
6
+ "exports": {
7
+ ".": "./src/proxy/index.js",
8
+ "./store": "./src/proxy/mailbox/store.js",
9
+ "./transport": "./src/gep/mailboxTransport.js"
10
+ },
11
+ "files": [
12
+ "src/proxy/",
13
+ "src/gep/mailboxTransport.js"
14
+ ],
15
+ "scripts": {
16
+ "test": "node --test test/mailboxStore.test.js test/proxyServer.test.js test/proxySettings.test.js test/taskMonitor.test.js"
17
+ },
18
+ "keywords": [
19
+ "evomap",
20
+ "proxy",
21
+ "mailbox",
22
+ "agent",
23
+ "a2a",
24
+ "gep"
25
+ ],
26
+ "license": "MIT",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/EvoMap/evolver"
30
+ },
31
+ "engines": {
32
+ "node": ">=18.0.0"
33
+ },
34
+ "dependencies": {},
35
+ "peerDependencies": {},
36
+ "publishConfig": {
37
+ "access": "public"
38
+ }
39
+ }
@@ -0,0 +1,143 @@
1
+ {
2
+ "version": 1,
3
+ "outDir": "dist-public",
4
+ "include": [
5
+ "assets/cover.png",
6
+ "index.js",
7
+ "package.json",
8
+ "README.public.md",
9
+ "README.zh-CN.md",
10
+ "README.ja-JP.md",
11
+ "README.ko-KR.md",
12
+ "SKILL.md",
13
+ "CONTRIBUTING.md",
14
+ "LICENSE",
15
+ "src/**",
16
+ "bundled-skills/**",
17
+ "scripts/*.js",
18
+ "test/*.test.js",
19
+ "test/helpers/**",
20
+ "examples/**",
21
+ ".github/**",
22
+ ".gitignore",
23
+ ".npmignore"
24
+ ],
25
+ "exclude": [
26
+ ".github/CODEOWNERS",
27
+ "assets/gep/candidates.jsonl",
28
+ "assets/gep/external_candidates.jsonl",
29
+ "assets/gep/genes.json",
30
+ "assets/gep/capsules.json",
31
+ "assets/gep/events.jsonl",
32
+ "assets/gep/genes.jsonl",
33
+ "assets/gep/capsules.jsonl",
34
+ "assets/gep/a2a/**",
35
+ "docs/**",
36
+ "memory/**",
37
+ "dist-public/**",
38
+ ".evolver/**",
39
+ "scripts/build_public.js",
40
+ "scripts/publish_public.js",
41
+ "scripts/pre_publish_check.js",
42
+ "scripts/normalize_skill2gep_genes.js",
43
+ "scripts/normalize_skill2gep_capsules.js",
44
+ "scripts/publish_skill2gep_bundle.js",
45
+ "scripts/repush_skill2gep_skills.js",
46
+ "scripts/evolver.service",
47
+ "scripts/com.evomap.evolver.plist",
48
+ "scripts/install-evolver-windows.ps1",
49
+ "public.manifest.json",
50
+ "test/Dockerfile",
51
+ "test/fixtures/**",
52
+ "test/llm_helper.js",
53
+ "test/proxyTraceExtractor.test.js",
54
+ "test/proxyAutoInject.test.js",
55
+ "test/vibe_test.js",
56
+ "test/build-exclude.test.js",
57
+ "test/npm-pack-includes-scripts.test.js",
58
+ "test/validator.test.js",
59
+ "test/validatorReportDiagnostics.test.js",
60
+ "test/selfPR.test.js",
61
+ "test/execBridge.test.js",
62
+ "test/autoDistillLlm.test.js",
63
+ "test/modelRouter.test.js",
64
+ "docker-compose.test.yml",
65
+ ".git/**",
66
+ ".cursor/**"
67
+ ],
68
+ "rename": {
69
+ "README.public.md": "README.md",
70
+ "bundled-skills": "skills"
71
+ },
72
+ "obfuscate": [
73
+ "src/evolve.js",
74
+ "src/evolve/guards.js",
75
+ "src/evolve/pipeline/collect.js",
76
+ "src/evolve/pipeline/signals.js",
77
+ "src/evolve/pipeline/hub.js",
78
+ "src/evolve/pipeline/enrich.js",
79
+ "src/evolve/pipeline/select.js",
80
+ "src/evolve/pipeline/dispatch.js",
81
+ "src/evolve/utils.js",
82
+ "src/gep/selector.js",
83
+ "src/gep/mutation.js",
84
+ "src/gep/solidify.js",
85
+ "src/gep/tokenSavings.js",
86
+ "src/gep/prompt.js",
87
+ "src/gep/candidates.js",
88
+ "src/gep/reflection.js",
89
+ "src/gep/narrativeMemory.js",
90
+ "src/gep/curriculum.js",
91
+ "src/gep/personality.js",
92
+ "src/gep/learningSignals.js",
93
+ "src/gep/memoryGraph.js",
94
+ "src/gep/memoryGraphAdapter.js",
95
+ "src/gep/openPRRegistry.js",
96
+ "src/gep/recallVerifier.js",
97
+ "src/gep/strategy.js",
98
+ "src/gep/candidateEval.js",
99
+ "src/gep/hubVerify.js",
100
+ "src/gep/crypto.js",
101
+ "src/gep/contentHash.js",
102
+ "src/gep/a2aProtocol.js",
103
+ "src/gep/hubSearch.js",
104
+ "src/gep/hubReview.js",
105
+ "src/gep/hubFetch.js",
106
+ "src/gep/policyCheck.js",
107
+ "src/gep/hash.js",
108
+ "src/gep/epigenetics.js",
109
+ "src/gep/deviceId.js",
110
+ "src/gep/envFingerprint.js",
111
+ "src/gep/antiAbuseTelemetry.js",
112
+ "src/gep/skillDistiller.js",
113
+ "src/gep/explore.js",
114
+ "src/gep/conversationSniffer.js",
115
+ "src/gep/execBridge.js",
116
+ "src/gep/autoDistillLlm.js",
117
+ "src/gep/autoDistillConv.js",
118
+ "src/gep/recallInject.js",
119
+ "src/gep/workspaceKeychain.js",
120
+ "src/proxy/inject.js",
121
+ "src/proxy/trace/extractor.js",
122
+ "src/proxy/trace/usage.js",
123
+ "src/proxy/extensions/traceControl.js"
124
+ ],
125
+ "rewrite": {
126
+ "package.json": {
127
+ "replace": [
128
+ {
129
+ "from": "\"name\": \"evolver\"",
130
+ "to": "\"name\": \"@evomap/evolver\""
131
+ }
132
+ ]
133
+ },
134
+ "README.zh-CN.md": {
135
+ "replace": [
136
+ {
137
+ "from": "本仓库作为 public 仓库的私有维护区。",
138
+ "to": "本仓库为公开发行版本。"
139
+ }
140
+ ]
141
+ }
142
+ }
143
+ }
@@ -168,6 +168,7 @@ function copyHookScripts(destDir, evolverRoot) {
168
168
  const scripts = [
169
169
  '_runtimePaths.js',
170
170
  '_memoryFiltering.js',
171
+ '_lockPaths.js',
171
172
  'evolver-session-start.js',
172
173
  'evolver-signal-detect.js',
173
174
  'evolver-session-end.js',
@@ -255,6 +256,7 @@ function removeHookScripts(hooksDir) {
255
256
  const scripts = [
256
257
  '_runtimePaths.js',
257
258
  '_memoryFiltering.js',
259
+ '_lockPaths.js',
258
260
  'evolver-session-start.js',
259
261
  'evolver-signal-detect.js',
260
262
  'evolver-session-end.js',
@@ -0,0 +1,74 @@
1
+ // _lockPaths.js
2
+ // Single source of truth for the daemon singleton-lock location and lease
3
+ // tunables, shared by the daemon (index.js) and the session-start hook's
4
+ // auto-restart guard (evolver-session-start.js).
5
+ //
6
+ // Issue #176: the hook used to replicate this logic inline ("keep index.js
7
+ // out of the hook's require graph"), which could silently diverge whenever
8
+ // the daemon's lock resolution changed. This module keeps that property —
9
+ // it depends only on fs/os/path — while making divergence structurally
10
+ // impossible.
11
+ //
12
+ // Deployed-layout constraint (PR #163): hook scripts are COPIED into the
13
+ // host's hooks dir (e.g. `.claude/hooks/`) and run from there, so every
14
+ // same-dir helper they require must be a sibling file listed in
15
+ // hookAdapter.js's copy/remove lists (enforced by test/adapters.test.js).
16
+
17
+ const fs = require('fs');
18
+ const os = require('os');
19
+ const path = require('path');
20
+
21
+ // Round-4 (see the history note above index.js's daemon-lock block): the
22
+ // pidfile previously defaulted to __dirname, which differs per install mode
23
+ // (global install vs dev clone vs npx cache), so two daemons launched under
24
+ // different install modes never saw each other's lock. Default now lives
25
+ // under the per-user state dir so all install modes converge.
26
+ // EVOLVER_LOCK_DIR still overrides for tests / sandboxed runs — note the
27
+ // basename differs from the default in that case (`evolver.pid` vs
28
+ // `instance.lock`).
29
+ function getLockFilePath(env) {
30
+ const e = env || process.env;
31
+ if (e.EVOLVER_LOCK_DIR) {
32
+ return path.join(e.EVOLVER_LOCK_DIR, 'evolver.pid');
33
+ }
34
+ // os.homedir() is cross-platform; process.env.HOME is unset on Windows.
35
+ return path.join(os.homedir(), '.evomap', 'instance.lock');
36
+ }
37
+
38
+ // Round-9: lease tunables for the daemon lock. A live daemon refreshes the
39
+ // lock mtime every LOCK_REFRESH_MS; a lock whose mtime is older than
40
+ // STALE_LOCK_TTL_MS (and that was written by a lease-aware daemon) is
41
+ // treated as stale even if its PID happens to be alive — closing the
42
+ // "crash + PID reuse -> new daemon silently refuses to start" hole and the
43
+ // "SIGKILL leaves a stale lock nobody reclaims" hole. The TTL is well above
44
+ // the heartbeat interval (default 6min) so a healthy daemon never trips it.
45
+ // On Windows, SIGTERM is implemented as TerminateProcess() (not a catchable
46
+ // signal), so the daemon's releaseLock() never runs and the lock file stays
47
+ // on disk with the dead PID — hence the shorter Windows TTL, with a 1-min
48
+ // refresh (3x margin against transient FS hiccups). Unix: 5 min TTL is
49
+ // 2.5x the 2-min refresh cadence.
50
+ const STALE_LOCK_TTL_MS = process.platform === 'win32' ? 3 * 60_000 : 5 * 60_000;
51
+ const LOCK_REFRESH_MS = process.platform === 'win32' ? 1 * 60_000 : 2 * 60_000;
52
+
53
+ // Returns true if the lock was written by a lease-aware daemon AND its
54
+ // mtime is older than the stale TTL — i.e. no live owner is refreshing it,
55
+ // so it is safe to treat the recorded PID as dead regardless of whether
56
+ // process.kill(pid, 0) resolves (the PID may have been reused). Locks
57
+ // written by pre-lease daemons (payload.lease !== true) are never judged
58
+ // stale by mtime, so we never falsely steal an older daemon's lock.
59
+ function lockIsStaleByLease(lockFile, payload) {
60
+ if (!payload || payload.lease !== true) return false;
61
+ try {
62
+ const ageMs = Date.now() - fs.statSync(lockFile).mtimeMs;
63
+ return ageMs > STALE_LOCK_TTL_MS;
64
+ } catch (_) {
65
+ return false;
66
+ }
67
+ }
68
+
69
+ module.exports = {
70
+ getLockFilePath,
71
+ lockIsStaleByLease,
72
+ STALE_LOCK_TTL_MS,
73
+ LOCK_REFRESH_MS,
74
+ };
@@ -9,6 +9,10 @@ const os = require('os');
9
9
 
10
10
  const { findEvolverRoot, findMemoryGraph, resolveProjectDir, resolveWorkspaceId, isGitWorkspace } = require('./_runtimePaths');
11
11
  const { filterRelevantOutcomes } = require('./_memoryFiltering');
12
+ // Top-level on purpose: a missing sibling helper in the deployed hooks dir
13
+ // must fail LOUD at load time (the #547 failure mode), not vanish inside
14
+ // _maybeRestartDaemon's catch-all and silently disable daemon auto-restart.
15
+ const lockPaths = require('./_lockPaths');
12
16
 
13
17
  // Auto-restart guard: if the evolver daemon is not running when a new agent
14
18
  // session starts, attempt a background restart. This covers the "idle-death"
@@ -35,43 +39,31 @@ function _maybeRestartDaemon(evolverRoot) {
35
39
  if (!lifecyclePath || !fs.existsSync(lifecyclePath)) return;
36
40
 
37
41
  // Check if daemon is running by looking for the PID file / lock file.
38
- // R12: index.js:getLockFilePath honors EVOLVER_LOCK_DIR. If that env is
39
- // set the lock file lives at <EVOLVER_LOCK_DIR>/evolver.pid (basename
40
- // differs from the default!); otherwise fall back to the canonical
41
- // ~/.evomap/instance.lock. We replicate the logic inline rather than
42
- // importing index.js, since pulling the daemon module into the hook
43
- // would load far more than we need.
44
- var lockFile = process.env.EVOLVER_LOCK_DIR
45
- ? path.join(process.env.EVOLVER_LOCK_DIR, 'evolver.pid')
46
- : path.join(os.homedir(), '.evomap', 'instance.lock');
47
- // R1: PID-reuse defense. process.kill(pid, 0) only proves SOME process
48
- // owns that PID -- after macOS sleep / OOM, the kernel may have reused
49
- // the slain daemon's PID for an unrelated process (Chrome tab, shell).
50
- // Mirror index.js:_lockIsStaleByLease (search for STALE_LOCK_TTL_MS
51
- // around line 373): a lease-aware daemon refreshes the lock mtime on a
52
- // timer, so if mtime is older than the TTL the daemon is dead/wedged
53
- // regardless of kill(0). Constants inlined to keep index.js out of the
54
- // hook's require graph.
55
- var STALE_LOCK_TTL_MS = process.platform === 'win32' ? 3 * 60_000 : 5 * 60_000;
42
+ // Lock resolution + lease staleness live in ./_lockPaths (issue #176) —
43
+ // the same module index.js uses, so the hook can never drift from the
44
+ // daemon again. It is fs/os/path-only, keeping index.js out of the
45
+ // hook's require graph (R12), and ships in hookAdapter.js's copy list
46
+ // for the deployed `.claude/hooks/` layout (PR #163).
47
+ var lockFile = lockPaths.getLockFilePath();
56
48
  var daemonRunning = false;
57
49
  try {
58
50
  if (fs.existsSync(lockFile)) {
59
51
  var raw = fs.readFileSync(lockFile, 'utf8').trim();
60
52
  var payload = raw && raw[0] === '{' ? JSON.parse(raw) : { pid: parseInt(raw, 10) };
61
53
  if (payload && payload.pid > 0) {
54
+ // R1: PID-reuse defense. process.kill(pid, 0) only proves SOME
55
+ // process owns that PID -- after macOS sleep / OOM, the kernel may
56
+ // have reused the slain daemon's PID for an unrelated process.
62
57
  try { process.kill(payload.pid, 0); daemonRunning = true; } catch (e) {
63
58
  // EPERM = process exists but owned by a different user; still a live daemon.
64
59
  if (e && e.code === 'EPERM') daemonRunning = true;
65
60
  }
66
- // Lease staleness overrides kill(0)=alive. Only trust mtime when
67
- // the payload came from a lease-aware daemon (matches index.js's
68
- // _lockIsStaleByLease guard) so we never falsely steal an older
69
- // pre-lease daemon's lock.
70
- if (daemonRunning && payload.lease === true) {
71
- try {
72
- var ageMs = Date.now() - fs.statSync(lockFile).mtimeMs;
73
- if (ageMs > STALE_LOCK_TTL_MS) daemonRunning = false;
74
- } catch (_) { /* stat failed: leave running flag as-is */ }
61
+ // Lease staleness overrides kill(0)=alive: a lease-aware daemon
62
+ // refreshes the lock mtime on a timer, so an expired lease means
63
+ // dead/wedged regardless of kill(0). Pre-lease locks are never
64
+ // judged stale by mtime (lockIsStaleByLease handles both).
65
+ if (daemonRunning && lockPaths.lockIsStaleByLease(lockFile, payload)) {
66
+ daemonRunning = false;
75
67
  }
76
68
  }
77
69
  }