@evomap/evolver 1.89.2 → 1.89.3

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 (65) hide show
  1. package/README.ja-JP.md +1 -3
  2. package/README.ko-KR.md +1 -3
  3. package/README.md +1 -3
  4. package/README.zh-CN.md +1 -3
  5. package/assets/gep/genes.seed.json +251 -0
  6. package/index.js +14 -47
  7. package/package.json +1 -1
  8. package/scripts/refresh_stars_badge.js +168 -0
  9. package/src/adapters/hookAdapter.js +2 -0
  10. package/src/adapters/scripts/_lockPaths.js +74 -0
  11. package/src/adapters/scripts/evolver-session-start.js +19 -27
  12. package/src/evolve/guards.js +1 -1
  13. package/src/evolve/pipeline/collect.js +1 -1
  14. package/src/evolve/pipeline/dispatch.js +1 -1
  15. package/src/evolve/pipeline/enrich.js +1 -1
  16. package/src/evolve/pipeline/hub.js +1 -1
  17. package/src/evolve/pipeline/select.js +1 -1
  18. package/src/evolve/pipeline/signals.js +1 -1
  19. package/src/evolve/utils.js +1 -1
  20. package/src/evolve.js +1 -1
  21. package/src/forceUpdate.js +200 -7
  22. package/src/gep/a2aProtocol.js +1 -1
  23. package/src/gep/autoDistillConv.js +1 -1
  24. package/src/gep/autoDistillLlm.js +1 -1
  25. package/src/gep/candidateEval.js +1 -1
  26. package/src/gep/candidates.js +1 -1
  27. package/src/gep/contentHash.js +1 -1
  28. package/src/gep/conversationSniffer.js +1 -1
  29. package/src/gep/crypto.js +1 -1
  30. package/src/gep/curriculum.js +1 -1
  31. package/src/gep/deviceId.js +1 -1
  32. package/src/gep/envFingerprint.js +1 -1
  33. package/src/gep/epigenetics.js +1 -1
  34. package/src/gep/execBridge.js +1 -1
  35. package/src/gep/explore.js +1 -1
  36. package/src/gep/hash.js +1 -1
  37. package/src/gep/hubFetch.js +1 -1
  38. package/src/gep/hubReview.js +1 -1
  39. package/src/gep/hubSearch.js +1 -1
  40. package/src/gep/hubVerify.js +1 -1
  41. package/src/gep/learningSignals.js +1 -1
  42. package/src/gep/memoryGraph.js +1 -1
  43. package/src/gep/memoryGraphAdapter.js +1 -1
  44. package/src/gep/mutation.js +1 -1
  45. package/src/gep/narrativeMemory.js +1 -1
  46. package/src/gep/openPRRegistry.js +1 -1
  47. package/src/gep/personality.js +1 -1
  48. package/src/gep/policyCheck.js +1 -1
  49. package/src/gep/prompt.js +1 -1
  50. package/src/gep/recallInject.js +1 -1
  51. package/src/gep/recallVerifier.js +1 -1
  52. package/src/gep/reflection.js +1 -1
  53. package/src/gep/selector.js +1 -1
  54. package/src/gep/skillDistiller.js +1 -1
  55. package/src/gep/solidify.js +1 -1
  56. package/src/gep/strategy.js +1 -1
  57. package/src/gep/tokenSavings.js +1 -1
  58. package/src/gep/workspaceKeychain.js +1 -1
  59. package/src/proxy/extensions/traceControl.js +1 -1
  60. package/src/proxy/index.js +4 -4
  61. package/src/proxy/inject.js +1 -1
  62. package/src/proxy/lifecycle/manager.js +11 -0
  63. package/src/proxy/router/messages_route.js +8 -0
  64. package/src/proxy/trace/extractor.js +1 -1
  65. package/src/proxy/trace/usage.js +1 -1
@@ -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
  }