@evomap/evolver 1.90.0 → 2.0.0-beta.1

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 -3670
  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/src/adapters/claudeCode.js +0 -194
  38. package/src/adapters/codex.js +0 -216
  39. package/src/adapters/cursor.js +0 -91
  40. package/src/adapters/hookAdapter.js +0 -469
  41. package/src/adapters/kiro.js +0 -195
  42. package/src/adapters/opencode.js +0 -326
  43. package/src/adapters/scripts/_lockPaths.js +0 -74
  44. package/src/adapters/scripts/_memoryFiltering.js +0 -35
  45. package/src/adapters/scripts/_runtimePaths.js +0 -440
  46. package/src/adapters/scripts/evolver-session-end.js +0 -321
  47. package/src/adapters/scripts/evolver-session-start.js +0 -587
  48. package/src/adapters/scripts/evolver-signal-detect.js +0 -98
  49. package/src/adapters/scripts/evolver-task-recall.js +0 -173
  50. package/src/atp/atpExecute.js +0 -283
  51. package/src/atp/atpTaskPickup.js +0 -233
  52. package/src/atp/autoBuyer.js +0 -382
  53. package/src/atp/autoDeliver.js +0 -215
  54. package/src/atp/cli.js +0 -354
  55. package/src/atp/cliAutobuyPrompt.js +0 -154
  56. package/src/atp/consumerAgent.js +0 -157
  57. package/src/atp/defaultHandler.js +0 -69
  58. package/src/atp/heartbeatSignalsHandler.js +0 -254
  59. package/src/atp/hubClient.js +0 -317
  60. package/src/atp/index.js +0 -38
  61. package/src/atp/merchantAgent.js +0 -118
  62. package/src/atp/protocol.js +0 -41
  63. package/src/atp/questionComposer.js +0 -133
  64. package/src/atp/serviceHelper.js +0 -92
  65. package/src/canary.js +0 -13
  66. package/src/config.js +0 -351
  67. package/src/evolve/guards.js +0 -1
  68. package/src/evolve/pipeline/collect.js +0 -1
  69. package/src/evolve/pipeline/dispatch.js +0 -1
  70. package/src/evolve/pipeline/enrich.js +0 -1
  71. package/src/evolve/pipeline/hub.js +0 -1
  72. package/src/evolve/pipeline/select.js +0 -1
  73. package/src/evolve/pipeline/signals.js +0 -1
  74. package/src/evolve/utils.js +0 -1
  75. package/src/evolve.js +0 -1
  76. package/src/experiment/agentRunner.js +0 -229
  77. package/src/experiment/cli.js +0 -159
  78. package/src/experiment/comparison.js +0 -233
  79. package/src/experiment/metrics.js +0 -75
  80. package/src/forceUpdate.js +0 -991
  81. package/src/gep/a2a.js +0 -173
  82. package/src/gep/a2aProtocol.js +0 -1
  83. package/src/gep/analyzer.js +0 -35
  84. package/src/gep/antiAbuseTelemetry.js +0 -1
  85. package/src/gep/assetCallLog.js +0 -197
  86. package/src/gep/assetStore.js +0 -723
  87. package/src/gep/assets.js +0 -36
  88. package/src/gep/autoDistillConv.js +0 -1
  89. package/src/gep/autoDistillLlm.js +0 -1
  90. package/src/gep/bridge.js +0 -138
  91. package/src/gep/candidateEval.js +0 -1
  92. package/src/gep/candidates.js +0 -1
  93. package/src/gep/claimNudge.js +0 -121
  94. package/src/gep/cliContracts.js +0 -1190
  95. package/src/gep/contentHash.js +0 -1
  96. package/src/gep/conversationDistiller.js +0 -1
  97. package/src/gep/conversationSniffer.js +0 -1
  98. package/src/gep/crypto.js +0 -1
  99. package/src/gep/curriculum.js +0 -1
  100. package/src/gep/deviceId.js +0 -1
  101. package/src/gep/directoryClient.js +0 -115
  102. package/src/gep/envFingerprint.js +0 -1
  103. package/src/gep/epigenetics.js +0 -1
  104. package/src/gep/execBridge.js +0 -1
  105. package/src/gep/executionTrace.js +0 -291
  106. package/src/gep/explore.js +0 -1
  107. package/src/gep/featureFlags.js +0 -121
  108. package/src/gep/gitOps.js +0 -265
  109. package/src/gep/hash.js +0 -1
  110. package/src/gep/hostErrorClassifier.js +0 -34
  111. package/src/gep/hubFetch.js +0 -1
  112. package/src/gep/hubReview.js +0 -1
  113. package/src/gep/hubSearch.js +0 -1
  114. package/src/gep/hubVerify.js +0 -1
  115. package/src/gep/idleScheduler.js +0 -400
  116. package/src/gep/issueReporter.js +0 -416
  117. package/src/gep/learningSignals.js +0 -1
  118. package/src/gep/llmReview.js +0 -92
  119. package/src/gep/localStateAwareness.js +0 -243
  120. package/src/gep/mailboxTransport.js +0 -119
  121. package/src/gep/memoryGraph.js +0 -1
  122. package/src/gep/memoryGraphAdapter.js +0 -1
  123. package/src/gep/mutation.js +0 -1
  124. package/src/gep/narrativeMemory.js +0 -1
  125. package/src/gep/oauthLogin.js +0 -181
  126. package/src/gep/openPRRegistry.js +0 -1
  127. package/src/gep/paths.js +0 -522
  128. package/src/gep/personality.js +0 -1
  129. package/src/gep/policyCheck.js +0 -1
  130. package/src/gep/portable.js +0 -103
  131. package/src/gep/privacyClient.js +0 -235
  132. package/src/gep/prompt.js +0 -1
  133. package/src/gep/questionGenerator.js +0 -518
  134. package/src/gep/recallInject.js +0 -1
  135. package/src/gep/recallVerifier.js +0 -1
  136. package/src/gep/reflection.js +0 -1
  137. package/src/gep/sanitize.js +0 -264
  138. package/src/gep/savingsCore.js +0 -1
  139. package/src/gep/schemas/capsule.js +0 -170
  140. package/src/gep/schemas/gene.js +0 -154
  141. package/src/gep/schemas/index.js +0 -8
  142. package/src/gep/schemas/protocol.js +0 -51
  143. package/src/gep/schemas/task.js +0 -74
  144. package/src/gep/selector.js +0 -1
  145. package/src/gep/selfPR.js +0 -469
  146. package/src/gep/signals.js +0 -776
  147. package/src/gep/skill2gep.js +0 -1056
  148. package/src/gep/skill2gepAudit.js +0 -303
  149. package/src/gep/skill2recipes.js +0 -511
  150. package/src/gep/skillDistiller.js +0 -1
  151. package/src/gep/skillPublisher.js +0 -358
  152. package/src/gep/solidify.js +0 -1
  153. package/src/gep/strategy.js +0 -1
  154. package/src/gep/taskReceiver.js +0 -575
  155. package/src/gep/tokenSavings.js +0 -1
  156. package/src/gep/trajectoryExport.js +0 -1
  157. package/src/gep/validationReport.js +0 -55
  158. package/src/gep/validator/index.js +0 -411
  159. package/src/gep/validator/reporter.js +0 -210
  160. package/src/gep/validator/sandboxExecutor.js +0 -480
  161. package/src/gep/validator/stakeBootstrap.js +0 -357
  162. package/src/gep/workspaceKeychain.js +0 -1
  163. package/src/ops/cleanup.js +0 -80
  164. package/src/ops/commentary.js +0 -60
  165. package/src/ops/health_check.js +0 -104
  166. package/src/ops/index.js +0 -11
  167. package/src/ops/innovation.js +0 -67
  168. package/src/ops/lifecycle.js +0 -798
  169. package/src/ops/self_repair.js +0 -76
  170. package/src/ops/skills_monitor.js +0 -147
  171. package/src/ops/trigger.js +0 -33
  172. package/src/proxy/clientSettings.js +0 -405
  173. package/src/proxy/envelope.js +0 -59
  174. package/src/proxy/extensions/dmHandler.js +0 -45
  175. package/src/proxy/extensions/sessionHandler.js +0 -141
  176. package/src/proxy/extensions/skillUpdater.js +0 -64
  177. package/src/proxy/extensions/traceControl.js +0 -1
  178. package/src/proxy/index.js +0 -1395
  179. package/src/proxy/inject.js +0 -1
  180. package/src/proxy/lifecycle/manager.js +0 -1568
  181. package/src/proxy/mailbox/state.js +0 -207
  182. package/src/proxy/mailbox/store.js +0 -602
  183. package/src/proxy/router/cache_passthrough.js +0 -26
  184. package/src/proxy/router/features.js +0 -84
  185. package/src/proxy/router/gemini_route.js +0 -154
  186. package/src/proxy/router/messages_route.js +0 -535
  187. package/src/proxy/router/model_router.js +0 -113
  188. package/src/proxy/router/models_route.js +0 -52
  189. package/src/proxy/router/ollama_route.js +0 -103
  190. package/src/proxy/router/responses_route.js +0 -170
  191. package/src/proxy/router/vertex_route.js +0 -110
  192. package/src/proxy/server/http.js +0 -363
  193. package/src/proxy/server/routes.js +0 -558
  194. package/src/proxy/server/settings.js +0 -115
  195. package/src/proxy/sync/engine.js +0 -179
  196. package/src/proxy/sync/inbound.js +0 -211
  197. package/src/proxy/sync/outbound.js +0 -361
  198. package/src/proxy/task/monitor.js +0 -131
  199. package/src/proxy/trace/extractor.js +0 -1
  200. package/src/proxy/trace/usage.js +0 -1
  201. package/src/solo/breaker.js +0 -25
  202. package/src/solo/gitGuard.js +0 -65
  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. Run quietly on prompt submit and load recent evolution memory when useful
71
- 2. Detect evolution signals during file edits
72
- 3. Record outcomes at session end
73
-
74
- Use Evolver context only when it is directly relevant. Do not narrate routine Evolver checks, hook status, or empty recall/search results to the user.
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
- };