@evomap/evolver 1.29.8 → 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/package.json +1 -1
- package/src/gep/skillDistiller.js +128 -22
- package/src/gep/skillPublisher.js +142 -34
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": {
|
|
@@ -223,32 +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
|
-
'- Output valid Gene JSON only (no markdown, no explanation)',
|
|
238
282
|
'',
|
|
239
|
-
'
|
|
240
|
-
'
|
|
241
|
-
'
|
|
242
|
-
'-
|
|
243
|
-
'- Good: "
|
|
244
|
-
'- Bad: "
|
|
245
|
-
'
|
|
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',
|
|
246
291
|
'',
|
|
247
|
-
'
|
|
248
|
-
'
|
|
249
|
-
'
|
|
250
|
-
'
|
|
251
|
-
'
|
|
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
|
+
'---',
|
|
252
298
|
'',
|
|
253
299
|
'SUCCESSFUL CAPSULES (grouped by pattern):',
|
|
254
300
|
JSON.stringify(samples, null, 2),
|
|
@@ -260,7 +306,7 @@ function buildDistillationPrompt(analysis, existingGenes, sampleCapsules) {
|
|
|
260
306
|
JSON.stringify(analysis, null, 2),
|
|
261
307
|
'',
|
|
262
308
|
'Output a single Gene JSON object with these fields:',
|
|
263
|
-
'{ "type": "Gene", "id": "gene_distilled_<descriptive-kebab-name>", "summary": "<clear
|
|
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" }',
|
|
264
310
|
].join('\n');
|
|
265
311
|
}
|
|
266
312
|
|
|
@@ -298,6 +344,34 @@ function deriveDescriptiveId(gene) {
|
|
|
298
344
|
return DISTILLED_ID_PREFIX + unique.slice(0, 5).join('-');
|
|
299
345
|
}
|
|
300
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
|
+
|
|
301
375
|
// ---------------------------------------------------------------------------
|
|
302
376
|
// Step 4: validateSynthesizedGene
|
|
303
377
|
// ---------------------------------------------------------------------------
|
|
@@ -311,16 +385,34 @@ function validateSynthesizedGene(gene, existingGenes) {
|
|
|
311
385
|
if (!Array.isArray(gene.signals_match) || gene.signals_match.length === 0) errors.push('missing or empty signals_match');
|
|
312
386
|
if (!Array.isArray(gene.strategy) || gene.strategy.length === 0) errors.push('missing or empty strategy');
|
|
313
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 ---
|
|
314
402
|
if (gene.id && !String(gene.id).startsWith(DISTILLED_ID_PREFIX)) {
|
|
315
403
|
gene.id = DISTILLED_ID_PREFIX + String(gene.id).replace(/^gene_/, '');
|
|
316
404
|
}
|
|
317
405
|
|
|
318
406
|
if (gene.id) {
|
|
319
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, '');
|
|
320
410
|
var needsRename = /^\d+$/.test(suffix) || /^\d{10,}/.test(suffix)
|
|
321
|
-
|| /^(cursor|vscode|vim|emacs|windsurf|copilot|cline|codex)[-_]?\d
|
|
411
|
+
|| /^(cursor|vscode|vim|emacs|windsurf|copilot|cline|codex)[-_]?\d*$/i.test(suffix);
|
|
322
412
|
if (needsRename) {
|
|
323
413
|
gene.id = deriveDescriptiveId(gene);
|
|
414
|
+
} else {
|
|
415
|
+
gene.id = DISTILLED_ID_PREFIX + suffix;
|
|
324
416
|
}
|
|
325
417
|
var cleanSuffix = String(gene.id).replace(DISTILLED_ID_PREFIX, '');
|
|
326
418
|
if (cleanSuffix.replace(/[-_]/g, '').length < 6) {
|
|
@@ -328,6 +420,7 @@ function validateSynthesizedGene(gene, existingGenes) {
|
|
|
328
420
|
}
|
|
329
421
|
}
|
|
330
422
|
|
|
423
|
+
// --- Summary fallback (summary was already sanitized above, this handles missing/short) ---
|
|
331
424
|
if (!gene.summary || typeof gene.summary !== 'string' || gene.summary.length < 10) {
|
|
332
425
|
if (Array.isArray(gene.strategy) && gene.strategy.length > 0) {
|
|
333
426
|
gene.summary = String(gene.strategy[0]).slice(0, 200);
|
|
@@ -336,6 +429,12 @@ function validateSynthesizedGene(gene, existingGenes) {
|
|
|
336
429
|
}
|
|
337
430
|
}
|
|
338
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 ---
|
|
339
438
|
if (!gene.constraints || typeof gene.constraints !== 'object') gene.constraints = {};
|
|
340
439
|
if (!Array.isArray(gene.constraints.forbidden_paths) || gene.constraints.forbidden_paths.length === 0) {
|
|
341
440
|
gene.constraints.forbidden_paths = ['.git', 'node_modules'];
|
|
@@ -347,6 +446,7 @@ function validateSynthesizedGene(gene, existingGenes) {
|
|
|
347
446
|
gene.constraints.max_files = DISTILLED_MAX_FILES;
|
|
348
447
|
}
|
|
349
448
|
|
|
449
|
+
// --- Validation command sanitization ---
|
|
350
450
|
var ALLOWED_PREFIXES = ['node ', 'npm ', 'npx '];
|
|
351
451
|
if (Array.isArray(gene.validation)) {
|
|
352
452
|
gene.validation = gene.validation.filter(function (cmd) {
|
|
@@ -359,11 +459,16 @@ function validateSynthesizedGene(gene, existingGenes) {
|
|
|
359
459
|
});
|
|
360
460
|
}
|
|
361
461
|
|
|
462
|
+
// --- Schema version ---
|
|
463
|
+
if (!gene.schema_version) gene.schema_version = '1.6.0';
|
|
464
|
+
|
|
465
|
+
// --- Duplicate ID check ---
|
|
362
466
|
var existingIds = new Set((existingGenes || []).map(function (g) { return g.id; }));
|
|
363
467
|
if (gene.id && existingIds.has(gene.id)) {
|
|
364
468
|
gene.id = gene.id + '_' + Date.now().toString(36);
|
|
365
469
|
}
|
|
366
470
|
|
|
471
|
+
// --- Signal overlap check ---
|
|
367
472
|
if (gene.signals_match && existingGenes && existingGenes.length > 0) {
|
|
368
473
|
var newSet = new Set(gene.signals_match.map(function (s) { return String(s).toLowerCase(); }));
|
|
369
474
|
for (var i = 0; i < existingGenes.length; i++) {
|
|
@@ -566,6 +671,7 @@ module.exports = {
|
|
|
566
671
|
prepareDistillation: prepareDistillation,
|
|
567
672
|
completeDistillation: completeDistillation,
|
|
568
673
|
validateSynthesizedGene: validateSynthesizedGene,
|
|
674
|
+
sanitizeSignalsMatch: sanitizeSignalsMatch,
|
|
569
675
|
shouldDistill: shouldDistill,
|
|
570
676
|
buildDistillationPrompt: buildDistillationPrompt,
|
|
571
677
|
extractJsonFromLlmResponse: extractJsonFromLlmResponse,
|
|
@@ -3,54 +3,90 @@
|
|
|
3
3
|
var { getHubUrl, buildHubHeaders, getNodeId } = require('./a2aProtocol');
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
|
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: ' +
|
|
69
|
+
'name: ' + displayName,
|
|
47
70
|
'description: ' + desc,
|
|
48
71
|
'---',
|
|
49
72
|
'',
|
|
50
|
-
'# ' +
|
|
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
|
-
|
|
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
|
-
|
|
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 ||
|
|
135
|
-
tags:
|
|
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:
|
|
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
|
};
|