@evomap/evolver 1.84.0 → 1.84.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/gep/genes.seed.json +17 -15
- package/index.js +52 -16
- package/package.json +4 -3
- package/src/adapters/claudeCode.js +44 -31
- package/src/adapters/codex.js +70 -26
- package/src/adapters/cursor.js +3 -1
- package/src/adapters/hookAdapter.js +142 -2
- package/src/adapters/kiro.js +6 -14
- package/src/adapters/opencode.js +6 -14
- package/src/adapters/scripts/_runtimePaths.js +114 -0
- package/src/adapters/scripts/evolver-session-end.js +37 -61
- package/src/adapters/scripts/evolver-session-start.js +1 -31
- package/src/atp/hubClient.js +3 -1
- package/src/config.js +20 -1
- 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/forceUpdate.js +5 -21
- package/src/gep/a2aProtocol.js +1 -1
- package/src/gep/assetStore.js +27 -6
- 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/directoryClient.js +4 -3
- package/src/gep/envFingerprint.js +1 -1
- package/src/gep/epigenetics.js +1 -1
- package/src/gep/explore.js +1 -1
- package/src/gep/gitOps.js +0 -5
- package/src/gep/hash.js +1 -1
- package/src/gep/hubFetch.js +1 -0
- 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/mailboxTransport.js +8 -5
- 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/openPRRegistry.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/recallVerifier.js +1 -1
- package/src/gep/reflection.js +1 -1
- package/src/gep/sanitize.js +2 -1
- package/src/gep/schemas/gene.js +70 -1
- package/src/gep/schemas/protocol.js +9 -1
- package/src/gep/selector.js +1 -1
- package/src/gep/selfPR.js +62 -34
- package/src/gep/skillDistiller.js +1 -1
- package/src/gep/skillPublisher.js +3 -2
- package/src/gep/solidify.js +1 -1
- package/src/gep/strategy.js +1 -1
- package/src/gep/taskReceiver.js +6 -5
- package/src/gep/validator/index.js +10 -6
- package/src/gep/validator/reporter.js +2 -1
- package/src/gep/validator/stakeBootstrap.js +2 -1
- package/src/ops/health_check.js +1 -11
- package/src/ops/lifecycle.js +1 -3
- package/src/proxy/index.js +69 -0
- package/src/proxy/lifecycle/manager.js +3 -2
- package/src/proxy/router/cache_passthrough.js +26 -0
- package/src/proxy/router/features.js +84 -0
- package/src/proxy/router/messages_route.js +242 -0
- package/src/proxy/router/model_router.js +113 -0
- package/src/proxy/server/http.js +108 -6
- package/src/proxy/server/routes.js +12 -2
- package/src/proxy/server/settings.js +43 -10
- package/src/proxy/sync/inbound.js +3 -2
- package/src/proxy/sync/outbound.js +2 -1
- package/src/webui/observer/interactions.js +22 -16
- package/scripts/check_wrapper_compat.js +0 -113
- package/src/gep/.integrity +0 -0
- package/src/gep/integrityCheck.js +0 -1
- package/src/gep/shield.js +0 -1
|
@@ -6,10 +6,12 @@
|
|
|
6
6
|
"id": "gene_gep_repair_from_errors",
|
|
7
7
|
"category": "repair",
|
|
8
8
|
"signals_match": [
|
|
9
|
-
"error",
|
|
10
|
-
"exception",
|
|
11
|
-
"failed",
|
|
12
|
-
"unstable"
|
|
9
|
+
"error|错误|异常|エラー|오류",
|
|
10
|
+
"exception|异常|例外|예외",
|
|
11
|
+
"failed|失败|失敗|실패|fail",
|
|
12
|
+
"unstable|不稳定|不安定|불안정",
|
|
13
|
+
"log_error",
|
|
14
|
+
"test_failure"
|
|
13
15
|
],
|
|
14
16
|
"preconditions": [
|
|
15
17
|
"signals contains error-related indicators"
|
|
@@ -39,11 +41,11 @@
|
|
|
39
41
|
"id": "gene_gep_optimize_prompt_and_assets",
|
|
40
42
|
"category": "optimize",
|
|
41
43
|
"signals_match": [
|
|
42
|
-
"protocol",
|
|
44
|
+
"protocol|协议|プロトコル|프로토콜",
|
|
43
45
|
"gep",
|
|
44
|
-
"prompt",
|
|
45
|
-
"audit",
|
|
46
|
-
"reusable"
|
|
46
|
+
"prompt|提示词|提示|プロンプト|프롬프트",
|
|
47
|
+
"audit|审计|監査|감사",
|
|
48
|
+
"reusable|可复用|再利用|재사용"
|
|
47
49
|
],
|
|
48
50
|
"preconditions": [
|
|
49
51
|
"need stricter, auditable evolution protocol outputs"
|
|
@@ -73,12 +75,12 @@
|
|
|
73
75
|
"id": "gene_gep_innovate_from_opportunity",
|
|
74
76
|
"category": "innovate",
|
|
75
77
|
"signals_match": [
|
|
76
|
-
"user_feature_request",
|
|
77
|
-
"user_improvement_suggestion",
|
|
78
|
-
"perf_bottleneck",
|
|
79
|
-
"capability_gap",
|
|
78
|
+
"user_feature_request|功能请求|機能リクエスト|기능요청",
|
|
79
|
+
"user_improvement_suggestion|改进建议|改善提案|개선제안",
|
|
80
|
+
"perf_bottleneck|性能瓶颈|パフォーマンス|성능병목",
|
|
81
|
+
"capability_gap|能力缺口|機能ギャップ|역량공백",
|
|
80
82
|
"stable_success_plateau",
|
|
81
|
-
"external_opportunity",
|
|
83
|
+
"external_opportunity|外部机会|外部機会|외부기회",
|
|
82
84
|
"bounty_task"
|
|
83
85
|
],
|
|
84
86
|
"preconditions": [
|
|
@@ -114,8 +116,8 @@
|
|
|
114
116
|
"signals_match": [
|
|
115
117
|
"high_tool_usage:exec",
|
|
116
118
|
"repeated_tool_usage:exec",
|
|
117
|
-
"tool_bypass",
|
|
118
|
-
"tool_loop",
|
|
119
|
+
"tool_bypass|工具绕过|ツール迂回|도구우회",
|
|
120
|
+
"tool_loop|工具循环|ツールループ|도구반복",
|
|
119
121
|
"high_tool_usage"
|
|
120
122
|
],
|
|
121
123
|
"preconditions": [
|
package/index.js
CHANGED
|
@@ -129,9 +129,9 @@ class CycleTimeoutError extends Error {
|
|
|
129
129
|
// in detached mode. So suicide-respawn (cycles >= max, RSS over budget, or the
|
|
130
130
|
// new cycle hard-timeout) opens a new cmd popup on every restart. We now skip
|
|
131
131
|
// the in-process detached spawn on Windows by default and rely on an external
|
|
132
|
-
// supervisor (
|
|
133
|
-
//
|
|
134
|
-
//
|
|
132
|
+
// supervisor (NSSM, pm2-windows, etc.) to respawn the daemon on non-zero exit.
|
|
133
|
+
// Users who insist can opt back in with EVOLVER_SUICIDE_WINDOWS=true (and accept
|
|
134
|
+
// the popups).
|
|
135
135
|
function spawnReplacementProcess({ reason, args, logPath }) {
|
|
136
136
|
const isWindows = process.platform === 'win32';
|
|
137
137
|
const allowOnWindows = parseBoolEnv(process.env.EVOLVER_SUICIDE_WINDOWS, false);
|
|
@@ -140,7 +140,7 @@ function spawnReplacementProcess({ reason, args, logPath }) {
|
|
|
140
140
|
'[Daemon] Skipping in-process respawn on Windows (' + reason + '). ' +
|
|
141
141
|
'Native Node spawn(detached, windowsHide) opens a cmd popup on every restart (Issue #528). ' +
|
|
142
142
|
'Set EVOLVER_SUICIDE_WINDOWS=true to opt back in. ' +
|
|
143
|
-
'Recommended: run evolver under
|
|
143
|
+
'Recommended: run evolver under an external supervisor (NSSM, pm2-windows, etc.) so it restarts on exit.'
|
|
144
144
|
);
|
|
145
145
|
return { spawned: false, reason: 'windows_default_skip' };
|
|
146
146
|
}
|
|
@@ -187,8 +187,14 @@ function getLastSignals(statePath) {
|
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
// Singleton Guard - prevent multiple evolver daemon instances
|
|
190
|
+
function getLockFilePath() {
|
|
191
|
+
// Allow tests / sandboxed runs to override the pid-file location so they
|
|
192
|
+
// do not collide with a real daemon's lock at the source-dir default.
|
|
193
|
+
const dir = process.env.EVOLVER_LOCK_DIR || __dirname;
|
|
194
|
+
return path.join(dir, 'evolver.pid');
|
|
195
|
+
}
|
|
190
196
|
function acquireLock() {
|
|
191
|
-
const lockFile =
|
|
197
|
+
const lockFile = getLockFilePath();
|
|
192
198
|
try {
|
|
193
199
|
try {
|
|
194
200
|
fs.writeFileSync(lockFile, String(process.pid), { flag: 'wx' });
|
|
@@ -217,7 +223,7 @@ function acquireLock() {
|
|
|
217
223
|
}
|
|
218
224
|
|
|
219
225
|
function releaseLock() {
|
|
220
|
-
const lockFile =
|
|
226
|
+
const lockFile = getLockFilePath();
|
|
221
227
|
try {
|
|
222
228
|
if (fs.existsSync(lockFile)) {
|
|
223
229
|
const pid = parseInt(fs.readFileSync(lockFile, 'utf8').trim(), 10);
|
|
@@ -280,22 +286,53 @@ async function main() {
|
|
|
280
286
|
releaseLock();
|
|
281
287
|
process.exit(1);
|
|
282
288
|
});
|
|
283
|
-
|
|
289
|
+
// Sliding window: only exit if many rejections cluster in a short
|
|
290
|
+
// period. A daemon running for weeks can accumulate harmless,
|
|
291
|
+
// unrelated rejections (transient network blips, hub timeouts);
|
|
292
|
+
// the original cumulative counter would eventually kill the
|
|
293
|
+
// process for noise. Cluster = real failure cascade.
|
|
294
|
+
const REJECTION_WINDOW_MS = 5 * 60 * 1000;
|
|
295
|
+
const REJECTION_THRESHOLD = 5;
|
|
296
|
+
let _rejectionTimestamps = [];
|
|
284
297
|
process.on('unhandledRejection', (reason) => {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
298
|
+
const now = Date.now();
|
|
299
|
+
_rejectionTimestamps.push(now);
|
|
300
|
+
_rejectionTimestamps = _rejectionTimestamps.filter(function (t) {
|
|
301
|
+
return now - t < REJECTION_WINDOW_MS;
|
|
302
|
+
});
|
|
303
|
+
console.error('[FATAL] Unhandled promise rejection (' + _rejectionTimestamps.length + ' in window):', reason && reason.stack ? reason.stack : String(reason));
|
|
304
|
+
if (_rejectionTimestamps.length >= REJECTION_THRESHOLD) {
|
|
305
|
+
console.error('[FATAL] ' + _rejectionTimestamps.length + ' unhandled rejections within ' + (REJECTION_WINDOW_MS / 1000) + 's. Exiting to avoid corrupt state.');
|
|
289
306
|
releaseLock();
|
|
290
307
|
process.exit(1);
|
|
291
308
|
}
|
|
292
309
|
});
|
|
293
310
|
|
|
294
311
|
process.env.EVOLVE_LOOP = 'true';
|
|
312
|
+
// Issue #96: from v1.85.0, --loop defaults EVOLVE_BRIDGE=true so the
|
|
313
|
+
// daemon actually evolves the working tree. The previous default of
|
|
314
|
+
// 'false' caused 33 days of empty cycling on Aurora — every cycle
|
|
315
|
+
// hit rejectPendingRun(reason=loop_bridge_disabled_autoreject_no_rollback)
|
|
316
|
+
// and produced no EvolutionEvent. Failed cycles still recover safely
|
|
317
|
+
// via rollbackTracked (src/gep/gitOps.js#rollbackTracked, mode=stash
|
|
318
|
+
// by default since v1.81.0): the daemon's changes get pushed to a
|
|
319
|
+
// stash entry the user can recover with `git stash pop`.
|
|
320
|
+
// Set EVOLVE_BRIDGE=false explicitly to opt back into observe-only.
|
|
295
321
|
if (!process.env.EVOLVE_BRIDGE) {
|
|
296
|
-
process.env.EVOLVE_BRIDGE = '
|
|
322
|
+
process.env.EVOLVE_BRIDGE = 'true';
|
|
297
323
|
}
|
|
324
|
+
const bridgeEnabled = String(process.env.EVOLVE_BRIDGE).toLowerCase() !== 'false';
|
|
298
325
|
console.log(`Loop mode enabled (internal daemon, bridge=${process.env.EVOLVE_BRIDGE}, verbose=${isVerbose}).`);
|
|
326
|
+
if (bridgeEnabled) {
|
|
327
|
+
console.warn('[Daemon] EVOLVE_BRIDGE=true (default since v1.85.0).');
|
|
328
|
+
console.warn('[Daemon] evolver may modify your working tree.');
|
|
329
|
+
console.warn('[Daemon] Failed cycles auto-stash via "git stash push --include-untracked".');
|
|
330
|
+
console.warn('[Daemon] Recover: git stash list | grep evolver-rollback');
|
|
331
|
+
console.warn('[Daemon] Set EVOLVE_BRIDGE=false to opt out (observe-only mode).');
|
|
332
|
+
} else {
|
|
333
|
+
console.warn('[Daemon] EVOLVE_BRIDGE=false: evolver will NOT modify your working tree (observe-only).');
|
|
334
|
+
console.warn('[Daemon] To enable real evolution: unset EVOLVE_BRIDGE or set it to "true".');
|
|
335
|
+
}
|
|
299
336
|
|
|
300
337
|
// Startup diagnostic: in daemon mode evolver consumes its own stdout
|
|
301
338
|
// instead of handing `sessions_spawn(...)` directives to a host
|
|
@@ -372,8 +409,7 @@ async function main() {
|
|
|
372
409
|
const idleThresholdMs = parseMs(process.env.EVOLVER_IDLE_THRESHOLD_MS, 500);
|
|
373
410
|
const pendingSleepMs = parseMs(
|
|
374
411
|
process.env.EVOLVE_PENDING_SLEEP_MS ||
|
|
375
|
-
process.env.EVOLVE_MIN_INTERVAL
|
|
376
|
-
process.env.FEISHU_EVOLVER_INTERVAL,
|
|
412
|
+
process.env.EVOLVE_MIN_INTERVAL,
|
|
377
413
|
120000
|
|
378
414
|
);
|
|
379
415
|
|
|
@@ -732,7 +768,7 @@ async function main() {
|
|
|
732
768
|
const summary = summaryFlag ? summaryFlag.slice('--summary='.length) : null;
|
|
733
769
|
|
|
734
770
|
try {
|
|
735
|
-
const res = solidify({
|
|
771
|
+
const res = await solidify({
|
|
736
772
|
intent: intent || undefined,
|
|
737
773
|
summary: summary || undefined,
|
|
738
774
|
dryRun,
|
|
@@ -982,7 +1018,7 @@ async function main() {
|
|
|
982
1018
|
if (args.includes('--approve')) {
|
|
983
1019
|
console.log('\n[Review] Approved. Running solidify...\n');
|
|
984
1020
|
try {
|
|
985
|
-
const res = solidify({
|
|
1021
|
+
const res = await solidify({
|
|
986
1022
|
intent: lastRun.intent || undefined,
|
|
987
1023
|
rollbackOnFailure: true,
|
|
988
1024
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evomap/evolver",
|
|
3
|
-
"version": "1.84.
|
|
3
|
+
"version": "1.84.2",
|
|
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": {
|
|
@@ -36,8 +36,9 @@
|
|
|
36
36
|
"node": ">=22.12"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@evomap/gep-sdk": "^1.
|
|
40
|
-
"dotenv": "^16.4.7"
|
|
39
|
+
"@evomap/gep-sdk": "^1.3.0",
|
|
40
|
+
"dotenv": "^16.4.7",
|
|
41
|
+
"undici": "^7.0.0"
|
|
41
42
|
},
|
|
42
43
|
"devDependencies": {
|
|
43
44
|
"javascript-obfuscator": "^5.4.1"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const { mergeJsonFile, copyHookScripts, appendSectionToFile, removeHookScripts } = require('./hookAdapter');
|
|
3
|
+
const { mergeJsonFile, copyHookScripts, appendSectionToFile, removeHookScripts, removeMarkedSection, assertSafeConfigDir } = require('./hookAdapter');
|
|
4
4
|
|
|
5
5
|
const HOOK_SCRIPTS_DIR_NAME = 'hooks';
|
|
6
6
|
const EVOLVER_MARKER = '<!-- evolver-evolution-memory -->';
|
|
@@ -65,6 +65,7 @@ function install({ configRoot, evolverRoot, force }) {
|
|
|
65
65
|
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
66
66
|
const hooksDir = path.join(claudeDir, HOOK_SCRIPTS_DIR_NAME);
|
|
67
67
|
const claudeMdPath = path.join(configRoot, 'CLAUDE.md');
|
|
68
|
+
assertSafeConfigDir(claudeDir, '.claude', { subdirs: [HOOK_SCRIPTS_DIR_NAME] });
|
|
68
69
|
|
|
69
70
|
if (!force && fs.existsSync(settingsPath)) {
|
|
70
71
|
try {
|
|
@@ -104,54 +105,66 @@ function uninstall({ configRoot }) {
|
|
|
104
105
|
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
105
106
|
const hooksDir = path.join(claudeDir, HOOK_SCRIPTS_DIR_NAME);
|
|
106
107
|
const claudeMdPath = path.join(configRoot, 'CLAUDE.md');
|
|
108
|
+
assertSafeConfigDir(claudeDir, '.claude', { subdirs: [HOOK_SCRIPTS_DIR_NAME] });
|
|
107
109
|
|
|
108
110
|
let changed = false;
|
|
109
111
|
|
|
112
|
+
// Strip evolver entries from settings.json. Even without the marker we
|
|
113
|
+
// still try to filter by command — a missing/dropped marker should not
|
|
114
|
+
// strand obvious evolver-owned entries (#538).
|
|
110
115
|
try {
|
|
111
116
|
if (fs.existsSync(settingsPath)) {
|
|
112
117
|
const data = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
return
|
|
125
|
-
})
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
118
|
+
let touched = false;
|
|
119
|
+
if (data.hooks) {
|
|
120
|
+
for (const event of Object.keys(data.hooks)) {
|
|
121
|
+
if (Array.isArray(data.hooks[event])) {
|
|
122
|
+
const beforeLen = data.hooks[event].length;
|
|
123
|
+
data.hooks[event] = data.hooks[event]
|
|
124
|
+
.map(matcher => {
|
|
125
|
+
if (!matcher || !Array.isArray(matcher.hooks)) return matcher;
|
|
126
|
+
const innerBefore = matcher.hooks.length;
|
|
127
|
+
const filtered = matcher.hooks.filter(h => {
|
|
128
|
+
const cmd = (h && h.command) || '';
|
|
129
|
+
return !cmd.includes('evolver-session') && !cmd.includes('evolver-signal');
|
|
130
|
+
});
|
|
131
|
+
// A matcher containing both evolver and user hooks shrinks
|
|
132
|
+
// its inner array without changing the outer matcher count.
|
|
133
|
+
// Track the inner-array shrink so `touched` reflects it.
|
|
134
|
+
if (filtered.length !== innerBefore) touched = true;
|
|
135
|
+
return { ...matcher, hooks: filtered };
|
|
136
|
+
})
|
|
137
|
+
.filter(matcher => matcher && Array.isArray(matcher.hooks) && matcher.hooks.length > 0);
|
|
138
|
+
if (data.hooks[event].length !== beforeLen) touched = true;
|
|
139
|
+
if (data.hooks[event].length === 0) delete data.hooks[event];
|
|
129
140
|
}
|
|
130
|
-
if (Object.keys(data.hooks).length === 0) delete data.hooks;
|
|
131
141
|
}
|
|
142
|
+
if (Object.keys(data.hooks).length === 0) delete data.hooks;
|
|
143
|
+
}
|
|
144
|
+
if (data._evolver_managed) {
|
|
132
145
|
delete data._evolver_managed;
|
|
146
|
+
touched = true;
|
|
147
|
+
}
|
|
148
|
+
if (touched) {
|
|
133
149
|
fs.writeFileSync(settingsPath, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
|
134
150
|
changed = true;
|
|
135
151
|
}
|
|
136
152
|
}
|
|
137
|
-
} catch {
|
|
153
|
+
} catch (e) {
|
|
154
|
+
console.warn(`[claude-code] Failed to clean ${settingsPath}: ${e.message || e}`);
|
|
155
|
+
}
|
|
138
156
|
|
|
139
157
|
const scripts = removeHookScripts(hooksDir);
|
|
140
158
|
if (scripts > 0) changed = true;
|
|
141
|
-
|
|
142
159
|
try {
|
|
143
|
-
if (fs.existsSync(
|
|
144
|
-
|
|
145
|
-
if (content.includes(EVOLVER_MARKER)) {
|
|
146
|
-
const idx = content.indexOf(EVOLVER_MARKER);
|
|
147
|
-
const nextSection = content.indexOf('\n## ', idx + EVOLVER_MARKER.length);
|
|
148
|
-
const endIdx = nextSection !== -1 ? nextSection : content.length;
|
|
149
|
-
content = content.slice(0, idx).trimEnd() + (nextSection !== -1 ? content.slice(endIdx) : '');
|
|
150
|
-
fs.writeFileSync(claudeMdPath, content.trimEnd() + '\n', 'utf8');
|
|
151
|
-
changed = true;
|
|
152
|
-
}
|
|
160
|
+
if (fs.existsSync(hooksDir) && fs.readdirSync(hooksDir).length === 0) {
|
|
161
|
+
fs.rmdirSync(hooksDir);
|
|
153
162
|
}
|
|
154
|
-
} catch { /*
|
|
163
|
+
} catch { /* best-effort */ }
|
|
164
|
+
|
|
165
|
+
if (removeMarkedSection(claudeMdPath, EVOLVER_MARKER)) {
|
|
166
|
+
changed = true;
|
|
167
|
+
}
|
|
155
168
|
|
|
156
169
|
console.log(changed
|
|
157
170
|
? '[claude-code] Uninstalled evolver hooks.'
|
package/src/adapters/codex.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const { mergeJsonFile, copyHookScripts, appendSectionToFile, removeHookScripts } = require('./hookAdapter');
|
|
3
|
+
const { mergeJsonFile, copyHookScripts, appendSectionToFile, removeHookScripts, removeMarkedSection, assertSafeConfigDir } = require('./hookAdapter');
|
|
4
4
|
|
|
5
5
|
const HOOK_SCRIPTS_DIR_NAME = 'hooks';
|
|
6
6
|
const EVOLVER_MARKER = '<!-- evolver-evolution-memory -->';
|
|
@@ -57,6 +57,34 @@ function ensureConfigToml(codexDir) {
|
|
|
57
57
|
return true;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
// Reverse of `ensureConfigToml`: drop the `codex_hooks = true` line and, if
|
|
61
|
+
// the surrounding `[features]` block becomes empty as a result, drop that
|
|
62
|
+
// header too. Other unrelated entries under `[features]` are preserved.
|
|
63
|
+
// Returns true when the file changed.
|
|
64
|
+
function cleanConfigToml(codexDir) {
|
|
65
|
+
const tomlPath = path.join(codexDir, 'config.toml');
|
|
66
|
+
let content;
|
|
67
|
+
try { content = fs.readFileSync(tomlPath, 'utf8'); } catch { return false; }
|
|
68
|
+
if (!/codex_hooks\s*=\s*true/i.test(content)) return false;
|
|
69
|
+
|
|
70
|
+
// Drop the `codex_hooks = true` line. The greedy `\s*` after `true`
|
|
71
|
+
// consumes the trailing newline plus any blank lines so the
|
|
72
|
+
// empty-`[features]` check below cannot be fooled by a stray blank
|
|
73
|
+
// line into treating the section as empty while user entries still
|
|
74
|
+
// follow.
|
|
75
|
+
let next = content.replace(/^\s*codex_hooks\s*=\s*true\s*\n?/im, '');
|
|
76
|
+
// Drop a now-empty `[features]` block. Two strict patterns avoid
|
|
77
|
+
// `$` with the /m flag — multiline `$` matches before any `\n`, so
|
|
78
|
+
// a single `(?=\s*$)` lookahead can succeed mid-file and strand
|
|
79
|
+
// user entries below the removed header (PR #94 round-3).
|
|
80
|
+
next = next.replace(/(^|\n)\[features\]\s*\n(?=\s*\[)/, '$1');
|
|
81
|
+
next = next.replace(/(^|\n)\[features\]\s*$/, '$1');
|
|
82
|
+
next = next.replace(/\n{3,}/g, '\n\n').trimEnd();
|
|
83
|
+
if (next.length > 0) next += '\n';
|
|
84
|
+
fs.writeFileSync(tomlPath, next, 'utf8');
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
|
|
60
88
|
function buildAgentsMdSection() {
|
|
61
89
|
return `${EVOLVER_MARKER}
|
|
62
90
|
## Evolution Memory (Evolver)
|
|
@@ -75,6 +103,7 @@ function install({ configRoot, evolverRoot, force }) {
|
|
|
75
103
|
const hooksJsonPath = path.join(codexDir, 'hooks.json');
|
|
76
104
|
const hooksDir = path.join(codexDir, HOOK_SCRIPTS_DIR_NAME);
|
|
77
105
|
const agentsMdPath = path.join(configRoot, 'AGENTS.md');
|
|
106
|
+
assertSafeConfigDir(codexDir, '.codex', { subdirs: [HOOK_SCRIPTS_DIR_NAME] });
|
|
78
107
|
|
|
79
108
|
if (!force && fs.existsSync(hooksJsonPath)) {
|
|
80
109
|
try {
|
|
@@ -119,48 +148,63 @@ function uninstall({ configRoot }) {
|
|
|
119
148
|
const hooksJsonPath = path.join(codexDir, 'hooks.json');
|
|
120
149
|
const hooksDir = path.join(codexDir, HOOK_SCRIPTS_DIR_NAME);
|
|
121
150
|
const agentsMdPath = path.join(configRoot, 'AGENTS.md');
|
|
151
|
+
assertSafeConfigDir(codexDir, '.codex', { subdirs: [HOOK_SCRIPTS_DIR_NAME] });
|
|
122
152
|
|
|
123
153
|
let changed = false;
|
|
124
154
|
|
|
155
|
+
// Strip evolver entries from hooks.json. Even when the
|
|
156
|
+
// `_evolver_managed` marker is missing (older install, hand-edited
|
|
157
|
+
// file), we still try to filter by command — a missing marker should
|
|
158
|
+
// not strand obvious evolver-owned entries (#538).
|
|
125
159
|
try {
|
|
126
160
|
if (fs.existsSync(hooksJsonPath)) {
|
|
127
161
|
const data = JSON.parse(fs.readFileSync(hooksJsonPath, 'utf8'));
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
162
|
+
let touched = false;
|
|
163
|
+
if (data.hooks) {
|
|
164
|
+
for (const event of Object.keys(data.hooks)) {
|
|
165
|
+
if (Array.isArray(data.hooks[event])) {
|
|
166
|
+
const before = data.hooks[event].length;
|
|
167
|
+
data.hooks[event] = data.hooks[event].filter(h => {
|
|
168
|
+
const cmd = (h && h.command) || '';
|
|
169
|
+
return !cmd.includes('evolver-session') && !cmd.includes('evolver-signal');
|
|
170
|
+
});
|
|
171
|
+
if (data.hooks[event].length !== before) touched = true;
|
|
172
|
+
if (data.hooks[event].length === 0) delete data.hooks[event];
|
|
138
173
|
}
|
|
139
|
-
if (Object.keys(data.hooks).length === 0) delete data.hooks;
|
|
140
174
|
}
|
|
175
|
+
if (Object.keys(data.hooks).length === 0) delete data.hooks;
|
|
176
|
+
}
|
|
177
|
+
if (data._evolver_managed) {
|
|
141
178
|
delete data._evolver_managed;
|
|
179
|
+
touched = true;
|
|
180
|
+
}
|
|
181
|
+
if (touched) {
|
|
142
182
|
fs.writeFileSync(hooksJsonPath, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
|
143
183
|
changed = true;
|
|
144
184
|
}
|
|
145
185
|
}
|
|
146
|
-
} catch {
|
|
186
|
+
} catch (e) {
|
|
187
|
+
console.warn(`[codex] Failed to clean ${hooksJsonPath}: ${e.message || e}`);
|
|
188
|
+
}
|
|
147
189
|
|
|
148
190
|
const scripts = removeHookScripts(hooksDir);
|
|
149
191
|
if (scripts > 0) changed = true;
|
|
150
|
-
|
|
192
|
+
// If hooks dir is now empty (only evolver scripts lived there), remove it
|
|
193
|
+
// so a subsequent install starts from a clean slate.
|
|
151
194
|
try {
|
|
152
|
-
if (fs.existsSync(
|
|
153
|
-
|
|
154
|
-
if (content.includes(EVOLVER_MARKER)) {
|
|
155
|
-
const idx = content.indexOf(EVOLVER_MARKER);
|
|
156
|
-
const nextSection = content.indexOf('\n## ', idx + EVOLVER_MARKER.length);
|
|
157
|
-
const endIdx = nextSection !== -1 ? nextSection : content.length;
|
|
158
|
-
content = content.slice(0, idx).trimEnd() + (nextSection !== -1 ? content.slice(endIdx) : '');
|
|
159
|
-
fs.writeFileSync(agentsMdPath, content.trimEnd() + '\n', 'utf8');
|
|
160
|
-
changed = true;
|
|
161
|
-
}
|
|
195
|
+
if (fs.existsSync(hooksDir) && fs.readdirSync(hooksDir).length === 0) {
|
|
196
|
+
fs.rmdirSync(hooksDir);
|
|
162
197
|
}
|
|
163
|
-
} catch { /*
|
|
198
|
+
} catch { /* best-effort */ }
|
|
199
|
+
|
|
200
|
+
if (cleanConfigToml(codexDir)) {
|
|
201
|
+
console.log('[codex] Removed codex_hooks flag from config.toml');
|
|
202
|
+
changed = true;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (removeMarkedSection(agentsMdPath, EVOLVER_MARKER)) {
|
|
206
|
+
changed = true;
|
|
207
|
+
}
|
|
164
208
|
|
|
165
209
|
console.log(changed
|
|
166
210
|
? '[codex] Uninstalled evolver hooks.'
|
|
@@ -169,4 +213,4 @@ function uninstall({ configRoot }) {
|
|
|
169
213
|
return { ok: true, removed: changed };
|
|
170
214
|
}
|
|
171
215
|
|
|
172
|
-
module.exports = { install, uninstall, buildCodexHooksJson, ensureConfigToml };
|
|
216
|
+
module.exports = { install, uninstall, buildCodexHooksJson, ensureConfigToml, cleanConfigToml };
|
package/src/adapters/cursor.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const { mergeJsonFile, copyHookScripts, removeEvolverHooks, removeHookScripts } = require('./hookAdapter');
|
|
3
|
+
const { mergeJsonFile, copyHookScripts, removeEvolverHooks, removeHookScripts, assertSafeConfigDir } = require('./hookAdapter');
|
|
4
4
|
|
|
5
5
|
const HOOK_SCRIPTS_DIR_NAME = 'hooks';
|
|
6
6
|
|
|
@@ -39,6 +39,7 @@ function install({ configRoot, evolverRoot, force }) {
|
|
|
39
39
|
const cursorDir = path.join(configRoot, '.cursor');
|
|
40
40
|
const hooksJsonPath = path.join(cursorDir, 'hooks.json');
|
|
41
41
|
const hooksDir = path.join(cursorDir, HOOK_SCRIPTS_DIR_NAME);
|
|
42
|
+
assertSafeConfigDir(cursorDir, '.cursor', { subdirs: [HOOK_SCRIPTS_DIR_NAME] });
|
|
42
43
|
|
|
43
44
|
if (!force && fs.existsSync(hooksJsonPath)) {
|
|
44
45
|
try {
|
|
@@ -73,6 +74,7 @@ function uninstall({ configRoot, evolverRoot }) {
|
|
|
73
74
|
const cursorDir = path.join(configRoot, '.cursor');
|
|
74
75
|
const hooksJsonPath = path.join(cursorDir, 'hooks.json');
|
|
75
76
|
const hooksDir = path.join(cursorDir, HOOK_SCRIPTS_DIR_NAME);
|
|
77
|
+
assertSafeConfigDir(cursorDir, '.cursor', { subdirs: [HOOK_SCRIPTS_DIR_NAME] });
|
|
76
78
|
|
|
77
79
|
const removed = removeEvolverHooks(hooksJsonPath);
|
|
78
80
|
const scripts = removeHookScripts(hooksDir);
|