@evomap/evolver 1.29.4 → 1.29.9
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 +180 -9
- package/src/gep/skillPublisher.js +159 -15
- 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.9",
|
|
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
|
|
|
@@ -223,18 +223,78 @@ function buildDistillationPrompt(analysis, existingGenes, sampleCapsules) {
|
|
|
223
223
|
});
|
|
224
224
|
|
|
225
225
|
return [
|
|
226
|
-
'You are a Gene synthesis engine for the GEP (
|
|
226
|
+
'You are a Gene synthesis engine for the GEP (Genome Evolution Protocol).',
|
|
227
|
+
'Your job is to distill successful evolution capsules into a high-quality, reusable Gene',
|
|
228
|
+
'that other AI agents can discover, fetch, and execute.',
|
|
227
229
|
'',
|
|
228
|
-
'
|
|
230
|
+
'## OUTPUT FORMAT',
|
|
231
|
+
'',
|
|
232
|
+
'Output ONLY a single valid JSON object (no markdown fences, no explanation).',
|
|
233
|
+
'',
|
|
234
|
+
'## GENE ID RULES (CRITICAL)',
|
|
235
|
+
'',
|
|
236
|
+
'- The id MUST start with "' + DISTILLED_ID_PREFIX + '" followed by a descriptive kebab-case name.',
|
|
237
|
+
'- The suffix MUST describe the core capability in 3-6 hyphen-separated words.',
|
|
238
|
+
'- NEVER include timestamps, numeric IDs, random numbers, tool names (cursor, vscode, etc.), or UUIDs.',
|
|
239
|
+
'- Good: "gene_distilled_retry-with-exponential-backoff", "gene_distilled_database-migration-rollback"',
|
|
240
|
+
'- Bad: "gene_distilled_cursor-1773331925711", "gene_distilled_1234567890", "gene_distilled_fix-1"',
|
|
241
|
+
'',
|
|
242
|
+
'## SUMMARY RULES',
|
|
243
|
+
'',
|
|
244
|
+
'- The "summary" MUST be a clear, human-readable sentence (30-200 chars) describing',
|
|
245
|
+
' WHAT capability this Gene provides and WHY it is useful.',
|
|
246
|
+
'- Write as if for a marketplace listing -- the summary is the first thing other agents see.',
|
|
247
|
+
'- Good: "Retry failed HTTP requests with exponential backoff, jitter, and circuit breaker to prevent cascade failures"',
|
|
248
|
+
'- Bad: "Distilled from capsules", "AI agent skill", "cursor automation", "1773331925711"',
|
|
249
|
+
'- NEVER include timestamps, build numbers, or tool names in the summary.',
|
|
250
|
+
'',
|
|
251
|
+
'## SIGNALS_MATCH RULES',
|
|
252
|
+
'',
|
|
253
|
+
'- Each signal MUST be a generic, reusable keyword that describes WHEN to trigger this Gene.',
|
|
254
|
+
'- Use lowercase_snake_case. Signals should be domain terms, not implementation artifacts.',
|
|
255
|
+
'- NEVER include timestamps, build numbers, tool names, session IDs, or random suffixes.',
|
|
256
|
+
'- Include 3-7 signals covering both the problem domain and the solution approach.',
|
|
257
|
+
'- Good: ["http_retry", "request_timeout", "exponential_backoff", "circuit_breaker", "resilience"]',
|
|
258
|
+
'- Bad: ["cursor_auto_1773331925711", "cli_headless_1773331925711", "bypass_123"]',
|
|
259
|
+
'',
|
|
260
|
+
'## STRATEGY RULES',
|
|
261
|
+
'',
|
|
262
|
+
'- Strategy steps MUST be actionable, concrete instructions an AI agent can execute.',
|
|
263
|
+
'- Each step should be a clear imperative sentence starting with a verb.',
|
|
264
|
+
'- Include 5-10 steps. Each step should be self-contained and specific.',
|
|
265
|
+
'- Do NOT describe what happened; describe what TO DO.',
|
|
266
|
+
'- Include rationale or context in parentheses when non-obvious.',
|
|
267
|
+
'- Where applicable, include inline code examples using backtick notation.',
|
|
268
|
+
'- Good: "Wrap the HTTP call in a retry loop with `maxRetries=3` and initial delay of 500ms"',
|
|
269
|
+
'- Bad: "Handle retries", "Fix the issue", "Improve reliability"',
|
|
270
|
+
'',
|
|
271
|
+
'## PRECONDITIONS RULES',
|
|
272
|
+
'',
|
|
273
|
+
'- List concrete, verifiable conditions that must be true before applying this Gene.',
|
|
274
|
+
'- Each precondition should be a testable statement, not a vague requirement.',
|
|
275
|
+
'- Good: "Project uses Node.js >= 18 with ES module support"',
|
|
276
|
+
'- Bad: "need to fix something"',
|
|
277
|
+
'',
|
|
278
|
+
'## CONSTRAINTS',
|
|
229
279
|
'',
|
|
230
|
-
'RULES:',
|
|
231
|
-
'- Strategy steps MUST be actionable operations, NOT summaries',
|
|
232
|
-
'- Each step must be a concrete instruction an AI agent can execute',
|
|
233
|
-
'- Do NOT describe what happened; describe what TO DO next time',
|
|
234
|
-
'- The Gene MUST have a unique id starting with "' + DISTILLED_ID_PREFIX + '"',
|
|
235
280
|
'- constraints.max_files MUST be <= ' + DISTILLED_MAX_FILES,
|
|
236
281
|
'- constraints.forbidden_paths MUST include at least [".git", "node_modules"]',
|
|
237
|
-
'
|
|
282
|
+
'',
|
|
283
|
+
'## VALIDATION',
|
|
284
|
+
'',
|
|
285
|
+
'- Validation commands MUST start with "node ", "npm ", or "npx " (security constraint).',
|
|
286
|
+
'- Include commands that actually verify the Gene was applied correctly.',
|
|
287
|
+
'- Good: "npx tsc --noEmit", "npm test"',
|
|
288
|
+
'- Bad: "node -v" (proves nothing about the Gene)',
|
|
289
|
+
'',
|
|
290
|
+
'## QUALITY BAR',
|
|
291
|
+
'',
|
|
292
|
+
'Imagine this Gene will be published on a marketplace for thousands of AI agents.',
|
|
293
|
+
'It should be as professional and useful as a well-written library README.',
|
|
294
|
+
'Ask yourself: "Would another agent find this Gene by searching for the signals?',
|
|
295
|
+
'Would the summary make them want to fetch it? Would the strategy be enough to execute?"',
|
|
296
|
+
'',
|
|
297
|
+
'---',
|
|
238
298
|
'',
|
|
239
299
|
'SUCCESSFUL CAPSULES (grouped by pattern):',
|
|
240
300
|
JSON.stringify(samples, null, 2),
|
|
@@ -246,7 +306,7 @@ function buildDistillationPrompt(analysis, existingGenes, sampleCapsules) {
|
|
|
246
306
|
JSON.stringify(analysis, null, 2),
|
|
247
307
|
'',
|
|
248
308
|
'Output a single Gene JSON object with these fields:',
|
|
249
|
-
'{ "type": "Gene", "id": "gene_distilled_
|
|
309
|
+
'{ "type": "Gene", "id": "gene_distilled_<descriptive-kebab-name>", "summary": "<clear marketplace-quality description>", "category": "repair|optimize|innovate", "signals_match": ["generic_signal_1", ...], "preconditions": ["Concrete condition 1", ...], "strategy": ["Step 1: verb ...", "Step 2: verb ...", ...], "constraints": { "max_files": N, "forbidden_paths": [".git", "node_modules", ...] }, "validation": ["npx tsc --noEmit", ...], "schema_version": "1.6.0" }',
|
|
250
310
|
].join('\n');
|
|
251
311
|
}
|
|
252
312
|
|
|
@@ -254,6 +314,64 @@ function distillRequestPath() {
|
|
|
254
314
|
return path.join(paths.getMemoryDir(), 'distill_request.json');
|
|
255
315
|
}
|
|
256
316
|
|
|
317
|
+
// ---------------------------------------------------------------------------
|
|
318
|
+
// Derive a descriptive ID from gene content when the LLM gives a bad name
|
|
319
|
+
// ---------------------------------------------------------------------------
|
|
320
|
+
function deriveDescriptiveId(gene) {
|
|
321
|
+
var words = [];
|
|
322
|
+
if (Array.isArray(gene.signals_match)) {
|
|
323
|
+
gene.signals_match.slice(0, 3).forEach(function (s) {
|
|
324
|
+
String(s).toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim().split(/\s+/).forEach(function (w) {
|
|
325
|
+
if (w.length >= 3 && words.length < 6) words.push(w);
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
if (words.length < 3 && gene.summary) {
|
|
330
|
+
var STOP = new Set(['the', 'and', 'for', 'with', 'from', 'that', 'this', 'into', 'when', 'are', 'was', 'has', 'had']);
|
|
331
|
+
String(gene.summary).toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim().split(/\s+/).forEach(function (w) {
|
|
332
|
+
if (w.length >= 3 && !STOP.has(w) && words.length < 6) words.push(w);
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
if (words.length < 3 && Array.isArray(gene.strategy) && gene.strategy.length > 0) {
|
|
336
|
+
String(gene.strategy[0]).toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim().split(/\s+/).forEach(function (w) {
|
|
337
|
+
if (w.length >= 3 && words.length < 6) words.push(w);
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
if (words.length < 2) words = ['auto', 'distilled', 'strategy'];
|
|
341
|
+
var unique = [];
|
|
342
|
+
var seen = new Set();
|
|
343
|
+
words.forEach(function (w) { if (!seen.has(w)) { seen.add(w); unique.push(w); } });
|
|
344
|
+
return DISTILLED_ID_PREFIX + unique.slice(0, 5).join('-');
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// ---------------------------------------------------------------------------
|
|
348
|
+
// Step 4: sanitizeSignalsMatch -- strip timestamps, random suffixes, tool names
|
|
349
|
+
// ---------------------------------------------------------------------------
|
|
350
|
+
function sanitizeSignalsMatch(signals) {
|
|
351
|
+
if (!Array.isArray(signals)) return [];
|
|
352
|
+
var cleaned = [];
|
|
353
|
+
signals.forEach(function (s) {
|
|
354
|
+
var sig = String(s || '').trim().toLowerCase();
|
|
355
|
+
if (!sig) return;
|
|
356
|
+
// Strip trailing timestamps (10+ digits) and random suffixes
|
|
357
|
+
sig = sig.replace(/[_-]\d{10,}$/g, '');
|
|
358
|
+
// Strip leading/trailing underscores/hyphens left over
|
|
359
|
+
sig = sig.replace(/^[_-]+|[_-]+$/g, '');
|
|
360
|
+
// Reject signals that are purely numeric
|
|
361
|
+
if (/^\d+$/.test(sig)) return;
|
|
362
|
+
// Reject signals that are just a tool name with optional number
|
|
363
|
+
if (/^(cursor|vscode|vim|emacs|windsurf|copilot|cline|codex|bypass|distill)[_-]?\d*$/i.test(sig)) return;
|
|
364
|
+
// Reject signals shorter than 3 chars after cleaning
|
|
365
|
+
if (sig.length < 3) return;
|
|
366
|
+
// Reject signals that still contain long numeric sequences (session IDs, etc.)
|
|
367
|
+
if (/\d{8,}/.test(sig)) return;
|
|
368
|
+
cleaned.push(sig);
|
|
369
|
+
});
|
|
370
|
+
// Deduplicate
|
|
371
|
+
var seen = {};
|
|
372
|
+
return cleaned.filter(function (s) { if (seen[s]) return false; seen[s] = true; return true; });
|
|
373
|
+
}
|
|
374
|
+
|
|
257
375
|
// ---------------------------------------------------------------------------
|
|
258
376
|
// Step 4: validateSynthesizedGene
|
|
259
377
|
// ---------------------------------------------------------------------------
|
|
@@ -267,10 +385,56 @@ function validateSynthesizedGene(gene, existingGenes) {
|
|
|
267
385
|
if (!Array.isArray(gene.signals_match) || gene.signals_match.length === 0) errors.push('missing or empty signals_match');
|
|
268
386
|
if (!Array.isArray(gene.strategy) || gene.strategy.length === 0) errors.push('missing or empty strategy');
|
|
269
387
|
|
|
388
|
+
// --- Signals sanitization (BEFORE id derivation so deriveDescriptiveId uses clean signals) ---
|
|
389
|
+
if (Array.isArray(gene.signals_match)) {
|
|
390
|
+
gene.signals_match = sanitizeSignalsMatch(gene.signals_match);
|
|
391
|
+
if (gene.signals_match.length === 0) {
|
|
392
|
+
errors.push('signals_match is empty after sanitization (all signals were invalid)');
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// --- Summary sanitization (BEFORE id derivation so deriveDescriptiveId uses clean summary) ---
|
|
397
|
+
if (gene.summary) {
|
|
398
|
+
gene.summary = gene.summary.replace(/\s*\d{10,}\s*$/g, '').replace(/\.\s*\d{10,}/g, '.').trim();
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// --- ID sanitization ---
|
|
270
402
|
if (gene.id && !String(gene.id).startsWith(DISTILLED_ID_PREFIX)) {
|
|
271
403
|
gene.id = DISTILLED_ID_PREFIX + String(gene.id).replace(/^gene_/, '');
|
|
272
404
|
}
|
|
273
405
|
|
|
406
|
+
if (gene.id) {
|
|
407
|
+
var suffix = String(gene.id).replace(DISTILLED_ID_PREFIX, '');
|
|
408
|
+
// Strip ALL embedded timestamps (10+ digit sequences) anywhere in the id
|
|
409
|
+
suffix = suffix.replace(/[-_]?\d{10,}[-_]?/g, '-').replace(/[-_]+/g, '-').replace(/^[-_]+|[-_]+$/g, '');
|
|
410
|
+
var needsRename = /^\d+$/.test(suffix) || /^\d{10,}/.test(suffix)
|
|
411
|
+
|| /^(cursor|vscode|vim|emacs|windsurf|copilot|cline|codex)[-_]?\d*$/i.test(suffix);
|
|
412
|
+
if (needsRename) {
|
|
413
|
+
gene.id = deriveDescriptiveId(gene);
|
|
414
|
+
} else {
|
|
415
|
+
gene.id = DISTILLED_ID_PREFIX + suffix;
|
|
416
|
+
}
|
|
417
|
+
var cleanSuffix = String(gene.id).replace(DISTILLED_ID_PREFIX, '');
|
|
418
|
+
if (cleanSuffix.replace(/[-_]/g, '').length < 6) {
|
|
419
|
+
gene.id = deriveDescriptiveId(gene);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// --- Summary fallback (summary was already sanitized above, this handles missing/short) ---
|
|
424
|
+
if (!gene.summary || typeof gene.summary !== 'string' || gene.summary.length < 10) {
|
|
425
|
+
if (Array.isArray(gene.strategy) && gene.strategy.length > 0) {
|
|
426
|
+
gene.summary = String(gene.strategy[0]).slice(0, 200);
|
|
427
|
+
} else if (Array.isArray(gene.signals_match) && gene.signals_match.length > 0) {
|
|
428
|
+
gene.summary = 'Strategy for: ' + gene.signals_match.slice(0, 3).join(', ');
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// --- Strategy quality: require minimum 3 steps ---
|
|
433
|
+
if (Array.isArray(gene.strategy) && gene.strategy.length < 3) {
|
|
434
|
+
errors.push('strategy must have at least 3 steps for a quality skill');
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// --- Constraints ---
|
|
274
438
|
if (!gene.constraints || typeof gene.constraints !== 'object') gene.constraints = {};
|
|
275
439
|
if (!Array.isArray(gene.constraints.forbidden_paths) || gene.constraints.forbidden_paths.length === 0) {
|
|
276
440
|
gene.constraints.forbidden_paths = ['.git', 'node_modules'];
|
|
@@ -282,6 +446,7 @@ function validateSynthesizedGene(gene, existingGenes) {
|
|
|
282
446
|
gene.constraints.max_files = DISTILLED_MAX_FILES;
|
|
283
447
|
}
|
|
284
448
|
|
|
449
|
+
// --- Validation command sanitization ---
|
|
285
450
|
var ALLOWED_PREFIXES = ['node ', 'npm ', 'npx '];
|
|
286
451
|
if (Array.isArray(gene.validation)) {
|
|
287
452
|
gene.validation = gene.validation.filter(function (cmd) {
|
|
@@ -294,11 +459,16 @@ function validateSynthesizedGene(gene, existingGenes) {
|
|
|
294
459
|
});
|
|
295
460
|
}
|
|
296
461
|
|
|
462
|
+
// --- Schema version ---
|
|
463
|
+
if (!gene.schema_version) gene.schema_version = '1.6.0';
|
|
464
|
+
|
|
465
|
+
// --- Duplicate ID check ---
|
|
297
466
|
var existingIds = new Set((existingGenes || []).map(function (g) { return g.id; }));
|
|
298
467
|
if (gene.id && existingIds.has(gene.id)) {
|
|
299
468
|
gene.id = gene.id + '_' + Date.now().toString(36);
|
|
300
469
|
}
|
|
301
470
|
|
|
471
|
+
// --- Signal overlap check ---
|
|
302
472
|
if (gene.signals_match && existingGenes && existingGenes.length > 0) {
|
|
303
473
|
var newSet = new Set(gene.signals_match.map(function (s) { return String(s).toLowerCase(); }));
|
|
304
474
|
for (var i = 0; i < existingGenes.length; i++) {
|
|
@@ -501,6 +671,7 @@ module.exports = {
|
|
|
501
671
|
prepareDistillation: prepareDistillation,
|
|
502
672
|
completeDistillation: completeDistillation,
|
|
503
673
|
validateSynthesizedGene: validateSynthesizedGene,
|
|
674
|
+
sanitizeSignalsMatch: sanitizeSignalsMatch,
|
|
504
675
|
shouldDistill: shouldDistill,
|
|
505
676
|
buildDistillationPrompt: buildDistillationPrompt,
|
|
506
677
|
extractJsonFromLlmResponse: extractJsonFromLlmResponse,
|
|
@@ -3,25 +3,90 @@
|
|
|
3
3
|
var { getHubUrl, buildHubHeaders, getNodeId } = require('./a2aProtocol');
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Sanitize a raw gene id into a human-readable kebab-case skill name.
|
|
7
|
+
* Returns null if the name is unsalvageable (pure numbers, tool name, etc.).
|
|
8
|
+
*/
|
|
9
|
+
function sanitizeSkillName(rawName) {
|
|
10
|
+
var name = rawName.replace(/[\r\n]+/g, '-').replace(/^gene_distilled_/, '').replace(/^gene_/, '').replace(/_/g, '-');
|
|
11
|
+
// Strip ALL embedded timestamps (10+ digit sequences) anywhere in the name
|
|
12
|
+
name = name.replace(/-?\d{10,}-?/g, '-').replace(/-+/g, '-').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
|
+
if (name.replace(/[-]/g, '').length < 6) return null;
|
|
17
|
+
return name;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Derive a Title Case display name from a kebab-case skill name.
|
|
22
|
+
* "retry-with-backoff" -> "Retry With Backoff"
|
|
23
|
+
*/
|
|
24
|
+
function toTitleCase(kebabName) {
|
|
25
|
+
return kebabName.split('-').map(function (w) {
|
|
26
|
+
if (!w) return '';
|
|
27
|
+
return w.charAt(0).toUpperCase() + w.slice(1);
|
|
28
|
+
}).join(' ');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Derive fallback name words from gene signals/summary when id is not usable.
|
|
33
|
+
*/
|
|
34
|
+
function deriveFallbackName(gene) {
|
|
35
|
+
var fallbackWords = [];
|
|
36
|
+
var STOP = new Set(['the', 'and', 'for', 'with', 'from', 'that', 'this', 'into', 'when', 'are', 'was', 'has', 'had', 'not', 'but', 'its']);
|
|
37
|
+
if (Array.isArray(gene.signals_match)) {
|
|
38
|
+
gene.signals_match.slice(0, 3).forEach(function (s) {
|
|
39
|
+
String(s).toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim().split(/\s+/).forEach(function (w) {
|
|
40
|
+
if (w.length >= 3 && !STOP.has(w) && fallbackWords.length < 5) fallbackWords.push(w);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
if (fallbackWords.length < 2 && gene.summary) {
|
|
45
|
+
String(gene.summary).toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim().split(/\s+/).forEach(function (w) {
|
|
46
|
+
if (w.length >= 3 && !STOP.has(w) && fallbackWords.length < 5) fallbackWords.push(w);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
var seen = {};
|
|
50
|
+
fallbackWords = fallbackWords.filter(function (w) { if (seen[w]) return false; seen[w] = true; return true; });
|
|
51
|
+
return fallbackWords.length >= 2 ? fallbackWords.join('-') : 'auto-distilled-skill';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Convert a Gene object into SKILL.md format -- marketplace-quality content.
|
|
7
56
|
*
|
|
8
57
|
* @param {object} gene - Gene asset
|
|
9
58
|
* @returns {string} SKILL.md content
|
|
10
59
|
*/
|
|
11
60
|
function geneToSkillMd(gene) {
|
|
12
|
-
var
|
|
13
|
-
var
|
|
61
|
+
var rawName = gene.id || 'unnamed-skill';
|
|
62
|
+
var name = sanitizeSkillName(rawName) || deriveFallbackName(gene);
|
|
63
|
+
var displayName = toTitleCase(name);
|
|
64
|
+
var desc = (gene.summary || '').replace(/[\r\n]+/g, ' ').replace(/\s*\d{10,}\s*$/g, '').trim();
|
|
65
|
+
if (!desc || desc.length < 10) desc = 'AI agent skill distilled from evolution experience.';
|
|
14
66
|
|
|
15
67
|
var lines = [
|
|
16
68
|
'---',
|
|
17
|
-
'name: ' +
|
|
69
|
+
'name: ' + displayName,
|
|
18
70
|
'description: ' + desc,
|
|
19
71
|
'---',
|
|
20
72
|
'',
|
|
21
|
-
'# ' +
|
|
73
|
+
'# ' + displayName,
|
|
74
|
+
'',
|
|
75
|
+
desc,
|
|
22
76
|
'',
|
|
23
77
|
];
|
|
24
78
|
|
|
79
|
+
// -- When to Use (derived from signals; preconditions go in their own section) --
|
|
80
|
+
if (gene.signals_match && gene.signals_match.length > 0) {
|
|
81
|
+
lines.push('## When to Use');
|
|
82
|
+
lines.push('');
|
|
83
|
+
lines.push('- When your project encounters: ' + gene.signals_match.slice(0, 4).map(function (s) {
|
|
84
|
+
return '`' + s + '`';
|
|
85
|
+
}).join(', '));
|
|
86
|
+
lines.push('');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// -- Trigger Signals --
|
|
25
90
|
if (gene.signals_match && gene.signals_match.length > 0) {
|
|
26
91
|
lines.push('## Trigger Signals');
|
|
27
92
|
lines.push('');
|
|
@@ -31,6 +96,7 @@ function geneToSkillMd(gene) {
|
|
|
31
96
|
lines.push('');
|
|
32
97
|
}
|
|
33
98
|
|
|
99
|
+
// -- Preconditions --
|
|
34
100
|
if (gene.preconditions && gene.preconditions.length > 0) {
|
|
35
101
|
lines.push('## Preconditions');
|
|
36
102
|
lines.push('');
|
|
@@ -40,27 +106,36 @@ function geneToSkillMd(gene) {
|
|
|
40
106
|
lines.push('');
|
|
41
107
|
}
|
|
42
108
|
|
|
109
|
+
// -- Strategy --
|
|
43
110
|
if (gene.strategy && gene.strategy.length > 0) {
|
|
44
111
|
lines.push('## Strategy');
|
|
45
112
|
lines.push('');
|
|
46
113
|
gene.strategy.forEach(function (step, i) {
|
|
47
|
-
|
|
114
|
+
var text = String(step);
|
|
115
|
+
var verb = extractStepVerb(text);
|
|
116
|
+
if (verb) {
|
|
117
|
+
lines.push((i + 1) + '. **' + verb + '** -- ' + stripLeadingVerb(text));
|
|
118
|
+
} else {
|
|
119
|
+
lines.push((i + 1) + '. ' + text);
|
|
120
|
+
}
|
|
48
121
|
});
|
|
49
122
|
lines.push('');
|
|
50
123
|
}
|
|
51
124
|
|
|
125
|
+
// -- Constraints --
|
|
52
126
|
if (gene.constraints) {
|
|
53
127
|
lines.push('## Constraints');
|
|
54
128
|
lines.push('');
|
|
55
129
|
if (gene.constraints.max_files) {
|
|
56
|
-
lines.push('- Max files: ' + gene.constraints.max_files);
|
|
130
|
+
lines.push('- Max files per invocation: ' + gene.constraints.max_files);
|
|
57
131
|
}
|
|
58
132
|
if (gene.constraints.forbidden_paths && gene.constraints.forbidden_paths.length > 0) {
|
|
59
|
-
lines.push('- Forbidden paths: ' + gene.constraints.forbidden_paths.join(', '));
|
|
133
|
+
lines.push('- Forbidden paths: ' + gene.constraints.forbidden_paths.map(function (p) { return '`' + p + '`'; }).join(', '));
|
|
60
134
|
}
|
|
61
135
|
lines.push('');
|
|
62
136
|
}
|
|
63
137
|
|
|
138
|
+
// -- Validation --
|
|
64
139
|
if (gene.validation && gene.validation.length > 0) {
|
|
65
140
|
lines.push('## Validation');
|
|
66
141
|
lines.push('');
|
|
@@ -72,6 +147,16 @@ function geneToSkillMd(gene) {
|
|
|
72
147
|
});
|
|
73
148
|
}
|
|
74
149
|
|
|
150
|
+
// -- Metadata --
|
|
151
|
+
lines.push('## Metadata');
|
|
152
|
+
lines.push('');
|
|
153
|
+
lines.push('- Category: `' + (gene.category || 'innovate') + '`');
|
|
154
|
+
lines.push('- Schema version: `' + (gene.schema_version || '1.6.0') + '`');
|
|
155
|
+
if (gene._distilled_meta && gene._distilled_meta.source_capsule_count) {
|
|
156
|
+
lines.push('- Distilled from: ' + gene._distilled_meta.source_capsule_count + ' successful capsules');
|
|
157
|
+
}
|
|
158
|
+
lines.push('');
|
|
159
|
+
|
|
75
160
|
lines.push('---');
|
|
76
161
|
lines.push('');
|
|
77
162
|
lines.push('*This Skill was generated by [Evolver](https://github.com/autogame-17/evolver) and is distributed under the [EvoMap Skill License (ESL-1.0)](https://evomap.ai/terms). Unauthorized redistribution, bulk scraping, or republishing is prohibited. See LICENSE file for full terms.*');
|
|
@@ -80,6 +165,31 @@ function geneToSkillMd(gene) {
|
|
|
80
165
|
return lines.join('\n');
|
|
81
166
|
}
|
|
82
167
|
|
|
168
|
+
/**
|
|
169
|
+
* Extract the leading verb from a strategy step for bolding.
|
|
170
|
+
* Only extracts a single verb to avoid splitting compound phrases.
|
|
171
|
+
* e.g. "Verify Cursor CLI installation" -> "Verify"
|
|
172
|
+
* "Run `npm test` to check" -> "Run"
|
|
173
|
+
* "Configure non-interactive mode" -> "Configure"
|
|
174
|
+
*/
|
|
175
|
+
function extractStepVerb(step) {
|
|
176
|
+
// Only match a capitalized verb at the very start (no leading backtick/special chars)
|
|
177
|
+
var match = step.match(/^([A-Z][a-z]+)/);
|
|
178
|
+
return match ? match[1] : '';
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Remove the leading verb from a step (already shown in bold).
|
|
183
|
+
*/
|
|
184
|
+
function stripLeadingVerb(step) {
|
|
185
|
+
var verb = extractStepVerb(step);
|
|
186
|
+
if (verb && step.startsWith(verb)) {
|
|
187
|
+
var rest = step.slice(verb.length).replace(/^[\s:.\-]+/, '');
|
|
188
|
+
return rest || step;
|
|
189
|
+
}
|
|
190
|
+
return step;
|
|
191
|
+
}
|
|
192
|
+
|
|
83
193
|
/**
|
|
84
194
|
* Publish a Gene as a Skill to the Hub skill store.
|
|
85
195
|
*
|
|
@@ -92,16 +202,37 @@ function publishSkillToHub(gene, opts) {
|
|
|
92
202
|
var hubUrl = getHubUrl();
|
|
93
203
|
if (!hubUrl) return Promise.resolve({ ok: false, error: 'no_hub_url' });
|
|
94
204
|
|
|
95
|
-
|
|
205
|
+
// Shallow-copy gene to avoid mutating the caller's object
|
|
206
|
+
var geneCopy = {};
|
|
207
|
+
Object.keys(gene).forEach(function (k) { geneCopy[k] = gene[k]; });
|
|
208
|
+
if (Array.isArray(geneCopy.signals_match)) {
|
|
209
|
+
try {
|
|
210
|
+
var distiller = require('./skillDistiller');
|
|
211
|
+
geneCopy.signals_match = distiller.sanitizeSignalsMatch(geneCopy.signals_match);
|
|
212
|
+
} catch (e) { /* distiller not available, skip */ }
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
var content = geneToSkillMd(geneCopy);
|
|
96
216
|
var nodeId = getNodeId();
|
|
97
|
-
var
|
|
217
|
+
var fmName = content.match(/^name:\s*(.+)$/m);
|
|
218
|
+
var derivedName = fmName ? fmName[1].trim().toLowerCase().replace(/[^a-z0-9]+/g, '_') : (gene.id || 'unnamed').replace(/^gene_/, '');
|
|
219
|
+
// Strip ALL embedded timestamps from skillId
|
|
220
|
+
derivedName = derivedName.replace(/_?\d{10,}_?/g, '_').replace(/_+/g, '_').replace(/^_|_$/g, '');
|
|
221
|
+
var skillId = 'skill_' + derivedName;
|
|
222
|
+
|
|
223
|
+
// Clean tags: use already-sanitized signals from geneCopy
|
|
224
|
+
var tags = opts.tags || geneCopy.signals_match || [];
|
|
225
|
+
tags = tags.filter(function (t) {
|
|
226
|
+
var s = String(t || '').trim();
|
|
227
|
+
return s.length >= 3 && !/^\d+$/.test(s) && !/\d{10,}/.test(s);
|
|
228
|
+
});
|
|
98
229
|
|
|
99
230
|
var body = {
|
|
100
231
|
sender_id: nodeId,
|
|
101
232
|
skill_id: skillId,
|
|
102
233
|
content: content,
|
|
103
|
-
category: opts.category ||
|
|
104
|
-
tags:
|
|
234
|
+
category: opts.category || geneCopy.category || null,
|
|
235
|
+
tags: tags,
|
|
105
236
|
};
|
|
106
237
|
|
|
107
238
|
var endpoint = hubUrl.replace(/\/+$/, '') + '/a2a/skill/store/publish';
|
|
@@ -134,12 +265,18 @@ function updateSkillOnHub(nodeId, skillId, content, opts, gene) {
|
|
|
134
265
|
var hubUrl = getHubUrl();
|
|
135
266
|
if (!hubUrl) return Promise.resolve({ ok: false, error: 'no_hub_url' });
|
|
136
267
|
|
|
268
|
+
var tags = opts.tags || gene.signals_match || [];
|
|
269
|
+
tags = tags.filter(function (t) {
|
|
270
|
+
var s = String(t || '').trim();
|
|
271
|
+
return s.length >= 3 && !/^\d+$/.test(s) && !/\d{10,}/.test(s);
|
|
272
|
+
});
|
|
273
|
+
|
|
137
274
|
var body = {
|
|
138
275
|
sender_id: nodeId,
|
|
139
276
|
skill_id: skillId,
|
|
140
277
|
content: content,
|
|
141
278
|
category: opts.category || gene.category || null,
|
|
142
|
-
tags:
|
|
279
|
+
tags: tags,
|
|
143
280
|
changelog: 'Iterative evolution update',
|
|
144
281
|
};
|
|
145
282
|
|
|
@@ -151,8 +288,13 @@ function updateSkillOnHub(nodeId, skillId, content, opts, gene) {
|
|
|
151
288
|
body: JSON.stringify(body),
|
|
152
289
|
signal: AbortSignal.timeout(15000),
|
|
153
290
|
})
|
|
154
|
-
.then(function (res) { return res.json(); })
|
|
155
|
-
.then(function (
|
|
291
|
+
.then(function (res) { return res.json().then(function (data) { return { status: res.status, data: data }; }); })
|
|
292
|
+
.then(function (result) {
|
|
293
|
+
if (result.status >= 200 && result.status < 300) {
|
|
294
|
+
return { ok: true, result: result.data };
|
|
295
|
+
}
|
|
296
|
+
return { ok: false, error: result.data?.error || 'update_failed', status: result.status };
|
|
297
|
+
})
|
|
156
298
|
.catch(function (err) { return { ok: false, error: err.message }; });
|
|
157
299
|
}
|
|
158
300
|
|
|
@@ -160,4 +302,6 @@ module.exports = {
|
|
|
160
302
|
geneToSkillMd: geneToSkillMd,
|
|
161
303
|
publishSkillToHub: publishSkillToHub,
|
|
162
304
|
updateSkillOnHub: updateSkillOnHub,
|
|
305
|
+
sanitizeSkillName: sanitizeSkillName,
|
|
306
|
+
toTitleCase: toTitleCase,
|
|
163
307
|
};
|
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}`);
|