@evomap/evolver 1.29.8 → 1.30.2

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.
@@ -3,54 +3,90 @@
3
3
  var { getHubUrl, buildHubHeaders, getNodeId } = require('./a2aProtocol');
4
4
 
5
5
  /**
6
- * Convert a Gene object into SKILL.md format (Claude/Anthropic style).
7
- *
8
- * @param {object} gene - Gene asset
9
- * @returns {string} SKILL.md content
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.).
10
8
  */
11
9
  function sanitizeSkillName(rawName) {
12
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
13
  if (/^\d{8,}/.test(name) || /^(cursor|vscode|vim|emacs|windsurf|copilot|cline|codex)[-]?\d*$/i.test(name)) {
14
14
  return null;
15
15
  }
16
- name = name.replace(/-?\d{10,}$/g, '').replace(/-+$/, '');
17
16
  if (name.replace(/[-]/g, '').length < 6) return null;
18
17
  return name;
19
18
  }
20
19
 
21
- function geneToSkillMd(gene) {
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);
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);
36
41
  });
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';
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
+ });
41
48
  }
42
- var desc = (gene.summary || 'AI agent skill distilled from evolution experience.').replace(/[\r\n]+/g, ' ').trim();
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.
56
+ *
57
+ * @param {object} gene - Gene asset
58
+ * @returns {string} SKILL.md content
59
+ */
60
+ function geneToSkillMd(gene) {
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.';
43
66
 
44
67
  var lines = [
45
68
  '---',
46
- 'name: ' + name,
69
+ 'name: ' + displayName,
47
70
  'description: ' + desc,
48
71
  '---',
49
72
  '',
50
- '# ' + name,
73
+ '# ' + displayName,
74
+ '',
75
+ desc,
51
76
  '',
52
77
  ];
53
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 --
54
90
  if (gene.signals_match && gene.signals_match.length > 0) {
55
91
  lines.push('## Trigger Signals');
56
92
  lines.push('');
@@ -60,6 +96,7 @@ function geneToSkillMd(gene) {
60
96
  lines.push('');
61
97
  }
62
98
 
99
+ // -- Preconditions --
63
100
  if (gene.preconditions && gene.preconditions.length > 0) {
64
101
  lines.push('## Preconditions');
65
102
  lines.push('');
@@ -69,27 +106,36 @@ function geneToSkillMd(gene) {
69
106
  lines.push('');
70
107
  }
71
108
 
109
+ // -- Strategy --
72
110
  if (gene.strategy && gene.strategy.length > 0) {
73
111
  lines.push('## Strategy');
74
112
  lines.push('');
75
113
  gene.strategy.forEach(function (step, i) {
76
- lines.push((i + 1) + '. ' + step);
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
+ }
77
121
  });
78
122
  lines.push('');
79
123
  }
80
124
 
125
+ // -- Constraints --
81
126
  if (gene.constraints) {
82
127
  lines.push('## Constraints');
83
128
  lines.push('');
84
129
  if (gene.constraints.max_files) {
85
- lines.push('- Max files: ' + gene.constraints.max_files);
130
+ lines.push('- Max files per invocation: ' + gene.constraints.max_files);
86
131
  }
87
132
  if (gene.constraints.forbidden_paths && gene.constraints.forbidden_paths.length > 0) {
88
- lines.push('- Forbidden paths: ' + gene.constraints.forbidden_paths.join(', '));
133
+ lines.push('- Forbidden paths: ' + gene.constraints.forbidden_paths.map(function (p) { return '`' + p + '`'; }).join(', '));
89
134
  }
90
135
  lines.push('');
91
136
  }
92
137
 
138
+ // -- Validation --
93
139
  if (gene.validation && gene.validation.length > 0) {
94
140
  lines.push('## Validation');
95
141
  lines.push('');
@@ -101,6 +147,16 @@ function geneToSkillMd(gene) {
101
147
  });
102
148
  }
103
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
+
104
160
  lines.push('---');
105
161
  lines.push('');
106
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.*');
@@ -109,6 +165,31 @@ function geneToSkillMd(gene) {
109
165
  return lines.join('\n');
110
166
  }
111
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
+
112
193
  /**
113
194
  * Publish a Gene as a Skill to the Hub skill store.
114
195
  *
@@ -121,18 +202,37 @@ function publishSkillToHub(gene, opts) {
121
202
  var hubUrl = getHubUrl();
122
203
  if (!hubUrl) return Promise.resolve({ ok: false, error: 'no_hub_url' });
123
204
 
124
- var content = geneToSkillMd(gene);
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);
125
216
  var nodeId = getNodeId();
126
217
  var fmName = content.match(/^name:\s*(.+)$/m);
127
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, '');
128
221
  var skillId = 'skill_' + derivedName;
129
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
+ });
229
+
130
230
  var body = {
131
231
  sender_id: nodeId,
132
232
  skill_id: skillId,
133
233
  content: content,
134
- category: opts.category || gene.category || null,
135
- tags: opts.tags || gene.signals_match || [],
234
+ category: opts.category || geneCopy.category || null,
235
+ tags: tags,
136
236
  };
137
237
 
138
238
  var endpoint = hubUrl.replace(/\/+$/, '') + '/a2a/skill/store/publish';
@@ -165,12 +265,18 @@ function updateSkillOnHub(nodeId, skillId, content, opts, gene) {
165
265
  var hubUrl = getHubUrl();
166
266
  if (!hubUrl) return Promise.resolve({ ok: false, error: 'no_hub_url' });
167
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
+
168
274
  var body = {
169
275
  sender_id: nodeId,
170
276
  skill_id: skillId,
171
277
  content: content,
172
278
  category: opts.category || gene.category || null,
173
- tags: opts.tags || gene.signals_match || [],
279
+ tags: tags,
174
280
  changelog: 'Iterative evolution update',
175
281
  };
176
282
 
@@ -196,4 +302,6 @@ module.exports = {
196
302
  geneToSkillMd: geneToSkillMd,
197
303
  publishSkillToHub: publishSkillToHub,
198
304
  updateSkillOnHub: updateSkillOnHub,
305
+ sanitizeSkillName: sanitizeSkillName,
306
+ toTitleCase: toTitleCase,
199
307
  };
@@ -20,6 +20,7 @@ const { buildValidationReport } = require('./validationReport');
20
20
  const { logAssetCall } = require('./assetCallLog');
21
21
  const { recordNarrative } = require('./narrativeMemory');
22
22
  const { isLlmReviewEnabled, runLlmReview } = require('./llmReview');
23
+ const { buildExecutionTrace } = require('./executionTrace');
23
24
 
24
25
  function nowIso() {
25
26
  return new Date().toISOString();
@@ -1225,6 +1226,22 @@ function solidify({ intent, summary, dryRun = false, rollbackOnFailure = true }
1225
1226
  memory_graph: memoryGraphPath(),
1226
1227
  },
1227
1228
  };
1229
+ // Build desensitized execution trace for cross-agent experience sharing
1230
+ const executionTrace = buildExecutionTrace({
1231
+ gene: geneUsed,
1232
+ mutation,
1233
+ signals,
1234
+ blast,
1235
+ constraintCheck,
1236
+ validation,
1237
+ canary,
1238
+ outcomeStatus,
1239
+ startedAt: validation.startedAt,
1240
+ });
1241
+ if (executionTrace) {
1242
+ event.execution_trace = executionTrace;
1243
+ }
1244
+
1228
1245
  event.asset_id = computeAssetId(event);
1229
1246
 
1230
1247
  let capsule = null;
@@ -1350,7 +1367,10 @@ function solidify({ intent, summary, dryRun = false, rollbackOnFailure = true }
1350
1367
  state.last_solidify = {
1351
1368
  run_id: runId, at: ts, event_id: event.id, capsule_id: capsuleId, outcome: event.outcome,
1352
1369
  };
1353
- if (!dryRun) writeStateForSolidify(state);
1370
+ if (!dryRun) {
1371
+ state.solidify_count = (state.solidify_count || 0) + 1;
1372
+ writeStateForSolidify(state);
1373
+ }
1354
1374
 
1355
1375
  if (!dryRun) {
1356
1376
  try {