@evomap/evolver 1.29.4 → 1.29.8

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.
package/SKILL.md CHANGED
@@ -2,6 +2,108 @@
2
2
  name: capability-evolver
3
3
  description: A self-evolution engine for AI agents. Analyzes runtime history to identify improvements and applies protocol-constrained evolution.
4
4
  tags: [meta, ai, self-improvement, core]
5
+ permissions: [network, shell]
6
+ metadata:
7
+ clawdbot:
8
+ requires:
9
+ bins: [node, git]
10
+ env: [A2A_NODE_ID]
11
+ files: ["src/**", "scripts/**", "assets/**"]
12
+ capabilities:
13
+ allow:
14
+ - execute: [git, node, npm]
15
+ - network: [api.github.com, evomap.ai]
16
+ - read: [workspace/**]
17
+ - write: [workspace/assets/**, workspace/memory/**]
18
+ deny:
19
+ - execute: ["!git", "!node", "!npm", "!ps", "!pgrep", "!df"]
20
+ - network: ["!api.github.com", "!*.evomap.ai"]
21
+ env_declarations:
22
+ - name: A2A_NODE_ID
23
+ required: true
24
+ description: EvoMap node identity. Set after node registration.
25
+ - name: A2A_HUB_URL
26
+ required: false
27
+ default: https://evomap.ai
28
+ description: EvoMap Hub API base URL.
29
+ - name: A2A_NODE_SECRET
30
+ required: false
31
+ description: Node authentication secret (issued by Hub on first hello).
32
+ - name: GITHUB_TOKEN
33
+ required: false
34
+ description: GitHub API token for auto-issue reporting and releases.
35
+ - name: EVOLVE_STRATEGY
36
+ required: false
37
+ default: balanced
38
+ description: "Evolution strategy: balanced, innovate, harden, repair-only, early-stabilize, steady-state, auto."
39
+ - name: EVOLVE_ALLOW_SELF_MODIFY
40
+ required: false
41
+ default: "false"
42
+ description: Allow evolution to modify evolver source code. NOT recommended.
43
+ - name: EVOLVE_LOAD_MAX
44
+ required: false
45
+ default: "2.0"
46
+ description: Max 1-min load average before evolver backs off.
47
+ - name: EVOLVER_ROLLBACK_MODE
48
+ required: false
49
+ default: hard
50
+ description: "Rollback strategy on failure: hard, stash, none."
51
+ - name: EVOLVER_LLM_REVIEW
52
+ required: false
53
+ default: "0"
54
+ description: Enable second-opinion LLM review before solidification.
55
+ - name: EVOLVER_AUTO_ISSUE
56
+ required: false
57
+ default: "0"
58
+ description: Auto-create GitHub issues on repeated failures.
59
+ - name: EVOLVER_MODEL_NAME
60
+ required: false
61
+ description: LLM model name injected into published asset metadata.
62
+ - name: MEMORY_GRAPH_REMOTE_URL
63
+ required: false
64
+ description: Remote memory graph service URL (optional KG integration).
65
+ - name: MEMORY_GRAPH_REMOTE_KEY
66
+ required: false
67
+ description: API key for remote memory graph service.
68
+ network_endpoints:
69
+ - host: api.github.com
70
+ purpose: Release creation, changelog publishing, auto-issue reporting
71
+ auth: GITHUB_TOKEN (Bearer)
72
+ optional: true
73
+ - host: evomap.ai (or A2A_HUB_URL)
74
+ purpose: A2A protocol (hello, heartbeat, publish, fetch, reviews, tasks)
75
+ auth: A2A_NODE_SECRET (Bearer)
76
+ optional: false
77
+ - host: MEMORY_GRAPH_REMOTE_URL
78
+ purpose: Remote knowledge graph sync
79
+ auth: MEMORY_GRAPH_REMOTE_KEY
80
+ optional: true
81
+ shell_commands:
82
+ - command: git
83
+ purpose: Version control (checkout, clean, log, status, diff, rebase --abort, merge --abort)
84
+ user_input: false
85
+ - command: node
86
+ purpose: Inline script execution for LLM review
87
+ user_input: false
88
+ - command: npm
89
+ purpose: "npm install --production for skill dependency healing"
90
+ user_input: false
91
+ - command: ps / pgrep / tasklist
92
+ purpose: Process discovery for lifecycle management
93
+ user_input: false
94
+ - command: df
95
+ purpose: Disk usage check (health monitoring)
96
+ user_input: false
97
+ file_access:
98
+ reads:
99
+ - "~/.evomap/node_id (node identity)"
100
+ - "workspace/assets/** (GEP assets)"
101
+ - "workspace/memory/** (evolution memory, narrative, reflection logs)"
102
+ - "workspace/package.json (version info)"
103
+ writes:
104
+ - "workspace/assets/gep/** (genes, capsules, events)"
105
+ - "workspace/memory/** (memory graph, narrative, reflection)"
106
+ - "workspace/src/** (evolved code, only when changes are solidified)"
5
107
  ---
6
108
 
7
109
  # 🧬 Capability Evolver
@@ -59,13 +161,69 @@ Do not hardcode the node ID in scripts. `getNodeId()` in `src/gep/a2aProtocol.js
59
161
 
60
162
  ## Configuration
61
163
 
62
- | Environment Variable | Default | Description |
164
+ ### Required Environment Variables
165
+
166
+ | Variable | Default | Description |
63
167
  |---|---|---|
64
- | `A2A_NODE_ID` | (required) | Your EvoMap node identity. Set this after node registration -- never hardcode it in scripts. Read automatically by `getNodeId()` in `a2aProtocol.js`. |
65
- | `EVOLVE_ALLOW_SELF_MODIFY` | `false` | Allow evolution to modify evolver's own source code. **NOT recommended for production.** Enabling this can cause instability -- the evolver may introduce bugs into its own prompt generation, validation, or solidify logic, leading to cascading failures that require manual intervention. Only enable for controlled experiments. |
66
- | `EVOLVE_LOAD_MAX` | `2.0` | Maximum 1-minute load average before evolver backs off. |
168
+ | `A2A_NODE_ID` | (required) | Your EvoMap node identity. Set after node registration -- never hardcode in scripts. |
169
+
170
+ ### Optional Environment Variables
171
+
172
+ | Variable | Default | Description |
173
+ |---|---|---|
174
+ | `A2A_HUB_URL` | `https://evomap.ai` | EvoMap Hub API base URL. |
175
+ | `A2A_NODE_SECRET` | (none) | Node authentication secret issued by Hub on first hello. Stored locally after registration. |
67
176
  | `EVOLVE_STRATEGY` | `balanced` | Evolution strategy: `balanced`, `innovate`, `harden`, `repair-only`, `early-stabilize`, `steady-state`, or `auto`. |
68
- | `EVOLVER_ROLLBACK_MODE` | `hard` | Rollback strategy when evolution fails. `hard`: use `git reset --hard` (destructive, original behavior). `stash`: use `git stash` to preserve changes for recovery. `none`: skip rollback entirely. Use `stash` for safer operation in active workspaces. |
177
+ | `EVOLVE_ALLOW_SELF_MODIFY` | `false` | Allow evolution to modify evolver's own source code. **NOT recommended for production.** |
178
+ | `EVOLVE_LOAD_MAX` | `2.0` | Maximum 1-minute load average before evolver backs off. |
179
+ | `EVOLVER_ROLLBACK_MODE` | `hard` | Rollback strategy on failure: `hard` (git reset --hard), `stash` (git stash), `none` (skip). Use `stash` for safer operation. |
180
+ | `EVOLVER_LLM_REVIEW` | `0` | Set to `1` to enable second-opinion LLM review before solidification. |
181
+ | `EVOLVER_AUTO_ISSUE` | `0` | Set to `1` to auto-create GitHub issues on repeated failures. Requires `GITHUB_TOKEN`. |
182
+ | `EVOLVER_ISSUE_REPO` | (none) | GitHub repo for auto-issue reporting (e.g. `EvoMap/evolver`). |
183
+ | `EVOLVER_MODEL_NAME` | (none) | LLM model name injected into published asset `model_name` field. |
184
+ | `GITHUB_TOKEN` | (none) | GitHub API token for release creation and auto-issue reporting. Also accepts `GH_TOKEN` or `GITHUB_PAT`. |
185
+ | `MEMORY_GRAPH_REMOTE_URL` | (none) | Remote knowledge graph service URL for memory sync. |
186
+ | `MEMORY_GRAPH_REMOTE_KEY` | (none) | API key for remote knowledge graph service. |
187
+ | `EVOLVE_REPORT_TOOL` | (auto) | Override report tool (e.g. `feishu-card`). |
188
+ | `RANDOM_DRIFT` | `0` | Enable random drift in evolution strategy selection. |
189
+
190
+ ### Network Endpoints
191
+
192
+ Evolver communicates with these external services. All are authenticated and documented.
193
+
194
+ | Endpoint | Auth | Purpose | Required |
195
+ |---|---|---|---|
196
+ | `{A2A_HUB_URL}/a2a/*` | `A2A_NODE_SECRET` (Bearer) | A2A protocol: hello, heartbeat, publish, fetch, reviews, tasks | Yes |
197
+ | `api.github.com/repos/*/releases` | `GITHUB_TOKEN` (Bearer) | Create releases, publish changelogs | No |
198
+ | `api.github.com/repos/*/issues` | `GITHUB_TOKEN` (Bearer) | Auto-create failure reports (sanitized via `redactString()`) | No |
199
+ | `{MEMORY_GRAPH_REMOTE_URL}/*` | `MEMORY_GRAPH_REMOTE_KEY` | Remote knowledge graph sync | No |
200
+
201
+ ### Shell Commands Used
202
+
203
+ Evolver uses `child_process` for the following commands. No user-controlled input is passed to shell.
204
+
205
+ | Command | Purpose |
206
+ |---|---|
207
+ | `git checkout`, `git clean`, `git log`, `git status`, `git diff` | Version control for evolution cycles |
208
+ | `git rebase --abort`, `git merge --abort` | Abort stuck git operations (self-repair) |
209
+ | `git reset --hard` | Rollback failed evolution (only when `EVOLVER_ROLLBACK_MODE=hard`) |
210
+ | `git stash` | Preserve failed evolution changes (when `EVOLVER_ROLLBACK_MODE=stash`) |
211
+ | `ps`, `pgrep`, `tasklist` | Process discovery for lifecycle management |
212
+ | `df -P` | Disk usage check (health monitoring fallback) |
213
+ | `npm install --production` | Repair missing skill dependencies |
214
+ | `node -e "..."` | Inline script execution for LLM review (no shell, uses `execFileSync`) |
215
+
216
+ ### File Access
217
+
218
+ | Direction | Paths | Purpose |
219
+ |---|---|---|
220
+ | Read | `~/.evomap/node_id` | Node identity persistence |
221
+ | Read | `assets/gep/*` | GEP gene/capsule/event data |
222
+ | Read | `memory/*` | Evolution memory, narrative, reflection logs |
223
+ | Read | `package.json` | Version information |
224
+ | Write | `assets/gep/*` | Updated genes, capsules, evolution events |
225
+ | Write | `memory/*` | Memory graph, narrative log, reflection log |
226
+ | Write | `src/**` | Evolved code (only during solidify, with git tracking) |
69
227
 
70
228
  ## GEP Protocol (Auditable Evolution)
71
229
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evomap/evolver",
3
- "version": "1.29.4",
3
+ "version": "1.29.8",
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": {
@@ -469,12 +469,14 @@ function sendHelloToHub() {
469
469
  }
470
470
 
471
471
  function getHubNodeSecret() {
472
+ if (process.env.A2A_NODE_SECRET) return process.env.A2A_NODE_SECRET;
472
473
  if (_cachedHubNodeSecret) return _cachedHubNodeSecret;
473
474
  var persisted = _loadPersistedNodeSecret();
474
475
  if (persisted) {
475
476
  _cachedHubNodeSecret = persisted;
476
477
  return persisted;
477
478
  }
479
+ if (process.env.A2A_HUB_TOKEN) return process.env.A2A_HUB_TOKEN;
478
480
  return null;
479
481
  }
480
482
 
package/src/gep/prompt.js CHANGED
@@ -134,10 +134,14 @@ ENSURE VALID JSON SYNTAX (escape quotes in strings).
134
134
 
135
135
  3. Gene (The Knowledge)
136
136
  - Reuse/update existing ID if possible. Create new only if novel pattern.
137
+ - ID MUST be descriptive: gene_<descriptive_name> (e.g., gene_retry_on_timeout)
138
+ - NEVER use timestamps, random numbers, or tool names (cursor, vscode, etc.) in IDs
139
+ - summary MUST be a clear human-readable sentence describing what the Gene does
137
140
  {
138
141
  "type": "Gene",
139
142
  "schema_version": "1.5.0",
140
- "id": "gene_<name>",
143
+ "id": "gene_<descriptive_name>",
144
+ "summary": "<clear description of what this gene does>",
141
145
  "category": "repair|optimize|innovate",
142
146
  "signals_match": ["<pattern>"],
143
147
  "preconditions": ["<condition>"],
@@ -439,24 +443,33 @@ When creating a new skill in skills/<name>/:
439
443
  |- assets/ (optional: templates, data files)
440
444
  Creating an empty directory or a directory missing index.js = FAILED.
441
445
  Do NOT create unnecessary files (README.md, CHANGELOG.md, INSTALLATION_GUIDE.md, etc.).
442
- 2. SKILL.MD FRONTMATTER: Every SKILL.md MUST start with YAML frontmatter:
446
+ 2. SKILL NAMING (CRITICAL):
447
+ a) <name> MUST be descriptive kebab-case (e.g., "log-rotation", "retry-handler", "cache-manager")
448
+ b) NEVER use timestamps, random numbers, tool names (cursor, vscode), or UUIDs as names
449
+ c) Names like "cursor-1773331925711", "skill-12345", "fix-1" = FAILED
450
+ d) Name must be 2-6 descriptive words separated by hyphens, conveying what the skill does
451
+ e) Good: "http-retry-with-backoff", "log-file-rotation", "config-validator"
452
+ f) Bad: "cursor-auto-1234", "new-skill", "test-skill", "my-skill"
453
+ 3. SKILL.MD FRONTMATTER: Every SKILL.md MUST start with YAML frontmatter:
443
454
  ---
444
455
  name: <skill-name>
445
456
  description: <what it does and when to use it>
446
457
  ---
458
+ The name MUST follow the naming rules above.
447
459
  The description is the triggering mechanism -- include WHAT the skill does and WHEN to use it.
448
- 3. CONCISENESS: SKILL.md body should be under 500 lines. Keep instructions lean.
460
+ Description must be a clear, complete sentence (min 20 chars). Generic descriptions = FAILED.
461
+ 4. CONCISENESS: SKILL.md body should be under 500 lines. Keep instructions lean.
449
462
  Only include information the agent does not already know. Move detailed reference
450
463
  material to references/ files, not into SKILL.md itself.
451
- 4. EXPORT VERIFICATION: Every exported function must be importable.
464
+ 5. EXPORT VERIFICATION: Every exported function must be importable.
452
465
  Run: node -e "const s = require('./skills/<name>'); console.log(Object.keys(s))"
453
466
  If this fails, the skill is broken. Fix before solidify.
454
- 5. NO HARDCODED SECRETS: Never embed API keys, tokens, or secrets in code.
467
+ 6. NO HARDCODED SECRETS: Never embed API keys, tokens, or secrets in code.
455
468
  Use process.env or .env references. Hardcoded App ID, App Secret, Bearer tokens = FAILED.
456
- 6. TEST BEFORE SOLIDIFY: Actually run the skill's core function to verify it works:
469
+ 7. TEST BEFORE SOLIDIFY: Actually run the skill's core function to verify it works:
457
470
  node -e "require('./skills/<name>').main ? require('./skills/<name>').main() : console.log('ok')"
458
471
  Scripts in scripts/ must also be tested by executing them.
459
- 7. ATOMIC CREATION: Create ALL files for a skill in a single cycle.
472
+ 8. ATOMIC CREATION: Create ALL files for a skill in a single cycle.
460
473
  Do not create a directory in one cycle and fill it in the next.
461
474
  Empty directories from failed cycles will be automatically cleaned up on rollback.
462
475
 
@@ -236,6 +236,20 @@ function buildDistillationPrompt(analysis, existingGenes, sampleCapsules) {
236
236
  '- constraints.forbidden_paths MUST include at least [".git", "node_modules"]',
237
237
  '- Output valid Gene JSON only (no markdown, no explanation)',
238
238
  '',
239
+ 'GENE ID NAMING RULES (CRITICAL):',
240
+ '- The id suffix (after "' + DISTILLED_ID_PREFIX + '") MUST be a descriptive kebab-case name',
241
+ ' derived from the strategy content or signals_match (e.g., "retry-on-timeout", "log-rotation-cleanup")',
242
+ '- NEVER use timestamps, random numbers, tool names (cursor, vscode, etc.), or UUIDs in the id',
243
+ '- Good: "gene_distilled_retry-on-timeout", "gene_distilled_cache-invalidation-strategy"',
244
+ '- Bad: "gene_distilled_cursor-1773331925711", "gene_distilled_1234567890", "gene_distilled_fix-1"',
245
+ '- The id suffix must be 3+ words separated by hyphens, describing the core capability',
246
+ '',
247
+ 'SUMMARY RULES:',
248
+ '- The "summary" field MUST be a clear, human-readable description (10-200 chars)',
249
+ '- It should describe WHAT the Gene does, not implementation details',
250
+ '- Good: "Retry failed HTTP requests with exponential backoff and circuit breaker"',
251
+ '- Bad: "Distilled from capsules", "AI agent skill", "cursor automation"',
252
+ '',
239
253
  'SUCCESSFUL CAPSULES (grouped by pattern):',
240
254
  JSON.stringify(samples, null, 2),
241
255
  '',
@@ -246,7 +260,7 @@ function buildDistillationPrompt(analysis, existingGenes, sampleCapsules) {
246
260
  JSON.stringify(analysis, null, 2),
247
261
  '',
248
262
  'Output a single Gene JSON object with these fields:',
249
- '{ "type": "Gene", "id": "gene_distilled_...", "category": "...", "signals_match": [...], "preconditions": [...], "strategy": [...], "constraints": { "max_files": N, "forbidden_paths": [...] }, "validation": [...] }',
263
+ '{ "type": "Gene", "id": "gene_distilled_<descriptive-kebab-name>", "summary": "<clear human-readable description>", "category": "...", "signals_match": [...], "preconditions": [...], "strategy": [...], "constraints": { "max_files": N, "forbidden_paths": [...] }, "validation": [...] }',
250
264
  ].join('\n');
251
265
  }
252
266
 
@@ -254,6 +268,36 @@ function distillRequestPath() {
254
268
  return path.join(paths.getMemoryDir(), 'distill_request.json');
255
269
  }
256
270
 
271
+ // ---------------------------------------------------------------------------
272
+ // Derive a descriptive ID from gene content when the LLM gives a bad name
273
+ // ---------------------------------------------------------------------------
274
+ function deriveDescriptiveId(gene) {
275
+ var words = [];
276
+ if (Array.isArray(gene.signals_match)) {
277
+ gene.signals_match.slice(0, 3).forEach(function (s) {
278
+ String(s).toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim().split(/\s+/).forEach(function (w) {
279
+ if (w.length >= 3 && words.length < 6) words.push(w);
280
+ });
281
+ });
282
+ }
283
+ if (words.length < 3 && gene.summary) {
284
+ var STOP = new Set(['the', 'and', 'for', 'with', 'from', 'that', 'this', 'into', 'when', 'are', 'was', 'has', 'had']);
285
+ String(gene.summary).toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim().split(/\s+/).forEach(function (w) {
286
+ if (w.length >= 3 && !STOP.has(w) && words.length < 6) words.push(w);
287
+ });
288
+ }
289
+ if (words.length < 3 && Array.isArray(gene.strategy) && gene.strategy.length > 0) {
290
+ String(gene.strategy[0]).toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim().split(/\s+/).forEach(function (w) {
291
+ if (w.length >= 3 && words.length < 6) words.push(w);
292
+ });
293
+ }
294
+ if (words.length < 2) words = ['auto', 'distilled', 'strategy'];
295
+ var unique = [];
296
+ var seen = new Set();
297
+ words.forEach(function (w) { if (!seen.has(w)) { seen.add(w); unique.push(w); } });
298
+ return DISTILLED_ID_PREFIX + unique.slice(0, 5).join('-');
299
+ }
300
+
257
301
  // ---------------------------------------------------------------------------
258
302
  // Step 4: validateSynthesizedGene
259
303
  // ---------------------------------------------------------------------------
@@ -271,6 +315,27 @@ function validateSynthesizedGene(gene, existingGenes) {
271
315
  gene.id = DISTILLED_ID_PREFIX + String(gene.id).replace(/^gene_/, '');
272
316
  }
273
317
 
318
+ if (gene.id) {
319
+ var suffix = String(gene.id).replace(DISTILLED_ID_PREFIX, '');
320
+ var needsRename = /^\d+$/.test(suffix) || /^\d{10,}/.test(suffix)
321
+ || /^(cursor|vscode|vim|emacs|windsurf|copilot|cline|codex)[-_]?\d*/i.test(suffix);
322
+ if (needsRename) {
323
+ gene.id = deriveDescriptiveId(gene);
324
+ }
325
+ var cleanSuffix = String(gene.id).replace(DISTILLED_ID_PREFIX, '');
326
+ if (cleanSuffix.replace(/[-_]/g, '').length < 6) {
327
+ gene.id = deriveDescriptiveId(gene);
328
+ }
329
+ }
330
+
331
+ if (!gene.summary || typeof gene.summary !== 'string' || gene.summary.length < 10) {
332
+ if (Array.isArray(gene.strategy) && gene.strategy.length > 0) {
333
+ gene.summary = String(gene.strategy[0]).slice(0, 200);
334
+ } else if (Array.isArray(gene.signals_match) && gene.signals_match.length > 0) {
335
+ gene.summary = 'Strategy for: ' + gene.signals_match.slice(0, 3).join(', ');
336
+ }
337
+ }
338
+
274
339
  if (!gene.constraints || typeof gene.constraints !== 'object') gene.constraints = {};
