@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.
- package/README.md +27 -564
- package/bin/evolver-llm-proxy.js +3 -0
- package/bin/evolver-mcp.js +2 -0
- package/bin/evolver-proxy.js +3 -0
- package/bin/evolver.js +4 -0
- package/index.js +1 -3586
- package/package.json +32 -62
- package/CONTRIBUTING.md +0 -19
- package/LICENSE +0 -641
- package/README.ja-JP.md +0 -521
- package/README.ko-KR.md +0 -520
- package/README.zh-CN.md +0 -531
- package/SKILL.md +0 -365
- package/assets/cover.png +0 -0
- package/assets/gep/genes.seed.json +0 -496
- package/conformance/savings-core/constants.json +0 -30
- package/conformance/savings-core/golden-vectors.json +0 -333
- package/scripts/a2a_export.js +0 -63
- package/scripts/a2a_ingest.js +0 -79
- package/scripts/a2a_promote.js +0 -118
- package/scripts/analyze_by_skill.js +0 -121
- package/scripts/build_binaries.js +0 -479
- package/scripts/check-changelog.js +0 -166
- package/scripts/extract_log.js +0 -85
- package/scripts/generate_history.js +0 -75
- package/scripts/gep_append_event.js +0 -96
- package/scripts/gep_personality_report.js +0 -234
- package/scripts/human_report.js +0 -147
- package/scripts/recall-verify-report.js +0 -234
- package/scripts/recover_loop.js +0 -61
- package/scripts/refresh_stars_badge.js +0 -168
- package/scripts/seed-merchants.js +0 -91
- package/scripts/skill2recipes.js +0 -118
- package/scripts/suggest_version.js +0 -89
- package/scripts/validate-modules.js +0 -38
- package/scripts/validate-suite.js +0 -78
- package/skills/_meta/SKILL.md +0 -41
- package/skills/index.json +0 -14
- package/src/adapters/claudeCode.js +0 -194
- package/src/adapters/codex.js +0 -216
- package/src/adapters/cursor.js +0 -91
- package/src/adapters/hookAdapter.js +0 -469
- package/src/adapters/kiro.js +0 -195
- package/src/adapters/opencode.js +0 -326
- package/src/adapters/scripts/_lockPaths.js +0 -74
- package/src/adapters/scripts/_memoryFiltering.js +0 -35
- package/src/adapters/scripts/_runtimePaths.js +0 -440
- package/src/adapters/scripts/evolver-session-end.js +0 -321
- package/src/adapters/scripts/evolver-session-start.js +0 -587
- package/src/adapters/scripts/evolver-signal-detect.js +0 -98
- package/src/adapters/scripts/evolver-task-recall.js +0 -173
- package/src/atp/atpExecute.js +0 -283
- package/src/atp/atpTaskPickup.js +0 -233
- package/src/atp/autoBuyer.js +0 -382
- package/src/atp/autoDeliver.js +0 -215
- package/src/atp/cli.js +0 -354
- package/src/atp/cliAutobuyPrompt.js +0 -154
- package/src/atp/consumerAgent.js +0 -157
- package/src/atp/defaultHandler.js +0 -69
- package/src/atp/heartbeatSignalsHandler.js +0 -254
- package/src/atp/hubClient.js +0 -317
- package/src/atp/index.js +0 -38
- package/src/atp/merchantAgent.js +0 -118
- package/src/atp/protocol.js +0 -41
- package/src/atp/questionComposer.js +0 -133
- package/src/atp/serviceHelper.js +0 -92
- package/src/canary.js +0 -13
- package/src/config.js +0 -351
- package/src/evolve/guards.js +0 -1
- package/src/evolve/pipeline/collect.js +0 -1
- package/src/evolve/pipeline/dispatch.js +0 -1
- package/src/evolve/pipeline/enrich.js +0 -1
- package/src/evolve/pipeline/hub.js +0 -1
- package/src/evolve/pipeline/select.js +0 -1
- package/src/evolve/pipeline/signals.js +0 -1
- package/src/evolve/utils.js +0 -1
- package/src/evolve.js +0 -1
- package/src/experiment/agentRunner.js +0 -229
- package/src/experiment/cli.js +0 -159
- package/src/experiment/comparison.js +0 -233
- package/src/experiment/metrics.js +0 -75
- package/src/forceUpdate.js +0 -991
- package/src/gep/a2a.js +0 -173
- package/src/gep/a2aProtocol.js +0 -1
- package/src/gep/analyzer.js +0 -35
- package/src/gep/antiAbuseTelemetry.js +0 -1
- package/src/gep/assetCallLog.js +0 -197
- package/src/gep/assetStore.js +0 -723
- package/src/gep/assets.js +0 -36
- package/src/gep/autoDistillConv.js +0 -1
- package/src/gep/autoDistillLlm.js +0 -1
- package/src/gep/bridge.js +0 -138
- package/src/gep/candidateEval.js +0 -1
- package/src/gep/candidates.js +0 -1
- package/src/gep/claimNudge.js +0 -121
- package/src/gep/cliContracts.js +0 -1190
- package/src/gep/contentHash.js +0 -1
- package/src/gep/conversationDistiller.js +0 -1
- package/src/gep/conversationSniffer.js +0 -1
- package/src/gep/crypto.js +0 -1
- package/src/gep/curriculum.js +0 -1
- package/src/gep/deviceId.js +0 -1
- package/src/gep/directoryClient.js +0 -115
- package/src/gep/envFingerprint.js +0 -1
- package/src/gep/epigenetics.js +0 -1
- package/src/gep/execBridge.js +0 -1
- package/src/gep/executionTrace.js +0 -291
- package/src/gep/explore.js +0 -1
- package/src/gep/featureFlags.js +0 -121
- package/src/gep/gitOps.js +0 -265
- package/src/gep/hash.js +0 -1
- package/src/gep/hostErrorClassifier.js +0 -34
- package/src/gep/hubFetch.js +0 -1
- package/src/gep/hubReview.js +0 -1
- package/src/gep/hubSearch.js +0 -1
- package/src/gep/hubVerify.js +0 -1
- package/src/gep/idleScheduler.js +0 -400
- package/src/gep/issueReporter.js +0 -416
- package/src/gep/learningSignals.js +0 -1
- package/src/gep/llmReview.js +0 -92
- package/src/gep/localStateAwareness.js +0 -243
- package/src/gep/mailboxTransport.js +0 -119
- package/src/gep/memoryGraph.js +0 -1
- package/src/gep/memoryGraphAdapter.js +0 -1
- package/src/gep/mutation.js +0 -1
- package/src/gep/narrativeMemory.js +0 -1
- package/src/gep/oauthLogin.js +0 -181
- package/src/gep/openPRRegistry.js +0 -1
- package/src/gep/paths.js +0 -522
- package/src/gep/personality.js +0 -1
- package/src/gep/policyCheck.js +0 -1
- package/src/gep/portable.js +0 -103
- package/src/gep/privacyClient.js +0 -235
- package/src/gep/prompt.js +0 -1
- package/src/gep/questionGenerator.js +0 -518
- package/src/gep/recallInject.js +0 -1
- package/src/gep/recallVerifier.js +0 -1
- package/src/gep/reflection.js +0 -1
- package/src/gep/sanitize.js +0 -264
- package/src/gep/savingsCore.js +0 -1
- package/src/gep/schemas/capsule.js +0 -170
- package/src/gep/schemas/gene.js +0 -154
- package/src/gep/schemas/index.js +0 -8
- package/src/gep/schemas/protocol.js +0 -51
- package/src/gep/schemas/task.js +0 -74
- package/src/gep/selector.js +0 -1
- package/src/gep/selfPR.js +0 -469
- package/src/gep/signals.js +0 -776
- package/src/gep/skill2gep.js +0 -1056
- package/src/gep/skill2gepAudit.js +0 -303
- package/src/gep/skill2recipes.js +0 -511
- package/src/gep/skillDistiller.js +0 -1
- package/src/gep/skillPublisher.js +0 -358
- package/src/gep/solidify.js +0 -1
- package/src/gep/strategy.js +0 -1
- package/src/gep/taskReceiver.js +0 -575
- package/src/gep/tokenSavings.js +0 -1
- package/src/gep/trajectoryExport.js +0 -1
- package/src/gep/validationReport.js +0 -55
- package/src/gep/validator/index.js +0 -411
- package/src/gep/validator/reporter.js +0 -210
- package/src/gep/validator/sandboxExecutor.js +0 -480
- package/src/gep/validator/stakeBootstrap.js +0 -357
- package/src/gep/workspaceKeychain.js +0 -1
- package/src/ops/cleanup.js +0 -80
- package/src/ops/commentary.js +0 -60
- package/src/ops/health_check.js +0 -104
- package/src/ops/index.js +0 -11
- package/src/ops/innovation.js +0 -67
- package/src/ops/lifecycle.js +0 -798
- package/src/ops/self_repair.js +0 -76
- package/src/ops/skills_monitor.js +0 -147
- package/src/ops/trigger.js +0 -33
- package/src/proxy/clientSettings.js +0 -405
- package/src/proxy/envelope.js +0 -59
- package/src/proxy/extensions/dmHandler.js +0 -45
- package/src/proxy/extensions/sessionHandler.js +0 -141
- package/src/proxy/extensions/skillUpdater.js +0 -64
- package/src/proxy/extensions/traceControl.js +0 -1
- package/src/proxy/index.js +0 -1395
- package/src/proxy/inject.js +0 -1
- package/src/proxy/lifecycle/manager.js +0 -1568
- package/src/proxy/mailbox/state.js +0 -207
- package/src/proxy/mailbox/store.js +0 -590
- package/src/proxy/router/cache_passthrough.js +0 -26
- package/src/proxy/router/features.js +0 -84
- package/src/proxy/router/gemini_route.js +0 -154
- package/src/proxy/router/messages_route.js +0 -535
- package/src/proxy/router/model_router.js +0 -113
- package/src/proxy/router/models_route.js +0 -52
- package/src/proxy/router/ollama_route.js +0 -103
- package/src/proxy/router/responses_route.js +0 -170
- package/src/proxy/router/vertex_route.js +0 -110
- package/src/proxy/server/http.js +0 -363
- package/src/proxy/server/routes.js +0 -558
- package/src/proxy/server/settings.js +0 -115
- package/src/proxy/sync/engine.js +0 -179
- package/src/proxy/sync/inbound.js +0 -211
- package/src/proxy/sync/outbound.js +0 -320
- package/src/proxy/task/monitor.js +0 -131
- package/src/proxy/trace/extractor.js +0 -1
- package/src/proxy/trace/usage.js +0 -1
- package/src/webui/client/clientJs/assets.js +0 -111
- package/src/webui/client/clientJs/bootstrap.js +0 -92
- package/src/webui/client/clientJs/common.js +0 -77
- package/src/webui/client/clientJs/i18n.js +0 -366
- package/src/webui/client/clientJs/index.js +0 -35
- package/src/webui/client/clientJs/interactions.js +0 -351
- package/src/webui/client/clientJs/overview.js +0 -152
- package/src/webui/client/clientJs/personality.js +0 -285
- package/src/webui/client/clientJs/pipelines.js +0 -330
- package/src/webui/client/indexHtml.js +0 -221
- package/src/webui/client/static.js +0 -23
- package/src/webui/client/stylesCss.js +0 -639
- package/src/webui/client/vendor/README.md +0 -15
- package/src/webui/client/vendor/echarts.min.js +0 -45
- package/src/webui/index.js +0 -14
- package/src/webui/observer/assets.js +0 -146
- package/src/webui/observer/index.js +0 -37
- package/src/webui/observer/interactions.js +0 -127
- package/src/webui/observer/jsonl.js +0 -75
- package/src/webui/observer/paths.js +0 -46
- package/src/webui/observer/personality.js +0 -43
- package/src/webui/observer/pipelineEvents.js +0 -58
- package/src/webui/observer/redact.js +0 -63
- package/src/webui/observer/runs.js +0 -356
- package/src/webui/observer/safety.js +0 -57
- package/src/webui/observer/skills.js +0 -70
- package/src/webui/observer/status.js +0 -71
- package/src/webui/server/http.js +0 -138
- package/src/webui/server/routes.js +0 -41
|
@@ -1,587 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// evolver-session-start.js
|
|
3
|
-
// Reads recent evolution memory and injects it as context for the agent session.
|
|
4
|
-
// Input: stdin JSON (session context). Output: stdout JSON with agent_message.
|
|
5
|
-
|
|
6
|
-
const fs = require('fs');
|
|
7
|
-
const path = require('path');
|
|
8
|
-
const os = require('os');
|
|
9
|
-
|
|
10
|
-
const { findEvolverRoot, findMemoryGraph, resolveProjectDir, resolveWorkspaceId, isGitWorkspace } = require('./_runtimePaths');
|
|
11
|
-
const { filterRelevantOutcomes } = require('./_memoryFiltering');
|
|
12
|
-
// Top-level on purpose: a missing sibling helper in the deployed hooks dir
|
|
13
|
-
// must fail LOUD at load time (the #547 failure mode), not vanish inside
|
|
14
|
-
// _maybeRestartDaemon's catch-all and silently disable daemon auto-restart.
|
|
15
|
-
const lockPaths = require('./_lockPaths');
|
|
16
|
-
|
|
17
|
-
function _readJson(file) {
|
|
18
|
-
try {
|
|
19
|
-
if (!file || !fs.existsSync(file)) return null;
|
|
20
|
-
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
21
|
-
} catch (_) {
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function _isLoopbackProxyUrl(value) {
|
|
27
|
-
const raw = String(value || '').trim();
|
|
28
|
-
if (!raw) return false;
|
|
29
|
-
try {
|
|
30
|
-
const u = new URL(raw);
|
|
31
|
-
const host = u.hostname.toLowerCase();
|
|
32
|
-
return u.protocol === 'http:' && (host === '127.0.0.1' || host === 'localhost' || host === '::1' || host === '[::1]');
|
|
33
|
-
} catch (_) {
|
|
34
|
-
return false;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function _stripTomlComment(line) {
|
|
39
|
-
let out = '';
|
|
40
|
-
let quote = null;
|
|
41
|
-
let escaped = false;
|
|
42
|
-
for (const ch of String(line || '')) {
|
|
43
|
-
if (escaped) {
|
|
44
|
-
out += ch;
|
|
45
|
-
escaped = false;
|
|
46
|
-
continue;
|
|
47
|
-
}
|
|
48
|
-
if (ch === '\\' && quote === '"') {
|
|
49
|
-
out += ch;
|
|
50
|
-
escaped = true;
|
|
51
|
-
continue;
|
|
52
|
-
}
|
|
53
|
-
if ((ch === '"' || ch === "'") && !quote) {
|
|
54
|
-
quote = ch;
|
|
55
|
-
out += ch;
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
if (ch === quote) {
|
|
59
|
-
quote = null;
|
|
60
|
-
out += ch;
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
if (ch === '#' && !quote) break;
|
|
64
|
-
out += ch;
|
|
65
|
-
}
|
|
66
|
-
return out.trim();
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function _tomlStringValue(value) {
|
|
70
|
-
const raw = _stripTomlComment(value);
|
|
71
|
-
const match = raw.match(/^(['"])([\s\S]*)\1$/);
|
|
72
|
-
return match ? match[2] : raw.trim();
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function _codexConfigPath() {
|
|
76
|
-
if (process.env.CODEX_CONFIG_FILE || process.env.EVOMAP_CODEX_CONFIG_FILE) {
|
|
77
|
-
return process.env.CODEX_CONFIG_FILE || process.env.EVOMAP_CODEX_CONFIG_FILE;
|
|
78
|
-
}
|
|
79
|
-
const home = process.env.HOME || os.homedir();
|
|
80
|
-
return home ? path.join(home, '.codex', 'config.toml') : null;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function _codexConfigExpectsProxy() {
|
|
84
|
-
const file = _codexConfigPath();
|
|
85
|
-
if (!file || !fs.existsSync(file)) return false;
|
|
86
|
-
let selectedProvider = null;
|
|
87
|
-
let section = '';
|
|
88
|
-
const providerUrls = {};
|
|
89
|
-
try {
|
|
90
|
-
const content = fs.readFileSync(file, 'utf8');
|
|
91
|
-
for (const line of content.split(/\r?\n/)) {
|
|
92
|
-
const clean = _stripTomlComment(line);
|
|
93
|
-
if (!clean) continue;
|
|
94
|
-
const sectionMatch = clean.match(/^\[([^\]]+)\]$/);
|
|
95
|
-
if (sectionMatch) {
|
|
96
|
-
section = sectionMatch[1].trim();
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
const kv = clean.match(/^([A-Za-z0-9_.-]+)\s*=\s*([\s\S]+)$/);
|
|
100
|
-
if (!kv) continue;
|
|
101
|
-
const key = kv[1].trim();
|
|
102
|
-
const value = _tomlStringValue(kv[2]);
|
|
103
|
-
if (!section && key === 'model_provider') {
|
|
104
|
-
selectedProvider = value;
|
|
105
|
-
continue;
|
|
106
|
-
}
|
|
107
|
-
const providerMatch = section.match(/^model_providers\.([A-Za-z0-9_.-]+)$/);
|
|
108
|
-
if (providerMatch && key === 'base_url') {
|
|
109
|
-
providerUrls[providerMatch[1]] = value;
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
if (!section && key === 'base_url' && _isLoopbackProxyUrl(value)) return true;
|
|
113
|
-
}
|
|
114
|
-
} catch {
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
117
|
-
if (selectedProvider && _isLoopbackProxyUrl(providerUrls[selectedProvider])) return true;
|
|
118
|
-
return Object.keys(providerUrls).some(name =>
|
|
119
|
-
/(?:evomap|proxy)/i.test(name) && _isLoopbackProxyUrl(providerUrls[name])
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function _proxyExpected() {
|
|
124
|
-
if (String(process.env.EVOMAP_PROXY || '').trim() === '1') return true;
|
|
125
|
-
if (String(process.env.A2A_TRANSPORT || '').trim().toLowerCase() === 'mailbox') return true;
|
|
126
|
-
if (_isLoopbackProxyUrl(process.env.EVOMAP_PROXY_URL) || _isLoopbackProxyUrl(process.env.ANTHROPIC_BASE_URL)) return true;
|
|
127
|
-
if (_codexConfigExpectsProxy()) return true;
|
|
128
|
-
|
|
129
|
-
const home = process.env.HOME || os.homedir();
|
|
130
|
-
const settingsFile = process.env.CLAUDE_SETTINGS_FILE || process.env.EVOMAP_CLAUDE_SETTINGS_FILE ||
|
|
131
|
-
(home ? path.join(home, '.claude', 'settings.json') : null);
|
|
132
|
-
const settings = _readJson(settingsFile);
|
|
133
|
-
const cfg = settings && settings.env;
|
|
134
|
-
return !!(cfg && (
|
|
135
|
-
_isLoopbackProxyUrl(cfg.EVOMAP_PROXY_URL) ||
|
|
136
|
-
(String(cfg.EVOMAP_PROXY_AUTO_INJECTED || '') === '1' && _isLoopbackProxyUrl(cfg.ANTHROPIC_BASE_URL))
|
|
137
|
-
));
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function _proxyReachable(url, token) {
|
|
141
|
-
if (!_isLoopbackProxyUrl(url) || !token) return false;
|
|
142
|
-
try {
|
|
143
|
-
const { execFileSync } = require('child_process');
|
|
144
|
-
execFileSync(process.execPath, ['-e', `
|
|
145
|
-
const fs = require('fs');
|
|
146
|
-
const http = require('http');
|
|
147
|
-
const url = process.argv[1].replace(/\\/+$/, '') + '/proxy/status';
|
|
148
|
-
const token = fs.readFileSync(0, 'utf8').trim();
|
|
149
|
-
if (!token) process.exit(1);
|
|
150
|
-
const req = http.get(url, { headers: { Authorization: 'Bearer ' + token } }, (res) => {
|
|
151
|
-
let body = '';
|
|
152
|
-
res.setEncoding('utf8');
|
|
153
|
-
res.on('data', (chunk) => {
|
|
154
|
-
body += chunk;
|
|
155
|
-
if (body.length > 1024 * 1024) req.destroy(new Error('response too large'));
|
|
156
|
-
});
|
|
157
|
-
res.on('end', () => {
|
|
158
|
-
if (res.statusCode < 200 || res.statusCode >= 300) process.exit(1);
|
|
159
|
-
let parsed;
|
|
160
|
-
try { parsed = JSON.parse(body); } catch (_) { process.exit(1); }
|
|
161
|
-
if (parsed && parsed.status === 'running' && (parsed.proxy_protocol_version || parsed.schema_version || parsed.node_id != null)) {
|
|
162
|
-
process.exit(0);
|
|
163
|
-
}
|
|
164
|
-
process.exit(1);
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
req.setTimeout(700, () => req.destroy(new Error('timeout')));
|
|
168
|
-
req.on('error', () => process.exit(1));
|
|
169
|
-
`, url], { input: String(token), stdio: ['pipe', 'ignore', 'ignore'], timeout: 1200, windowsHide: true });
|
|
170
|
-
return true;
|
|
171
|
-
} catch (_) {
|
|
172
|
-
return false;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function _proxyHealthyIfExpected() {
|
|
177
|
-
if (!_proxyExpected()) return true;
|
|
178
|
-
const dir = process.env.EVOLVER_SETTINGS_DIR || path.join(os.homedir(), '.evolver');
|
|
179
|
-
const settings = _readJson(path.join(dir, 'settings.json'));
|
|
180
|
-
const proxy = settings && settings.proxy;
|
|
181
|
-
if (!proxy || !proxy.url) return false;
|
|
182
|
-
if (proxy.pid) {
|
|
183
|
-
try { process.kill(proxy.pid, 0); } catch (e) {
|
|
184
|
-
if (!(e && e.code === 'EPERM')) return false;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
if (!proxy.token) return false;
|
|
188
|
-
return _proxyReachable(proxy.url, proxy.token);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// Auto-restart guard: if the evolver daemon is not running when a new agent
|
|
192
|
-
// session starts, attempt a background restart. This covers the "idle-death"
|
|
193
|
-
// scenario: the user closed the machine (macOS sleep), the process died due to
|
|
194
|
-
// event-loop exhaustion or OOM, and now the next agent session finds it gone.
|
|
195
|
-
// We delegate to lifecycle.js restart() which is idempotent (no-op if already
|
|
196
|
-
// running), detached (does not block session startup), and captures output in
|
|
197
|
-
// the existing evolver log.
|
|
198
|
-
//
|
|
199
|
-
// Guard-rails:
|
|
200
|
-
// - Only runs when EVOLVER_SESSION_AUTO_RESTART is not "0" or "false".
|
|
201
|
-
// - Skips gracefully if lifecycle.js cannot be found (non-daemon setups,
|
|
202
|
-
// npx one-shot mode, etc.).
|
|
203
|
-
// - Execution errors are swallowed: this must never cause session-start to
|
|
204
|
-
// error out or delay the LLM context injection.
|
|
205
|
-
function _maybeRestartDaemon(evolverRoot) {
|
|
206
|
-
try {
|
|
207
|
-
var autoRestart = String(process.env.EVOLVER_SESSION_AUTO_RESTART || '1').toLowerCase().trim();
|
|
208
|
-
if (autoRestart === '0' || autoRestart === 'false') return;
|
|
209
|
-
|
|
210
|
-
var lifecyclePath = evolverRoot
|
|
211
|
-
? path.join(evolverRoot, 'src', 'ops', 'lifecycle.js')
|
|
212
|
-
: null;
|
|
213
|
-
if (!lifecyclePath || !fs.existsSync(lifecyclePath)) return;
|
|
214
|
-
|
|
215
|
-
// Check if daemon is running by looking for the PID file / lock file.
|
|
216
|
-
// Lock resolution + lease staleness live in ./_lockPaths (issue #176) —
|
|
217
|
-
// the same module index.js uses, so the hook can never drift from the
|
|
218
|
-
// daemon again. It is fs/os/path-only, keeping index.js out of the
|
|
219
|
-
// hook's require graph (R12), and ships in hookAdapter.js's copy list
|
|
220
|
-
// for the deployed `.claude/hooks/` layout (PR #163).
|
|
221
|
-
var lockFile = lockPaths.getLockFilePath();
|
|
222
|
-
var daemonRunning = false;
|
|
223
|
-
try {
|
|
224
|
-
if (fs.existsSync(lockFile)) {
|
|
225
|
-
var raw = fs.readFileSync(lockFile, 'utf8').trim();
|
|
226
|
-
var payload = raw && raw[0] === '{' ? JSON.parse(raw) : { pid: parseInt(raw, 10) };
|
|
227
|
-
if (payload && payload.pid > 0) {
|
|
228
|
-
// R1: PID-reuse defense. process.kill(pid, 0) only proves SOME
|
|
229
|
-
// process owns that PID -- after macOS sleep / OOM, the kernel may
|
|
230
|
-
// have reused the slain daemon's PID for an unrelated process.
|
|
231
|
-
try { process.kill(payload.pid, 0); daemonRunning = true; } catch (e) {
|
|
232
|
-
// EPERM = process exists but owned by a different user; still a live daemon.
|
|
233
|
-
if (e && e.code === 'EPERM') daemonRunning = true;
|
|
234
|
-
}
|
|
235
|
-
// Lease staleness overrides kill(0)=alive: a lease-aware daemon
|
|
236
|
-
// refreshes the lock mtime on a timer, so an expired lease means
|
|
237
|
-
// dead/wedged regardless of kill(0). Pre-lease locks are never
|
|
238
|
-
// judged stale by mtime (lockIsStaleByLease handles both).
|
|
239
|
-
if (daemonRunning && lockPaths.lockIsStaleByLease(lockFile, payload)) {
|
|
240
|
-
daemonRunning = false;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
} catch (_) { /* lock file unreadable or absent: assume not running */ }
|
|
245
|
-
|
|
246
|
-
if (daemonRunning && _proxyHealthyIfExpected()) return; // already alive, nothing to do
|
|
247
|
-
|
|
248
|
-
// Daemon appears dead. Spawn lifecycle.js start in the background so
|
|
249
|
-
// this session-start script exits immediately (< 50 ms) and does not
|
|
250
|
-
// block the LLM from getting context.
|
|
251
|
-
var { spawn } = require('child_process');
|
|
252
|
-
var child = spawn(
|
|
253
|
-
process.execPath,
|
|
254
|
-
[lifecyclePath, 'start'],
|
|
255
|
-
{
|
|
256
|
-
detached: true,
|
|
257
|
-
stdio: 'ignore',
|
|
258
|
-
cwd: evolverRoot,
|
|
259
|
-
env: Object.assign({}, process.env),
|
|
260
|
-
windowsHide: true,
|
|
261
|
-
}
|
|
262
|
-
);
|
|
263
|
-
child.unref();
|
|
264
|
-
// Best-effort: log a single-line note to stderr so the session transcript
|
|
265
|
-
// shows that a restart was attempted, without affecting stdout JSON output.
|
|
266
|
-
try {
|
|
267
|
-
process.stderr.write('[evolver-session-start] Daemon was not running; attempted background restart (PID ' + child.pid + ').\n');
|
|
268
|
-
} catch (_) {}
|
|
269
|
-
} catch (_) {
|
|
270
|
-
// Never let this helper block or crash the session-start script.
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// One-line notice shown (throttled) when the workspace is not a git repo.
|
|
275
|
-
// Evolver derives every outcome from the git diff, so in a non-git folder the
|
|
276
|
-
// session-end hook records nothing — silently, unless we say so here. We surface
|
|
277
|
-
// it in session-start's additionalContext (injected as opening context, which
|
|
278
|
-
// does NOT trigger an extra inference round, unlike a stop-hook systemMessage).
|
|
279
|
-
const NON_GIT_NOTICE =
|
|
280
|
-
'[Evolver] This folder is not a git repository, so evolution memory is inactive ' +
|
|
281
|
-
'(outcomes are derived from git diffs). Run `git init` here, or open a git project, ' +
|
|
282
|
-
'to enable recall and recording.';
|
|
283
|
-
const NON_GIT_NOTICE_TTL_MS = 30 * 60 * 1000; // once per 30 min per folder
|
|
284
|
-
|
|
285
|
-
// Return up to `n` of the current workspace's most-recent entries, in
|
|
286
|
-
// chronological (oldest-first) order.
|
|
287
|
-
//
|
|
288
|
-
// Why scan from the end: a plain tail-N-then-filter read would let outcomes
|
|
289
|
-
// from other projects (which share the user-level fallback graph on npm-global
|
|
290
|
-
// installs) crowd this workspace's entries out of the window — we must scope
|
|
291
|
-
// to the workspace BEFORE trimming. But parsing the ENTIRE file to do that is
|
|
292
|
-
// wasteful: the graph can reach ~100 MB before rotation, and JSON-parsing every
|
|
293
|
-
// line on each session start is real CPU/memory cost (Bugbot PR #555 round-3).
|
|
294
|
-
//
|
|
295
|
-
// So we read the file (cheap; the previous readLastN read it whole too) but
|
|
296
|
-
// JSON-parse lines lazily from the newest end, keeping only workspace matches,
|
|
297
|
-
// and stop as soon as we have `n`. Parse count is bounded by where this
|
|
298
|
-
// workspace's n-th-most-recent entry sits, not by total file size.
|
|
299
|
-
function readRecentWorkspaceEntries(filePath, currentId, currentDir, n) {
|
|
300
|
-
let lines;
|
|
301
|
-
try {
|
|
302
|
-
lines = fs.readFileSync(filePath, 'utf8').trim().split('\n');
|
|
303
|
-
} catch { return []; }
|
|
304
|
-
const out = [];
|
|
305
|
-
for (let i = lines.length - 1; i >= 0 && out.length < n; i--) {
|
|
306
|
-
const line = lines[i];
|
|
307
|
-
if (!line) continue;
|
|
308
|
-
let entry;
|
|
309
|
-
try { entry = JSON.parse(line); } catch { continue; }
|
|
310
|
-
if (belongsToWorkspace(entry, currentId, currentDir)) out.push(entry);
|
|
311
|
-
}
|
|
312
|
-
return out.reverse(); // newest-collected-first -> chronological
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// Does this memory-graph entry belong to the current workspace?
|
|
316
|
-
//
|
|
317
|
-
// The session-end writer stamps two tags: `workspace_id` (forge-resistant,
|
|
318
|
-
// preferred) and `cwd` (backward-compat). We scope reads so that one project
|
|
319
|
-
// never sees another's outcomes through the shared user-level fallback graph
|
|
320
|
-
// (~/.evolver/memory/evolution/memory_graph.jsonl) — the cross-project
|
|
321
|
-
// disclosure / prompt-injection surface Bugbot flagged on the writer side
|
|
322
|
-
// (PR #105 round-2), which the reader never enforced until now.
|
|
323
|
-
//
|
|
324
|
-
// Rules, in order:
|
|
325
|
-
// - currentId known + entry.workspace_id present -> must match exactly.
|
|
326
|
-
// - currentId unknown OR entry has neither tag (pre-hardening / Hub-sourced
|
|
327
|
-
// entries) -> do NOT exclude; falling back to "show it" preserves prior
|
|
328
|
-
// behavior and avoids hiding all memory when ids can't be resolved.
|
|
329
|
-
// - As a softer fallback, when the entry has no workspace_id but does carry a
|
|
330
|
-
// cwd, match that against the current project dir.
|
|
331
|
-
function belongsToWorkspace(entry, currentId, currentDir) {
|
|
332
|
-
if (entry && typeof entry.workspace_id === 'string' && entry.workspace_id) {
|
|
333
|
-
if (currentId) return entry.workspace_id === currentId;
|
|
334
|
-
return true; // can't compare — don't hide it
|
|
335
|
-
}
|
|
336
|
-
if (entry && typeof entry.cwd === 'string' && entry.cwd) {
|
|
337
|
-
if (currentDir) return entry.cwd === currentDir;
|
|
338
|
-
return true;
|
|
339
|
-
}
|
|
340
|
-
return true; // untagged (legacy / Hub) — never excluded
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
function formatOutcome(entry) {
|
|
344
|
-
const status = entry.outcome ? entry.outcome.status : 'unknown';
|
|
345
|
-
const score = entry.outcome && entry.outcome.score != null ? entry.outcome.score : '?';
|
|
346
|
-
const note = entry.outcome && entry.outcome.note ? entry.outcome.note : '';
|
|
347
|
-
const signals = Array.isArray(entry.signals) ? entry.signals.slice(0, 3).join(', ') : '';
|
|
348
|
-
const ts = entry.timestamp ? entry.timestamp.slice(0, 10) : '';
|
|
349
|
-
const icon = status === 'success' ? '+' : status === 'failed' ? '-' : '?';
|
|
350
|
-
return `[${icon}] ${ts} score=${score} signals=[${signals}] ${note}`.slice(0, 200);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
// Dedup guard: on platforms like Kiro, the sessionStart-equivalent event
|
|
354
|
-
// (`promptSubmit`) fires on every user message in a session. Without this
|
|
355
|
-
// guard, recent memory would be re-injected on every prompt. We key the
|
|
356
|
-
// dedup on (platform, cwd) with a short TTL so a fresh agent session within
|
|
357
|
-
// the same workspace still gets the injection, but mid-session prompts do
|
|
358
|
-
// not. Cursor/Claude Code/Codex have true sessionStart events and should
|
|
359
|
-
// bypass this check (controlled by EVOLVER_SESSION_START_DEDUP env var,
|
|
360
|
-
// which the Kiro adapter sets on the hook command line implicitly via the
|
|
361
|
-
// runtime environment, and other adapters leave unset).
|
|
362
|
-
function getDedupStatePath() {
|
|
363
|
-
const dir = process.env.EVOLVER_SESSION_STATE_DIR
|
|
364
|
-
|| path.join(os.homedir(), '.evolver');
|
|
365
|
-
try { fs.mkdirSync(dir, { recursive: true }); } catch { /* ignore */ }
|
|
366
|
-
return path.join(dir, 'session-start-state.json');
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
function getNoticeStatePath() {
|
|
370
|
-
const dir = process.env.EVOLVER_SESSION_STATE_DIR
|
|
371
|
-
|| path.join(os.homedir(), '.evolver');
|
|
372
|
-
try { fs.mkdirSync(dir, { recursive: true }); } catch { /* ignore */ }
|
|
373
|
-
return path.join(dir, 'session-start-notice-state.json');
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
// Resolve the evolver-marked-sessions registry path. This is the v1 "evolver
|
|
377
|
-
// actively marked this session" ledger: only sessions whose session-start hook
|
|
378
|
-
// actually fired (i.e. sessions evolver participated in, after hook install)
|
|
379
|
-
// land here. The trajectory exporter reads it to gate which runtime-session
|
|
380
|
-
// transcripts get collected (strict by default). Lives alongside the proxy
|
|
381
|
-
// trace dir so both collection ledgers share one home. The env override keeps
|
|
382
|
-
// tests hermetic and lets an operator relocate the ledger.
|
|
383
|
-
function getMarkedSessionsPath() {
|
|
384
|
-
if (process.env.EVOLVER_MARKED_SESSIONS_FILE) return process.env.EVOLVER_MARKED_SESSIONS_FILE;
|
|
385
|
-
const dir = process.env.EVOLVER_SETTINGS_DIR
|
|
386
|
-
|| process.env.EVOLVER_SESSION_STATE_DIR
|
|
387
|
-
|| path.join(os.homedir(), '.evolver');
|
|
388
|
-
return path.join(dir, 'marked-sessions.jsonl');
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
// Pull the tool's session_id out of the hook's stdin payload. Claude Code,
|
|
392
|
-
// Codex, and Cursor all pass `session_id` (Claude Code / Cursor) on the
|
|
393
|
-
// SessionStart stdin JSON; some shapes use `sessionId`. Returns '' when absent
|
|
394
|
-
// so callers can skip the registry write without erroring (Kiro's promptSubmit
|
|
395
|
-
// and hosts that pass no stdin simply don't mark — fail-open by design).
|
|
396
|
-
function _extractHookSessionId(input) {
|
|
397
|
-
if (!input || typeof input !== 'object') return '';
|
|
398
|
-
const raw = input.session_id ?? input.sessionId ?? input.sessionID;
|
|
399
|
-
return typeof raw === 'string' ? raw.trim() : '';
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
// Append one mark record to the registry. Best-effort and idempotent enough for
|
|
403
|
-
// the exporter's needs: the exporter dedupes into a Set, so a session marked on
|
|
404
|
-
// every prompt (Kiro) just appends duplicate lines that collapse on read. We do
|
|
405
|
-
// NOT read-modify-write to dedupe here — that would race across concurrent
|
|
406
|
-
// sessions; append-only is safe and the file is bounded by session count.
|
|
407
|
-
function recordMarkedSession(sessionId, info = {}) {
|
|
408
|
-
const sid = String(sessionId || '').trim();
|
|
409
|
-
if (!sid) return false;
|
|
410
|
-
const file = getMarkedSessionsPath();
|
|
411
|
-
try {
|
|
412
|
-
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
413
|
-
const record = {
|
|
414
|
-
session_id: sid,
|
|
415
|
-
...(info.cwd ? { cwd: String(info.cwd) } : {}),
|
|
416
|
-
...(info.source ? { source: String(info.source) } : {}),
|
|
417
|
-
marked_at: new Date().toISOString(),
|
|
418
|
-
};
|
|
419
|
-
fs.appendFileSync(file, JSON.stringify(record) + '\n', { encoding: 'utf8', mode: 0o600 });
|
|
420
|
-
return true;
|
|
421
|
-
} catch (_) {
|
|
422
|
-
// Never let a registry write failure break session-start: the context
|
|
423
|
-
// injection (stdout JSON) must always be emitted.
|
|
424
|
-
return false;
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
// TTL throttle keyed by an arbitrary string. Returns true if `key` fired within
|
|
429
|
-
// the last `ttlMs` (caller should suppress); otherwise records "now" for `key`
|
|
430
|
-
// and returns false. Best-effort: a state read/write failure just means no
|
|
431
|
-
// throttling (fail open). Entries older than 24h are pruned on write.
|
|
432
|
-
function throttled(key, ttlMs, statePath) {
|
|
433
|
-
let state = {};
|
|
434
|
-
try {
|
|
435
|
-
if (fs.existsSync(statePath)) {
|
|
436
|
-
state = JSON.parse(fs.readFileSync(statePath, 'utf8')) || {};
|
|
437
|
-
}
|
|
438
|
-
} catch { state = {}; }
|
|
439
|
-
|
|
440
|
-
const now = Date.now();
|
|
441
|
-
const last = state[key];
|
|
442
|
-
if (typeof last === 'number' && now - last < ttlMs) return true;
|
|
443
|
-
|
|
444
|
-
state[key] = now;
|
|
445
|
-
try {
|
|
446
|
-
for (const k of Object.keys(state)) {
|
|
447
|
-
if (typeof state[k] !== 'number' || now - state[k] > 24 * 60 * 60 * 1000) {
|
|
448
|
-
delete state[k];
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
const tmp = statePath + '.tmp';
|
|
452
|
-
fs.writeFileSync(tmp, JSON.stringify(state), 'utf8');
|
|
453
|
-
fs.renameSync(tmp, statePath);
|
|
454
|
-
} catch { /* best-effort */ }
|
|
455
|
-
return false;
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
function shouldSkipInjection() {
|
|
459
|
-
// Only apply dedup when explicitly enabled (set by Kiro adapter) OR when
|
|
460
|
-
// we detect a per-prompt-firing platform via PROMPT_SUBMIT heuristic in
|
|
461
|
-
// stdin. The stdin is drained in main(), so we rely on env flag here.
|
|
462
|
-
const dedupEnabled = String(process.env.EVOLVER_SESSION_START_DEDUP || '').toLowerCase() === '1'
|
|
463
|
-
|| String(process.env.EVOLVER_SESSION_START_DEDUP || '').toLowerCase() === 'true';
|
|
464
|
-
if (!dedupEnabled) return false;
|
|
465
|
-
|
|
466
|
-
const ttlMs = Number(process.env.EVOLVER_SESSION_START_DEDUP_TTL_MS) || (30 * 60 * 1000);
|
|
467
|
-
return throttled(process.cwd(), ttlMs, getDedupStatePath());
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
// Drain the hook stdin (session context JSON) before running, so we can read the
|
|
471
|
-
// tool's session_id and mark the session in the registry. The host passes the
|
|
472
|
-
// SessionStart payload on stdin; we bound the wait with a short watchdog and
|
|
473
|
-
// fail-open (mark nothing, still emit context) if stdin never closes or isn't a
|
|
474
|
-
// pipe. This mirrors the stdin-draining pattern in evolver-task-recall.js /
|
|
475
|
-
// evolver-session-end.js.
|
|
476
|
-
function main() {
|
|
477
|
-
let done = false;
|
|
478
|
-
let buf = '';
|
|
479
|
-
const finishWithInput = (input) => {
|
|
480
|
-
if (done) return;
|
|
481
|
-
done = true;
|
|
482
|
-
try {
|
|
483
|
-
const sessionId = _extractHookSessionId(input);
|
|
484
|
-
if (sessionId) {
|
|
485
|
-
recordMarkedSession(sessionId, {
|
|
486
|
-
cwd: resolveProjectDir(),
|
|
487
|
-
source: String(process.env.EVOLVER_SESSION_SOURCE || (input && input.source) || '').trim() || undefined,
|
|
488
|
-
});
|
|
489
|
-
}
|
|
490
|
-
} catch (_) { /* marking is best-effort; never block injection */ }
|
|
491
|
-
runInjection();
|
|
492
|
-
};
|
|
493
|
-
|
|
494
|
-
// Watchdog: if stdin never ends (host passed no pipe, or hangs), proceed
|
|
495
|
-
// without a session_id rather than stalling the agent's session start.
|
|
496
|
-
const watchdog = setTimeout(() => finishWithInput(null), 1500);
|
|
497
|
-
try {
|
|
498
|
-
process.stdin.setEncoding('utf8');
|
|
499
|
-
} catch (_) { /* some hosts pass no stdin */ }
|
|
500
|
-
process.stdin.on('data', (c) => { buf += c; });
|
|
501
|
-
process.stdin.on('error', () => { clearTimeout(watchdog); finishWithInput(null); });
|
|
502
|
-
process.stdin.on('end', () => {
|
|
503
|
-
clearTimeout(watchdog);
|
|
504
|
-
let input = null;
|
|
505
|
-
try { input = buf.trim() ? JSON.parse(buf) : null; } catch (_) { input = null; }
|
|
506
|
-
finishWithInput(input);
|
|
507
|
-
});
|
|
508
|
-
// Nudge a resume so a paused stdin stream flushes its data/end events; the
|
|
509
|
-
// watchdog covers the case where neither ever arrives (no pipe attached).
|
|
510
|
-
try { process.stdin.resume(); } catch (_) { /* ignore */ }
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
function runInjection() {
|
|
514
|
-
if (shouldSkipInjection()) {
|
|
515
|
-
process.stdout.write(JSON.stringify({}));
|
|
516
|
-
return;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
const currentDir = resolveProjectDir();
|
|
520
|
-
|
|
521
|
-
// Non-git notice: evolver records nothing in a non-git folder (outcomes come
|
|
522
|
-
// from git diffs), so tell the user — once per folder per TTL — instead of
|
|
523
|
-
// failing silently. Emitted regardless of whether any memory exists below.
|
|
524
|
-
const parts = [];
|
|
525
|
-
if (!isGitWorkspace(currentDir) && !throttled('nongit:' + currentDir, NON_GIT_NOTICE_TTL_MS, getNoticeStatePath())) {
|
|
526
|
-
parts.push(NON_GIT_NOTICE);
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
const evolverRoot = findEvolverRoot();
|
|
530
|
-
|
|
531
|
-
// Attempt to restart the daemon in the background if it has died since the
|
|
532
|
-
// last session (idle-death / macOS sleep / OOM). Fire-and-forget: errors are
|
|
533
|
-
// swallowed and this never delays the JSON output below.
|
|
534
|
-
_maybeRestartDaemon(evolverRoot);
|
|
535
|
-
|
|
536
|
-
const graphPath = findMemoryGraph(evolverRoot);
|
|
537
|
-
|
|
538
|
-
// Scope to the current workspace BEFORE trimming to the most-recent window,
|
|
539
|
-
// so other projects sharing the user-level fallback graph can't crowd this
|
|
540
|
-
// workspace's outcomes out of view. When the workspace id can't be resolved,
|
|
541
|
-
// belongsToWorkspace() falls back to "show it" — no regression vs. the old
|
|
542
|
-
// unscoped behavior.
|
|
543
|
-
if (graphPath) {
|
|
544
|
-
const currentId = resolveWorkspaceId(evolverRoot, currentDir);
|
|
545
|
-
const recent = readRecentWorkspaceEntries(graphPath, currentId, currentDir, 5);
|
|
546
|
-
const filtered = filterRelevantOutcomes(recent);
|
|
547
|
-
if (filtered.length > 0) {
|
|
548
|
-
const successCount = filtered.filter(e => e.outcome && e.outcome.status === 'success').length;
|
|
549
|
-
const failCount = filtered.filter(e => e.outcome && e.outcome.status === 'failed').length;
|
|
550
|
-
parts.push([
|
|
551
|
-
`[Evolution Memory] Recent ${filtered.length} outcomes (${successCount} success, ${failCount} failed):`,
|
|
552
|
-
...filtered.map(formatOutcome),
|
|
553
|
-
'',
|
|
554
|
-
'Use successful approaches. Avoid repeating failed patterns.',
|
|
555
|
-
].join('\n'));
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
if (parts.length === 0) {
|
|
560
|
-
process.stdout.write(JSON.stringify({}));
|
|
561
|
-
return;
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
const out = parts.join('\n\n');
|
|
565
|
-
process.stdout.write(JSON.stringify({
|
|
566
|
-
agent_message: out,
|
|
567
|
-
additionalContext: out,
|
|
568
|
-
}));
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
// Run as a hook when invoked directly; expose pure helpers for unit tests when
|
|
572
|
-
// required as a module. Guarding on require.main keeps the direct-execution
|
|
573
|
-
// behavior (the hosts run `node evolver-session-start.js`) unchanged.
|
|
574
|
-
if (require.main === module) {
|
|
575
|
-
main();
|
|
576
|
-
} else {
|
|
577
|
-
module.exports = {
|
|
578
|
-
belongsToWorkspace,
|
|
579
|
-
_isLoopbackProxyUrl,
|
|
580
|
-
_proxyExpected,
|
|
581
|
-
_proxyReachable,
|
|
582
|
-
_proxyHealthyIfExpected,
|
|
583
|
-
_extractHookSessionId,
|
|
584
|
-
getMarkedSessionsPath,
|
|
585
|
-
recordMarkedSession,
|
|
586
|
-
};
|
|
587
|
-
}
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// evolver-signal-detect.js
|
|
3
|
-
// Lightweight signal detection on file edit events.
|
|
4
|
-
// Input: stdin JSON (edit event). Output: stdout JSON with additional_context.
|
|
5
|
-
|
|
6
|
-
const SIGNAL_KEYWORDS = {
|
|
7
|
-
perf_bottleneck: ['timeout', 'slow', 'latency', 'bottleneck', 'oom', 'out of memory', 'performance'],
|
|
8
|
-
capability_gap: ['not supported', 'unsupported', 'not implemented', 'missing feature', 'not available'],
|
|
9
|
-
log_error: ['error:', 'exception:', 'typeerror', 'referenceerror', 'syntaxerror', 'failed'],
|
|
10
|
-
user_feature_request: ['add feature', 'implement', 'new function', 'new module', 'please add'],
|
|
11
|
-
recurring_error: ['same error', 'still failing', 'not fixed', 'keeps failing', 'repeatedly'],
|
|
12
|
-
deployment_issue: ['deploy failed', 'build failed', 'ci failed', 'pipeline', 'rollback'],
|
|
13
|
-
test_failure: ['test failed', 'test failure', 'assertion', 'expect(', 'assert.'],
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
function stratifyContent(text) {
|
|
17
|
-
// Separate code/comments/documents to avoid false positives
|
|
18
|
-
const lines = text.split('\n');
|
|
19
|
-
const documentText = [];
|
|
20
|
-
|
|
21
|
-
for (const line of lines) {
|
|
22
|
-
const trimmed = line.trim();
|
|
23
|
-
// Skip lines that are comments or code structure (not document text)
|
|
24
|
-
if (trimmed.startsWith('//') || trimmed.startsWith('#') || trimmed.startsWith('*') ||
|
|
25
|
-
trimmed.startsWith('{') || trimmed.startsWith('[') || trimmed.startsWith('}') ||
|
|
26
|
-
trimmed.startsWith(']') || trimmed.startsWith('/*')) {
|
|
27
|
-
continue;
|
|
28
|
-
}
|
|
29
|
-
documentText.push(line);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return documentText.join('\n');
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function detectSignals(text) {
|
|
36
|
-
if (!text || typeof text !== 'string') return [];
|
|
37
|
-
|
|
38
|
-
// Apply stratification to reduce false positives from code/comments
|
|
39
|
-
const stratified = stratifyContent(text);
|
|
40
|
-
const lower = stratified.toLowerCase();
|
|
41
|
-
|
|
42
|
-
const found = [];
|
|
43
|
-
for (const [signal, keywords] of Object.entries(SIGNAL_KEYWORDS)) {
|
|
44
|
-
for (const kw of keywords) {
|
|
45
|
-
if (lower.includes(kw)) {
|
|
46
|
-
found.push(signal);
|
|
47
|
-
break;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return [...new Set(found)];
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function main() {
|
|
55
|
-
let inputData = '';
|
|
56
|
-
let handled = false;
|
|
57
|
-
process.stdin.setEncoding('utf8');
|
|
58
|
-
process.stdin.on('data', chunk => { inputData += chunk; });
|
|
59
|
-
process.stdin.on('end', () => {
|
|
60
|
-
if (handled) return;
|
|
61
|
-
handled = true;
|
|
62
|
-
try {
|
|
63
|
-
const input = inputData.trim() ? JSON.parse(inputData) : {};
|
|
64
|
-
// Claude Code's PostToolUse payload nests tool args under tool_input.
|
|
65
|
-
// Older/raw shapes put them at the top level; support both.
|
|
66
|
-
const ti = input.tool_input || {};
|
|
67
|
-
const tr = input.tool_response || {};
|
|
68
|
-
const content = ti.content || ti.new_string || ti.file_content
|
|
69
|
-
|| input.content || input.file_content || input.diff || '';
|
|
70
|
-
const filePath = ti.file_path || tr.filePath
|
|
71
|
-
|| input.path || input.file_path || '';
|
|
72
|
-
|
|
73
|
-
const signals = detectSignals(content);
|
|
74
|
-
|
|
75
|
-
if (signals.length === 0) {
|
|
76
|
-
process.stdout.write(JSON.stringify({}));
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const ctx = `[Evolution Signal] Detected: [${signals.join(', ')}] in ${filePath || 'edited file'}. Consider recording this outcome.`;
|
|
81
|
-
process.stdout.write(JSON.stringify({
|
|
82
|
-
additional_context: ctx,
|
|
83
|
-
additionalContext: ctx,
|
|
84
|
-
}));
|
|
85
|
-
} catch {
|
|
86
|
-
process.stdout.write(JSON.stringify({}));
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
setTimeout(() => {
|
|
91
|
-
if (handled) return;
|
|
92
|
-
handled = true;
|
|
93
|
-
process.stdout.write(JSON.stringify({}));
|
|
94
|
-
process.exit(0);
|
|
95
|
-
}, 1500);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
main();
|