@evomap/evolver 1.87.2 → 1.87.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 -1
  2. package/README.ko-KR.md +1 -1
  3. package/README.md +9 -8
  4. package/README.zh-CN.md +9 -8
  5. package/package.json +1 -1
  6. package/scripts/build_binaries.js +31 -7
  7. package/src/atp/atpExecute.js +35 -8
  8. package/src/atp/autoBuyer.js +71 -16
  9. package/src/atp/autoDeliver.js +16 -0
  10. package/src/atp/cliAutobuyPrompt.js +8 -22
  11. package/src/atp/hubClient.js +42 -4
  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/gep/a2aProtocol.js +1 -1
  22. package/src/gep/assetStore.js +52 -5
  23. package/src/gep/candidateEval.js +1 -1
  24. package/src/gep/candidates.js +1 -1
  25. package/src/gep/contentHash.js +1 -1
  26. package/src/gep/crypto.js +1 -1
  27. package/src/gep/curriculum.js +1 -1
  28. package/src/gep/deviceId.js +1 -1
  29. package/src/gep/envFingerprint.js +1 -1
  30. package/src/gep/epigenetics.js +1 -1
  31. package/src/gep/explore.js +1 -1
  32. package/src/gep/hash.js +1 -1
  33. package/src/gep/hubFetch.js +1 -1
  34. package/src/gep/hubReview.js +1 -1
  35. package/src/gep/hubSearch.js +1 -1
  36. package/src/gep/hubVerify.js +1 -1
  37. package/src/gep/learningSignals.js +1 -1
  38. package/src/gep/memoryGraph.js +1 -1
  39. package/src/gep/memoryGraphAdapter.js +1 -1
  40. package/src/gep/mutation.js +1 -1
  41. package/src/gep/narrativeMemory.js +1 -1
  42. package/src/gep/openPRRegistry.js +1 -1
  43. package/src/gep/paths.js +6 -2
  44. package/src/gep/personality.js +1 -1
  45. package/src/gep/policyCheck.js +1 -1
  46. package/src/gep/prompt.js +1 -1
  47. package/src/gep/recallVerifier.js +1 -1
  48. package/src/gep/reflection.js +1 -1
  49. package/src/gep/sanitize.js +57 -3
  50. package/src/gep/selector.js +1 -1
  51. package/src/gep/selfPR.js +34 -1
  52. package/src/gep/skill2gep.js +108 -29
  53. package/src/gep/skillDistiller.js +1 -1
  54. package/src/gep/solidify.js +1 -1
  55. package/src/gep/strategy.js +1 -1
  56. package/src/gep/workspaceKeychain.js +1 -1
  57. package/src/proxy/lifecycle/manager.js +97 -37
  58. package/src/proxy/router/messages_route.js +25 -0
  59. package/src/proxy/sync/engine.js +68 -31
  60. package/assets/gep/candidates.jsonl +0 -1
  61. package/assets/gep/capsules.json +0 -4
  62. package/assets/gep/events.jsonl +0 -0
  63. package/assets/gep/failed_capsules.json +0 -4
  64. package/assets/gep/genes.json +0 -245
  65. package/assets/gep/genes.jsonl +0 -0
package/src/gep/selfPR.js CHANGED
@@ -40,12 +40,29 @@ const STATE_FILE = 'self_pr_state.json';
40
40
  // fail-safe behavior (reject all files). We therefore stay silent on load
41
41
  // failure here and only surface a warning when maybeCreatePR is actually
42
42
  // invoked but the manifest cannot be read.
43
+ //
44
+ // Failed loads are retried after MANIFEST_RETRY_TTL_MS (default 5 min) so a
45
+ // transient FS error during process start (build script still writing the
46
+ // file, NFS hiccup, permission flap) does not freeze the cache at null for
47
+ // the entire daemon lifetime, silently disabling self-PR for days or weeks
48
+ // with no recovery. A successful load remains sticky because the manifest is
49
+ // effectively read-only at runtime.
43
50
  let _obfuscatedFilesCache; // undefined = not loaded; Set | null after first attempt
