wolverine-ai 2.6.2 → 2.6.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wolverine-ai",
3
- "version": "2.6.2",
3
+ "version": "2.6.4",
4
4
  "description": "Self-healing Node.js server framework powered by AI. Catches crashes, diagnoses errors, generates fixes, verifies, and restarts — automatically.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -55,7 +55,7 @@ class ResearchAgent {
55
55
 
56
56
  const namespace = success ? "fixes" : "errors";
57
57
  const prefix = success ? "FIXED" : "FAILED";
58
- const text = `${prefix}: ${safeError} in ${filePath}. ${success ? "Solution" : "Attempted"}: ${safeExplanation}`;
58
+ const text = `${prefix}: ${safeError} in ${filePath}. ${success ? "Solution" : "Attempted (DO NOT REPEAT)"}: ${safeExplanation}`;
59
59
 
60
60
  await this.brain.remember(namespace, text, { type: success ? "fix-success" : "fix-failure", file: filePath });
61
61
  console.log(chalk.gray(` 🧠 ${success ? "✅" : "❌"} Recorded ${prefix.toLowerCase()} for ${safeError.slice(0, 50)}`));
@@ -413,6 +413,29 @@ async function _healImpl({ stderr, cwd, sandbox, notifier, rateLimiter, backupMa
413
413
  });
414
414
  }
415
415
 
416
+ // Record outcome to brain — both successes AND failures with full context
417
+ if (brain && brain._initialized) {
418
+ try {
419
+ if (goalResult.success) {
420
+ await brain.remember("fixes",
421
+ `FIXED (${goalResult.mode}, iter ${goalResult.iteration}): ${parsed.errorMessage} in ${parsed.filePath}:${parsed.line}. ` +
422
+ `Solution: ${goalResult.explanation?.slice(0, 200)}. Files: ${(goalResult.agentStats?.filesModified || []).join(", ")}`,
423
+ { type: "fix-success", file: parsed.filePath, error: parsed.errorMessage, mode: goalResult.mode }
424
+ );
425
+ } else {
426
+ // Record failure with all attempts so brain knows what NOT to do
427
+ const attemptSummary = (goalResult.attempts || []).map(a =>
428
+ `Iter ${a.iteration} (${a.mode}): ${a.explanation?.slice(0, 80)}`
429
+ ).join("; ");
430
+ await brain.remember("errors",
431
+ `UNRESOLVED: ${parsed.errorMessage} in ${parsed.filePath}:${parsed.line}. ` +
432
+ `Tried ${goalResult.iteration} iterations: ${attemptSummary}. All failed.`,
433
+ { type: "fix-failure", file: parsed.filePath, error: parsed.errorMessage, attempts: goalResult.iteration }
434
+ );
435
+ }
436
+ } catch {}
437
+ }
438
+
416
439
  if (goalResult.success) {
417
440
  if (logger) logger.info(EVENT_TYPES.HEAL_SUCCESS, goalResult.explanation, { iteration: goalResult.iteration, mode: goalResult.mode });
418
441
  return { healed: true, ...goalResult };
@@ -90,80 +90,6 @@ function isNewer(latest, current) {
90
90
  return false;
91
91
  }
92
92
 
93
- /**
94
- * Protect ALL user files before update and restore after.
95
- * The entire server/ directory is sacred — auto-update must never touch it.
96
- * Also protects .env files and any user config.
97
- */
98
- function backupUserFiles(cwd) {
99
- const backups = {};
100
-
101
- // Protect config files
102
- const protectedFiles = [".env.local", ".env"];
103
-
104
- // Protect ALL .wolverine/ state (backups, brain, events, usage, repairs)
105
- const wolvDir = path.join(cwd, ".wolverine");
106
- if (fs.existsSync(wolvDir)) {
107
- const walkState = (dir, base) => {
108
- try {
109
- const entries = fs.readdirSync(dir, { withFileTypes: true });
110
- for (const entry of entries) {
111
- const fullPath = path.join(dir, entry.name);
112
- const relPath = path.join(base, entry.name).replace(/\\/g, "/");
113
- if (entry.isDirectory()) { walkState(fullPath, relPath); }
114
- else {
115
- try {
116
- const stat = fs.statSync(fullPath);
117
- if (stat.size <= 10 * 1024 * 1024) { // skip files > 10MB
118
- backups[relPath] = fs.readFileSync(fullPath, "utf-8");
119
- }
120
- } catch {}
121
- }
122
- }
123
- } catch {}
124
- };
125
- walkState(wolvDir, ".wolverine");
126
- }
127
- for (const file of protectedFiles) {
128
- const fullPath = path.join(cwd, file);
129
- if (fs.existsSync(fullPath)) {
130
- backups[file] = fs.readFileSync(fullPath, "utf-8");
131
- }
132
- }
133
-
134
- // Protect entire server/ directory (recursive)
135
- const serverDir = path.join(cwd, "server");
136
- if (fs.existsSync(serverDir)) {
137
- const walk = (dir, base) => {
138
- try {
139
- const entries = fs.readdirSync(dir, { withFileTypes: true });
140
- for (const entry of entries) {
141
- if (entry.name === "node_modules") continue;
142
- const fullPath = path.join(dir, entry.name);
143
- const relPath = path.join(base, entry.name).replace(/\\/g, "/");
144
- if (entry.isDirectory()) { walk(fullPath, relPath); }
145
- else {
146
- try { backups[relPath] = fs.readFileSync(fullPath, "utf-8"); } catch {}
147
- }
148
- }
149
- } catch {}
150
- };
151
- walk(serverDir, "server");
152
- }
153
-
154
- return backups;
155
- }
156
-
157
- function restoreUserFiles(cwd, backups) {
158
- for (const [file, content] of Object.entries(backups)) {
159
- const fullPath = path.join(cwd, file);
160
- try {
161
- fs.mkdirSync(path.dirname(fullPath), { recursive: true });
162
- fs.writeFileSync(fullPath, content, "utf-8");
163
- } catch {}
164
- }
165
- }
166
-
167
93
  /**
168
94
  * Detect if this is a git repo or an npm install.
169
95
  */
@@ -214,51 +140,6 @@ function checkForUpdate(cwd) {
214
140
  }
215
141
  }
