@evomap/evolver 1.89.20 → 2.0.0-beta.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 (231) hide show
  1. package/README.md +27 -564
  2. package/bin/evolver-llm-proxy.js +3 -0
  3. package/bin/evolver-mcp.js +2 -0
  4. package/bin/evolver-proxy.js +3 -0
  5. package/bin/evolver.js +4 -0
  6. package/index.js +1 -3586
  7. package/package.json +32 -62
  8. package/CONTRIBUTING.md +0 -19
  9. package/LICENSE +0 -641
  10. package/README.ja-JP.md +0 -521
  11. package/README.ko-KR.md +0 -520
  12. package/README.zh-CN.md +0 -531
  13. package/SKILL.md +0 -365
  14. package/assets/cover.png +0 -0
  15. package/assets/gep/genes.seed.json +0 -496
  16. package/conformance/savings-core/constants.json +0 -30
  17. package/conformance/savings-core/golden-vectors.json +0 -333
  18. package/scripts/a2a_export.js +0 -63
  19. package/scripts/a2a_ingest.js +0 -79
  20. package/scripts/a2a_promote.js +0 -118
  21. package/scripts/analyze_by_skill.js +0 -121
  22. package/scripts/build_binaries.js +0 -479
  23. package/scripts/check-changelog.js +0 -166
  24. package/scripts/extract_log.js +0 -85
  25. package/scripts/generate_history.js +0 -75
  26. package/scripts/gep_append_event.js +0 -96
  27. package/scripts/gep_personality_report.js +0 -234
  28. package/scripts/human_report.js +0 -147
  29. package/scripts/recall-verify-report.js +0 -234
  30. package/scripts/recover_loop.js +0 -61
  31. package/scripts/refresh_stars_badge.js +0 -168
  32. package/scripts/seed-merchants.js +0 -91
  33. package/scripts/skill2recipes.js +0 -118
  34. package/scripts/suggest_version.js +0 -89
  35. package/scripts/validate-modules.js +0 -38
  36. package/scripts/validate-suite.js +0 -78
  37. package/skills/_meta/SKILL.md +0 -41
  38. package/skills/index.json +0 -14
  39. package/src/adapters/claudeCode.js +0 -194
  40. package/src/adapters/codex.js +0 -216
  41. package/src/adapters/cursor.js +0 -91
  42. package/src/adapters/hookAdapter.js +0 -469
  43. package/src/adapters/kiro.js +0 -195
  44. package/src/adapters/opencode.js +0 -326
  45. package/src/adapters/scripts/_lockPaths.js +0 -74
  46. package/src/adapters/scripts/_memoryFiltering.js +0 -35
  47. package/src/adapters/scripts/_runtimePaths.js +0 -440
  48. package/src/adapters/scripts/evolver-session-end.js +0 -321
  49. package/src/adapters/scripts/evolver-session-start.js +0 -587
  50. package/src/adapters/scripts/evolver-signal-detect.js +0 -98
  51. package/src/adapters/scripts/evolver-task-recall.js +0 -173
  52. package/src/atp/atpExecute.js +0 -283
  53. package/src/atp/atpTaskPickup.js +0 -233
  54. package/src/atp/autoBuyer.js +0 -382
  55. package/src/atp/autoDeliver.js +0 -215
  56. package/src/atp/cli.js +0 -354
  57. package/src/atp/cliAutobuyPrompt.js +0 -154
  58. package/src/atp/consumerAgent.js +0 -157
  59. package/src/atp/defaultHandler.js +0 -69
  60. package/src/atp/heartbeatSignalsHandler.js +0 -254
  61. package/src/atp/hubClient.js +0 -317
  62. package/src/atp/index.js +0 -38
  63. package/src/atp/merchantAgent.js +0 -118
  64. package/src/atp/protocol.js +0 -41
  65. package/src/atp/questionComposer.js +0 -133
  66. package/src/atp/serviceHelper.js +0 -92
  67. package/src/canary.js +0 -13
  68. package/src/config.js +0 -351
  69. package/src/evolve/guards.js +0 -1
  70. package/src/evolve/pipeline/collect.js +0 -1
  71. package/src/evolve/pipeline/dispatch.js +0 -1
  72. package/src/evolve/pipeline/enrich.js +0 -1
  73. package/src/evolve/pipeline/hub.js +0 -1
  74. package/src/evolve/pipeline/select.js +0 -1
  75. package/src/evolve/pipeline/signals.js +0 -1
  76. package/src/evolve/utils.js +0 -1
  77. package/src/evolve.js +0 -1
  78. package/src/experiment/agentRunner.js +0 -229
  79. package/src/experiment/cli.js +0 -159
  80. package/src/experiment/comparison.js +0 -233
  81. package/src/experiment/metrics.js +0 -75
  82. package/src/forceUpdate.js +0 -991
  83. package/src/gep/a2a.js +0 -173
  84. package/src/gep/a2aProtocol.js +0 -1
  85. package/src/gep/analyzer.js +0 -35
  86. package/src/gep/antiAbuseTelemetry.js +0 -1
  87. package/src/gep/assetCallLog.js +0 -197
  88. package/src/gep/assetStore.js +0 -723
  89. package/src/gep/assets.js +0 -36
  90. package/src/gep/autoDistillConv.js +0 -1
  91. package/src/gep/autoDistillLlm.js +0 -1
  92. package/src/gep/bridge.js +0 -138
  93. package/src/gep/candidateEval.js +0 -1
  94. package/src/gep/candidates.js +0 -1
  95. package/src/gep/claimNudge.js +0 -121
  96. package/src/gep/cliContracts.js +0 -1190
  97. package/src/gep/contentHash.js +0 -1
  98. package/src/gep/conversationDistiller.js +0 -1
  99. package/src/gep/conversationSniffer.js +0 -1
  100. package/src/gep/crypto.js +0 -1
  101. package/src/gep/curriculum.js +0 -1
  102. package/src/gep/deviceId.js +0 -1
  103. package/src/gep/directoryClient.js +0 -115
  104. package/src/gep/envFingerprint.js +0 -1
  105. package/src/gep/epigenetics.js +0 -1
  106. package/src/gep/execBridge.js +0 -1
  107. package/src/gep/executionTrace.js +0 -291
  108. package/src/gep/explore.js +0 -1
  109. package/src/gep/featureFlags.js +0 -121
  110. package/src/gep/gitOps.js +0 -265
  111. package/src/gep/hash.js +0 -1
  112. package/src/gep/hostErrorClassifier.js +0 -34
  113. package/src/gep/hubFetch.js +0 -1
  114. package/src/gep/hubReview.js +0 -1
  115. package/src/gep/hubSearch.js +0 -1
  116. package/src/gep/hubVerify.js +0 -1
  117. package/src/gep/idleScheduler.js +0 -400
  118. package/src/gep/issueReporter.js +0 -416
  119. package/src/gep/learningSignals.js +0 -1
  120. package/src/gep/llmReview.js +0 -92
  121. package/src/gep/localStateAwareness.js +0 -243
  122. package/src/gep/mailboxTransport.js +0 -119
  123. package/src/gep/memoryGraph.js +0 -1
  124. package/src/gep/memoryGraphAdapter.js +0 -1
  125. package/src/gep/mutation.js +0 -1
  126. package/src/gep/narrativeMemory.js +0 -1
  127. package/src/gep/oauthLogin.js +0 -181
  128. package/src/gep/openPRRegistry.js +0 -1
  129. package/src/gep/paths.js +0 -522
  130. package/src/gep/personality.js +0 -1
  131. package/src/gep/policyCheck.js +0 -1
  132. package/src/gep/portable.js +0 -103
  133. package/src/gep/privacyClient.js +0 -235
  134. package/src/gep/prompt.js +0 -1
  135. package/src/gep/questionGenerator.js +0 -518
  136. package/src/gep/recallInject.js +0 -1
  137. package/src/gep/recallVerifier.js +0 -1
  138. package/src/gep/reflection.js +0 -1
  139. package/src/gep/sanitize.js +0 -264
  140. package/src/gep/savingsCore.js +0 -1
  141. package/src/gep/schemas/capsule.js +0 -170
  142. package/src/gep/schemas/gene.js +0 -154
  143. package/src/gep/schemas/index.js +0 -8
  144. package/src/gep/schemas/protocol.js +0 -51
  145. package/src/gep/schemas/task.js +0 -74
  146. package/src/gep/selector.js +0 -1
  147. package/src/gep/selfPR.js +0 -469
  148. package/src/gep/signals.js +0 -776
  149. package/src/gep/skill2gep.js +0 -1056
  150. package/src/gep/skill2gepAudit.js +0 -303
  151. package/src/gep/skill2recipes.js +0 -511
  152. package/src/gep/skillDistiller.js +0 -1
  153. package/src/gep/skillPublisher.js +0 -358
  154. package/src/gep/solidify.js +0 -1
  155. package/src/gep/strategy.js +0 -1
  156. package/src/gep/taskReceiver.js +0 -575
  157. package/src/gep/tokenSavings.js +0 -1
  158. package/src/gep/trajectoryExport.js +0 -1
  159. package/src/gep/validationReport.js +0 -55
  160. package/src/gep/validator/index.js +0 -411
  161. package/src/gep/validator/reporter.js +0 -210
  162. package/src/gep/validator/sandboxExecutor.js +0 -480
  163. package/src/gep/validator/stakeBootstrap.js +0 -357
  164. package/src/gep/workspaceKeychain.js +0 -1
  165. package/src/ops/cleanup.js +0 -80
  166. package/src/ops/commentary.js +0 -60
  167. package/src/ops/health_check.js +0 -104
  168. package/src/ops/index.js +0 -11
  169. package/src/ops/innovation.js +0 -67
  170. package/src/ops/lifecycle.js +0 -798
  171. package/src/ops/self_repair.js +0 -76
  172. package/src/ops/skills_monitor.js +0 -147
  173. package/src/ops/trigger.js +0 -33
  174. package/src/proxy/clientSettings.js +0 -405
  175. package/src/proxy/envelope.js +0 -59
  176. package/src/proxy/extensions/dmHandler.js +0 -45
  177. package/src/proxy/extensions/sessionHandler.js +0 -141
  178. package/src/proxy/extensions/skillUpdater.js +0 -64
  179. package/src/proxy/extensions/traceControl.js +0 -1
  180. package/src/proxy/index.js +0 -1395
  181. package/src/proxy/inject.js +0 -1
  182. package/src/proxy/lifecycle/manager.js +0 -1568
  183. package/src/proxy/mailbox/state.js +0 -207
  184. package/src/proxy/mailbox/store.js +0 -590
  185. package/src/proxy/router/cache_passthrough.js +0 -26
  186. package/src/proxy/router/features.js +0 -84
  187. package/src/proxy/router/gemini_route.js +0 -154
  188. package/src/proxy/router/messages_route.js +0 -535
  189. package/src/proxy/router/model_router.js +0 -113
  190. package/src/proxy/router/models_route.js +0 -52
  191. package/src/proxy/router/ollama_route.js +0 -103
  192. package/src/proxy/router/responses_route.js +0 -170
  193. package/src/proxy/router/vertex_route.js +0 -110
  194. package/src/proxy/server/http.js +0 -363
  195. package/src/proxy/server/routes.js +0 -558
  196. package/src/proxy/server/settings.js +0 -115
  197. package/src/proxy/sync/engine.js +0 -179
  198. package/src/proxy/sync/inbound.js +0 -211
  199. package/src/proxy/sync/outbound.js +0 -320
  200. package/src/proxy/task/monitor.js +0 -131
  201. package/src/proxy/trace/extractor.js +0 -1
  202. package/src/proxy/trace/usage.js +0 -1
  203. package/src/webui/client/clientJs/assets.js +0 -111
  204. package/src/webui/client/clientJs/bootstrap.js +0 -92
  205. package/src/webui/client/clientJs/common.js +0 -77
  206. package/src/webui/client/clientJs/i18n.js +0 -366
  207. package/src/webui/client/clientJs/index.js +0 -35
  208. package/src/webui/client/clientJs/interactions.js +0 -351
  209. package/src/webui/client/clientJs/overview.js +0 -152
  210. package/src/webui/client/clientJs/personality.js +0 -285
  211. package/src/webui/client/clientJs/pipelines.js +0 -330
  212. package/src/webui/client/indexHtml.js +0 -221
  213. package/src/webui/client/static.js +0 -23
  214. package/src/webui/client/stylesCss.js +0 -639
  215. package/src/webui/client/vendor/README.md +0 -15
  216. package/src/webui/client/vendor/echarts.min.js +0 -45
  217. package/src/webui/index.js +0 -14
  218. package/src/webui/observer/assets.js +0 -146
  219. package/src/webui/observer/index.js +0 -37
  220. package/src/webui/observer/interactions.js +0 -127
  221. package/src/webui/observer/jsonl.js +0 -75
  222. package/src/webui/observer/paths.js +0 -46
  223. package/src/webui/observer/personality.js +0 -43
  224. package/src/webui/observer/pipelineEvents.js +0 -58
  225. package/src/webui/observer/redact.js +0 -63
  226. package/src/webui/observer/runs.js +0 -356
  227. package/src/webui/observer/safety.js +0 -57
  228. package/src/webui/observer/skills.js +0 -70
  229. package/src/webui/observer/status.js +0 -71
  230. package/src/webui/server/http.js +0 -138
  231. package/src/webui/server/routes.js +0 -41
