obol-ai 0.3.8 → 0.3.10

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/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 0.3.10
2
+ - add CLI commands for evolve and curiosity, verbose logging
3
+ - fix pattern analysis: enforce factual observations, use incrementObservation
4
+
1
5
  ## 0.3.8
2
6
  - override haiku to sonnet when recent history has tool use
3
7
 
package/bin/obol.js CHANGED
@@ -94,4 +94,20 @@ program
94
94
  await reauth();
95
95
  });
96
96
 
97
+ program
98
+ .command('evolve [userId]')
99
+ .description('Trigger an evolution cycle manually')
100
+ .action(async (userId) => {
101
+ const { evolve } = require('../src/cli/evolve');
102
+ await evolve({ userId: userId ? parseInt(userId) : undefined });
103
+ });
104
+
105
+ program
106
+ .command('curiosity [userId]')
107
+ .description('Trigger a curiosity cycle manually')
108
+ .action(async (userId) => {
109
+ const { curiosity } = require('../src/cli/curiosity');
110
+ await curiosity({ userId: userId ? parseInt(userId) : undefined });
111
+ });
112
+
97
113
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "obol-ai",
3
- "version": "0.3.8",
3
+ "version": "0.3.10",
4
4
  "description": "Self-evolving AI assistant that learns, remembers, and acts on its own. Persistent vector memory, self-rewriting personality, proactive heartbeats.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -10,6 +10,10 @@
10
10
  "start": "node src/index.js",
11
11
  "test": "vitest run",
12
12
  "test:watch": "vitest",
13
+ "evolve": "node bin/obol.js evolve",
14
+ "curiosity": "node bin/obol.js curiosity",
15
+ "bench": "node src/cli/prompt-bench.js",
16
+ "debug": "node src/cli/prompt-debug.js",
13
17
  "prepublishOnly": "node src/cli/changelog.js",
14
18
  "postinstall": "pip3 install faster-whisper 2>/dev/null || pip install faster-whisper 2>/dev/null || echo 'Note: faster-whisper not installed (Python/pip unavailable). Voice transcription will be disabled.'"
15
19
  },
