@evomap/evolver 1.70.0 → 1.72.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.
Files changed (45) hide show
  1. package/assets/gep/candidates.jsonl +4 -6
  2. package/index.js +76 -6
  3. package/package.json +1 -1
  4. package/scripts/validate-suite.js +21 -6
  5. package/src/adapters/hookAdapter.js +3 -1
  6. package/src/adapters/kiro.js +203 -0
  7. package/src/adapters/scripts/evolver-session-start.js +62 -0
  8. package/src/atp/autoBuyer.js +12 -6
  9. package/src/atp/autoDeliver.js +199 -0
  10. package/src/atp/cliAutobuyPrompt.js +4 -3
  11. package/src/atp/hubClient.js +20 -0
  12. package/src/atp/index.js +4 -1
  13. package/src/evolve.js +1 -1
  14. package/src/gep/.integrity +0 -0
  15. package/src/gep/a2aProtocol.js +1 -1
  16. package/src/gep/candidateEval.js +1 -1
  17. package/src/gep/candidates.js +1 -1
  18. package/src/gep/contentHash.js +1 -1
  19. package/src/gep/crypto.js +1 -1
  20. package/src/gep/curriculum.js +1 -1
  21. package/src/gep/deviceId.js +1 -1
  22. package/src/gep/envFingerprint.js +1 -1
  23. package/src/gep/explore.js +1 -1
  24. package/src/gep/hubReview.js +1 -1
  25. package/src/gep/hubSearch.js +1 -1
  26. package/src/gep/hubVerify.js +1 -1
  27. package/src/gep/integrityCheck.js +1 -1
  28. package/src/gep/learningSignals.js +1 -1
  29. package/src/gep/memoryGraph.js +1 -1
  30. package/src/gep/memoryGraphAdapter.js +1 -1
  31. package/src/gep/mutation.js +1 -1
  32. package/src/gep/narrativeMemory.js +1 -1
  33. package/src/gep/personality.js +1 -1
  34. package/src/gep/policyCheck.js +1 -1
  35. package/src/gep/prompt.js +1 -1
  36. package/src/gep/reflection.js +1 -1
  37. package/src/gep/selector.js +1 -1
  38. package/src/gep/shield.js +1 -1
  39. package/src/gep/skillDistiller.js +1 -1
  40. package/src/gep/solidify.js +1 -1
  41. package/src/gep/strategy.js +1 -1
  42. package/src/gep/validator/sandboxExecutor.js +11 -2
  43. package/src/proxy/lifecycle/manager.js +5 -1
  44. package/src/proxy/mailbox/store.js +5 -0
  45. package/src/proxy/server/http.js +47 -4
@@ -1,6 +1,4 @@
1
- {"type":"CapabilityCandidate","id":"cand_b9a66a5c","title":"Harden session log detection and fallback behavior","source":"signals","created_at":"2026-04-27T05:47:45.106Z","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"}}
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-27T10:30:27.039Z","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"}}
2
+ {"type":"CapabilityCandidate","id":"cand_b9a66a5c","title":"Harden session log detection and fallback behavior","source":"signals","created_at":"2026-04-27T10:30:56.391Z","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"}}
3
+ {"type":"CapabilityCandidate","id":"cand_b9a66a5c","title":"Harden session log detection and fallback behavior","source":"signals","created_at":"2026-04-27T10:31:11.248Z","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"}}
4
+ {"type":"CapabilityCandidate","id":"cand_b9a66a5c","title":"Harden session log detection and fallback behavior","source":"signals","created_at":"2026-04-27T10:31:26.375Z","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: opt-in capability-gap auto-buyer (default OFF, must be explicitly enabled).
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 || 'off').toLowerCase().trim();
278
- const autoBuyOn = autoBuyRaw === 'on' || autoBuyRaw === '1' || autoBuyRaw === 'true';
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] EVOLVER_ATP_AUTOBUY=on but no hub URL configured, skipping.');
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
- outDir = path.join('.', 'skills', safeId);
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
- fs.writeFileSync(path.join(outDir, safeName), file.content, 'utf8');
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evomap/evolver",
3
- "version": "1.70.0",
3
+ "version": "1.72.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
- const { execSync } = require('child_process');
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 dir = pat.replace(/\/\*\.test\.js$/, '');
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 = execSync(cmd, {
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
 
@@ -1,14 +1,16 @@
1
- // ATP Auto-Buyer (opt-in)
1
+ // ATP Auto-Buyer (opt-out, default ON as of ATP liquidity unlock)
2
2
  // Converts capability gaps into ATP orders with strict budget caps and
3
- // 24h question-level deduplication. Default OFF; enabled by setting
4
- // EVOLVER_ATP_AUTOBUY=on (also accepts 1/true). Budget caps:
3
+ // 24h question-level deduplication. Disable by setting
4
+ // EVOLVER_ATP_AUTOBUY=off. Budget caps:
5
5
  // ATP_AUTOBUY_DAILY_CAP_CREDITS (default 50)
6
6
  // ATP_AUTOBUY_PER_ORDER_CAP_CREDITS (default 10)
7
7
  // Cold-start safety: the first 5 minutes after process start use a half-cap
8
8
  // to protect against misconfiguration loops on restart storms.
9
9
  //
10
10
  // Integration contract:
11
- // 1) Call start({ dailyCap, perOrderCap }) once when EVOLVER_ATP_AUTOBUY=on.
11
+ // 1) Call start({ dailyCap, perOrderCap }) once at Evolver boot. The
12
+ // evolve loop does this at the top of every cycle; start() is
13
+ // idempotent so the repeated call is a no-op.
12
14
  // 2) Call considerOrder({ signals, question, capabilities, budget, ... })
13
15
  // from the evolve loop whenever a capability gap is detected.
14
16
  // 3) Result shape: { ok, skipped?, reason?, data?, error? }.
@@ -48,8 +50,12 @@ function _todayKey(now) {
48
50
  }
49
51
 
50
52
  function _isEnabled() {
51
- const raw = (process.env.EVOLVER_ATP_AUTOBUY || 'off').toLowerCase().trim();
52
- return raw === 'on' || raw === '1' || raw === 'true';
53
+ // Default ON: the evolve loop starts autoBuyer at the top of every cycle
54
+ // so new users get ATP buyer routing out of the box. Disable by setting
55
+ // EVOLVER_ATP_AUTOBUY=off. Budget caps (DAILY_CAP + PER_ORDER_CAP) keep
56
+ // the downside bounded even when this is on.
57
+ const raw = (process.env.EVOLVER_ATP_AUTOBUY || 'on').toLowerCase().trim();
58
+ return raw !== 'off' && raw !== '0' && raw !== 'false';
53
59
  }
54
60
 
55
61
  function _emptyLedger() {