@evomap/evolver 1.70.0 → 1.74.0
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/assets/gep/candidates.jsonl +1 -6
- package/index.js +123 -7
- package/package.json +1 -1
- package/scripts/validate-suite.js +21 -6
- package/src/adapters/hookAdapter.js +3 -1
- package/src/adapters/kiro.js +203 -0
- package/src/adapters/scripts/evolver-session-start.js +62 -0
- package/src/atp/atpExecute.js +285 -0
- package/src/atp/atpTaskPickup.js +233 -0
- package/src/atp/autoBuyer.js +12 -6
- package/src/atp/autoDeliver.js +199 -0
- package/src/atp/cliAutobuyPrompt.js +4 -3
- package/src/atp/hubClient.js +20 -0
- package/src/atp/index.js +10 -1
- package/src/atp/questionComposer.js +133 -0
- package/src/evolve.js +1 -1
- package/src/gep/.integrity +0 -0
- package/src/gep/a2aProtocol.js +1 -1
- package/src/gep/candidateEval.js +1 -1
- package/src/gep/candidates.js +1 -1
- package/src/gep/contentHash.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/envFingerprint.js +1 -1
- package/src/gep/explore.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/integrityCheck.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/personality.js +1 -1
- package/src/gep/policyCheck.js +1 -1
- package/src/gep/prompt.js +1 -1
- package/src/gep/reflection.js +1 -1
- package/src/gep/selector.js +1 -1
- package/src/gep/shield.js +1 -1
- package/src/gep/skillDistiller.js +1 -1
- package/src/gep/solidify.js +1 -1
- package/src/gep/strategy.js +1 -1
- package/src/gep/validator/sandboxExecutor.js +11 -2
- package/src/proxy/lifecycle/manager.js +5 -1
- package/src/proxy/mailbox/store.js +5 -0
- package/src/proxy/server/http.js +47 -4
|
@@ -1,6 +1 @@
|
|
|
1
|
-
{"type":"CapabilityCandidate","id":"cand_b9a66a5c","title":"Harden session log detection and fallback behavior","source":"signals","created_at":"2026-04-
|
|
2
|
-
{"type":"CapabilityCandidate","id":"cand_b9a66a5c","title":"Harden session log detection and fallback behavior","source":"signals","created_at":"2026-04-27T05:47:47.051Z","signals":["bounty_task","external_task","seo","approaches","different","compare","comparison","note","within","optimization","keyword","red","xiaohongshu","xiaohongshu-ops","memory_missing","user_missing","session_logs_missing"],"tags":["bounty_task","external_task","seo","approaches","different","compare","comparison","note","within","optimization","keyword","red","xiaohongshu","xiaohongshu-ops","memory_missing","user_missing","session_logs_missing","area:orchestration","area:memory"],"shape":{"title":"Harden session log detection and fallback behavior","input":"Recent session transcript + memory snippets + user instructions","output":"A safe, auditable evolution patch guided by GEP assets","invariants":"Protocol order, small reversible patches, validation, append-only events","params":"Signals: bounty_task, external_task, seo, approaches, different, compare, comparison, note, within, optimization, keyword, red, xiaohongshu, xiaohongshu-ops, memory_missing, user_missing, session_logs_missing","failure_points":"Missing signals, over-broad changes, skipped validation, missing knowledge solidification","evidence":"Signal present: session_logs_missing"}}
|
|
3
|
-
{"type":"CapabilityCandidate","id":"cand_b9a66a5c","title":"Harden session log detection and fallback behavior","source":"signals","created_at":"2026-04-27T05:47:48.939Z","signals":["bounty_task","external_task","seo","approaches","different","compare","comparison","note","within","optimization","keyword","red","xiaohongshu","xiaohongshu-ops","memory_missing","user_missing","session_logs_missing"],"tags":["bounty_task","external_task","seo","approaches","different","compare","comparison","note","within","optimization","keyword","red","xiaohongshu","xiaohongshu-ops","memory_missing","user_missing","session_logs_missing","area:orchestration","area:memory"],"shape":{"title":"Harden session log detection and fallback behavior","input":"Recent session transcript + memory snippets + user instructions","output":"A safe, auditable evolution patch guided by GEP assets","invariants":"Protocol order, small reversible patches, validation, append-only events","params":"Signals: bounty_task, external_task, seo, approaches, different, compare, comparison, note, within, optimization, keyword, red, xiaohongshu, xiaohongshu-ops, memory_missing, user_missing, session_logs_missing","failure_points":"Missing signals, over-broad changes, skipped validation, missing knowledge solidification","evidence":"Signal present: session_logs_missing"}}
|
|
4
|
-
{"type":"CapabilityCandidate","id":"cand_b9a66a5c","title":"Harden session log detection and fallback behavior","source":"signals","created_at":"2026-04-27T05:49:00.343Z","signals":["bounty_task","external_task","seo","approaches","different","compare","comparison","note","within","optimization","keyword","red","xiaohongshu","xiaohongshu-ops","memory_missing","user_missing","session_logs_missing"],"tags":["bounty_task","external_task","seo","approaches","different","compare","comparison","note","within","optimization","keyword","red","xiaohongshu","xiaohongshu-ops","memory_missing","user_missing","session_logs_missing","area:orchestration","area:memory"],"shape":{"title":"Harden session log detection and fallback behavior","input":"Recent session transcript + memory snippets + user instructions","output":"A safe, auditable evolution patch guided by GEP assets","invariants":"Protocol order, small reversible patches, validation, append-only events","params":"Signals: bounty_task, external_task, seo, approaches, different, compare, comparison, note, within, optimization, keyword, red, xiaohongshu, xiaohongshu-ops, memory_missing, user_missing, session_logs_missing","failure_points":"Missing signals, over-broad changes, skipped validation, missing knowledge solidification","evidence":"Signal present: session_logs_missing"}}
|
|
5
|
-
{"type":"CapabilityCandidate","id":"cand_b9a66a5c","title":"Harden session log detection and fallback behavior","source":"signals","created_at":"2026-04-27T05:49:02.561Z","signals":["bounty_task","external_task","skills","monetize","how","monetization","reduce","optimization","prompt","negative","image-generation","ai-art","ai-image-generation","memory_missing","user_missing","session_logs_missing"],"tags":["bounty_task","external_task","skills","monetize","how","monetization","reduce","optimization","prompt","negative","image-generation","ai-art","ai-image-generation","memory_missing","user_missing","session_logs_missing","problem:protocol","action:optimize","area:prompt","area:orchestration","area:memory","area:skills"],"shape":{"title":"Harden session log detection and fallback behavior","input":"Recent session transcript + memory snippets + user instructions","output":"A safe, auditable evolution patch guided by GEP assets","invariants":"Protocol order, small reversible patches, validation, append-only events","params":"Signals: bounty_task, external_task, skills, monetize, how, monetization, reduce, optimization, prompt, negative, image-generation, ai-art, ai-image-generation, memory_missing, user_missing, session_logs_missing","failure_points":"Missing signals, over-broad changes, skipped validation, missing knowledge solidification","evidence":"Signal present: session_logs_missing"}}
|
|
6
|
-
{"type":"CapabilityCandidate","id":"cand_b9a66a5c","title":"Harden session log detection and fallback behavior","source":"signals","created_at":"2026-04-27T05:49:04.551Z","signals":["bounty_task","external_task","seo","approaches","different","compare","comparison","note","within","optimization","keyword","red","xiaohongshu","xiaohongshu-ops","memory_missing","user_missing","session_logs_missing"],"tags":["bounty_task","external_task","seo","approaches","different","compare","comparison","note","within","optimization","keyword","red","xiaohongshu","xiaohongshu-ops","memory_missing","user_missing","session_logs_missing","area:orchestration","area:memory"],"shape":{"title":"Harden session log detection and fallback behavior","input":"Recent session transcript + memory snippets + user instructions","output":"A safe, auditable evolution patch guided by GEP assets","invariants":"Protocol order, small reversible patches, validation, append-only events","params":"Signals: bounty_task, external_task, seo, approaches, different, compare, comparison, note, within, optimization, keyword, red, xiaohongshu, xiaohongshu-ops, memory_missing, user_missing, session_logs_missing","failure_points":"Missing signals, over-broad changes, skipped validation, missing knowledge solidification","evidence":"Signal present: session_logs_missing"}}
|
|
1
|
+
{"type":"CapabilityCandidate","id":"cand_b9a66a5c","title":"Harden session log detection and fallback behavior","source":"signals","created_at":"2026-04-28T03:36:59.386Z","signals":["memory_missing","user_missing","session_logs_missing"],"tags":["memory_missing","user_missing","session_logs_missing","area:memory"],"shape":{"title":"Harden session log detection and fallback behavior","input":"Recent session transcript + memory snippets + user instructions","output":"A safe, auditable evolution patch guided by GEP assets","invariants":"Protocol order, small reversible patches, validation, append-only events","params":"Signals: memory_missing, user_missing, session_logs_missing","failure_points":"Missing signals, over-broad changes, skipped validation, missing knowledge solidification","evidence":"Signal present: session_logs_missing"}}
|
package/index.js
CHANGED
|
@@ -266,7 +266,10 @@ async function main() {
|
|
|
266
266
|
console.warn('[ATP] Auto-init failed: ' + (atpInitErr && atpInitErr.message || atpInitErr));
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
-
// ATP:
|
|
269
|
+
// ATP: capability-gap auto-buyer. Default ON as of ATP liquidity
|
|
270
|
+
// unlock; disable with EVOLVER_ATP_AUTOBUY=off. Also starts the
|
|
271
|
+
// merchant-side auto-deliver daemon so claimed ATP tasks actually
|
|
272
|
+
// call submitDelivery and settle instead of expiring.
|
|
270
273
|
try {
|
|
271
274
|
try {
|
|
272
275
|
const { runPrompt } = require('./src/atp/cliAutobuyPrompt');
|
|
@@ -274,8 +277,8 @@ async function main() {
|
|
|
274
277
|
} catch (promptErr) {
|
|
275
278
|
console.warn('[ATP-AutoBuyer] first-run prompt failed: ' + (promptErr && promptErr.message || promptErr));
|
|
276
279
|
}
|
|
277
|
-
const autoBuyRaw = (process.env.EVOLVER_ATP_AUTOBUY || '
|
|
278
|
-
const autoBuyOn = autoBuyRaw
|
|
280
|
+
const autoBuyRaw = (process.env.EVOLVER_ATP_AUTOBUY || 'on').toLowerCase().trim();
|
|
281
|
+
const autoBuyOn = autoBuyRaw !== 'off' && autoBuyRaw !== '0' && autoBuyRaw !== 'false';
|
|
279
282
|
if (autoBuyOn) {
|
|
280
283
|
const hubUrl = process.env.A2A_HUB_URL || process.env.EVOMAP_HUB_URL || '';
|
|
281
284
|
if (hubUrl) {
|
|
@@ -285,7 +288,20 @@ async function main() {
|
|
|
285
288
|
perOrderCap: Number(process.env.ATP_AUTOBUY_PER_ORDER_CAP_CREDITS) || undefined,
|
|
286
289
|
});
|
|
287
290
|
} else {
|
|
288
|
-
console.warn('[ATP-AutoBuyer]
|
|
291
|
+
console.warn('[ATP-AutoBuyer] autobuy enabled but no hub URL configured, skipping.');
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
const autoDeliverRaw = (process.env.EVOLVER_ATP_AUTODELIVER || 'on').toLowerCase().trim();
|
|
295
|
+
const autoDeliverOn = autoDeliverRaw !== 'off' && autoDeliverRaw !== '0' && autoDeliverRaw !== 'false';
|
|
296
|
+
if (autoDeliverOn) {
|
|
297
|
+
const hubUrl = process.env.A2A_HUB_URL || process.env.EVOMAP_HUB_URL || '';
|
|
298
|
+
if (hubUrl) {
|
|
299
|
+
const autoDeliver = require('./src/atp/autoDeliver');
|
|
300
|
+
autoDeliver.start({
|
|
301
|
+
pollMs: Number(process.env.ATP_AUTODELIVER_POLL_MS) || undefined,
|
|
302
|
+
});
|
|
303
|
+
} else {
|
|
304
|
+
console.warn('[ATP-AutoDeliver] autodeliver enabled but no hub URL configured, skipping.');
|
|
289
305
|
}
|
|
290
306
|
}
|
|
291
307
|
} catch (autoBuyInitErr) {
|
|
@@ -847,6 +863,23 @@ async function main() {
|
|
|
847
863
|
const data = await resp.json();
|
|
848
864
|
const outFlag = args.find(a => typeof a === 'string' && a.startsWith('--out='));
|
|
849
865
|
const safeId = String(data.skill_id || skillId).replace(/[^a-zA-Z0-9_\-\.]/g, '_');
|
|
866
|
+
// Reject safeId values that would either stay inside cwd instead of
|
|
867
|
+
// descending into skills/, or escape cwd entirely. The sanitizing regex
|
|
868
|
+
// above permits `.`, so `..` / `.` / empty survive it; `path.join('.',
|
|
869
|
+
// 'skills', '..')` collapses to `.` which turns the download directory
|
|
870
|
+
// into the user's working directory and lets Hub-supplied bundled_files
|
|
871
|
+
// overwrite `index.js`, `package.json`, etc. See GHSA-cfcj-hqpf-hccf.
|
|
872
|
+
if (
|
|
873
|
+
safeId === '' ||
|
|
874
|
+
safeId === '.' ||
|
|
875
|
+
safeId === '..' ||
|
|
876
|
+
safeId.includes('/') ||
|
|
877
|
+
safeId.includes('\\') ||
|
|
878
|
+
safeId.includes('\0')
|
|
879
|
+
) {
|
|
880
|
+
console.error('[fetch] Hub returned an invalid skill_id: ' + JSON.stringify(safeId));
|
|
881
|
+
process.exit(1);
|
|
882
|
+
}
|
|
850
883
|
let outDir;
|
|
851
884
|
if (outFlag) {
|
|
852
885
|
const rawOut = outFlag.slice('--out='.length);
|
|
@@ -869,7 +902,16 @@ async function main() {
|
|
|
869
902
|
}
|
|
870
903
|
outDir = resolvedOut;
|
|
871
904
|
} else {
|
|
872
|
-
|
|
905
|
+
// Defense in depth: apply the same traversal check to the default
|
|
906
|
+
// branch so any remaining path-smuggling shape in `safeId` is caught.
|
|
907
|
+
const candidate = path.resolve(process.cwd(), 'skills', safeId);
|
|
908
|
+
const skillsRoot = path.resolve(process.cwd(), 'skills');
|
|
909
|
+
const rel = path.relative(skillsRoot, candidate);
|
|
910
|
+
if (rel.startsWith('..') || path.isAbsolute(rel)) {
|
|
911
|
+
console.error('[fetch] Hub-provided skill_id escapes skills/ directory: ' + JSON.stringify(safeId));
|
|
912
|
+
process.exit(1);
|
|
913
|
+
}
|
|
914
|
+
outDir = candidate;
|
|
873
915
|
}
|
|
874
916
|
|
|
875
917
|
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
|
|
@@ -885,12 +927,24 @@ async function main() {
|
|
|
885
927
|
'.yml', '.yaml',
|
|
886
928
|
]);
|
|
887
929
|
const MAX_SKILL_FILE_BYTES = 512 * 1024;
|
|
930
|
+
// Even with outDir locked to skills/, a legitimate-looking skill can
|
|
931
|
+
// ship a bundled file named `package.json`, `index.js`, or any other
|
|
932
|
+
// top-level project artifact whose name collides with something the
|
|
933
|
+
// user may later copy back up. Prefix-guard the resolved path so every
|
|
934
|
+
// write stays strictly within the resolved outDir (no trailing `/..`
|
|
935
|
+
// in basename, no absolute path smuggling) and never points at cwd.
|
|
936
|
+
const resolvedOutDir = path.resolve(outDir);
|
|
937
|
+
const resolvedCwd = path.resolve(process.cwd());
|
|
888
938
|
|
|
889
939
|
const bundled = Array.isArray(data.bundled_files) ? data.bundled_files : [];
|
|
890
940
|
const skippedFiles = [];
|
|
891
941
|
for (const file of bundled) {
|
|
892
942
|
if (!file || !file.name || typeof file.content !== 'string') continue;
|
|
893
943
|
const safeName = path.basename(file.name);
|
|
944
|
+
if (!safeName || safeName === '.' || safeName === '..') {
|
|
945
|
+
skippedFiles.push(String(file.name));
|
|
946
|
+
continue;
|
|
947
|
+
}
|
|
894
948
|
const ext = path.extname(safeName).toLowerCase();
|
|
895
949
|
if (!ALLOWED_SKILL_EXTENSIONS.has(ext)) {
|
|
896
950
|
console.warn('[fetch] Skipped skill file with disallowed extension: ' + safeName);
|
|
@@ -902,7 +956,23 @@ async function main() {
|
|
|
902
956
|
skippedFiles.push(safeName);
|
|
903
957
|
continue;
|
|
904
958
|
}
|
|
905
|
-
|
|
959
|
+
const destPath = path.resolve(resolvedOutDir, safeName);
|
|
960
|
+
const relToOut = path.relative(resolvedOutDir, destPath);
|
|
961
|
+
if (relToOut.startsWith('..') || path.isAbsolute(relToOut)) {
|
|
962
|
+
console.warn('[fetch] Skipped bundled file whose resolved path escapes outDir: ' + safeName);
|
|
963
|
+
skippedFiles.push(safeName);
|
|
964
|
+
continue;
|
|
965
|
+
}
|
|
966
|
+
// Never let a bundled write touch the evolver's own cwd -- this is
|
|
967
|
+
// the concrete attack shape from GHSA-cfcj-hqpf-hccf (fetch default
|
|
968
|
+
// branch writing to `./index.js`). outDir should always be under
|
|
969
|
+
// skills/ now, but belt-and-braces keep the guarantee explicit.
|
|
970
|
+
if (path.dirname(destPath) === resolvedCwd) {
|
|
971
|
+
console.warn('[fetch] Skipped bundled file that would land in cwd: ' + safeName);
|
|
972
|
+
skippedFiles.push(safeName);
|
|
973
|
+
continue;
|
|
974
|
+
}
|
|
975
|
+
fs.writeFileSync(destPath, file.content, 'utf8');
|
|
906
976
|
}
|
|
907
977
|
|
|
908
978
|
console.log('[fetch] Skill downloaded to: ' + outDir);
|
|
@@ -1002,6 +1072,45 @@ async function main() {
|
|
|
1002
1072
|
process.exit(1);
|
|
1003
1073
|
}
|
|
1004
1074
|
|
|
1075
|
+
} else if (command === 'atp-complete') {
|
|
1076
|
+
// Invoked by a spawned Cursor sub-session after it has written the ATP
|
|
1077
|
+
// task answer to a file. Drives publish -> task/complete -> atp/deliver.
|
|
1078
|
+
try {
|
|
1079
|
+
const subArgs = args.slice(1);
|
|
1080
|
+
function flag(name) {
|
|
1081
|
+
const pref = '--' + name + '=';
|
|
1082
|
+
const hit = subArgs.find(function (a) { return typeof a === 'string' && a.startsWith(pref); });
|
|
1083
|
+
return hit ? hit.slice(pref.length) : null;
|
|
1084
|
+
}
|
|
1085
|
+
function list(name) {
|
|
1086
|
+
const raw = flag(name);
|
|
1087
|
+
if (!raw) return null;
|
|
1088
|
+
return raw.split(',').map(function (s) { return String(s).trim(); }).filter(Boolean);
|
|
1089
|
+
}
|
|
1090
|
+
const taskId = flag('task-id');
|
|
1091
|
+
const orderId = flag('order-id');
|
|
1092
|
+
const answerFile = flag('answer-file');
|
|
1093
|
+
const summary = flag('summary');
|
|
1094
|
+
const capabilities = list('capabilities');
|
|
1095
|
+
const signals = list('signals');
|
|
1096
|
+
if (!taskId || !orderId || !answerFile) {
|
|
1097
|
+
console.error('[ATP-Complete] Missing required flags: --task-id, --order-id, --answer-file');
|
|
1098
|
+
console.error('Usage: node index.js atp-complete --task-id=<tid> --order-id=<oid> --answer-file=<path> [--summary="..."] [--capabilities=cap1,cap2] [--signals=sig1,sig2]');
|
|
1099
|
+
process.exit(2);
|
|
1100
|
+
}
|
|
1101
|
+
const { completeAtpTask } = require('./src/atp/atpExecute');
|
|
1102
|
+
const res = await completeAtpTask({ taskId, orderId, answerFile, summary, capabilities, signals });
|
|
1103
|
+
if (res && res.ok) {
|
|
1104
|
+
console.log('[ATP-Complete] OK asset_id=' + res.assetId + (res.deliveryId ? ' delivery_id=' + res.deliveryId : ''));
|
|
1105
|
+
process.exit(0);
|
|
1106
|
+
}
|
|
1107
|
+
console.error('[ATP-Complete] FAILED stage=' + (res && res.stage) + ' error=' + (res && res.error));
|
|
1108
|
+
process.exit(1);
|
|
1109
|
+
} catch (atpCompleteErr) {
|
|
1110
|
+
console.error('[ATP-Complete] Error:', atpCompleteErr && atpCompleteErr.message || atpCompleteErr);
|
|
1111
|
+
process.exit(1);
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1005
1114
|
} else if (command === 'buy' || command === 'orders' || command === 'verify') {
|
|
1006
1115
|
try {
|
|
1007
1116
|
const atpCli = require('./src/atp/cli');
|
|
@@ -1031,7 +1140,7 @@ async function main() {
|
|
|
1031
1140
|
}
|
|
1032
1141
|
|
|
1033
1142
|
} else {
|
|
1034
|
-
console.log(`Usage: node index.js [run|/evolve|solidify|review|distill|fetch|asset-log|setup-hooks|buy|orders|verify] [--loop]
|
|
1143
|
+
console.log(`Usage: node index.js [run|/evolve|solidify|review|distill|fetch|asset-log|setup-hooks|buy|orders|verify|atp-complete] [--loop]
|
|
1035
1144
|
- fetch flags:
|
|
1036
1145
|
- --skill=<id> | -s <id> (skill ID to download)
|
|
1037
1146
|
- --out=<dir> (output directory, default: ./skills/<skill_id>)
|
|
@@ -1071,6 +1180,13 @@ async function main() {
|
|
|
1071
1180
|
- --json (raw JSON)
|
|
1072
1181
|
- verify <orderId> (confirm delivery or trigger AI judge)
|
|
1073
1182
|
- --action=confirm|ai_judge (default confirm)
|
|
1183
|
+
- atp-complete (internal: spawned Cursor sub-session uses this to settle an ATP task)
|
|
1184
|
+
- --task-id=<tid> (Hub task id, required)
|
|
1185
|
+
- --order-id=<oid> (ATP DeliveryProof id, required)
|
|
1186
|
+
- --answer-file=<path> (file containing the merchant answer, required)
|
|
1187
|
+
- --summary="..." (capsule summary, optional)
|
|
1188
|
+
- --capabilities=a,b (listing capabilities, optional)
|
|
1189
|
+
- --signals=s1,s2 (task signals, optional)
|
|
1074
1190
|
|
|
1075
1191
|
Validator role (decentralized validation, default ON since v1.69.0):
|
|
1076
1192
|
- EVOLVER_VALIDATOR_ENABLED=0 opt out (env beats persisted flag and default)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evomap/evolver",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.74.0",
|
|
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": {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
// Usage: node scripts/validate-suite.js [test-glob-pattern]
|
|
1
|
+
// Usage: node scripts/validate-suite.js [test-glob-pattern | test-file]
|
|
2
2
|
// Runs the evolver test suite -- repo root is derived from script location, no shell glob needed.
|
|
3
|
-
|
|
3
|
+
// Accepts either a directory glob pattern (e.g. `test/*.test.js`) or a concrete test file path.
|
|
4
|
+
// See community PR #514.
|
|
5
|
+
const { execFileSync } = require('child_process');
|
|
4
6
|
const path = require('path');
|
|
5
7
|
const fs = require('fs');
|
|
6
8
|
|
|
@@ -8,10 +10,22 @@ const EVOLVER_REPO_ROOT = path.join(__dirname, '..');
|
|
|
8
10
|
const pattern = process.argv[2] || 'test/*.test.js';
|
|
9
11
|
|
|
10
12
|
function expandTestGlob(repoRoot, pat) {
|
|
11
|
-
const
|
|
13
|
+
const fullPattern = path.isAbsolute(pat) ? pat : path.join(repoRoot, pat);
|
|
14
|
+
if (fs.existsSync(fullPattern) && fs.statSync(fullPattern).isFile()) {
|
|
15
|
+
return fullPattern.endsWith('.test.js') ? [fullPattern] : [];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const dir = path.dirname(pat);
|
|
19
|
+
const basenamePattern = path.basename(pat);
|
|
12
20
|
const fullDir = path.isAbsolute(dir) ? dir : path.join(repoRoot, dir);
|
|
21
|
+
if (!fs.existsSync(fullDir) || !fs.statSync(fullDir).isDirectory()) return [];
|
|
22
|
+
|
|
23
|
+
const escaped = basenamePattern
|
|
24
|
+
.replace(/[.+?^${}()|[\]\\]/g, '\\$&')
|
|
25
|
+
.replace(/\*/g, '.*');
|
|
26
|
+
const matcher = new RegExp('^' + escaped + '$');
|
|
13
27
|
return fs.readdirSync(fullDir)
|
|
14
|
-
.filter(f => f.endsWith('.test.js'))
|
|
28
|
+
.filter(f => f.endsWith('.test.js') && matcher.test(f))
|
|
15
29
|
.map(f => path.join(fullDir, f))
|
|
16
30
|
.sort();
|
|
17
31
|
}
|
|
@@ -22,7 +36,6 @@ if (files.length === 0) {
|
|
|
22
36
|
process.exit(1);
|
|
23
37
|
}
|
|
24
38
|
|
|
25
|
-
const cmd = 'node --test ' + files.join(' ');
|
|
26
39
|
const env = Object.assign({}, process.env, {
|
|
27
40
|
NODE_ENV: 'test',
|
|
28
41
|
EVOLVER_REPO_ROOT,
|
|
@@ -30,9 +43,11 @@ const env = Object.assign({}, process.env, {
|
|
|
30
43
|
});
|
|
31
44
|
delete env.EVOLVE_BRIDGE;
|
|
32
45
|
delete env.OPENCLAW_WORKSPACE;
|
|
46
|
+
// Clear NODE_TEST_CONTEXT so nested runs from within node --test work.
|
|
47
|
+
delete env.NODE_TEST_CONTEXT;
|
|
33
48
|
|
|
34
49
|
try {
|
|
35
|
-
const output =
|
|
50
|
+
const output = execFileSync(process.execPath, ['--test', ...files], {
|
|
36
51
|
cwd: EVOLVER_REPO_ROOT,
|
|
37
52
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
38
53
|
timeout: 180000,
|
|
@@ -6,6 +6,7 @@ const PLATFORMS = {
|
|
|
6
6
|
cursor: { name: 'Cursor', configDir: '.cursor', detector: '.cursor' },
|
|
7
7
|
'claude-code': { name: 'Claude Code', configDir: '.claude', detector: '.claude' },
|
|
8
8
|
codex: { name: 'Codex', configDir: '.codex', detector: '.codex' },
|
|
9
|
+
kiro: { name: 'Kiro', configDir: '.kiro', detector: '.kiro' },
|
|
9
10
|
};
|
|
10
11
|
|
|
11
12
|
function detectPlatform(cwd) {
|
|
@@ -35,6 +36,7 @@ function loadAdapter(platformId) {
|
|
|
35
36
|
case 'cursor': return require('./cursor');
|
|
36
37
|
case 'claude-code': return require('./claudeCode');
|
|
37
38
|
case 'codex': return require('./codex');
|
|
39
|
+
case 'kiro': return require('./kiro');
|
|
38
40
|
default: return null;
|
|
39
41
|
}
|
|
40
42
|
}
|
|
@@ -163,7 +165,7 @@ async function setupHooks({ platform, cwd, force, uninstall, evolverRoot } = {})
|
|
|
163
165
|
const platformId = platform || detectPlatform(effectiveCwd);
|
|
164
166
|
|
|
165
167
|
if (!platformId) {
|
|
166
|
-
console.error('[setup-hooks] Could not detect platform. Use --platform=cursor|claude-code|codex');
|
|
168
|
+
console.error('[setup-hooks] Could not detect platform. Use --platform=cursor|claude-code|codex|kiro');
|
|
167
169
|
return { ok: false, error: 'platform_not_detected' };
|
|
168
170
|
}
|
|
169
171
|
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { copyHookScripts, removeHookScripts } = require('./hookAdapter');
|
|
4
|
+
|
|
5
|
+
const HOOK_SCRIPTS_DIR_NAME = 'hooks';
|
|
6
|
+
const EVOLVER_MARKER = '<!-- evolver-evolution-memory -->';
|
|
7
|
+
const HOOK_FILE_SUFFIX = '.kiro.hook';
|
|
8
|
+
const HOOK_FILES = {
|
|
9
|
+
sessionStart: 'evolver-session-start.kiro.hook',
|
|
10
|
+
signalDetect: 'evolver-signal-detect.kiro.hook',
|
|
11
|
+
sessionEnd: 'evolver-session-end.kiro.hook',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function buildHookConfig(kind, scriptsBase) {
|
|
15
|
+
// Kiro has no dedicated sessionStart event; `promptSubmit` is the closest
|
|
16
|
+
// analogue. The session-start JS script itself guards against per-prompt
|
|
17
|
+
// re-injection via a session-scoped state file when
|
|
18
|
+
// EVOLVER_SESSION_START_DEDUP=1 is set (injected inline into the command
|
|
19
|
+
// because Kiro runCommand has no env field).
|
|
20
|
+
const sessionStartCmd = `EVOLVER_SESSION_START_DEDUP=1 node ${scriptsBase}/evolver-session-start.js`;
|
|
21
|
+
const hookTemplates = {
|
|
22
|
+
sessionStart: {
|
|
23
|
+
name: 'Evolver Session Start',
|
|
24
|
+
version: '1',
|
|
25
|
+
description:
|
|
26
|
+
'Reads recent evolution memory from the local memory graph and injects it as context when a prompt is submitted.',
|
|
27
|
+
when: { type: 'promptSubmit' },
|
|
28
|
+
then: {
|
|
29
|
+
type: 'runCommand',
|
|
30
|
+
command: sessionStartCmd,
|
|
31
|
+
timeout: 3,
|
|
32
|
+
},
|
|
33
|
+
_evolver_managed: true,
|
|
34
|
+
},
|
|
35
|
+
signalDetect: {
|
|
36
|
+
name: 'Evolver Signal Detect',
|
|
37
|
+
version: '1',
|
|
38
|
+
description:
|
|
39
|
+
'Detects evolution signals (errors, perf bottlenecks, capability gaps, test failures) in file content after write operations.',
|
|
40
|
+
when: { type: 'postToolUse', toolTypes: ['write'] },
|
|
41
|
+
then: {
|
|
42
|
+
type: 'runCommand',
|
|
43
|
+
command: `node ${scriptsBase}/evolver-signal-detect.js`,
|
|
44
|
+
timeout: 2,
|
|
45
|
+
},
|
|
46
|
+
_evolver_managed: true,
|
|
47
|
+
},
|
|
48
|
+
sessionEnd: {
|
|
49
|
+
name: 'Evolver Session End',
|
|
50
|
+
version: '1',
|
|
51
|
+
description:
|
|
52
|
+
'Records evolution outcome at session end by analyzing git diff stats and writing to the local memory graph.',
|
|
53
|
+
when: { type: 'agentStop' },
|
|
54
|
+
then: {
|
|
55
|
+
type: 'runCommand',
|
|
56
|
+
command: `node ${scriptsBase}/evolver-session-end.js`,
|
|
57
|
+
timeout: 8,
|
|
58
|
+
},
|
|
59
|
+
_evolver_managed: true,
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
return hookTemplates[kind];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function buildAgentsMdSection() {
|
|
66
|
+
return `${EVOLVER_MARKER}
|
|
67
|
+
## Evolution Memory (Evolver)
|
|
68
|
+
|
|
69
|
+
This project uses evolver for self-evolution. Hooks automatically:
|
|
70
|
+
1. Inject recent evolution memory on prompt submit
|
|
71
|
+
2. Detect evolution signals during file edits
|
|
72
|
+
3. Record outcomes at session end
|
|
73
|
+
|
|
74
|
+
For substantive tasks, call \`gep_recall\` before work and \`gep_record_outcome\` after.
|
|
75
|
+
Signals: log_error, perf_bottleneck, user_feature_request, capability_gap, deployment_issue, test_failure.`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function appendSectionToFile(filePath, marker, content) {
|
|
79
|
+
let existing = '';
|
|
80
|
+
try { existing = fs.readFileSync(filePath, 'utf8'); } catch { /* new file */ }
|
|
81
|
+
if (existing.includes(marker)) return false;
|
|
82
|
+
const separator = existing.length > 0 && !existing.endsWith('\n') ? '\n\n' : '\n';
|
|
83
|
+
fs.writeFileSync(filePath, existing + separator + content + '\n', 'utf8');
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function writeHookFile(hooksDir, fileName, config) {
|
|
88
|
+
const tmp = path.join(hooksDir, fileName + '.tmp');
|
|
89
|
+
const dest = path.join(hooksDir, fileName);
|
|
90
|
+
fs.writeFileSync(tmp, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
|
91
|
+
fs.renameSync(tmp, dest);
|
|
92
|
+
return dest;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function isEvolverManagedHookFile(filePath) {
|
|
96
|
+
try {
|
|
97
|
+
const raw = fs.readFileSync(filePath, 'utf8').trim();
|
|
98
|
+
if (!raw) return false;
|
|
99
|
+
const data = JSON.parse(raw);
|
|
100
|
+
if (data && data._evolver_managed === true) return true;
|
|
101
|
+
if (typeof data.name === 'string' && /^evolver\b/i.test(data.name)) return true;
|
|
102
|
+
if (data.then && typeof data.then.command === 'string' &&
|
|
103
|
+
/evolver-(session|signal)/.test(data.then.command)) return true;
|
|
104
|
+
} catch { /* treat as non-evolver */ }
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function install({ configRoot, evolverRoot, force }) {
|
|
109
|
+
const kiroDir = path.join(configRoot, '.kiro');
|
|
110
|
+
const hooksDir = path.join(kiroDir, HOOK_SCRIPTS_DIR_NAME);
|
|
111
|
+
const agentsMdPath = path.join(configRoot, 'AGENTS.md');
|
|
112
|
+
const scriptsBase = '.kiro/hooks';
|
|
113
|
+
|
|
114
|
+
const hookPaths = Object.values(HOOK_FILES).map(name => path.join(hooksDir, name));
|
|
115
|
+
|
|
116
|
+
if (!force) {
|
|
117
|
+
const existingEvolverHook = hookPaths.find(p => fs.existsSync(p) && isEvolverManagedHookFile(p));
|
|
118
|
+
if (existingEvolverHook) {
|
|
119
|
+
console.log('[kiro] Evolver hooks already installed. Use --force to overwrite.');
|
|
120
|
+
return { ok: true, skipped: true };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
fs.mkdirSync(hooksDir, { recursive: true });
|
|
125
|
+
|
|
126
|
+
const written = [];
|
|
127
|
+
for (const [kind, fileName] of Object.entries(HOOK_FILES)) {
|
|
128
|
+
const cfg = buildHookConfig(kind, scriptsBase);
|
|
129
|
+
const dest = writeHookFile(hooksDir, fileName, cfg);
|
|
130
|
+
written.push(dest);
|
|
131
|
+
console.log('[kiro] Wrote ' + dest);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const copied = copyHookScripts(hooksDir, path.join(evolverRoot, 'src', 'adapters'));
|
|
135
|
+
console.log('[kiro] Copied ' + copied.length + ' hook scripts to ' + hooksDir);
|
|
136
|
+
|
|
137
|
+
const injected = appendSectionToFile(agentsMdPath, EVOLVER_MARKER, buildAgentsMdSection());
|
|
138
|
+
if (injected) {
|
|
139
|
+
console.log('[kiro] Injected evolution section into ' + agentsMdPath);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
console.log('[kiro] Installation complete.');
|
|
143
|
+
console.log('[kiro] Kiro auto-discovers *.kiro.hook files in .kiro/hooks/ -- no restart needed.');
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
ok: true,
|
|
147
|
+
platform: 'kiro',
|
|
148
|
+
files: [...written, agentsMdPath, ...copied],
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function uninstall({ configRoot }) {
|
|
153
|
+
const kiroDir = path.join(configRoot, '.kiro');
|
|
154
|
+
const hooksDir = path.join(kiroDir, HOOK_SCRIPTS_DIR_NAME);
|
|
155
|
+
const agentsMdPath = path.join(configRoot, 'AGENTS.md');
|
|
156
|
+
|
|
157
|
+
let changed = false;
|
|
158
|
+
let removedCount = 0;
|
|
159
|
+
|
|
160
|
+
if (fs.existsSync(hooksDir)) {
|
|
161
|
+
try {
|
|
162
|
+
const entries = fs.readdirSync(hooksDir);
|
|
163
|
+
for (const entry of entries) {
|
|
164
|
+
if (!entry.endsWith(HOOK_FILE_SUFFIX)) continue;
|
|
165
|
+
const full = path.join(hooksDir, entry);
|
|
166
|
+
if (isEvolverManagedHookFile(full)) {
|
|
167
|
+
try { fs.unlinkSync(full); removedCount++; changed = true; } catch { /* ignore */ }
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
} catch { /* ignore */ }
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const scripts = removeHookScripts(hooksDir);
|
|
174
|
+
if (scripts > 0) changed = true;
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
if (fs.existsSync(agentsMdPath)) {
|
|
178
|
+
let content = fs.readFileSync(agentsMdPath, 'utf8');
|
|
179
|
+
if (content.includes(EVOLVER_MARKER)) {
|
|
180
|
+
const idx = content.indexOf(EVOLVER_MARKER);
|
|
181
|
+
const nextSection = content.indexOf('\n## ', idx + EVOLVER_MARKER.length);
|
|
182
|
+
const endIdx = nextSection !== -1 ? nextSection : content.length;
|
|
183
|
+
content = content.slice(0, idx).trimEnd() + (nextSection !== -1 ? content.slice(endIdx) : '');
|
|
184
|
+
fs.writeFileSync(agentsMdPath, content.trimEnd() + '\n', 'utf8');
|
|
185
|
+
changed = true;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
} catch { /* ignore */ }
|
|
189
|
+
|
|
190
|
+
console.log(changed
|
|
191
|
+
? `[kiro] Uninstalled evolver hooks (${removedCount} hook files + ${scripts} scripts removed).`
|
|
192
|
+
: '[kiro] No evolver hooks found to uninstall.');
|
|
193
|
+
|
|
194
|
+
return { ok: true, removed: changed };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
module.exports = {
|
|
198
|
+
install,
|
|
199
|
+
uninstall,
|
|
200
|
+
buildHookConfig,
|
|
201
|
+
isEvolverManagedHookFile,
|
|
202
|
+
HOOK_FILES,
|
|
203
|
+
};
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const path = require('path');
|
|
8
|
+
const os = require('os');
|
|
8
9
|
|
|
9
10
|
function findEvolverRoot() {
|
|
10
11
|
const candidates = [
|
|
@@ -58,7 +59,68 @@ function formatOutcome(entry) {
|
|
|
58
59
|
return `[${icon}] ${ts} score=${score} signals=[${signals}] ${note}`.slice(0, 200);
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
// Dedup guard: on platforms like Kiro, the sessionStart-equivalent event
|
|
63
|
+
// (`promptSubmit`) fires on every user message in a session. Without this
|
|
64
|
+
// guard, recent memory would be re-injected on every prompt. We key the
|
|
65
|
+
// dedup on (platform, cwd) with a short TTL so a fresh agent session within
|
|
66
|
+
// the same workspace still gets the injection, but mid-session prompts do
|
|
67
|
+
// not. Cursor/Claude Code/Codex have true sessionStart events and should
|
|
68
|
+
// bypass this check (controlled by EVOLVER_SESSION_START_DEDUP env var,
|
|
69
|
+
// which the Kiro adapter sets on the hook command line implicitly via the
|
|
70
|
+
// runtime environment, and other adapters leave unset).
|
|
71
|
+
function getDedupStatePath() {
|
|
72
|
+
const dir = process.env.EVOLVER_SESSION_STATE_DIR
|
|
73
|
+
|| path.join(os.homedir(), '.evolver');
|
|
74
|
+
try { fs.mkdirSync(dir, { recursive: true }); } catch { /* ignore */ }
|
|
75
|
+
return path.join(dir, 'session-start-state.json');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function shouldSkipInjection() {
|
|
79
|
+
// Only apply dedup when explicitly enabled (set by Kiro adapter) OR when
|
|
80
|
+
// we detect a per-prompt-firing platform via PROMPT_SUBMIT heuristic in
|
|
81
|
+
// stdin. The stdin is drained in main(), so we rely on env flag here.
|
|
82
|
+
const dedupEnabled = String(process.env.EVOLVER_SESSION_START_DEDUP || '').toLowerCase() === '1'
|
|
83
|
+
|| String(process.env.EVOLVER_SESSION_START_DEDUP || '').toLowerCase() === 'true';
|
|
84
|
+
if (!dedupEnabled) return false;
|
|
85
|
+
|
|
86
|
+
const ttlMs = Number(process.env.EVOLVER_SESSION_START_DEDUP_TTL_MS) || (30 * 60 * 1000);
|
|
87
|
+
const key = process.cwd();
|
|
88
|
+
const statePath = getDedupStatePath();
|
|
89
|
+
|
|
90
|
+
let state = {};
|
|
91
|
+
try {
|
|
92
|
+
if (fs.existsSync(statePath)) {
|
|
93
|
+
state = JSON.parse(fs.readFileSync(statePath, 'utf8')) || {};
|
|
94
|
+
}
|
|
95
|
+
} catch { state = {}; }
|
|
96
|
+
|
|
97
|
+
const now = Date.now();
|
|
98
|
+
const last = state[key];
|
|
99
|
+
if (typeof last === 'number' && now - last < ttlMs) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
state[key] = now;
|
|
104
|
+
try {
|
|
105
|
+
for (const k of Object.keys(state)) {
|
|
106
|
+
if (typeof state[k] !== 'number' || now - state[k] > 24 * 60 * 60 * 1000) {
|
|
107
|
+
delete state[k];
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const tmp = statePath + '.tmp';
|
|
111
|
+
fs.writeFileSync(tmp, JSON.stringify(state), 'utf8');
|
|
112
|
+
fs.renameSync(tmp, statePath);
|
|
113
|
+
} catch { /* best-effort */ }
|
|
114
|
+
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
|
|
61
118
|
function main() {
|
|
119
|
+
if (shouldSkipInjection()) {
|
|
120
|
+
process.stdout.write(JSON.stringify({}));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
62
124
|
const evolverRoot = findEvolverRoot();
|
|
63
125
|
const graphPath = findMemoryGraph(evolverRoot);
|
|
64
126
|
|