44
51
  let _manifestLoadError = null;
52
+ let _manifestLoadFailedAt = 0; // ms timestamp of the last failed load attempt
45
53
  let _warnedAboutMissingManifest = false;
54
+ let _manifestRetryTtlMs = 5 * 60 * 1000;
46
55
 
47
56
  function loadObfuscatedFromManifest() {
48
- if (_obfuscatedFilesCache !== undefined) return _obfuscatedFilesCache;
57
+ // Hit on a successful previous load — manifest does not change at runtime.
58
+ if (_obfuscatedFilesCache instanceof Set) return _obfuscatedFilesCache;
59
+ // Within the retry window after a failure: skip the FS hit, return cached
60
+ // null so callers stay in the fail-safe branch without hammering the disk.
61
+ if (_obfuscatedFilesCache === null &&
62
+ (Date.now() - _manifestLoadFailedAt) < _manifestRetryTtlMs) {
63
+ return null;
64
+ }
65
+ // First-ever attempt OR retry window elapsed since the last failure.
49
66
  try {
50
67
  const manifestPath = path.join(getEvolverInstallRoot(), 'public.manifest.json');
51
68
  const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
@@ -60,9 +77,16 @@ function loadObfuscatedFromManifest() {
60
77
  }
61
78
  }
62
79
  _obfuscatedFilesCache = new Set(manifest.obfuscate.map((f) => f.replace(/\\/g, '/').replace(/^\.\/+/, '')));
80
+ _manifestLoadError = null;
81
+ _manifestLoadFailedAt = 0;
82
+ // Allow a future failure (after this success) to surface its warning
83
+ // once, instead of staying silent because an earlier failure already
84
+ // warned in this process.
85
+ _warnedAboutMissingManifest = false;
63
86
  } catch (e) {
64
87
  _manifestLoadError = e.message;
65
88
  _obfuscatedFilesCache = null;
89
+ _manifestLoadFailedAt = Date.now();
66
90
  }
67
91
  return _obfuscatedFilesCache;
68
92
  }
@@ -72,7 +96,15 @@ function loadObfuscatedFromManifest() {
72
96
  function _resetObfuscatedCache() {
73
97
  _obfuscatedFilesCache = undefined;
74
98
  _manifestLoadError = null;
99
+ _manifestLoadFailedAt = 0;
75
100
  _warnedAboutMissingManifest = false;
101
+ _manifestRetryTtlMs = 5 * 60 * 1000;
102
+ }
103
+
104
+ // Test-only: shrink (or grow) the retry TTL so a test can exercise the
105
+ // retry-after-transient-failure path without sleeping for real time.
106
+ function _setManifestRetryTtlForTests(ms) {
107
+ _manifestRetryTtlMs = Number(ms) || 0;
76
108
  }
77
109
 
78
110
  // Files that are included in the public manifest (superset patterns).
@@ -433,4 +465,5 @@ module.exports = {
433
465
  // For testing
434
466
  _loadObfuscatedFromManifest: loadObfuscatedFromManifest,
435
467
  _resetObfuscatedCache,
468
+ _setManifestRetryTtlForTests,
436
469
  };
@@ -31,6 +31,16 @@ const envFingerprint = require('./envFingerprint');
31
31
  const a2a = require('./a2aProtocol');
32
32
 
33
33
  const SKILL2GEP_ID_PREFIX = 'gene_s2g_';
34
+
35
+ // Max strategy steps kept on a distilled Gene. The old value (10) silently
36
+ // truncated multi-section Skills, dropping the *governance* tail
37
+ // (candidate-gating, Human Gate, Output Contract, rollback) that lives at the
38
+ // end of a well-formed SKILL.md. extractSteps emits each list item flatly, so
39
+ // a rich Skill (workflow + governance sections) yields ~25-27 short one-line
40
+ // steps; the cap must clear that to keep the tail. 28 covers a well-formed
41
+ // SKILL.md while staying compact (short one-liners, far below a full Skill's
42
+ // token weight). Genuinely longer Skills are still bounded here.
43
+ const MAX_STRATEGY_STEPS = 28;
34
44
  const CAPSULE_ID_PREFIX = 'cap_s2g_';
35
45
  const LOG_FILE = 'skill2gep_log.jsonl';
36
46
  const STATE_FILE = 'skill2gep_state.json';
@@ -131,6 +141,7 @@ function parseSkillMd(skillMd) {
131
141
  });