275
340
  if (!Array.isArray(gene.constraints.forbidden_paths) || gene.constraints.forbidden_paths.length === 0) {
276
341
  gene.constraints.forbidden_paths = ['.git', 'node_modules'];
@@ -8,9 +8,38 @@ var { getHubUrl, buildHubHeaders, getNodeId } = require('./a2aProtocol');
8
8
  * @param {object} gene - Gene asset
9
9
  * @returns {string} SKILL.md content
10
10
  */
11
+ function sanitizeSkillName(rawName) {
12
+ var name = rawName.replace(/[\r\n]+/g, '-').replace(/^gene_distilled_/, '').replace(/^gene_/, '').replace(/_/g, '-');
13
+ if (/^\d{8,}/.test(name) || /^(cursor|vscode|vim|emacs|windsurf|copilot|cline|codex)[-]?\d*$/i.test(name)) {
14
+ return null;
15
+ }
16
+ name = name.replace(/-?\d{10,}$/g, '').replace(/-+$/, '');
17
+ if (name.replace(/[-]/g, '').length < 6) return null;
18
+ return name;
19
+ }
20
+
11
21
  function geneToSkillMd(gene) {
12
- var name = (gene.id || 'unnamed-skill').replace(/^gene_distilled_/, '').replace(/_/g, '-');
13
- var desc = gene.summary || 'AI agent skill distilled from evolution experience.';
22
+ var rawName = gene.id || 'unnamed-skill';
23
+ var name = sanitizeSkillName(rawName);
24
+ if (!name) {
25
+ var fallbackWords = [];
26
+ if (Array.isArray(gene.signals_match)) {
27
+ gene.signals_match.slice(0, 3).forEach(function (s) {
28
+ String(s).toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim().split(/\s+/).forEach(function (w) {
29
+ if (w.length >= 3 && fallbackWords.length < 5) fallbackWords.push(w);
30
+ });
31
+ });
32
+ }
33
+ if (fallbackWords.length < 2 && gene.summary) {
34
+ String(gene.summary).toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim().split(/\s+/).forEach(function (w) {
35
+ if (w.length >= 3 && fallbackWords.length < 5) fallbackWords.push(w);
36
+ });
37
+ }
38
+ var seen = {};
39
+ fallbackWords = fallbackWords.filter(function (w) { if (seen[w]) return false; seen[w] = true; return true; });
40
+ name = fallbackWords.length >= 2 ? fallbackWords.join('-') : 'auto-distilled-skill';
41
+ }
42
+ var desc = (gene.summary || 'AI agent skill distilled from evolution experience.').replace(/[\r\n]+/g, ' ').trim();
14
43
 
15
44
  var lines = [
16
45
  '---',
@@ -94,7 +123,9 @@ function publishSkillToHub(gene, opts) {
94
123
 
95
124
  var content = geneToSkillMd(gene);
96
125
  var nodeId = getNodeId();
97
- var skillId = 'skill_' + (gene.id || 'unnamed').replace(/^gene_/, '');
126
+ var fmName = content.match(/^name:\s*(.+)$/m);
127
+ var derivedName = fmName ? fmName[1].trim().toLowerCase().replace(/[^a-z0-9]+/g, '_') : (gene.id || 'unnamed').replace(/^gene_/, '');
128
+ var skillId = 'skill_' + derivedName;
98
129
 
99
130
  var body = {
100
131
  sender_id: nodeId,
@@ -151,8 +182,13 @@ function updateSkillOnHub(nodeId, skillId, content, opts, gene) {
151
182
  body: JSON.stringify(body),
152
183
  signal: AbortSignal.timeout(15000),
153
184
  })
154
- .then(function (res) { return res.json(); })
155
- .then(function (data) { return { ok: true, result: data }; })
185
+ .then(function (res) { return res.json().then(function (data) { return { status: res.status, data: data }; }); })
186
+ .then(function (result) {
187
+ if (result.status >= 200 && result.status < 300) {
188
+ return { ok: true, result: result.data };
189
+ }
190
+ return { ok: false, error: result.data?.error || 'update_failed', status: result.status };
191
+ })
156
192
  .catch(function (err) { return { ok: false, error: err.message }; });
157
193
  }
158
194
 
@@ -136,6 +136,7 @@ function readOpenclawConstraintPolicy() {
136
136
  includeExtensions: Array.isArray(pol.includeExtensions) ? pol.includeExtensions.map(String) : defaults.includeExtensions,
137
137
  };
138
138
  } catch (_) {
139
+ console.warn('[evolver] readOpenclawConstraintPolicy failed:', _ && _.message || _);
139
140
  return defaults;
140
141
  }
141
142
  }
@@ -159,7 +160,9 @@ function matchAnyRegex(rel, regexList) {
159
160
  for (const raw of Array.isArray(regexList) ? regexList : []) {
160
161
  try {
161
162
  if (new RegExp(String(raw), 'i').test(rel)) return true;
162
- } catch (_) {}
163
+ } catch (_) {
164
+ console.warn('[evolver] matchAnyRegex invalid pattern:', raw, _ && _.message || _);
165
+ }
163
166
  }
164
167
  return false;
165
168
  }
