@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 +163 -5
- package/package.json +1 -1
- package/src/gep/a2aProtocol.js +2 -0
- package/src/gep/prompt.js +20 -7
- package/src/gep/skillDistiller.js +66 -1
- package/src/gep/skillPublisher.js +41 -5
- package/src/gep/solidify.js +26 -9
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
|
-
|
|
164
|
+
### Required Environment Variables
|
|
165
|
+
|
|
166
|
+
| Variable | Default | Description |
|
|
63
167
|
|---|---|---|
|
|
64
|
-
| `A2A_NODE_ID` | (required) | Your EvoMap node identity. Set
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
| `
|
|
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.
|
|
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": {
|
package/src/gep/a2aProtocol.js
CHANGED
|
@@ -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_<
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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_
|
|
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
|
|
13
|
-
var
|
|
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
|
|
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 (
|
|
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
|
|
package/src/gep/solidify.js
CHANGED
|
@@ -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) {
|
|
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) {
|
|
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
|
-
|
|
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}`);
|