132
142
  Object.keys(sections).forEach((k) => { sections[k] = sections[k].join('\n').trim(); });
133
143
 
144
+ // Return the FIRST matching section (kept for signals, which wants one block).
134
145
  function pickSection(keywords) {
135
146
  for (const kw of keywords) {
136
147
  for (const k of Object.keys(sections)) {
@@ -140,9 +151,60 @@ function parseSkillMd(skillMd) {
140
151
  return '';
141
152
  }
142
153
 
154
+ // Return ALL matching sections concatenated, in document order. A SKILL.md
155
+ // often spreads positive steps across several headed sections ("Quick
156
+ // Workflow", "Human Gate Defaults", "Output Contract"); picking only the
157
+ // first dropped the governance tail. Each section's title is preserved as a
158
+ // step-context line so a trailing "## Human Gate" still contributes its
159
+ // bullets. De-duplicated by section key.
160
+ function pickSectionsAll(keywords) {
161
+ const seen = new Set();
162
+ const out = [];
163
+ for (const k of Object.keys(sections)) {
164
+ if (keywords.some((kw) => k.indexOf(kw) !== -1) && !seen.has(k)) {
165
+ seen.add(k);
166
+ out.push(sections[k]);
167
+ }
168
+ }
169
+ return out.join('\n');
170
+ }
171
+
172
+ // Extract ordered steps from a markdown block: every list item becomes its
173
+ // own step, in document order. This is the pre-PR flat behaviour, kept
174
+ // deliberately simple — an earlier version folded indented sub-bullets into
175
+ // their parent step to look tidier, but that indentation logic grew a long
176
+ // tail of edge cases (section-trim interaction, length-filtered parents,
177
+ // cross-section indentation). Folding was only cosmetic; flat extraction
178
+ // preserves the same governance tail with no indentation reasoning at all.
179
+ // opts.minLen / opts.maxLen bound each item (defaults 5..300, matching the
180
+ // original strategy/avoid gate). Preconditions pass {minLen: 1,
181
+ // maxLen: Infinity} so short prerequisites like "Git"/"npm" survive.
182
+ function extractSteps(block, opts) {
183
+ const minLen = opts && typeof opts.minLen === 'number' ? opts.minLen : 5;
184
+ const maxLen = opts && typeof opts.maxLen === 'number' ? opts.maxLen : 300;
185
+ const steps = [];
186
+ for (const line of String(block || '').split(/\n/)) {
187
+ const m = line.match(/^\s*(?:\d+\.|[-*])\s+(.+?)\s*$/);
188
+ if (!m) continue;
189
+ const txt = m[1].trim();
190
+ if (txt.length >= minLen && txt.length <= maxLen) steps.push(txt);
191
+ }
192
+ return steps;
193
+ }
194
+
143
195
  const signals = [];
196
+ // Section keywords are matched against lower-cased headings. A SKILL.md may
197
+ // be authored in Chinese (e.g. game-* skills use "## 何时使用" / "## 触发条件"),
198
+ // whose heading key never contains an English token, so the CJK synonyms
199
+ // below are required for those skills to contribute signals/strategy/avoid
200
+ // at all — without them the distiller silently falls back to a thin gene.
201
+ // NOTE: this only fixes *section matching*. The signal tokenizer below still
202
+ // keeps ASCII [a-z0-9_] only, so signals for a Chinese skill come from its
203
+ // (English) frontmatter description, not from CJK body words. CJK signal
204
+ // tokenization needs a word segmenter and is intentionally out of scope here.
144
205
  const signalSource = (frontmatter.description || '') + '\n' + pickSection([
145
206
  'trigger', 'when to use', 'when', 'use when', 'scenario',
207
+ '何时使用', '什么时候使用', '触发条件', '触发', '使用场景', '核心目标', '适用',
146
208
  ]);
147
209
  signalSource.split(/[`,.\n]/).forEach((tok) => {
148
210
  const s = tok.trim().toLowerCase().replace(/[^a-z0-9_]/g, '_').replace(/^_+|_+$/g, '');
@@ -151,28 +213,27 @@ function parseSkillMd(skillMd) {
151
213
  }
152
214
  });
153
215
 
154
- const strategy = [];
155
- const strategyBlock = pickSection(['workflow', 'strategy', 'steps', 'procedure', 'quick start', 'how to']);
156
- strategyBlock.split(/\n/).forEach((line) => {
157
- const step = line.match(/^\s*(?:\d+\.|[-*])\s+(.+?)\s*$/);
158
- if (step) {
159
- const s = step[1].trim();
160
- if (s.length >= 5 && s.length <= 300) strategy.push(s);
161
- }
162
- });
216
+ // Strategy spans the workflow AND the governance tail (Human Gate, Output
217
+ // Contract) concatenate all matching sections so the candidate/gate/rollback
218
+ // discipline survives, and fold nested sub-bullets into their parent step.
219
+ const strategyBlock = pickSectionsAll([
220
+ 'workflow', 'strategy', 'steps', 'procedure', 'quick start', 'how to',
221
+ 'human gate', 'output contract', 'release', 'rollback', 'promotion',
222
+ // CJK synonyms: positive workflow + governance-tail headings.
223
+ '工作流', '流程', '步骤', '核心方法', '方法', '快速规则', '规则',
224
+ '输出门', '输出门槛', '人工确认', '人工门', '回滚', '发布', '晋级',
225
+ ]);
226
+ const strategy = extractSteps(strategyBlock);
163
227
 
164
- const avoid = [];
165
- const avoidBlock = pickSection(['avoid', 'pitfall', 'anti-pattern', 'common mistake', 'do not', 'forbidden']);
166
- avoidBlock.split(/\n/).forEach((line) => {
167
- const step = line.match(/^\s*(?:\d+\.|[-*])\s+(.+?)\s*$/);
168
- if (step) {
169
- const s = step[1].trim();
170
- if (s.length >= 5 && s.length <= 300) avoid.push(s);
171
- }
172
- });
228
+ const avoidBlock = pickSectionsAll([
229
+ 'avoid', 'pitfall', 'anti-pattern', 'common mistake', 'do not', 'forbidden', "don't",
230
+ // CJK synonyms: anti-pattern / "do not" headings.
231
+ '不要做', '不要', '常见错误', '避免', '陷阱', '禁止',
232
+ ]);
233
+ const avoid = extractSteps(avoidBlock);
173
234
 
174
235
  const validation = [];
175
- const valBlock = pickSection(['validation', 'test', 'verify', 'check']);
236
+ const valBlock = pickSection(['validation', 'test', 'verify', 'check', '校验', '验证', '测试', '检查']);
176
237
  const fenceRe = /```(?:bash|sh|shell)?\s*\n([\s\S]*?)\n```/g;
177
238
  let fm;
178
239
  while ((fm = fenceRe.exec(valBlock)) !== null) {
@@ -182,12 +243,10 @@ function parseSkillMd(skillMd) {
182
243
  });
183
244
  }
184
245
 
185
- const preconditions = [];
186
- const preBlock = pickSection(['precondition', 'requirement', 'prerequisite']);
187
- preBlock.split(/\n/).forEach((line) => {
188
- const step = line.match(/^\s*(?:\d+\.|[-*])\s+(.+?)\s*$/);
189
- if (step) preconditions.push(step[1].trim());
190
- });
246
+ // Preconditions keep the pre-PR behaviour: no length gate, no folding, so
247
+ // short items like "Git"/"npm" survive and preconditions_extracted is stable.
248
+ const preBlock = pickSection(['precondition', 'requirement', 'prerequisite', '前置条件', '前置', '先决条件', '要求']);
249
+ const preconditions = extractSteps(preBlock, { minLen: 1, maxLen: Infinity });
191
250
 
192
251
  return {
193
252
  frontmatter: frontmatter,
@@ -195,7 +254,7 @@ function parseSkillMd(skillMd) {
195
254
  name: frontmatter.name || (sections['_preamble'] || '').split(/\n/)[0].replace(/^#+\s*/, '').trim(),
196
255
  description: frontmatter.description || '',
197
256
  signals_match: signals.slice(0, 8),
198
- strategy: strategy.slice(0, 10),
257
+ strategy: strategy.slice(0, MAX_STRATEGY_STEPS),
199
258
  avoid: avoid.slice(0, 5),
200
259
  validation: validation.slice(0, 5),
201
260
  preconditions: preconditions.slice(0, 4),
@@ -280,7 +339,7 @@ function synthesizeGene(parsed, execution, opts) {
280
339
  preconditions: (parsed.preconditions && parsed.preconditions.length > 0)
281
340
  ? parsed.preconditions
282
341
  : ['Skill ' + (parsed.name || 'unknown') + ' has just been executed locally'],
283
- strategy: strategy.slice(0, 10),
342
+ strategy: strategy.slice(0, MAX_STRATEGY_STEPS),
284
343
  avoid: avoid,
285
344
  constraints: {
286
345
  max_files: (opts && opts.maxFiles) || skillDistiller.DISTILLED_MAX_FILES,
@@ -309,8 +368,27 @@ function synthesizeGene(parsed, execution, opts) {
309
368
 
310
369
  function inferCategory(signals, description) {
311
370
  const hay = ((description || '') + ' ' + (signals || []).join(' ')).toLowerCase();
312
- if (/error|fail|repair|rollback|bug|fix|guard/.test(hay)) return 'repair';
313
- if (/feature|add|implement|new capability|innovate/.test(hay)) return 'innovate';
371
+ // Priority repair -> innovate -> optimize, mirroring the sibling
372
+ // inferCategoryFromSignals() in skillDistiller.js / solidify.js.
373
+ //
374
+ // REPAIR set uses SUBSTRING matching (no \b): it must catch both inflected
375
+ // forms ("errors", "fixed", "crashes") and the project's underscore signal
376
+ // format ("log_error", "test_failure"), which a \b-anchored regex breaks
377
+ // (\b treats `_` as a word char, so "error" inside "log_error" has no
378
+ // boundary). Changes vs. the pre-PR original:
379
+ // - repair: removed "rollback"/"guard" — cross-cutting safety words common
380
+ // in *optimize* skills (e.g. paranoia-ai-system-evolver lists "rollback"
381
+ // in its safe-change method) that must not by themselves force repair.
382
+ // - innovate: "add" is matched with a \b word boundary so it catches the
383
+ // verb ("add a dashboard") without false-positives on address/additional/
384
+ // padding (the pre-PR bare-substring "add" matched all of those). The
385
+ // innovate set only reads natural-language description, so \b is safe here.
386
+ if (/error|fail|bug|crash|broken|incident|regress|debug|repair|fix/.test(hay)) {
387
+ return 'repair';
388
+ }
389
+ if (/feature|\badd\b|implement|new capability|capability|innovate|greenfield|prototype/.test(hay)) {
390
+ return 'innovate';
391
+ }
314
392
  return 'optimize';
315
393
  }
316
394
 
@@ -679,6 +757,7 @@ module.exports = {
679
757
  RATIONALE_TEXT,
680
758
  parseSkillMd,
681
759
  synthesizeGene,
760
+ inferCategory,
682
761
  detectForgery,
683
762
  assembleCapsule,
684
763
  runOnSkillInvocation,