@@ -339,7 +342,9 @@ function checkConstraints({ gene, blast, blastRadiusEstimate, repoRoot }) {
339
342
  if (entries.length < 2) {
340
343
  warnings.push('incomplete_skill: skills/' + skillName + '/ has only ' + entries.length + ' file(s). New skills should have at least index.js + SKILL.md.');
341
344
  }
342
- } catch (e) { /* dir might not exist yet */ }
345
+ } catch (e) {
346
+ console.warn('[evolver] checkConstraints skill dir read failed:', skillName, e && e.message || e);
347
+ }
343
348
  });
344
349
  }
345
350
 
@@ -381,7 +386,9 @@ function writeStateForSolidify(state) {
381
386
  const statePath = path.join(getEvolutionDir(), 'evolution_solidify_state.json');
382
387
  try {
383
388
  if (!fs.existsSync(memoryDir)) fs.mkdirSync(memoryDir, { recursive: true });
384
- } catch {}
389
+ } catch (e) {
390
+ console.warn('[evolver] writeStateForSolidify mkdir failed:', memoryDir, e && e.message || e);
391
+ }
385
392
  const tmp = `${statePath}.tmp`;
386
393
  fs.writeFileSync(tmp, JSON.stringify(state, null, 2) + '\n', 'utf8');