216
142
 
217
- /**
218
- * Merge new brain seed docs into existing brain.
219
- * Reads the updated brain.js SEED_DOCS, compares topics with what's
220
- * already stored in .wolverine/brain/, and appends only new ones.
221
- * Existing memories (errors, fixes, learnings) are never touched.
222
- *
223
- * @returns {number} count of new seed docs added
224
- */
225
- function _mergeBrainSeeds(cwd) {
226
- try {
227
- // Load the brain store directly
228
- const storePath = path.join(cwd, ".wolverine", "brain", "store.json");
229
- if (!fs.existsSync(storePath)) return 0;
230
-
231
- const store = JSON.parse(fs.readFileSync(storePath, "utf-8"));
232
- const existingTexts = new Set();
233
- for (const ns of Object.values(store.namespaces || {})) {
234
- for (const entry of (ns || [])) {
235
- if (entry.text) existingTexts.add(entry.text.slice(0, 80));
236
- }
237
- }
238
-
239
- // Load fresh seed docs from the updated brain.js
240
- // Clear require cache to get the new version
241
- const brainPath = path.join(cwd, "src", "brain", "brain.js");
242
- delete require.cache[require.resolve(brainPath)];
243
- const brainModule = require(brainPath);
244
-
245
- // Access seed docs — they're in the module's closure, but brain.init() re-seeds them.
246
- // Instead, we'll read the file and extract them
247
- const brainSource = fs.readFileSync(brainPath, "utf-8");
248
- const seedMatch = brainSource.match(/const SEED_DOCS = \[([\s\S]*?)\n\];/);
249
- if (!seedMatch) return 0;
250
-
251
- // Count how many seed doc topics are new
252
- // We can't easily parse the JS array, but we can trigger brain.init() on next restart
253
- // which will re-seed. The brain's init() already only adds seeds if namespace is empty.
254
- // So we just need to signal that seeds should be refreshed.
255
- const seedRefreshPath = path.join(cwd, ".wolverine", "brain", ".seed-refresh");
256
- fs.writeFileSync(seedRefreshPath, new Date().toISOString(), "utf-8");
257
-
258
- return 1; // signal that refresh is pending
259
- } catch { return 0; }
260
- }
261
-
262
143
  /**
263
144
  * Start auto-update schedule. Checks every hour (configurable).
264
145
  * If autoUpdate is enabled and a new version is found, upgrades and signals restart.
@@ -1,66 +1,62 @@
1
1
  /**
2
2
  * Update Skill — safe self-updating for the wolverine framework.
3
3
  *
4
- * WARNING: raw `npm install` or `git pull` can overwrite:
5
- * - server/ (user's live code, routes, config, database)
6
- * - .wolverine/ (brain memories, backups, events, repair history, usage)
7
- * - .env.local (API keys, secrets)
4
+ * WARNING: raw `npm install` or `git pull` can overwrite server/ and .wolverine/.
5
+ * This skill updates ONLY framework files and never touches the server.
8
6
  *
9
- * This skill does it safely:
10
- * 1. Creates a pre-update snapshot in ~/.wolverine-safe-backups/ (outside project, never erased)
11
- * 2. Backs up all user files to memory
12
- * 3. Selectively updates ONLY framework files (src/, bin/, package.json)
13
- * 4. Restores all user files
14
- * 5. Merges new brain seed docs (append, not replace)
15
- * 6. Verifies the update didn't break anything
7
+ * What it does:
8
+ * 1. Creates emergency backup of server/ + brain (small, no node_modules)
9
+ * 2. Selectively updates ONLY framework files (src/, bin/, package.json)
10
+ * 3. server/ is NEVER touched no backup/restore dance needed
11
+ * 4. Signals brain to merge new seed docs on next boot
16
12
  *
17
- * Callable as:
18
- * wolverine --update (CLI)
19
- * npx wolverine-update (npm)
20
- * require("wolverine-ai").safeUpdate(cwd) (programmatic)
13
+ * Emergency backup is for rollback ONLY if something goes wrong.
14
+ * Located in ~/.wolverine-safe-backups/ (outside project, survives everything).
21
15
  */
