@evomap/evolver 1.89.17 → 1.89.19
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/README.ja-JP.md +1 -1
- package/README.ko-KR.md +1 -1
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/index.js +272 -18
- package/package.json +7 -2
- package/src/adapters/claudeCode.js +16 -18
- package/src/adapters/hookAdapter.js +92 -5
- package/src/adapters/scripts/evolver-session-start.js +98 -0
- package/src/atp/atpExecute.js +3 -1
- package/src/atp/hubClient.js +5 -3
- package/src/atp/serviceHelper.js +3 -2
- package/src/evolve/guards.js +1 -1
- package/src/evolve/pipeline/collect.js +1 -1
- package/src/evolve/pipeline/dispatch.js +1 -1
- package/src/evolve/pipeline/enrich.js +1 -1
- package/src/evolve/pipeline/hub.js +1 -1
- package/src/evolve/pipeline/select.js +1 -1
- package/src/evolve/pipeline/signals.js +1 -1
- package/src/evolve/utils.js +1 -1
- package/src/evolve.js +1 -1
- package/src/gep/a2aProtocol.js +1 -1
- package/src/gep/antiAbuseTelemetry.js +1 -1
- package/src/gep/autoDistillConv.js +1 -1
- package/src/gep/autoDistillLlm.js +1 -1
- package/src/gep/candidateEval.js +1 -1
- package/src/gep/candidates.js +1 -1
- package/src/gep/cliContracts.js +37 -1
- package/src/gep/contentHash.js +1 -1
- package/src/gep/conversationDistiller.js +1 -1
- package/src/gep/conversationSniffer.js +1 -1
- package/src/gep/crypto.js +1 -1
- package/src/gep/curriculum.js +1 -1
- package/src/gep/deviceId.js +1 -1
- package/src/gep/directoryClient.js +7 -3
- package/src/gep/envFingerprint.js +1 -1
- package/src/gep/epigenetics.js +1 -1
- package/src/gep/execBridge.js +1 -1
- package/src/gep/explore.js +1 -1
- package/src/gep/hash.js +1 -1
- package/src/gep/hubFetch.js +1 -1
- package/src/gep/hubReview.js +1 -1
- package/src/gep/hubSearch.js +1 -1
- package/src/gep/hubVerify.js +1 -1
- package/src/gep/learningSignals.js +1 -1
- package/src/gep/memoryGraph.js +1 -1
- package/src/gep/memoryGraphAdapter.js +1 -1
- package/src/gep/mutation.js +1 -1
- package/src/gep/narrativeMemory.js +1 -1
- package/src/gep/oauthLogin.js +3 -5
- package/src/gep/openPRRegistry.js +1 -1
- package/src/gep/personality.js +1 -1
- package/src/gep/policyCheck.js +1 -1
- package/src/gep/privacyClient.js +28 -10
- package/src/gep/prompt.js +1 -1
- package/src/gep/recallInject.js +1 -1
- package/src/gep/recallVerifier.js +1 -1
- package/src/gep/reflection.js +1 -1
- package/src/gep/savingsCore.js +1 -1
- package/src/gep/selector.js +1 -1
- package/src/gep/skill2gep.js +329 -43
- package/src/gep/skill2gepAudit.js +303 -0
- package/src/gep/skillDistiller.js +1 -1
- package/src/gep/skillPublisher.js +8 -3
- package/src/gep/solidify.js +1 -1
- package/src/gep/strategy.js +1 -1
- package/src/gep/taskReceiver.js +3 -2
- package/src/gep/tokenSavings.js +1 -1
- package/src/gep/trajectoryExport.js +1 -0
- package/src/gep/validator/index.js +7 -2
- package/src/gep/validator/reporter.js +7 -2
- package/src/gep/validator/stakeBootstrap.js +7 -2
- package/src/gep/workspaceKeychain.js +1 -1
- package/src/proxy/clientSettings.js +405 -0
- package/src/proxy/extensions/traceControl.js +1 -1
- package/src/proxy/index.js +13 -5
- package/src/proxy/inject.js +1 -1
- package/src/proxy/lifecycle/manager.js +82 -16
- package/src/proxy/mailbox/state.js +207 -0
- package/src/proxy/mailbox/store.js +164 -64
- package/src/proxy/router/messages_route.js +56 -4
- package/src/proxy/server/http.js +40 -6
- package/src/proxy/sync/inbound.js +31 -12
- package/src/proxy/sync/outbound.js +164 -24
- package/src/proxy/trace/extractor.js +1 -1
- package/src/proxy/trace/usage.js +1 -1
package/src/gep/skill2gep.js
CHANGED
|
@@ -29,6 +29,7 @@ const skillDistiller = require('./skillDistiller');
|
|
|
29
29
|
const skillPublisher = require('./skillPublisher');
|
|
30
30
|
const envFingerprint = require('./envFingerprint');
|
|
31
31
|
const a2a = require('./a2aProtocol');
|
|
32
|
+
const audit = require('./skill2gepAudit');
|
|
32
33
|
|
|
33
34
|
const SKILL2GEP_ID_PREFIX = 'gene_s2g_';
|
|
34
35
|
|
|
@@ -261,22 +262,133 @@ function parseSkillMd(skillMd) {
|
|
|
261
262
|
};
|
|
262
263
|
}
|
|
263
264
|
|
|
265
|
+
// ---------------------------------------------------------------------------
|
|
266
|
+
// Provenance classification (the central finding of TaskGenome Bench, §3.1):
|
|
267
|
+
// a Gene's value depends on WHERE it came from, not on being short.
|
|
268
|
+
//
|
|
269
|
+
// evolved -- distilled from a real solve -> fail -> mutate -> pass
|
|
270
|
+
// trajectory. The corrective insight that flipped the outcome
|
|
271
|
+
// is the high-value payload. These beat Skills (+8.7..+15.5pp).
|
|
272
|
+
// distilled -- transcribed from reference/teacher text with no real failing
|
|
273
|
+
// trajectory to learn from. The report shows these tend to be
|
|
274
|
+
// WORSE than Skills (-3.2..-11.2pp), so we flag/downgrade them.
|
|
275
|
+
// manual -- pure SKILL.md transcription, no execution evidence at all.
|
|
276
|
+
// ---------------------------------------------------------------------------
|
|
277
|
+
function classifyProvenance(execution) {
|
|
278
|
+
const ex = execution || {};
|
|
279
|
+
const rollouts = Array.isArray(ex.rollouts) ? ex.rollouts : [];
|
|
280
|
+
const mutationLog = Array.isArray(ex.mutation_log) ? ex.mutation_log : [];
|
|
281
|
+
const status = ex.status ? String(ex.status) : null;
|
|
282
|
+
const blast = ex.blast_radius || null;
|
|
283
|
+
const hasBlast = blast && (Number(blast.files || 0) > 0 || Number(blast.lines || 0) > 0);
|
|
284
|
+
|
|
285
|
+
const failedRollouts = rollouts.filter((r) => r && String(r.status) === 'failed').length;
|
|
286
|
+
const passedRollouts = rollouts.some((r) => r && String(r.status) === 'success');
|
|
287
|
+
const overcameFailure = mutationLog.length > 0 || (failedRollouts > 0 && (passedRollouts || status === 'success'));
|
|
288
|
+
|
|
289
|
+
if (status === 'success' && hasBlast && overcameFailure) return 'evolved';
|
|
290
|
+
// Anything carrying real execution evidence (a status, a rollout, or an
|
|
291
|
+
// overcome-failure log) but not meeting the evolved bar is "distilled" -- it
|
|
292
|
+
// has evidence, just not a verified fail->pass-with-blast trajectory. Only a
|
|
293
|
+
// run with NO evidence at all is "manual" (per docs/skill2gep.md). A success
|
|
294
|
+
// with mutation_log but zero blast radius must therefore be distilled, not
|
|
295
|
+
// manual.
|
|
296
|
+
if ((ex.reference_distilled === true) || status || rollouts.length > 0 || mutationLog.length > 0) {
|
|
297
|
+
return 'distilled';
|
|
298
|
+
}
|
|
299
|
+
return 'manual';
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Build the corrective-insight strategy for an evolved Gene. The insight that
|
|
303
|
+
// flipped fail -> pass goes FIRST (the case-study shape), then any
|
|
304
|
+
// LLM-distilled steps the host supplied, then the Skill's own workflow steps.
|
|
305
|
+
function buildEvolvedStrategy(parsed, execution) {
|
|
306
|
+
const strategy = [];
|
|
307
|
+
const insight = execution && execution.corrective_insight
|
|
308
|
+
? String(execution.corrective_insight).trim()
|
|
309
|
+
: '';
|
|
310
|
+
if (insight && insight.length >= 5) {
|
|
311
|
+
strategy.push(insight.length <= 300 ? insight : insight.slice(0, 297) + '...');
|
|
312
|
+
}
|
|
313
|
+
const distilled = Array.isArray(execution && execution.distilled_strategy) ? execution.distilled_strategy : [];
|
|
314
|
+
distilled.forEach((s) => {
|
|
315
|
+
const t = String(s || '').trim();
|
|
316
|
+
if (t.length >= 5 && strategy.indexOf(t) === -1) strategy.push(t.length <= 300 ? t : t.slice(0, 297) + '...');
|
|
317
|
+
});
|
|
318
|
+
(parsed.strategy || []).forEach((s) => { if (strategy.indexOf(s) === -1) strategy.push(s); });
|
|
319
|
+
return strategy;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Turn the error categories the trajectory overcame into verifiable
|
|
323
|
+
// preconditions ("a prior attempt failed with X; confirm it is handled").
|
|
324
|
+
function preconditionsFromErrors(execution) {
|
|
325
|
+
const mutationLog = Array.isArray(execution && execution.mutation_log) ? execution.mutation_log : [];
|
|
326
|
+
const out = [];
|
|
327
|
+
const seen = new Set();
|
|
328
|
+
for (const err of mutationLog) {
|
|
329
|
+
const e = String(err || '').trim();
|
|
330
|
+
if (!e || seen.has(e)) continue;
|
|
331
|
+
seen.add(e);
|
|
332
|
+
const human = e.replace(/_/g, ' ');
|
|
333
|
+
out.push('A prior attempt failed with "' + human + '"; verify this condition is handled before trusting the approach.');
|
|
334
|
+
if (out.length >= 4) break;
|
|
335
|
+
}
|
|
336
|
+
return out;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Quality score in [0,1]. Evolved trajectories with a recorded corrective
|
|
340
|
+
// insight score highest; pure transcription with no evidence scores lowest.
|
|
341
|
+
function computeQualityScore(source, parsed, execution) {
|
|
342
|
+
const ex = execution || {};
|
|
343
|
+
let score;
|
|
344
|
+
if (source === 'evolved') {
|
|
345
|
+
score = 0.7;
|
|
346
|
+
if (ex.corrective_insight && String(ex.corrective_insight).trim().length >= 5) score += 0.15;
|
|
347
|
+
const depth = Array.isArray(ex.mutation_log) ? ex.mutation_log.length
|
|
348
|
+
: (Array.isArray(ex.rollouts) ? ex.rollouts.length - 1 : 0);
|
|
349
|
+
if (depth >= 1) score += Math.min(0.15, depth * 0.05);
|
|
350
|
+
} else if (source === 'distilled') {
|
|
351
|
+
score = 0.4;
|
|
352
|
+
} else {
|
|
353
|
+
score = 0.3;
|
|
354
|
+
}
|
|
355
|
+
const strategySteps = (parsed.strategy || []).length;
|
|
356
|
+
if (strategySteps >= 4) score += 0.05;
|
|
357
|
+
if ((parsed.avoid || []).length >= 1) score += 0.05;
|
|
358
|
+
return Math.max(0, Math.min(1, Number(score.toFixed(3))));
|
|
359
|
+
}
|
|
360
|
+
|
|
264
361
|
// ---------------------------------------------------------------------------
|
|
265
362
|
// Synthesize a draft Gene from parsed Skill + execution trace.
|
|
363
|
+
//
|
|
364
|
+
// The strategy/preconditions content depends on provenance:
|
|
365
|
+
// - evolved -> corrective insight first, overcome-errors -> preconditions.
|
|
366
|
+
// - otherwise -> Skill transcription (legacy behavior), tagged so consumers
|
|
367
|
+
// know it was not learned from a real run.
|
|
368
|
+
//
|
|
266
369
|
// Validation is delegated to skillDistiller.validateSynthesizedGene() so that
|
|
267
370
|
// we reuse the sanitization, ID-rewrite, forbidden-path, and validation-cmd
|
|
268
371
|
// policy rules already hardened there.
|
|
269
372
|
// ---------------------------------------------------------------------------
|
|
270
373
|
function synthesizeGene(parsed, execution, opts) {
|
|
271
|
-
|
|
374
|
+
execution = execution || {};
|
|
375
|
+
opts = opts || {};
|
|
376
|
+
const traceSignals = Array.isArray(execution.signals) ? execution.signals : [];
|
|
272
377
|
const mergedSignals = Array.from(new Set([].concat(parsed.signals_match || [], traceSignals)));
|
|
273
378
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
//
|
|
277
|
-
//
|
|
278
|
-
|
|
279
|
-
|
|
379
|
+
const source = classifyProvenance(execution);
|
|
380
|
+
|
|
381
|
+
// Strategy source depends on provenance. For an evolved trajectory the
|
|
382
|
+
// corrective insight leads (this is what beats a Skill); otherwise we
|
|
383
|
+
// transcribe the Skill's own workflow, tagged so consumers know it was not
|
|
384
|
+
// learned from a real run.
|
|
385
|
+
let strategy;
|
|
386
|
+
if (source === 'evolved') {
|
|
387
|
+
strategy = buildEvolvedStrategy(parsed, execution);
|
|
388
|
+
} else {
|
|
389
|
+
strategy = [];
|
|
390
|
+
(parsed.strategy || []).forEach((s) => strategy.push(s));
|
|
391
|
+
}
|
|
280
392
|
if (strategy.length < 3) {
|
|
281
393
|
strategy.push('Identify the dominant trigger signals from the Skill description.');
|
|
282
394
|
strategy.push('Apply the smallest targeted change that satisfies the Skill workflow.');
|
|
@@ -284,24 +396,41 @@ function synthesizeGene(parsed, execution, opts) {
|
|
|
284
396
|
}
|
|
285
397
|
const avoid = Array.isArray(parsed.avoid) ? parsed.avoid.slice(0, 5) : [];
|
|
286
398
|
|
|
399
|
+
// Preconditions: for evolved Genes, the error categories the trajectory had
|
|
400
|
+
// to overcome become verifiable preconditions; the Skill's declared
|
|
401
|
+
// preconditions (and any host-distilled ones) are appended.
|
|
402
|
+
let preconditions;
|
|
403
|
+
if (source === 'evolved') {
|
|
404
|
+
preconditions = preconditionsFromErrors(execution)
|
|
405
|
+
.concat(Array.isArray(execution.distilled_preconditions) ? execution.distilled_preconditions.map(String) : [])
|
|
406
|
+
.concat(parsed.preconditions || []);
|
|
407
|
+
} else {
|
|
408
|
+
preconditions = (parsed.preconditions && parsed.preconditions.length > 0)
|
|
409
|
+
? parsed.preconditions.slice()
|
|
410
|
+
: ['Skill ' + (parsed.name || 'unknown') + ' has just been executed locally'];
|
|
411
|
+
}
|
|
412
|
+
if (preconditions.length === 0) {
|
|
413
|
+
preconditions = ['Skill ' + (parsed.name || 'unknown') + ' has just been executed locally'];
|
|
414
|
+
}
|
|
415
|
+
|
|
287
416
|
// Filter validation commands through the same allow-list that
|
|
288
|
-
// validateSynthesizedGene will later apply (node/npm/npx only).
|
|
289
|
-
//
|
|
290
|
-
//
|
|
291
|
-
//
|
|
292
|
-
//
|
|
293
|
-
//
|
|
294
|
-
//
|
|
295
|
-
//
|
|
296
|
-
//
|
|
417
|
+
// validateSynthesizedGene will later apply (node/npm/npx only). Per the
|
|
418
|
+
// Gene-Bench DISTILL contract ("validation: [] only; do not add bogus
|
|
419
|
+
// console-log validations"), we no longer inject a near-trivial
|
|
420
|
+
// 'node --version' when nothing runnable is found -- an empty validation
|
|
421
|
+
// list is the correct outcome for a Gene asset.
|
|
422
|
+
//
|
|
423
|
+
// strict mode is a different consumer: skill2recipes calls us with
|
|
424
|
+
// strict=true because a recipe STEP must carry a real, runnable check (its
|
|
425
|
+
// verify stage executes the commands). An empty validation there would
|
|
426
|
+
// verify nothing, so strict still rejects it.
|
|
297
427
|
const policyCheck = require('./policyCheck');
|
|
298
428
|
const rawValidations = Array.isArray(parsed.validation) ? parsed.validation : [];
|
|
299
429
|
const allowedValidations = rawValidations
|
|
300
430
|
.map((v) => String(v || '').trim())
|
|
301
431
|
.filter((v) => v && policyCheck.isValidationCommandAllowed(v));
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
if (strict && fallbackUsed) {
|
|
432
|
+
const validation = allowedValidations;
|
|
433
|
+
if (Boolean(opts.strict) && validation.length === 0) {
|
|
305
434
|
return {
|
|
306
435
|
valid: false,
|
|
307
436
|
errors: [
|
|
@@ -310,59 +439,112 @@ function synthesizeGene(parsed, execution, opts) {
|
|
|
310
439
|
+ 'Rewrite the Skill\'s validation section with those, or drop --strict.',
|
|
311
440
|
],
|
|
312
441
|
gene: null,
|
|
442
|
+
source: source,
|
|
313
443
|
};
|
|
314
444
|
}
|
|
315
|
-
const validation = fallbackUsed ? ['node --version'] : allowedValidations;
|
|
316
445
|
|
|
317
|
-
// Quality
|
|
318
|
-
//
|
|
319
|
-
|
|
320
|
-
const avoidCount = (parsed.avoid || []).length;
|
|
321
|
-
const strategySteps = (parsed.strategy || []).length;
|
|
446
|
+
// Quality: a coarse score (used by the quality gate to downgrade thin
|
|
447
|
+
// distilled Genes) plus descriptive heuristics for reviewers.
|
|
448
|
+
const qualityScore = computeQualityScore(source, parsed, execution);
|
|
322
449
|
const qualityHeuristics = {
|
|
323
|
-
strategy_steps:
|
|
324
|
-
avoid_count:
|
|
450
|
+
strategy_steps: (parsed.strategy || []).length,
|
|
451
|
+
avoid_count: (parsed.avoid || []).length,
|
|
325
452
|
validation_declared_count: rawValidations.length,
|
|
326
453
|
validation_runnable_count: allowedValidations.length,
|
|
327
|
-
validation_fallback_used: fallbackUsed,
|
|
328
454
|
signals_extracted: (parsed.signals_match || []).length,
|
|
329
455
|
preconditions_extracted: (parsed.preconditions || []).length,
|
|
456
|
+
trajectory_depth: Array.isArray(execution.mutation_log) ? execution.mutation_log.length
|
|
457
|
+
: (Array.isArray(execution.rollouts) ? Math.max(0, execution.rollouts.length - 1) : 0),
|
|
458
|
+
has_corrective_insight: Boolean(execution.corrective_insight
|
|
459
|
+
&& String(execution.corrective_insight).trim().length >= 5),
|
|
330
460
|
};
|
|
331
461
|
|
|
332
|
-
const skillSlug = slugify(parsed.name ||
|
|
333
|
-
|
|
462
|
+
const skillSlug = slugify(parsed.name || opts.skillName || 'skill');
|
|
463
|
+
let draft = {
|
|
334
464
|
type: 'Gene',
|
|
335
465
|
id: SKILL2GEP_ID_PREFIX + skillSlug,
|
|
336
466
|
summary: (parsed.description || strategy[0] || 'Reusable strategy distilled from Skill').slice(0, 200),
|
|
337
467
|
category: inferCategory(mergedSignals, parsed.description),
|
|
338
468
|
signals_match: mergedSignals.slice(0, 8),
|
|
339
|
-
preconditions:
|
|
340
|
-
? parsed.preconditions
|
|
341
|
-
: ['Skill ' + (parsed.name || 'unknown') + ' has just been executed locally'],
|
|
469
|
+
preconditions: preconditions.slice(0, 6),
|
|
342
470
|
strategy: strategy.slice(0, MAX_STRATEGY_STEPS),
|
|
343
471
|
avoid: avoid,
|
|
344
472
|
constraints: {
|
|
345
|
-
max_files:
|
|
473
|
+
max_files: opts.maxFiles || skillDistiller.DISTILLED_MAX_FILES,
|
|
346
474
|
forbidden_paths: ['.git', 'node_modules'],
|
|
347
475
|
},
|
|
348
476
|
validation: validation,
|
|
349
477
|
schema_version: '1.6.0',
|
|
350
478
|
_source: {
|
|
351
479
|
kind: 'skill2gep',
|
|
480
|
+
generation_source: source,
|
|
352
481
|
skill_name: parsed.name || null,
|
|
353
|
-
skill_platform:
|
|
354
|
-
skill_hash: opts
|
|
482
|
+
skill_platform: opts.platform || null,
|
|
483
|
+
skill_hash: opts.skillHash ? opts.skillHash : null,
|
|
355
484
|
rationale_paper: RATIONALE_LINKS.paper,
|
|
356
485
|
paper_scope: 'code-science (arXiv:2604.15097, 45 tasks, Gemini 3.1 Pro/Flash Lite)',
|
|
357
486
|
claims_outside_scope: 'assumption',
|
|
487
|
+
quality_score: qualityScore,
|
|
488
|
+
overcame_errors: Array.isArray(execution.mutation_log) ? execution.mutation_log.slice(0, 8) : [],
|
|
358
489
|
quality_heuristics: qualityHeuristics,
|
|
359
490
|
},
|
|
360
491
|
};
|
|
361
492
|
|
|
493
|
+
// Mechanical leakage audit (Gene-Bench Stage-3): strip any hard literal that
|
|
494
|
+
// appears only in the run's hidden text (final solution / verifier feedback)
|
|
495
|
+
// and not in the public SKILL.md. Run BEFORE validateSynthesizedGene so the
|
|
496
|
+
// sanitized payload is what gets ID-rewritten and persisted.
|
|
497
|
+
let auditInfo = { leaks_found_count: 0, redacted: false };
|
|
498
|
+
if (opts.skillMd) {
|
|
499
|
+
const privateVocab = audit.buildPrivateVocab(opts.skillMd, execution);
|
|
500
|
+
const leaks = audit.findLeakage(draft, privateVocab);
|
|
501
|
+
if (leaks.length > 0) {
|
|
502
|
+
draft = audit.redactPrivateLiterals(draft, privateVocab);
|
|
503
|
+
const residual = audit.findLeakage(draft, privateVocab);
|
|
504
|
+
// Record only counts/locations, never the private literals themselves --
|
|
505
|
+
// storing the leaked token verbatim in the published asset would defeat
|
|
506
|
+
// the audit.
|
|
507
|
+
auditInfo = {
|
|
508
|
+
leaks_found_count: leaks.length,
|
|
509
|
+
leak_locations: Array.from(new Set(leaks.map((l) => l.location))),
|
|
510
|
+
redacted: true,
|
|
511
|
+
residual_leak_count: residual.length,
|
|
512
|
+
};
|
|
513
|
+
draft._source.leakage_audit = auditInfo;
|
|
514
|
+
if (opts.strict && residual.length > 0) {
|
|
515
|
+
return {
|
|
516
|
+
valid: false,
|
|
517
|
+
errors: ['strict mode: leakage audit could not remove ' + residual.length
|
|
518
|
+
+ ' private literal(s) at: ' + Array.from(new Set(residual.map((l) => l.location))).join(', ')],
|
|
519
|
+
gene: null,
|
|
520
|
+
source: source,
|
|
521
|
+
quality_score: qualityScore,
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
// The audit may have dropped validation commands that carried a private
|
|
525
|
+
// literal. Re-assert strict mode's runnable-validation requirement here,
|
|
526
|
+
// since the earlier check ran before redaction.
|
|
527
|
+
if (opts.strict && (!Array.isArray(draft.validation) || draft.validation.length === 0)) {
|
|
528
|
+
return {
|
|
529
|
+
valid: false,
|
|
530
|
+
errors: ['strict mode: all runnable validation commands were dropped by the '
|
|
531
|
+
+ 'leakage audit (they contained private literals), leaving no verifiable check.'],
|
|
532
|
+
gene: null,
|
|
533
|
+
source: source,
|
|
534
|
+
quality_score: qualityScore,
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
362
540
|
const assetsDir = paths.getGepAssetsDir();
|
|
363
541
|
const existingGenesJson = readJsonSafe(path.join(assetsDir, 'genes.json'), { genes: [] });
|
|
364
542
|
const existingGenes = Array.isArray(existingGenesJson.genes) ? existingGenesJson.genes : [];
|
|
365
543
|
const result = skillDistiller.validateSynthesizedGene(draft, existingGenes);
|
|
544
|
+
// Surface provenance + quality so the caller's quality gate can act on it.
|
|
545
|
+
result.source = source;
|
|
546
|
+
result.quality_score = qualityScore;
|
|
547
|
+
result.audit = auditInfo;
|
|
366
548
|
return result;
|
|
367
549
|
}
|
|
368
550
|
|
|
@@ -392,6 +574,53 @@ function inferCategory(signals, description) {
|
|
|
392
574
|
return 'optimize';
|
|
393
575
|
}
|
|
394
576
|
|
|
577
|
+
// ---------------------------------------------------------------------------
|
|
578
|
+
// LLM distillation = the host agent.
|
|
579
|
+
//
|
|
580
|
+
// The evolver engine has no in-process LLM client and never spawns one. The
|
|
581
|
+
// "LLM" IS the host agent (Claude Code / Cursor / Codex) that just ran the
|
|
582
|
+
// Skill -- it already has the full execution in context. So this stage does
|
|
583
|
+
// not call out anywhere: it simply consumes the distillation the host agent
|
|
584
|
+
// provides inline on opts.execution (docs/skill2gep.md):
|
|
585
|
+
//
|
|
586
|
+
// - execution.corrective_insight : the single fix that flipped fail -> pass
|
|
587
|
+
// (becomes strategy[0]).
|
|
588
|
+
// - execution.distilled_payload : optional { corrective_insight, strategy,
|
|
589
|
+
// preconditions } the host already wrote.
|
|
590
|
+
//
|
|
591
|
+
// If the host supplied neither, synthesizeGene falls back to the mechanical
|
|
592
|
+
// corrective distillation. Zero network, zero subprocess, in-budget.
|
|
593
|
+
// ---------------------------------------------------------------------------
|
|
594
|
+
function _hostDistilledPayload(execution) {
|
|
595
|
+
const p = execution && execution.distilled_payload;
|
|
596
|
+
if (!p || typeof p !== 'object') return null;
|
|
597
|
+
const out = {};
|
|
598
|
+
if (Array.isArray(p.strategy) && p.strategy.length) out.strategy = p.strategy.map(String);
|
|
599
|
+
if (Array.isArray(p.preconditions)) out.preconditions = p.preconditions.map(String);
|
|
600
|
+
if (typeof p.corrective_insight === 'string') out.corrective_insight = p.corrective_insight;
|
|
601
|
+
return Object.keys(out).length ? out : null;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function distillWithLLM(parsed, execution, opts) { // eslint-disable-line no-unused-vars
|
|
605
|
+
execution = execution || {};
|
|
606
|
+
|
|
607
|
+
// A top-level corrective_insight and a distilled_payload are not mutually
|
|
608
|
+
// exclusive, so do not early-return on the insight alone -- merge both.
|
|
609
|
+
const hostPayload = _hostDistilledPayload(execution);
|
|
610
|
+
if (!execution.corrective_insight && !hostPayload) {
|
|
611
|
+
return execution; // nothing from the host -> mechanical fallback
|
|
612
|
+
}
|
|
613
|
+
const merged = Object.assign({}, execution);
|
|
614
|
+
if (hostPayload) {
|
|
615
|
+
if (!merged.corrective_insight && hostPayload.corrective_insight) {
|
|
616
|
+
merged.corrective_insight = hostPayload.corrective_insight;
|
|
617
|
+
}
|
|
618
|
+
if (hostPayload.strategy) merged.distilled_strategy = hostPayload.strategy;
|
|
619
|
+
if (hostPayload.preconditions) merged.distilled_preconditions = hostPayload.preconditions;
|
|
620
|
+
}
|
|
621
|
+
return merged;
|
|
622
|
+
}
|
|
623
|
+
|
|
395
624
|
// ---------------------------------------------------------------------------
|
|
396
625
|
// Forgery guard: a Capsule with status=success but no execution evidence is
|
|
397
626
|
// rejected outright. This is the single most important defence against agents
|
|
@@ -537,10 +766,19 @@ function runOnSkillInvocation(opts) {
|
|
|
537
766
|
|
|
538
767
|
// Idempotency: if we've already distilled this exact skill content + the
|
|
539
768
|
// same execution fingerprint, skip to avoid duplicate community uploads.
|
|
769
|
+
// Include the evolved-trajectory + host-distillation fields in the
|
|
770
|
+
// idempotency key: they change the synthesized Gene, so a later, richer host
|
|
771
|
+
// distillation of the same trace must NOT be short-circuited as
|
|
772
|
+
// already_distilled with a stale Gene.
|
|
773
|
+
const ex0 = opts.execution || {};
|
|
540
774
|
const execHash = shortHash(JSON.stringify({
|
|
541
|
-
trace:
|
|
542
|
-
br:
|
|
543
|
-
status:
|
|
775
|
+
trace: ex0.trace || [],
|
|
776
|
+
br: ex0.blast_radius || null,
|
|
777
|
+
status: ex0.status || null,
|
|
778
|
+
mutation_log: ex0.mutation_log || null,
|
|
779
|
+
rollouts: ex0.rollouts || null,
|
|
780
|
+
corrective_insight: ex0.corrective_insight || null,
|
|
781
|
+
distilled_payload: ex0.distilled_payload || null,
|
|
544
782
|
}));
|
|
545
783
|
const state = readState();
|
|
546
784
|
const seenKey = skillHash + ':' + execHash;
|
|
@@ -549,10 +787,21 @@ function runOnSkillInvocation(opts) {
|
|
|
549
787
|
}
|
|
550
788
|
|
|
551
789
|
const parsed = parseSkillMd(skillMd);
|
|
552
|
-
|
|
790
|
+
|
|
791
|
+
// Consume any distillation the host agent (the LLM) supplied inline on the
|
|
792
|
+
// execution record. This never calls out -- it just promotes a host-provided
|
|
793
|
+
// corrective_insight / distilled_payload onto the execution before synthesis.
|
|
794
|
+
let execution = opts.execution || {};
|
|
795
|
+
try {
|
|
796
|
+
const enriched = distillWithLLM(parsed, execution, { skillMd: skillMd });
|
|
797
|
+
if (enriched) execution = enriched;
|
|
798
|
+
} catch (_) { /* non-fatal: keep the original execution record */ }
|
|
799
|
+
|
|
800
|
+
const geneResult = synthesizeGene(parsed, execution, {
|
|
553
801
|
skillName: opts.skillName || parsed.name,
|
|
554
802
|
platform: opts.platform || null,
|
|
555
803
|
skillHash: skillHash,
|
|
804
|
+
skillMd: skillMd,
|
|
556
805
|
strict: Boolean(opts.strict),
|
|
557
806
|
});
|
|
558
807
|
if (!geneResult.valid) {
|
|
@@ -564,14 +813,41 @@ function runOnSkillInvocation(opts) {
|
|
|
564
813
|
}
|
|
565
814
|
const gene = geneResult.gene;
|
|
566
815
|
|
|
816
|
+
// Quality gate (operationalizes the TaskGenome Bench finding that
|
|
817
|
+
// reference-distilled Genes can be WORSE than Skills). A low-quality
|
|
818
|
+
// distilled/manual Gene is downgraded to Gene-only and flagged; strict mode
|
|
819
|
+
// refuses it. Evolved Genes are never gated. A malformed env value parses to
|
|
820
|
+
// NaN -> treat as "gate disabled" (0) rather than passing everything.
|
|
821
|
+
const minQualityRaw = Number(process.env.SKILL2GEP_MIN_QUALITY);
|
|
822
|
+
const minQuality = Number.isFinite(minQualityRaw) ? minQualityRaw : 0;
|
|
823
|
+
let qualityGate = null;
|
|
824
|
+
if (geneResult.source !== 'evolved' && geneResult.quality_score < minQuality) {
|
|
825
|
+
qualityGate = {
|
|
826
|
+
reason: 'low_quality_distilled_gene',
|
|
827
|
+
source: geneResult.source,
|
|
828
|
+
quality_score: geneResult.quality_score,
|
|
829
|
+
note: 'reference-distilled/manual Gene below SKILL2GEP_MIN_QUALITY; '
|
|
830
|
+
+ 'TaskGenome Bench shows such Genes may underperform the source Skill.',
|
|
831
|
+
};
|
|
832
|
+
if (opts.strict) {
|
|
833
|
+
appendJsonl(logPath(), {
|
|
834
|
+
timestamp: new Date().toISOString(), status: 'quality_gate_rejected',
|
|
835
|
+
skill: opts.skillName || parsed.name, gate: qualityGate,
|
|
836
|
+
});
|
|
837
|
+
return { ok: false, reason: 'quality_gate_rejected', gate: qualityGate };
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
567
841
|
let capsule = null;
|
|
568
842
|
let capsuleDiag = null;
|
|
569
|
-
|
|
570
|
-
|
|
843
|
+
// A quality-gated Gene is published as Gene-only: do not mint a Capsule that
|
|
844
|
+
// would advertise it as a verified success.
|
|
845
|
+
if (execution && execution.status && !qualityGate) {
|
|
846
|
+
const forgery = detectForgery(execution);
|
|
571
847
|
if (forgery) {
|
|
572
848
|
capsuleDiag = { reason: 'capsule_rejected_forgery', detail: forgery };
|
|
573
849
|
} else {
|
|
574
|
-
const capRes = assembleCapsule(gene,
|
|
850
|
+
const capRes = assembleCapsule(gene, execution, { scenario: opts.scenario || parsed.name });
|
|
575
851
|
if (capRes.ok) capsule = capRes.capsule; else capsuleDiag = capRes;
|
|
576
852
|
}
|
|
577
853
|
}
|
|
@@ -629,6 +905,10 @@ function runOnSkillInvocation(opts) {
|
|
|
629
905
|
status: 'distilled',
|
|
630
906
|
skill: opts.skillName || parsed.name,
|
|
631
907
|
gene_id: gene.id,
|
|
908
|
+
generation_source: geneResult.source,
|
|
909
|
+
quality_score: geneResult.quality_score,
|
|
910
|
+
quality_gate: qualityGate,
|
|
911
|
+
leakage_audit: geneResult.audit,
|
|
632
912
|
capsule_id: capsule ? capsule.id : null,
|
|
633
913
|
capsule_diagnostic: capsuleDiag,
|
|
634
914
|
persist_errors: persistErrors,
|
|
@@ -639,6 +919,10 @@ function runOnSkillInvocation(opts) {
|
|
|
639
919
|
ok: true,
|
|
640
920
|
gene: gene,
|
|
641
921
|
capsule: capsule,
|
|
922
|
+
generation_source: geneResult.source,
|
|
923
|
+
quality_score: geneResult.quality_score,
|
|
924
|
+
quality_gate: qualityGate,
|
|
925
|
+
leakage_audit: geneResult.audit,
|
|
642
926
|
capsule_diagnostic: capsuleDiag,
|
|
643
927
|
persist_errors: persistErrors,
|
|
644
928
|
publish_requested: shouldPublish,
|
|
@@ -756,7 +1040,9 @@ module.exports = {
|
|
|
756
1040
|
RATIONALE_LINKS,
|
|
757
1041
|
RATIONALE_TEXT,
|
|
758
1042
|
parseSkillMd,
|
|
1043
|
+
classifyProvenance,
|
|
759
1044
|
synthesizeGene,
|
|
1045
|
+
distillWithLLM,
|
|
760
1046
|
inferCategory,
|
|
761
1047
|
detectForgery,
|
|
762
1048
|
assembleCapsule,
|