387
394
  fs.renameSync(tmp, statePath);
@@ -559,7 +566,9 @@ function detectDestructiveChanges({ repoRoot, changedFiles, baselineUntracked })
559
566
  if (stat.isFile() && stat.size === 0) {
560
567
  violations.push(`CRITICAL_FILE_EMPTIED: ${norm}`);
561
568
  }
562
- } catch (e) {}
569
+ } catch (e) {
570
+ console.warn('[evolver] detectDestructiveChanges stat failed:', norm, e && e.message || e);
571
+ }
563
572
  }
564
573
  }
565
574
  }
@@ -716,7 +725,9 @@ function rollbackNewUntrackedFiles({ repoRoot, baselineUntracked }) {
716
725
  fs.unlinkSync(normAbs);
717
726
  deleted.push(safeRel);
718
727
  }
719
- } catch (e) {}
728
+ } catch (e) {
729
+ console.warn('[evolver] rollbackNewUntrackedFiles unlink failed:', safeRel, e && e.message || e);
730
+ }
720
731
  }
721
732
  if (skipped.length > 0) {
722
733
  console.log(`[Rollback] Skipped ${skipped.length} critical protected file(s): ${skipped.slice(0, 5).join(', ')}`);
@@ -749,7 +760,9 @@ function rollbackNewUntrackedFiles({ repoRoot, baselineUntracked }) {
749
760
  fs.rmdirSync(dirAbs);
750
761
  removedDirs.push(sortedDirs[si]);
751
762
  }
752
- } catch (e) { /* ignore -- dir may already be gone */ }
763
+ } catch (e) {
764
+ console.warn('[evolver] rollbackNewUntrackedFiles rmdir failed:', sortedDirs[si], e && e.message || e);
765
+ }
753
766
  }