package/src/analysis.js CHANGED
@@ -124,10 +124,10 @@ async function structureReport(client, report, scheduler, patterns, chatId, time
124
124
  items: {
125
125
  type: 'object',
126
126
  properties: {
127
- key: { type: 'string', description: 'Stable identifier e.g. "timing.active_hours"' },
127
+ key: { type: 'string', description: 'Stable dot-notation identifier for this pattern, e.g. "timing.active_hours", "mood.stress_signals", "humor.style"' },
128
128
  dimension: { type: 'string', enum: ['timing', 'mood', 'humor', 'engagement', 'communication', 'topics'] },
129
- summary: { type: 'string', description: 'Human-readable statement e.g. "Usually active 7-10pm"' },
130
- data: { type: 'object', description: 'Structured supporting data' },
129
+ summary: { type: 'string', description: 'Factual observation about the user, e.g. "Most active between 7-10pm on weekdays", "Uses sarcasm and dry humor when relaxed"' },
130
+ data: { type: 'object', description: 'Factual evidence only. Examples: {"peak_hours":["19:00-22:00"],"peak_days":["mon","wed","fri"]} or {"preferred_topics":["crypto","music"]} or {"avg_message_length":"short","uses_caps":false}. Never put notes, commentary, or meta-analysis here.' },
131
131
  confidence: { type: 'number', description: '0-1' },
132
132
  },
133
133
  required: ['key', 'dimension', 'summary', 'confidence'],
@@ -139,9 +139,11 @@ async function structureReport(client, report, scheduler, patterns, chatId, time
139
139
  }];
140
140
 
141
141
  try {
142
+ const patternGuidance = `Extract behavioral patterns about this user from the report. Each pattern must be a factual observation about the user's behavior — not notes about your analysis process. If you see the same pattern in the existing list, reuse its exact key and update the summary/confidence. Skip patterns already at confidence >0.8 unless new evidence contradicts them.`;
143
+
142
144
  const system = formattedPatterns
143
- ? `Existing behavioral patterns for this user:\n${formattedPatterns}\n\n---\n\nConvert this analytical report into structured data using the save_analysis tool. Use existing patterns to calibrate confidence scores (higher if confirming, consider skipping if already well-established at >0.8). Flag contradictions in pattern data.`
144
- : 'Convert this analytical report into structured data using the save_analysis tool. Extract all follow-ups and patterns mentioned.';
145
+ ? `Existing behavioral patterns for this user:\n${formattedPatterns}\n\n---\n\n${patternGuidance}`
146
+ : `Convert this analytical report into structured data using the save_analysis tool. ${patternGuidance}`;
145
147
 
146
148
  const response = await client.messages.create({
147
149
  model: 'claude-sonnet-4-6',
@@ -169,8 +171,10 @@ async function structureReport(client, report, scheduler, patterns, chatId, time
169
171
 
170
172
  for (const p of patternList) {
171
173
  if (!p.key || !p.dimension || !p.summary) continue;
172
- await patterns.upsert(p.key, p.dimension, p.summary, p.data || {}, p.confidence || 0.5).catch(e =>
173
- console.error('[analysis] Failed to upsert pattern:', e.message)
174
+ const existing = await patterns.get(p.key).catch(() => null);
175
+ const save = existing ? patterns.incrementObservation : patterns.upsert;
176
+ await save(p.key, p.dimension, p.summary, p.data || {}, p.confidence || 0.5).catch(e =>
177
+ console.error('[analysis] Failed to save pattern:', e.message)
174
178
  );
175
179
  }
176
180
 
@@ -0,0 +1,62 @@
1
+ const { loadConfig, ensureUserDir } = require('../config');
2
+ const { createMemory } = require('../memory');
3
+ const { createSelfMemory } = require('../memory/self');
4
+ const { createPatterns } = require('../soul/patterns');
5
+ const { createAnthropicClient } = require('../claude/client');
6
+ const { runCuriosity } = require('../curiosity');
7
+
8
+ async function runCuriosityCli({ userId: userIdArg } = {}) {
9
+ process.env.OBOL_VERBOSE = '1';
10
+
11
+ const config = loadConfig({ resolve: false });
12
+
13
+ if (!config?.anthropic) {
14
+ console.error('Anthropic not configured. Run: obol init');
15
+ process.exit(1);
16
+ }
17
+
18
+ if (!config?.supabase) {
19
+ console.error('Supabase not configured. Run: obol init');
20
+ process.exit(1);
21
+ }
22
+
23
+ const allowedUsers = config.telegram?.allowedUsers || [];
24
+ const userId = userIdArg || allowedUsers[0];
25
+
26
+ if (!userId) {
27
+ console.error('No user ID found. Pass <userId> or configure telegram.allowedUsers');
28
+ process.exit(1);
29
+ }
30
+
31
+ const userDir = ensureUserDir(userId);
32
+
33
+ console.log(`Starting curiosity cycle for user ${userId}...`);
34
+
35
+ const client = createAnthropicClient(config.anthropic);
36
+ const selfMemory = await createSelfMemory(config.supabase, 0);
37
+ const memory = await createMemory(config.supabase, userId).catch(() => null);
38
+ const patterns = await createPatterns(config.supabase, userId).catch(() => null);
39
+
40
+ const parts = [];
41
+ if (memory) {
42
+ const recent = await memory.recent({ limit: 3 }).catch(() => []);
43
+ if (recent.length) parts.push(recent.map(m => `- ${m.content}`).join('\n'));
44
+ }
45
+ if (patterns) {
46
+ const fmt = await patterns.format().catch(() => null);
47
+ if (fmt) parts.push(fmt);
48
+ }
49
+ const peopleContext = parts.join('\n\n') || undefined;
50
+
51
+ try {
52
+ const result = await runCuriosity(client, selfMemory, userId, { memory, patterns, peopleContext, userDir });
53
+ console.log(`\nCuriosity cycle complete — stored ${result.count} things`);
54
+ } catch (e) {
55
+ console.error(`Curiosity cycle failed: ${e.message}`);
56
+ process.exit(1);
57
+ } finally {
58
+ delete process.env.OBOL_VERBOSE;
59
+ }
60
+ }
61
+
62
+ module.exports = { curiosity: runCuriosityCli };
@@ -0,0 +1,72 @@
1
+ const { loadConfig, ensureUserDir } = require('../config');
2
+ const { createMemory } = require('../memory');
3
+ const { createSelfMemory } = require('../memory/self');
4
+ const { createMessageLog } = require('../messages');
5
+ const { createAnthropicClient } = require('../claude/client');
6
+
7
+ async function runEvolve({ userId: userIdArg } = {}) {
8
+ process.env.OBOL_VERBOSE = '1';
9
+
10
+ const config = loadConfig({ resolve: false });
11
+
12
+ if (!config?.anthropic) {
13
+ console.error('Anthropic not configured. Run: obol init');
14
+ process.exit(1);
15
+ }
16
+
17
+ if (!config?.supabase) {
18
+ console.error('Supabase not configured. Run: obol init');
19
+ process.exit(1);
20
+ }
21
+
22
+ const allowedUsers = config.telegram?.allowedUsers || [];
23
+ const userId = userIdArg || allowedUsers[0];
24
+
25
+ if (!userId) {
26
+ console.error('No user ID found. Pass <userId> or configure telegram.allowedUsers');
27
+ process.exit(1);
28
+ }
29
+
30
+ const userDir = ensureUserDir(userId);
31
+
32
+ console.log(`Starting evolution for user ${userId}...`);
33
+
34
+ const client = createAnthropicClient(config.anthropic);
35
+ const memory = await createMemory(config.supabase, userId);
36
+ const selfMemory = await createSelfMemory(config.supabase, 0).catch(() => null);
37
+ const messageLog = createMessageLog(config.supabase, memory, config.anthropic, userId, userDir);
38
+
39
+ try {
40
+ const { evolve } = require('../evolve');
41
+ const result = await evolve(client, messageLog, memory, userDir, config.supabase, selfMemory);
42
+
43
+ console.log(`\nEvolution #${result.evolutionNumber} complete`);
44
+ console.log(` Soul: ${result.previousLength} → ${result.newLength} chars`);
45
+
46
+ if (result.scriptsFixed) console.log(' Scripts: fixed after test regression');
47
+ else if (result.scriptsRolledBack) console.log(' Scripts: rolled back (tests failed)');
48
+
49
+ if (result.upgrades?.length > 0) {
50
+ console.log(' New capabilities:');
51
+ for (const u of result.upgrades) {
52
+ console.log(` - ${u.name}: ${u.description}`);
53
+ }
54
+ }
55
+
56
+ if (result.deployedApps?.length > 0) {
57
+ console.log(' Deployed:');
58
+ for (const app of result.deployedApps) {
59
+ console.log(` - ${app.name}${app.url ? ` → ${app.url}` : ` (failed: ${app.error})`}`);
60
+ }
61
+ }
62
+
63
+ if (result.changelog) console.log(`\n ${result.changelog}`);
64
+ } catch (e) {
65
+ console.error(`Evolution failed: ${e.message}`);
66
+ process.exit(1);
67
+ } finally {
68
+ delete process.env.OBOL_VERBOSE;
69
+ }
70
+ }
71
+
72
+ module.exports = { evolve: runEvolve };
@@ -107,10 +107,12 @@ You can search your own memory to see what you already know before looking thing
107
107
  context ? `What you have access to:\n\n${context}` : null,
108
108
  ].filter(Boolean).join('\n\n');
109
109
 
110
+ const log = process.env.OBOL_VERBOSE ? (msg) => console.log(`[curiosity] ${msg}`) : () => {};
110
111
  const messages = [{ role: 'user', content: `What are you curious about right now?` }];
111
112
  let stored = 0;
112
113
 
113
114
  for (let i = 0; i < MAX_ITERATIONS; i++) {
115
+ log(`Iteration ${i + 1}/${MAX_ITERATIONS}...`);
114
116
  const response = await client.messages.create({
115
117
  model: RESEARCH_MODEL,
116
118
  max_tokens: 2000,
@@ -121,12 +123,24 @@ You can search your own memory to see what you already know before looking thing
121
123
 
122
124
  messages.push({ role: 'assistant', content: response.content });
123
125
 
124
- if (response.stop_reason === 'end_turn') break;
126
+ const textBlocks = response.content.filter(b => b.type === 'text').map(b => b.text).join(' ');
127
+ if (textBlocks) log(` Text: ${textBlocks.substring(0, 200)}`);
128
+ log(` Stop reason: ${response.stop_reason}`);
129
+
130
+ if (response.stop_reason === 'end_turn') {
131
+ if (stored === 0 && i < 3) {
132
+ log(' No saves yet — nudging to continue...');
133
+ messages.push({ role: 'user', content: 'Keep going — search the web, explore that thread, and save what you find with the remember tool. Don\'t just reflect, actually look things up.' });
134
+ continue;
135
+ }
136
+ break;
137
+ }
125
138
  if (response.stop_reason !== 'tool_use') break;
126
139
 
127
140
  const toolResults = [];
128
141
  for (const block of response.content) {
129
142
  if (block.type !== 'tool_use') continue;
143
+ log(` Tool: ${block.name}${block.name === 'remember' ? ` — ${block.input.content?.substring(0, 100)}` : block.name === 'web_search' ? ` — "${block.input.query}"` : block.name === 'knowledge_search' ? ` — "${block.input.query}"` : ''}`);
130
144
 
131
145
  if (block.name === 'remember') {
132
146
  try {
@@ -1,11 +1,11 @@
1
1
  async function backupSnapshot(message, userDir) {
2
2
  try {
3
3
  const { loadConfig } = require('../config');
4
- const cfg = loadConfig();
5
- if (cfg?.github) {
6
- const { runBackup } = require('../backup');
7
- await runBackup(cfg.github, message, userDir);
8
- }
4
+ const cfg = loadConfig({ resolve: false });
5
+ if (!cfg?.github?.token || !cfg?.github?.username || !cfg?.github?.repo) return;
6
+ const resolved = loadConfig();
7
+ const { runBackup } = require('../backup');
8
+ await runBackup(resolved.github, message, userDir);
9
9
  } catch {}
10
10
  }
11
11
 
@@ -45,7 +45,8 @@ async function fetchRecentMessages(messageLog, state) {
45
45
  `${messageLog.url}/rest/v1/obol_messages?order=created_at.asc&limit=500&select=role,content,created_at${userFilter}${sinceFilter}`,
46
46
  { headers: messageLog.headers }
47
47
  );
48
- return await res.json();
48
+ const data = await res.json();
49
+ return Array.isArray(data) ? data : [];
49
50
  } catch (e) {
50
51
  console.error('[evolve] Failed to fetch recent messages:', e.message);
51
52
  return [];
@@ -73,7 +74,8 @@ async function fetchMemories(memory, messageLog, state) {
73
74
  `${url}/rest/v1/obol_memory?select=content,category,importance&order=importance.desc,accessed_at.desc&limit=20${memUserFilter}`,
74
75
  { headers }
75
76
  );
76
- coreMemories = await res.json();
77
+ const coreData = await res.json();
78
+ coreMemories = Array.isArray(coreData) ? coreData : [];
77
79
  } catch (e) {
78
80
  console.error('[evolve] Failed to fetch core memories:', e.message);
79
81
  }
@@ -85,7 +87,8 @@ async function fetchMemories(memory, messageLog, state) {
85
87
  `${url}/rest/v1/obol_memory?select=content,category,importance,tags,created_at,source&order=created_at.asc&limit=100${memUserFilter}${sinceFilter}`,
86
88
  { headers }
87
89
  );
88
- recentMemories = await res.json();
90
+ const recentData = await res.json();
91
+ recentMemories = Array.isArray(recentData) ? recentData : [];
89
92
  } catch (e) {
90
93
  console.error('[evolve] Failed to fetch recent memories:', e.message);
91
94
  }
@@ -22,6 +22,8 @@ const MODELS = {
22
22
  const MAX_FIX_ATTEMPTS = 1;
23
23
 
24
24
  async function evolve(claudeClient, messageLog, memory, userDir, supabaseConfig = null, selfMemory = null) {
25
+ const log = process.env.OBOL_VERBOSE ? (msg) => console.log(`[evolve] ${msg}`) : () => {};
26
+
25
27
  const { PERSONALITY_DIR } = require('../soul');
26
28
  const baseDir = userDir || OBOL_DIR;
27
29
  const state = loadEvolutionState(userDir);
@@ -33,16 +35,20 @@ async function evolve(claudeClient, messageLog, memory, userDir, supabaseConfig
33
35
  const testsDir = path.join(baseDir, 'tests');
34
36
  const commandsDir = path.join(baseDir, 'commands');
35
37
 
38
+ log('Loading current personality files...');
36
39
  const currentSoul = fs.existsSync(soulPath) ? fs.readFileSync(soulPath, 'utf-8') : '';
37
40
  const currentUser = fs.existsSync(userPath) ? fs.readFileSync(userPath, 'utf-8') : '';
38
41
  const currentAgents = fs.existsSync(agentsPath) ? fs.readFileSync(agentsPath, 'utf-8') : '';
39
42
  const currentScripts = readDir(scriptsDir);
40
43
  const currentTests = readDir(testsDir);
41
44
  const currentCommands = readDir(commandsDir);
45
+ log(` Soul: ${currentSoul.length} chars, Scripts: ${Object.keys(currentScripts).length}, Tests: ${Object.keys(currentTests).length}, Commands: ${Object.keys(currentCommands).length}`);
42
46
 
47
+ log('Fetching messages and memories...');
43
48
  const recentMessages = await fetchRecentMessages(messageLog, state);
44
49
  const { coreMemories, recentMemories } = await fetchMemories(memory, messageLog, state);
45
50
  const selfMemories = await fetchSelfMemories(selfMemory);
51
+ log(` Messages: ${recentMessages.length}, Core memories: ${coreMemories.length}, Recent memories: ${recentMemories.length}, Self memories: ${selfMemories.length}`);
46
52
 
47
53
  let previousSoul = '';
48
54
  const archiveDir = path.join(PERSONALITY_DIR, 'evolution');
@@ -53,6 +59,7 @@ async function evolve(claudeClient, messageLog, memory, userDir, supabaseConfig
53
59
  .sort();
54
60
  if (archives.length > 0) {
55
61
  previousSoul = fs.readFileSync(path.join(archiveDir, archives[archives.length - 1]), 'utf-8');
62
+ log(` Previous soul: ${archives[archives.length - 1]} (${previousSoul.length} chars)`);
56
63
  }
57
64
  }
58
65
  } catch {}
@@ -78,10 +85,13 @@ async function evolve(claudeClient, messageLog, memory, userDir, supabaseConfig
78
85
  .join('\n\n') || '(no commands)';
79
86
 
80
87
  const evolutionNumber = state.evolutionCount + 1;
88
+ log(`Evolution #${evolutionNumber} (last: ${state.lastEvolution || 'never'})`);
81
89
 
90
+ log('Pre-evolution backup...');
82
91
  await backupSnapshot(`pre-evolution #${evolutionNumber}`, userDir);
83
92
 
84
93
  if (memory && recentMessages.length >= 4) {
94
+ log('Deep consolidating memory...');
85
95
  await deepConsolidateMemory(claudeClient, memory, recentMessages, evolutionNumber, MODELS.personality).catch(e =>
86
96
  console.error('[evolve] Deep consolidation failed:', e.message)
87
97
  );
@@ -90,6 +100,7 @@ async function evolve(claudeClient, messageLog, memory, userDir, supabaseConfig
90
100
  const isFirstEvolution = !currentSoul;
91
101
  let growthReport = '';
92
102
  if (!isFirstEvolution && (recentMemories.length > 0 || recentMessages.length > 0 || selfMemories.length > 0)) {
103
+ log('Running growth analysis...');
93
104
  try {
94
105
  const growthResponse = await claudeClient.messages.create({
95
106
  model: MODELS.personality,
@@ -122,12 +133,15 @@ ${transcript.substring(0, 30000)}`,
122
133
  }],
123
134
  });
124
135
  growthReport = growthResponse.content.filter(b => b.type === 'text').map(b => b.text).join('\n');
136
+ log(` Growth report: ${growthReport.length} chars`);
125
137
  } catch (e) {
126
138
  console.error('[evolve] Growth analysis failed:', e.message);
127
139
  }
128
140
  }
129
141
 
142
+ log('Running baseline tests...');
130
143
  const baselineResults = runTests(testsDir);
144
+ log(` Baseline: ${baselineResults.passed} passed, ${baselineResults.failed} failed`);
131
145
 
132
146
  const firstEvolutionPreamble = isFirstEvolution ? `
133
147
  ## FIRST EVOLUTION — IMPORTANT
@@ -152,6 +166,7 @@ A pre-evolution analysis has been conducted comparing your previous state agains
152
166
  baselineResults,
153
167
  });
154
168
 
169
+ log('Running main evolution (this takes a while)...');
155
170
  const response = await claudeClient.messages.create({
156
171
  model: MODELS.personality,
157
172
  max_tokens: 16384,
@@ -191,6 +206,7 @@ ${transcript || '(no conversations yet)'}
191
206
  Evolve. Rewrite everything that needs rewriting. Write tests for every script. Keep what works. Fix what doesn't.${growthReport ? ' Use the growth report to guide personality continuity and trait adjustments.' : ''}`
192
207
  }],
193
208
  });
209
+ log(' Evolution response received');
194
210
 
195
211
  const responseText = response.content.filter(b => b.type === 'text').map(b => b.text).join('\n');
196
212
 
@@ -211,11 +227,14 @@ Evolve. Rewrite everything that needs rewriting. Write tests for every script. K
211
227
  throw new Error('Evolution produced empty or too-short SOUL.md');
212
228
  }
213
229
 
230
+ log(` New soul: ${result.soul.length} chars`);
231
+
214
232
  let scriptsRolledBack = false;
215
233
  const hasNewTests = result.tests && typeof result.tests === 'object' && Object.keys(result.tests).length > 0;
216
234
  const hasNewScripts = result.scripts && typeof result.scripts === 'object' && Object.keys(result.scripts).length > 0;
217
235
 
218
236
  if (hasNewTests) {
237
+ log(`Writing ${Object.keys(result.tests).length} new tests...`);
219
238
  syncDir(testsDir, result.tests);
220
239
  for (const f of Object.keys(result.tests)) {
221
240
  try { fs.chmodSync(path.join(testsDir, f), 0o755); } catch {}
@@ -225,6 +244,7 @@ Evolve. Rewrite everything that needs rewriting. Write tests for every script. K
225
244
  const preRefactorResults = hasNewTests ? runTests(testsDir) : baselineResults;
226
245
 
227
246
  if (hasNewScripts) {
247
+ log(`Writing ${Object.keys(result.scripts).length} new scripts...`);
228
248
  syncDir(scriptsDir, result.scripts);
229
249
  for (const f of Object.keys(result.scripts)) {
230
250
  try { fs.chmodSync(path.join(scriptsDir, f), 0o755); } catch {}
@@ -234,11 +254,14 @@ Evolve. Rewrite everything that needs rewriting. Write tests for every script. K
234
254
  let scriptsFixed = false;
235
255
 
236
256
  if (hasNewTests || hasNewScripts) {
257
+ log('Running post-refactor tests...');
237
258
  let postRefactorResults = runTests(testsDir);
259
+ log(` Post-refactor: ${postRefactorResults.passed} passed, ${postRefactorResults.failed} failed`);
238
260
 
239
261
  let fixAttempt = 0;
240
262
  while (postRefactorResults.failed > preRefactorResults.failed && fixAttempt < MAX_FIX_ATTEMPTS) {
241
263
  fixAttempt++;
264
+ log(` Fix attempt ${fixAttempt}/${MAX_FIX_ATTEMPTS}...`);
242
265
 
243
266
  try {
244
267
  const fixResponse = await claudeClient.messages.create({
@@ -288,6 +311,7 @@ Fix the scripts. Tests define correct behavior.`
288
311
 
289
312
  if (postRefactorResults.failed <= preRefactorResults.failed) {
290
313
  scriptsFixed = true;
314
+ log(' Scripts fixed');
291
315
  }
292
316
  }
293
317
  }
@@ -297,6 +321,7 @@ Fix the scripts. Tests define correct behavior.`
297
321
  }
298
322
 
299
323
  if (postRefactorResults.failed > preRefactorResults.failed) {
324
+ log(' Rolling back scripts...');
300
325
  syncDir(scriptsDir, currentScripts);
301
326
  for (const f of Object.keys(currentScripts)) {
302
327
  try { fs.chmodSync(path.join(scriptsDir, f), 0o755); } catch {}
@@ -312,6 +337,7 @@ Fix the scripts. Tests define correct behavior.`
312
337
  }
313
338
  }
314
339
 
340
+ log('Archiving previous soul...');
315
341
  fs.mkdirSync(archiveDir, { recursive: true });
316
342
  if (currentSoul) {
317
343
  const timestamp = new Date().toISOString().slice(0, 10);
@@ -321,6 +347,7 @@ Fix the scripts. Tests define correct behavior.`
321
347
  );
322
348
  }
323
349
 
350
+ log('Writing new soul...');
324
351
  fs.writeFileSync(soulPath, result.soul);
325
352
  if (supabaseConfig) {
326
353
  const { backup } = require('../soul');
@@ -330,22 +357,27 @@ Fix the scripts. Tests define correct behavior.`
330
357
  }
331
358
 
332
359
  if (result.user && result.user.length > 50) {
360
+ log('Writing USER.md...');
333
361
  fs.writeFileSync(userPath, result.user);
334
362
  }
335
363
 
336
364
  if (result.agents && result.agents.length > 50) {
365
+ log('Writing AGENTS.md...');
337
366
  fs.writeFileSync(agentsPath, result.agents);
338
367
  }
339
368
 
340
369
  if (result.commands && typeof result.commands === 'object') {
341
370
  if (Object.keys(result.commands).length > 0 || Object.keys(currentCommands).length > 0) {
371
+ log(`Writing ${Object.keys(result.commands).length} commands...`);
342
372
  syncDir(commandsDir, result.commands);
343
373
  }
344
374
  }
345
375
 
376
+ log('Building and deploying apps...');
346
377
  const deployedApps = await buildAndDeployApps(result, baseDir);
347
378
 
348
379
  if (result.dependencies && Array.isArray(result.dependencies) && result.dependencies.length > 0) {
380
+ log(`Installing dependencies: ${result.dependencies.join(', ')}...`);
349
381
  try {
350
382
  const validDeps = result.dependencies.filter(isValidNpmPackage);
351
383
  if (validDeps.length === 0) throw new Error('No valid package names found');
@@ -370,6 +402,7 @@ Fix the scripts. Tests define correct behavior.`
370
402
  saveEvolutionState(state, userDir);
371
403
 
372
404
  if (memory) {
405
+ log('Saving evolution memory...');
373
406
  const changelog = result.changelog || `Evolution #${evolutionNumber} completed.`;
374
407
  const rollbackNote = scriptsRolledBack ? ' Scripts rolled back due to test regression.' : scriptsFixed ? ' Scripts fixed after test regression.' : '';
375
408
  await memory.add(
@@ -385,8 +418,10 @@ Fix the scripts. Tests define correct behavior.`
385
418
  }
386
419
  }
387
420
 
421
+ log('Post-evolution backup...');
388
422
  await backupSnapshot(`post-evolution #${evolutionNumber}`, userDir);
389
423
 
424
+ log('Done');
390
425
  return {
391
426
  evolutionNumber,
392
427
  previousLength: currentSoul.length,