22
16
 
23
17
  const { execSync } = require("child_process");
24
18
  const fs = require("fs");
25
19
  const path = require("path");
26
20
  const chalk = require("chalk");
21
+ const os = require("os");
27
22
 
28
23
  const PACKAGE_NAME = "wolverine-ai";
29
- const SAFE_BACKUP_DIR = path.join(require("os").homedir(), ".wolverine-safe-backups");
30
- const SAFE_SNAPSHOTS_DIR = path.join(SAFE_BACKUP_DIR, "snapshots");
24
+ const SAFE_BACKUP_DIR = path.join(os.homedir(), ".wolverine-safe-backups");
25
+
26
+ // Files/dirs to SKIP when backing up server/ (these are huge and not user code)
27
+ const SKIP_DIRS = new Set(["node_modules", ".git", ".wolverine", "dist", ".next", ".cache"]);
31
28
 
32
29
  /**
33
- * Create a safe backup snapshot outside the project directory.
34
- * These survive git clean, rm -rf node_modules, even rm -rf .wolverine.
30
+ * Create an emergency backup server/ code + brain only.
31
+ * Small and fast. No node_modules, no backup-of-backups.
35
32
  */
36
33
  function createSafeBackup(cwd) {
37
34
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
38
35
  const backupDir = path.join(SAFE_BACKUP_DIR, "updates", timestamp);
39
36
  fs.mkdirSync(backupDir, { recursive: true });
40
37
 
41
- const dirsToBackup = [
42
- { src: ".wolverine", label: "brain/backups/events/usage" },
43
- { src: "server", label: "server code" },
44
- ];
45
- const filesToBackup = [".env.local", ".env"];
46
-
47
38
  let fileCount = 0;
48
39
 
49
- for (const { src } of dirsToBackup) {
50
- const srcPath = path.join(cwd, src);
51
- if (!fs.existsSync(srcPath)) continue;
52
- const destPath = path.join(backupDir, src);
53
- _copyDirRecursive(srcPath, destPath);
54
- fileCount += _countFiles(destPath);
40
+ // Backup server/ (user code only skip node_modules, .git, etc.)
41
+ const serverDir = path.join(cwd, "server");
42
+ if (fs.existsSync(serverDir)) {
43
+ fileCount += _copyDirSelective(serverDir, path.join(backupDir, "server"));
55
44
  }
56
45
 
57
- for (const file of filesToBackup) {
58
- const srcPath = path.join(cwd, file);
59
- if (!fs.existsSync(srcPath)) continue;
60
- fs.copyFileSync(srcPath, path.join(backupDir, file));
46
+ // Backup brain vectors (the learned knowledge)
47
+ const brainStore = path.join(cwd, ".wolverine", "brain", "vectors.json");
48
+ if (fs.existsSync(brainStore)) {
49
+ fs.mkdirSync(path.join(backupDir, ".wolverine", "brain"), { recursive: true });
50
+ fs.copyFileSync(brainStore, path.join(backupDir, ".wolverine", "brain", "vectors.json"));
61
51
  fileCount++;
62
52
  }
63
53
 
54
+ // Backup .env files
55
+ for (const f of [".env.local", ".env"]) {
56
+ const fp = path.join(cwd, f);
57
+ if (fs.existsSync(fp)) { fs.copyFileSync(fp, path.join(backupDir, f)); fileCount++; }
58
+ }
59
+
64
60
  // Write manifest
65
61
  fs.writeFileSync(path.join(backupDir, "manifest.json"), JSON.stringify({
66
62
  timestamp: Date.now(),
@@ -68,6 +64,7 @@ function createSafeBackup(cwd) {
68
64
  cwd,
69
65
  version: _getCurrentVersion(cwd),
70
66
  fileCount,
67
+ type: "pre-update",
71
68
  }, null, 2), "utf-8");
72
69
 
73
70
  return { dir: backupDir, fileCount, timestamp };
@@ -80,10 +77,10 @@ function listSafeBackups() {
80
77
  const updatesDir = path.join(SAFE_BACKUP_DIR, "updates");
81
78
  if (!fs.existsSync(updatesDir)) return [];
82
79
  return fs.readdirSync(updatesDir)
83
- .filter(d => fs.statSync(path.join(updatesDir, d)).isDirectory())
80
+ .filter(d => { try { return fs.statSync(path.join(updatesDir, d)).isDirectory(); } catch { return false; } })
84
81
  .map(d => {
85
82
  try {
86
- const manifest = JSON.parse(fs.readFileSync(path.join(SAFE_BACKUP_DIR, "updates", d, "manifest.json"), "utf-8"));
83
+ const manifest = JSON.parse(fs.readFileSync(path.join(updatesDir, d, "manifest.json"), "utf-8"));
87
84
  return { dir: d, ...manifest };
88
85
  } catch { return { dir: d }; }
89
86
  })
@@ -91,39 +88,41 @@ function listSafeBackups() {
91
88
  }
92
89
 
93
90
  /**
94
- * Restore from a safe backup.
91
+ * Restore from a safe backup (emergency only).
95
92
  */
96
93
  function restoreFromSafeBackup(cwd, backupName) {
97
94
  const backupDir = path.join(SAFE_BACKUP_DIR, "updates", backupName);
98
95
  if (!fs.existsSync(backupDir)) throw new Error(`Backup not found: ${backupName}`);
99
96
 
100
- const dirsToRestore = [".wolverine", "server"];
101
- const filesToRestore = [".env.local", ".env"];
102
-
103
97
  let restored = 0;
104
- for (const dir of dirsToRestore) {
105
- const srcPath = path.join(backupDir, dir);
106
- if (!fs.existsSync(srcPath)) continue;
107
- const destPath = path.join(cwd, dir);
108
- _copyDirRecursive(srcPath, destPath);
109
- restored += _countFiles(srcPath);
98
+
99
+ // Restore server/
100
+ const serverSrc = path.join(backupDir, "server");
101
+ if (fs.existsSync(serverSrc)) {
102
+ restored += _copyDirSelective(serverSrc, path.join(cwd, "server"));
110
103
  }
111
- for (const file of filesToRestore) {
112
- const srcPath = path.join(backupDir, file);
113
- if (!fs.existsSync(srcPath)) continue;
114
- fs.copyFileSync(srcPath, path.join(cwd, file));
104
+
105
+ // Restore brain
106
+ const brainSrc = path.join(backupDir, ".wolverine", "brain", "vectors.json");
107
+ if (fs.existsSync(brainSrc)) {
108
+ const dest = path.join(cwd, ".wolverine", "brain", "vectors.json");
109
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
110
+ fs.copyFileSync(brainSrc, dest);
115
111
  restored++;
116
112
  }
113
+
114
+ // Restore .env files
115
+ for (const f of [".env.local", ".env"]) {
116
+ const src = path.join(backupDir, f);
117
+ if (fs.existsSync(src)) { fs.copyFileSync(src, path.join(cwd, f)); restored++; }
118
+ }
119
+
117
120
  return { restored, backupDir };
118
121
  }
119
122
 
120
123
  /**
121
124
  * Safe update — the main entry point.
122
- * Call this instead of raw npm install or git pull.
123
- *
124
- * @param {string} cwd — project root
125
- * @param {object} options — { logger, dryRun }
126
- * @returns {{ success, from, to, backupDir, error? }}
125
+ * Updates framework files ONLY. server/ is never touched.
127
126
  */
128
127
  function safeUpdate(cwd, options = {}) {
129
128
  const { logger, dryRun } = options;
@@ -140,7 +139,6 @@ function safeUpdate(cwd, options = {}) {
140
139
  }).trim();
141
140
  } catch {}
142
141
 
143
- // Also check git remote
144
142
  const isGit = _isGitRepo(cwd);
145
143
  if (isGit) {
146
144
  try {
@@ -167,20 +165,16 @@ function safeUpdate(cwd, options = {}) {
167
165
  return { success: true, from: currentVersion, to: latestVersion, dryRun: true };
168
166
  }
169
167
 
170
- // 2. Create safe backup (outside project, survives everything)
171
- console.log(chalk.gray(" 🔒 Creating safe backup..."));
168
+ // 2. Emergency backup (server code + brain only — small and fast)
169
+ console.log(chalk.gray(" 🔒 Creating emergency backup..."));
172
170
  const backup = createSafeBackup(cwd);
173
171
  console.log(chalk.gray(` 🔒 Backed up ${backup.fileCount} files to ${backup.dir}`));
174
- if (logger) logger.info("update.backup", `Safe backup: ${backup.fileCount} files`, { dir: backup.dir });
175
-
176
- // 3. Backup user files to memory (belt + suspenders)
177
- const memoryBackup = _backupToMemory(cwd);
178
- console.log(chalk.gray(` 🔒 Memory backup: ${Object.keys(memoryBackup).length} files`));
172
+ if (logger) logger.info("update.backup", `Emergency backup: ${backup.fileCount} files`, { dir: backup.dir });
179
173
 
180
174
  try {
181
- // 4. Update framework ONLY
175
+ // 3. Update framework ONLY — server/ is never touched
182
176
  if (isGit) {
183
- console.log(chalk.blue(" 📦 Selective git update (server/ + .wolverine/ untouched)"));
177
+ console.log(chalk.blue(" 📦 Selective git update (server/ untouched)"));
184
178
  const frameworkPaths = "src/ bin/ package.json package-lock.json examples/ tests/ CLAUDE.md README.md CHANGELOG.md .npmignore";
185
179
  execSync(`git checkout origin/master -- ${frameworkPaths}`, { cwd, stdio: "pipe", timeout: 30000 });
186
180
  execSync("npm install --production", { cwd, stdio: "pipe", timeout: 120000 });
@@ -190,29 +184,22 @@ function safeUpdate(cwd, options = {}) {
190
184
  execSync(cmd, { cwd, stdio: "pipe", timeout: 120000 });
191
185
  }
192
186
 
193
- // 5. Restore user files from memory
194
- _restoreFromMemory(cwd, memoryBackup);
195
- console.log(chalk.gray(` 🔒 Restored ${Object.keys(memoryBackup).length} user files`));
196
-
197
- // 6. Signal brain to merge new seeds on next boot
187
+ // 4. Signal brain to merge new seeds on next boot
198
188
  const seedRefreshDir = path.join(cwd, ".wolverine", "brain");
199
189
  fs.mkdirSync(seedRefreshDir, { recursive: true });
200
190
  fs.writeFileSync(path.join(seedRefreshDir, ".seed-refresh"), new Date().toISOString(), "utf-8");
201
191
  console.log(chalk.gray(" 🧠 Brain seed merge scheduled for next boot"));
202
192
 
203
- // 7. Verify
204
193
  const newVersion = _getCurrentVersion(cwd);
205
194
  console.log(chalk.green(` ✅ Updated: ${currentVersion} → ${newVersion}`));
206
- console.log(chalk.gray(` 🔒 Safe backup at: ${backup.dir}`));
195
+ console.log(chalk.gray(` 🔒 Emergency backup at: ${backup.dir}`));
207
196
  if (logger) logger.info("update.success", `Updated ${currentVersion} → ${newVersion}`, { from: currentVersion, to: newVersion });
208
197
 
209
198
  return { success: true, from: currentVersion, to: newVersion, backupDir: backup.dir };
210
199
  } catch (err) {
211
- // Restore from memory on failure
212
- _restoreFromMemory(cwd, memoryBackup);
213
200
  const errMsg = (err.message || "").slice(0, 100);
214
201
  console.log(chalk.red(` ❌ Update failed: ${errMsg}`));
215
- console.log(chalk.yellow(` 🔒 Restore from safe backup: wolverine --restore ${backup.timestamp}`));
202
+ console.log(chalk.yellow(` 🔒 Restore with: wolverine --restore ${backup.timestamp}`));
216
203
  if (logger) logger.warn("update.failed", `Update failed: ${errMsg}`, { from: currentVersion });
217
204
  return { success: false, from: currentVersion, to: latestVersion, error: errMsg, backupDir: backup.dir };
218
205
  }
@@ -235,82 +222,41 @@ function _isNewer(a, b) {
235
222
  return false;
236
223
  }
237
224
 
238
- function _copyDirRecursive(src, dest) {
225
+ /**
226
+ * Copy directory recursively, skipping node_modules/.git/.wolverine/dist etc.
227
+ * Returns file count.
228
+ */
229
+ function _copyDirSelective(src, dest) {
239
230
  fs.mkdirSync(dest, { recursive: true });
231
+ let count = 0;
240
232
  for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
241
- if (entry.name === "node_modules") continue;
233
+ if (SKIP_DIRS.has(entry.name)) continue;
242
234
  const s = path.join(src, entry.name), d = path.join(dest, entry.name);
243
- if (entry.isDirectory()) _copyDirRecursive(s, d);
244
- else { try { fs.copyFileSync(s, d); } catch {} }
245
- }
246
- }
247
-
248
- function _countFiles(dir) {
249
- let count = 0;
250
- try {
251
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
252
- if (entry.isDirectory()) count += _countFiles(path.join(dir, entry.name));
253
- else count++;
254
- }
255
- } catch {}
256
- return count;
257
- }
258
-
259
- function _backupToMemory(cwd) {
260
- const backups = {};
261
- const protect = ["server", ".wolverine"];
262
- const protectFiles = [".env.local", ".env"];
263
-
264
- for (const dir of protect) {
265
- const dirPath = path.join(cwd, dir);
266
- if (!fs.existsSync(dirPath)) continue;
267
- const walk = (d, base) => {
235
+ if (entry.isDirectory()) { count += _copyDirSelective(s, d); }
236
+ else {
268
237
  try {
269
- for (const entry of fs.readdirSync(d, { withFileTypes: true })) {
270
- if (entry.name === "node_modules") continue;
271
- const full = path.join(d, entry.name), rel = path.join(base, entry.name).replace(/\\/g, "/");
272
- if (entry.isDirectory()) walk(full, rel);
273
- else { try { const s = fs.statSync(full); if (s.size <= 10*1024*1024) backups[rel] = fs.readFileSync(full); } catch {} }
274
- }
238
+ const stat = fs.statSync(s);
239
+ if (stat.size <= 5 * 1024 * 1024) { fs.copyFileSync(s, d); count++; } // skip >5MB
275
240
  } catch {}
276
- };
277
- walk(dirPath, dir);
278
- }
279
- for (const f of protectFiles) {
280
- const fp = path.join(cwd, f);
281
- if (fs.existsSync(fp)) backups[f] = fs.readFileSync(fp);
282
- }
283
- return backups;
284
- }
285
-
286
- function _restoreFromMemory(cwd, backups) {
287
- for (const [rel, content] of Object.entries(backups)) {
288
- const fp = path.join(cwd, rel);
289
- try { fs.mkdirSync(path.dirname(fp), { recursive: true }); fs.writeFileSync(fp, content); } catch {}
241
+ }
290
242
  }
243
+ return count;
291
244
  }
292
245
 
293
246
  // ── Skill Metadata ──
294
247
 
295
248
  const SKILL_NAME = "update";
296
- const SKILL_DESCRIPTION = "Safe self-updating for wolverine framework. Creates safe backup outside project (~/. wolverine-safe-backups/), selectively updates only framework files (src/, bin/, package.json), restores all user files (server/, .wolverine/, .env), merges new brain seeds. Never use raw npm install or git pull — they overwrite server code and brain memories.";
249
+ const SKILL_DESCRIPTION = "Safe self-updating for wolverine framework. Creates emergency backup of server code + brain (no node_modules), selectively updates only framework files (src/, bin/, package.json), never touches server/. Never use raw npm install or git pull.";
297
250
  const SKILL_KEYWORDS = ["update", "upgrade", "version", "install", "pull", "self-update", "auto-update", "framework", "safe"];
298
251
  const SKILL_USAGE = `// Safe update (programmatic)
299
252
  const { safeUpdate } = require("wolverine-ai");
300
- const result = await safeUpdate(process.cwd());
301
- // { success: true, from: "2.5.3", to: "2.6.0", backupDir: "~/.wolverine-safe-backups/..." }
302
-
303
- // List safe backups
304
- const { listSafeBackups } = require("wolverine-ai");
305
- const backups = listSafeBackups();
306
-
307
- // Restore from safe backup
308
- const { restoreFromSafeBackup } = require("wolverine-ai");
309
- restoreFromSafeBackup(process.cwd(), "2026-04-02T21-15-00");
253
+ const result = safeUpdate(process.cwd());
310
254
 
311
- // CLI: wolverine --update
312
- // CLI: wolverine --update --dry-run
313
- // CLI: wolverine --restore 2026-04-02T21-15-00`;
255
+ // CLI
256
+ // wolverine --update
257
+ // wolverine --update --dry-run
258
+ // wolverine --backups
259
+ // wolverine --restore 2026-04-02T21-15-00`;
314
260
 
315
261
  module.exports = {
316
262
  SKILL_NAME, SKILL_DESCRIPTION, SKILL_KEYWORDS, SKILL_USAGE,