754
767
  if (removedDirs.length > 0) {
755
768
  console.log('[Rollback] Removed ' + removedDirs.length + ' empty director' + (removedDirs.length === 1 ? 'y' : 'ies') + ': ' + removedDirs.slice(0, 5).join(', '));
@@ -1226,7 +1239,9 @@ function solidify({ intent, summary, dryRun = false, rollbackOnFailure = true }
1226
1239
  const list = require('./assetStore').loadCapsules();
1227
1240
  prevCapsule = Array.isArray(list) ? list.find(c => c && c.type === 'Capsule' && String(c.id) === selectedCapsuleId) : null;
1228
1241
  }
1229
- } catch (e) {}
1242
+ } catch (e) {
1243
+ console.warn('[evolver] solidify loadCapsules failed:', e && e.message || e);
1244
+ }
1230
1245
  const successReason = buildSuccessReason({ gene: geneUsed, signals, blast, mutation, score });
1231
1246
  const capsuleDiff = captureDiffSnapshot(repoRoot);
1232
1247
  const capsuleContent = buildCapsuleContent({ intent, gene: geneUsed, signals, blast, mutation, score });
@@ -1302,7 +1317,7 @@ function solidify({ intent, summary, dryRun = false, rollbackOnFailure = true }
1302
1317
  applyEpigeneticMarks(geneUsed, envFp, outcomeStatus);
1303
1318
  upsertGene(geneUsed);
1304
1319
  } catch (e) {
1305
- // Non-blocking: epigenetic mark failure must not break solidify
1320
+ console.warn('[evolver] applyEpigeneticMarks failed (non-blocking):', e && e.message || e);
1306
1321
  }
1307
1322
  }
1308
1323
 
@@ -1326,7 +1341,9 @@ function solidify({ intent, summary, dryRun = false, rollbackOnFailure = true }
1326
1341
  if (personalityState) {
1327
1342
  updatePersonalityStats({ personalityState, outcome: outcomeStatus, score, notes: `event:${event.id}` });
1328
1343
  }
1329
- } catch (e) {}
1344
+ } catch (e) {
1345
+ console.warn('[evolver] updatePersonalityStats failed:', e && e.message || e);
1346
+ }
1330
1347
  }
1331
1348
 
1332
1349
  const runId = lastRun && lastRun.run_id ? String(lastRun.run_id) : stableHash(`${parentEventId || 'root'}|${geneId || 'none'}|${signalKey}`);