@@ -1,469 +0,0 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const os = require('os');
4
-
5
- const PLATFORMS = {
6
- cursor: { name: 'Cursor', configDir: '.cursor', detector: '.cursor' },
7
- 'claude-code': { name: 'Claude Code', configDir: '.claude', detector: '.claude' },
8
- codex: { name: 'Codex', configDir: '.codex', detector: '.codex' },
9
- kiro: { name: 'Kiro', configDir: '.kiro', detector: '.kiro' },
10
- opencode: { name: 'opencode', configDir: '.opencode', detector: '.opencode' },
11
- };
12
-
13
- // Detect the host agent from runtime environment signals, which are far more
14
- // reliable than scanning for `.claude` / `.cursor` directories when both exist
15
- // on disk (e.g. under $HOME). Precedence when signals conflict (#590):
16
- // 1. strong Claude Code signals (set only by the Claude Code runtime)
17
- // 2. Cursor signals
18
- // 3. Codex signals
19
- // 4. CLAUDE_PROJECT_DIR — a weak Claude alias some hosts (incl. Cursor's
20
- // Claude integration) export, so it loses to explicit Cursor signals.
21
- function detectPlatformFromEnv(env = process.env) {
22
- const hasStrongClaudeSignal = env.CLAUDECODE || env.CLAUDE_CODE_ENTRYPOINT;
23
- if (hasStrongClaudeSignal) {
24
- return 'claude-code';
25
- }
26
-
27
- const hasCursorSignal =
28
- env.CURSOR_TRACE_ID ||
29
- env.CURSOR_SESSION_ID ||
30
- env.CURSOR_PROJECT_DIR ||
31
- env.CURSOR_AGENT ||
32
- String(env.TERM_PROGRAM || '').toLowerCase() === 'cursor';
33
- if (hasCursorSignal) {
34
- return 'cursor';
35
- }
36
- const hasCodexSignal =
37
- env.CODEX_THREAD_ID ||
38
- env.CODEX_SHELL ||
39
- env.CODEX_CI ||
40
- env.CODEX_INTERNAL_ORIGINATOR_OVERRIDE;
41
- if (hasCodexSignal) {
42
- return 'codex';
43
- }
44
- if (env.CLAUDE_PROJECT_DIR) {
45
- return 'claude-code';
46
- }
47
- return null;
48
- }
49
-
50
- function detectPlatform(cwd) {
51
- const envPlatform = detectPlatformFromEnv();
52
- if (envPlatform) return envPlatform;
53
-
54
- const root = cwd || process.cwd();
55
- const home = os.homedir();
56
- for (const [id, meta] of Object.entries(PLATFORMS)) {
57
- if (fs.existsSync(path.join(root, meta.detector))) return id;
58
- }
59
- for (const [id, meta] of Object.entries(PLATFORMS)) {
60
- if (fs.existsSync(path.join(home, meta.detector))) return id;
61
- }
62
- return null;
63
- }
64
-
65
- // Quote `value` as a single shell argument for the host that runs hook
66
- // `command` strings (POSIX sh, or cmd.exe on Windows).
67
- function shellQuoteForHost(value) {
68
- const raw = String(value);
69
- if (process.platform === 'win32') {
70
- return `"${raw.replace(/"/g, '\\"')}"`;
71
- }
72
- return `'${raw.replace(/'/g, "'\\''")}'`;
73
- }
74
-
75
- // Build a hook `command` that runs `scriptPath` with node WITHOUT exposing the
76
- // path bytes to the shell. The absolute path is base64-encoded into a `node -e`
77
- // wrapper that decodes it and spawns the real script with `shell:false`, so
78
- // spaces / `$()` / backticks / `%VAR%` in the path can never be expanded by sh
79
- // or cmd.exe (#590). The plaintext basename is appended as a final, inert arg
80
- // so uninstall/reinstall can still match evolver-owned hooks by command
81
- // substring (see isEvolverHookCommand) even though the path itself is encoded.
82
- //
83
- // Shared across adapters so there is exactly one implementation to audit.
84
- function buildSafeNodeHookCommand(scriptPath) {
85
- const encodedPath = Buffer.from(String(scriptPath), 'utf8').toString('base64');
86
- const js = [
87
- "const{spawnSync}=require('child_process')",
88
- `const p=Buffer.from('${encodedPath}','base64').toString('utf8')`,
89
- "const r=spawnSync(process.execPath,[p],{stdio:'inherit',shell:false})",
90
- "if(r.error){console.error(r.error.message||String(r.error));process.exit(1)}",
91
- "process.exit(r.status==null?1:r.status)",
92
- ].join(';');
93
- return `node -e ${shellQuoteForHost(js)} ${path.basename(scriptPath)}`;
94
- }
95
-
96
- function resolveConfigRoot(platformId, cwd) {
97
- const root = cwd || process.cwd();
98
- const home = os.homedir();
99
- const meta = PLATFORMS[platformId];
100
- if (!meta) return null;
101
- if (fs.existsSync(path.join(root, meta.detector))) return root;
102
- if (fs.existsSync(path.join(home, meta.detector))) return home;
103
- return root;
104
- }
105
-
106
- function loadAdapter(platformId) {
107
- switch (platformId) {
108
- case 'cursor': return require('./cursor');
109
- case 'claude-code': return require('./claudeCode');
110
- case 'codex': return require('./codex');
111
- case 'kiro': return require('./kiro');
112
- case 'opencode': return require('./opencode');
113
- default: return null;
114
- }
115
- }
116
-
117
- function mergeJsonFile(filePath, patch, { markerKey = '_evolver_managed' } = {}) {
118
- let existing = {};
119
- try {
120
- if (fs.existsSync(filePath)) {
121
- const raw = fs.readFileSync(filePath, 'utf8').trim();
122
- if (raw) existing = JSON.parse(raw);
123
- }
124
- } catch { /* start fresh */ }
125
- const merged = mergeWithHooksUnion(existing, patch);
126
- merged[markerKey] = true;
127
- const tmp = filePath + '.tmp';
128
- fs.writeFileSync(tmp, JSON.stringify(merged, null, 2) + '\n', 'utf8');
129
- fs.renameSync(tmp, filePath);
130
- return merged;
131
- }
132
-
133
- // Like deepMerge, but for `hooks.<event>` arrays specifically: instead of
134
- // replacing the user's existing entries, keep them and append/refresh evolver-
135
- // owned entries (matched by command containing `evolver-session/-signal`).
136
- // This preserves user-installed Stop/SessionStart hooks (#539) while still
137
- // updating evolver hooks across reinstalls.
138
- function mergeWithHooksUnion(target, source) {
139
- const result = deepMerge(target, source);
140
- if (
141
- target && target.hooks && typeof target.hooks === 'object' &&
142
- source && source.hooks && typeof source.hooks === 'object'
143
- ) {
144
- for (const event of Object.keys(source.hooks)) {
145
- const tArr = Array.isArray(target.hooks[event]) ? target.hooks[event] : null;
146
- const sArr = Array.isArray(source.hooks[event]) ? source.hooks[event] : null;
147
- if (tArr && sArr) {
148
- const userEntries = tArr
149
- .map(stripEvolverCommands)
150
- .filter(Boolean);
151
- result.hooks[event] = [...userEntries, ...sArr];
152
- }
153
- }
154
- }
155
- return result;
156
- }
157
-
158
- function stripEvolverCommands(entry) {
159
- if (!entry || typeof entry !== 'object') return entry;
160
- if (typeof entry.command === 'string' && isEvolverHookCommand(entry.command)) {
161
- return null;
162
- }
163
- if (!Array.isArray(entry.hooks)) return entry;
164
-
165
- const hooks = entry.hooks.filter(h => {
166
- const cmd = h && h.command;
167
- return !(typeof cmd === 'string' && isEvolverHookCommand(cmd));
168
- });
169
- if (hooks.length === 0) return null;
170
- if (hooks.length === entry.hooks.length) return entry;
171
- return { ...entry, hooks };
172
- }
173
-
174
- // Pull all `command` strings out of an event entry, supporting both flat
175
- // shape (Codex: `{type, command}`) and Claude Code matcher shape
176
- // (`{matcher, hooks: [{type, command}]}`). Returns [] when neither applies.
177
- function collectCommands(entry) {
178
- if (!entry || typeof entry !== 'object') return [];
179
- const out = [];
180
- if (typeof entry.command === 'string') out.push(entry.command);
181
- if (Array.isArray(entry.hooks)) {
182
- for (const h of entry.hooks) {
183
- if (h && typeof h.command === 'string') out.push(h.command);
184
- }
185
- }
186
- return out;
187
- }
188
-
189
- function isEvolverHookCommand(command) {
190
- if (typeof command !== 'string') return false;
191
- return command.includes('evolver-session') ||
192
- command.includes('evolver-signal') ||
193
- command.includes('evolver-task-recall') ||
194
- // Legacy installs briefly shipped this companion daemon hook. Treat it
195
- // as evolver-owned so reinstall/merge can remove it instead of preserving
196
- // a stale supervisor that may point clients at a dead proxy.
197
- command.includes('evolver-daemon-start');
198
- }
199
-
200
- function deepMerge(target, source) {
201
- const result = { ...target };
202
- for (const key of Object.keys(source)) {
203
- if (
204
- source[key] && typeof source[key] === 'object' && !Array.isArray(source[key]) &&
205
- result[key] && typeof result[key] === 'object' && !Array.isArray(result[key])
206
- ) {
207
- result[key] = deepMerge(result[key], source[key]);
208
- } else {
209
- result[key] = source[key];
210
- }
211
- }
212
- return result;
213
- }
214
-
215
- // Refuse to write/read through a symbolic link at the adapter's
216
- // platform config dir (`<root>/.codex`, `<root>/.claude`, …) or any
217
- // nested adapter-owned subdir (`hooks/`, `plugins/`, …). A
218
- // repository-controlled symlink at any of these paths would let
219
- // install/uninstall writes land on attacker-chosen files outside the
220
- // workspace (PR #94 round-4 surfaced the top-level case; round-5
221
- // surfaced that a hostile repo can keep `.codex` real and only
222
- // symlink `.codex/hooks`). Missing dirs are fine — install will
223
- // create them.
224
- function assertSafeConfigDir(dir, label, { subdirs = [] } = {}) {
225
- assertNotSymlink(dir, label || 'config dir');
226
- for (const sub of subdirs) {
227
- assertNotSymlink(path.join(dir, sub), `${label || 'config dir'}/${sub}`);
228
- }
229
- }
230
-
231
- function assertNotSymlink(p, label) {
232
- let st;
233
- try {
234
- st = fs.lstatSync(p);
235
- } catch (e) {
236
- if (e && e.code === 'ENOENT') return;
237
- throw e;
238
- }
239
- if (st.isSymbolicLink()) {
240
- throw new Error(
241
- `[setup-hooks] Refusing to operate: ${label} ${p} is a ` +
242
- `symbolic link. evolver will not follow symlinks for ` +
243
- `adapter-owned dirs — a hostile workspace could redirect ` +
244
- `writes/unlinks outside the project root. Replace it with a ` +
245
- `real directory and rerun.`
246
- );
247
- }
248
- }
249
-
250
- function copyHookScripts(destDir, evolverRoot) {
251
- const scriptsDir = path.join(evolverRoot || __dirname, 'scripts');
252
- // Every helper required by the entry-point hooks via `require('./_xxx')`
253
- // resolves relative to the *destination* (`__dirname` after copy), so
254
- // every such helper MUST appear here or the hook crashes with
255
- // MODULE_NOT_FOUND at runtime. Two regressions of this shape have shipped
256
- // already:
257
- // - PR #94 review caught `_runtimePaths.js` missing from this list.
258
- // - Issue #547 (rendigua, v1.87.0): `_memoryFiltering.js` was added to
259
- // evolver-session-start.js but not here, so fresh installs failed
260
- // immediately on `node .codex/hooks/evolver-session-start.js`.
261
- // To keep future helpers from re-living this, the regression test in
262
- // test/adapters.test.js scans every `require('./_*')` in the source
263
- // adapter scripts and asserts the target file is in this list.
264
- const scripts = [
265
- '_runtimePaths.js',
266
- '_memoryFiltering.js',
267
- '_lockPaths.js',
268
- 'evolver-session-start.js',
269
- 'evolver-signal-detect.js',
270
- 'evolver-session-end.js',
271
- 'evolver-task-recall.js',
272
- ];
273
- fs.mkdirSync(destDir, { recursive: true });
274
- const copied = [];
275
- for (const name of scripts) {
276
- const src = path.join(scriptsDir, name);
277
- const dest = path.join(destDir, name);
278
- if (!fs.existsSync(src)) {
279
- console.warn(`[setup-hooks] Warning: script not found: ${src}`);
280
- continue;
281
- }
282
- // PR #94 round-6 HIGH: reject if the destination is a pre-planted
283
- // symlink. fs.copyFileSync follows symlinks at the destination, so
284
- // a hostile repo that pre-creates `.codex/hooks/evolver-session-end.js`
285
- // pointing at e.g. `~/.bashrc` would have its target overwritten with
286
- // evolver script content. Round-5 closed the directory hole; this
287
- // closes the per-file hole.
288
- assertNotSymlink(dest, `hook destination ${name}`);
289
- fs.copyFileSync(src, dest);
290
- // NOTE(windows): fs.chmodSync is a no-op on Windows; hook scripts remain
291
- // executable via file extension association (.js), not Unix mode bits.
292
- try { fs.chmodSync(dest, 0o755); } catch { /* best-effort; no-op on Windows */ }
293
- copied.push(dest);
294
- }
295
- return copied;
296
- }
297
-
298
- function appendSectionToFile(filePath, marker, content) {
299
- let existing = '';
300
- try { existing = fs.readFileSync(filePath, 'utf8'); } catch { /* new file */ }
301
- if (existing.includes(marker)) {
302
- console.log(`[setup-hooks] Section already present in ${filePath}, skipping.`);
303
- return false;
304
- }
305
- const separator = existing.length > 0 && !existing.endsWith('\n') ? '\n\n' : '\n';
306
- fs.writeFileSync(filePath, existing + separator + content + '\n', 'utf8');
307
- return true;
308
- }
309
-
310
- function removeEvolverHooks(filePath, { markerKey = '_evolver_managed' } = {}) {
311
- try {
312
- if (!fs.existsSync(filePath)) return false;
313
- const raw = fs.readFileSync(filePath, 'utf8').trim();
314
- if (!raw) return false;
315
- const data = JSON.parse(raw);
316
- if (!data[markerKey]) return false;
317
-
318
- let changed = false;
319
- if (data.hooks) {
320
- for (const event of Object.keys(data.hooks)) {
321
- if (Array.isArray(data.hooks[event])) {
322
- const before = data.hooks[event].length;
323
- data.hooks[event] = data.hooks[event].filter(h => {
324
- const cmd = h.command || '';
325
- return !isEvolverHookCommand(cmd);
326
- });
327
- if (data.hooks[event].length !== before) changed = true;
328
- if (data.hooks[event].length === 0) delete data.hooks[event];
329
- }
330
- }
331
- if (Object.keys(data.hooks).length === 0) delete data.hooks;
332
- }
333
- if (data.mcpServers) {
334
- // Claude Code / Codex: hooks in mcpServers sub-key -- not relevant, skip
335
- }
336
- delete data[markerKey];
337
- const tmp = filePath + '.tmp';
338
- fs.writeFileSync(tmp, JSON.stringify(data, null, 2) + '\n', 'utf8');
339
- fs.renameSync(tmp, filePath);
340
- return changed;
341
- } catch {
342
- return false;
343
- }
344
- }
345
-
346
- function removeHookScripts(hooksDir) {
347
- // Must mirror the install list above. If install copies a helper but
348
- // uninstall doesn't remove it, `setup-hooks --uninstall` leaves orphan
349
- // files behind that the user then has to clean up by hand (#547 fix
350
- // would have introduced exactly this gap if only the install side
351
- // had been updated).
352
- const scripts = [
353
- '_runtimePaths.js',
354
- '_memoryFiltering.js',
355
- '_lockPaths.js',
356
- 'evolver-session-start.js',
357
- 'evolver-signal-detect.js',
358
- 'evolver-session-end.js',
359
- 'evolver-task-recall.js',
360
- ];
361
- let removed = 0;
362
- for (const name of scripts) {
363
- const p = path.join(hooksDir, name);
364
- try {
365
- if (fs.existsSync(p)) { fs.unlinkSync(p); removed++; }
366
- } catch (e) {
367
- // Surface unlink failures so users can see why a "successful"
368
- // uninstall left files behind (Windows file-locking, perms, …).
369
- console.warn(`[setup-hooks] Failed to remove ${p}: ${e.message || e}`);
370
- }
371
- }
372
- return removed;
373
- }
374
-
375
- // Remove a marker-bracketed section from a markdown file. Used by adapter
376
- // uninstall to clean up CLAUDE.md / AGENTS.md without nuking surrounding
377
- // user content.
378
- //
379
- // The previous inline implementations (codex/claude/kiro/opencode) searched
380
- // for the *next* `\n## ` after the marker, which matched evolver's own
381
- // `## Evolution Memory` heading and left the entire injected section in
382
- // place (#538). This helper skips any `## ` heading on the same line as the
383
- // marker, then looks for the next H2 to know where the user's content
384
- // resumes.
385
- function removeMarkedSection(filePath, marker) {
386
- try {
387
- if (!fs.existsSync(filePath)) return false;
388
- const raw = fs.readFileSync(filePath, 'utf8');
389
- const idx = raw.indexOf(marker);
390
- if (idx === -1) return false;
391
-
392
- // Skip past the marker line (and any heading on the same line).
393
- let scanFrom = idx + marker.length;
394
- const eol = raw.indexOf('\n', scanFrom);
395
- if (eol !== -1) scanFrom = eol + 1;
396
-
397
- // Skip past evolver's own `## ...` heading line if present.
398
- if (raw.startsWith('## ', scanFrom)) {
399
- const eol2 = raw.indexOf('\n', scanFrom);
400
- scanFrom = eol2 !== -1 ? eol2 + 1 : raw.length;
401
- }
402
-
403
- const nextSection = raw.indexOf('\n## ', scanFrom);
404
- const endIdx = nextSection !== -1 ? nextSection : raw.length;
405
- const before = raw.slice(0, idx).trimEnd();
406
- const after = nextSection !== -1 ? raw.slice(endIdx) : '';
407
- const next = (before ? before + (after.startsWith('\n') ? '' : '\n') : '') + after;
408
- fs.writeFileSync(filePath, next.trimEnd() + '\n', 'utf8');
409
- return true;
410
- } catch (e) {
411
- console.warn(`[setup-hooks] Failed to clean section in ${filePath}: ${e.message || e}`);
412
- return false;
413
- }
414
- }
415
-
416
- async function setupHooks({ platform, cwd, force, uninstall, evolverRoot } = {}) {
417
- const effectiveCwd = cwd || process.cwd();
418
- const effectiveEvolverRoot = evolverRoot || path.resolve(__dirname, '..');
419
- const platformId = platform || detectPlatform(effectiveCwd);
420
-
421
- if (!platformId) {
422
- console.error('[setup-hooks] Could not detect platform. Use --platform=cursor|claude-code|codex|kiro|opencode');
423
- return { ok: false, error: 'platform_not_detected' };
424
- }
425
-
426
- const meta = PLATFORMS[platformId];
427
- if (!meta) {
428
- console.error(`[setup-hooks] Unknown platform: ${platformId}`);
429
- return { ok: false, error: 'unknown_platform' };
430
- }
431
-
432
- const configRoot = resolveConfigRoot(platformId, effectiveCwd);
433
- const adapter = loadAdapter(platformId);
434
- if (!adapter) {
435
- console.error(`[setup-hooks] No adapter found for ${platformId}`);
436
- return { ok: false, error: 'no_adapter' };
437
- }
438
-
439
- console.log(`[setup-hooks] Platform: ${meta.name}`);
440
- console.log(`[setup-hooks] Config root: ${configRoot}`);
441
-
442
- if (uninstall) {
443
- return adapter.uninstall({ configRoot, evolverRoot: effectiveEvolverRoot });
444
- }
445
-
446
- return adapter.install({ configRoot, evolverRoot: effectiveEvolverRoot, force });
447
- }
448
-
449
- module.exports = {
450
- detectPlatformFromEnv,
451
- detectPlatform,
452
- buildSafeNodeHookCommand,
453
- resolveConfigRoot,
454
- loadAdapter,
455
- mergeJsonFile,
456
- deepMerge,
457
- mergeWithHooksUnion,
458
- collectCommands,
459
- isEvolverHookCommand,
460
- copyHookScripts,
461
- appendSectionToFile,
462
- assertSafeConfigDir,
463
- assertNotSymlink,
464
- removeEvolverHooks,
465
- removeHookScripts,
466
- removeMarkedSection,
467
- setupHooks,
468
- PLATFORMS,
469
- };
@@ -1,195 +0,0 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const { copyHookScripts, removeHookScripts, removeMarkedSection, assertSafeConfigDir } = 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
- assertSafeConfigDir(kiroDir, '.kiro', { subdirs: [HOOK_SCRIPTS_DIR_NAME] });
114
-
115
- const hookPaths = Object.values(HOOK_FILES).map(name => path.join(hooksDir, name));
116
-
117
- if (!force) {
118
- const existingEvolverHook = hookPaths.find(p => fs.existsSync(p) && isEvolverManagedHookFile(p));
119
- if (existingEvolverHook) {
120
- console.log('[kiro] Evolver hooks already installed. Use --force to overwrite.');
121
- return { ok: true, skipped: true };
122
- }
123
- }
124
-
125
- fs.mkdirSync(hooksDir, { recursive: true });
126
-
127
- const written = [];
128
- for (const [kind, fileName] of Object.entries(HOOK_FILES)) {
129
- const cfg = buildHookConfig(kind, scriptsBase);
130
- const dest = writeHookFile(hooksDir, fileName, cfg);
131
- written.push(dest);
132
- console.log('[kiro] Wrote ' + dest);
133
- }
134
-
135
- const copied = copyHookScripts(hooksDir, path.join(evolverRoot, 'src', 'adapters'));
136
- console.log('[kiro] Copied ' + copied.length + ' hook scripts to ' + hooksDir);
137
-
138
- const injected = appendSectionToFile(agentsMdPath, EVOLVER_MARKER, buildAgentsMdSection());
139
- if (injected) {
140
- console.log('[kiro] Injected evolution section into ' + agentsMdPath);
141
- }
142
-
143
- console.log('[kiro] Installation complete.');
144
- console.log('[kiro] Kiro auto-discovers *.kiro.hook files in .kiro/hooks/ -- no restart needed.');
145
-
146
- return {
147
- ok: true,
148
- platform: 'kiro',
149
- files: [...written, agentsMdPath, ...copied],
150
- };
151
- }
152
-
153
- function uninstall({ configRoot }) {
154
- const kiroDir = path.join(configRoot, '.kiro');
155
- const hooksDir = path.join(kiroDir, HOOK_SCRIPTS_DIR_NAME);
156
- const agentsMdPath = path.join(configRoot, 'AGENTS.md');
157
- assertSafeConfigDir(kiroDir, '.kiro', { subdirs: [HOOK_SCRIPTS_DIR_NAME] });
158
-
159
- let changed = false;
160
- let removedCount = 0;
161
-
162
- if (fs.existsSync(hooksDir)) {
163
- try {
164
- const entries = fs.readdirSync(hooksDir);
165
- for (const entry of entries) {
166
- if (!entry.endsWith(HOOK_FILE_SUFFIX)) continue;
167
- const full = path.join(hooksDir, entry);
168
- if (isEvolverManagedHookFile(full)) {
169
- try { fs.unlinkSync(full); removedCount++; changed = true; } catch { /* ignore */ }
170
- }
171
- }
172
- } catch { /* ignore */ }
173
- }
174
-
175
- const scripts = removeHookScripts(hooksDir);
176
- if (scripts > 0) changed = true;
177
-
178
- if (removeMarkedSection(agentsMdPath, EVOLVER_MARKER)) {
179
- changed = true;
180
- }
181
-
182
- console.log(changed
183
- ? `[kiro] Uninstalled evolver hooks (${removedCount} hook files + ${scripts} scripts removed).`
184
- : '[kiro] No evolver hooks found to uninstall.');
185
-
186
- return { ok: true, removed: changed };
187
- }
188
-
189
- module.exports = {
190
- install,
191
- uninstall,
192
- buildHookConfig,
193
- isEvolverManagedHookFile,
194
- HOOK_FILES,
195
- };