wolverine-ai 2.5.2 → 2.6.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 CHANGED
@@ -462,11 +462,24 @@ Wolverine checks npm for new versions hourly and upgrades itself automatically.
462
462
  }
463
463
  ```
464
464
 
465
+ ```bash
466
+ # Manual safe update
467
+ wolverine --update # check + upgrade safely
468
+ wolverine --update --dry-run # check only, no changes
469
+ wolverine --backups # list safe backups
470
+ wolverine --restore 2026-04-02 # restore from safe backup
471
+ ```
472
+
465
473
  **How it works:**
466
- - Checks `npm view wolverine-ai version` against installed version
467
- - If newer: backs up `settings.json`, `.env.local`, `.env` runs `npm install wolverine-ai@latest` → restores configs → restarts
468
- - Protected files never overwritten during update
469
- - First check 30s after startup, then every hour
474
+ 1. Creates safe backup in `~/.wolverine-safe-backups/` (outside project, survives everything)
475
+ 2. Backs up `server/`, `.wolverine/`, `.env` to memory
476
+ 3. Selectively updates ONLY `src/`, `bin/`, `package.json` (git checkout or npm install)
477
+ 4. Restores all user files (server code, brain, backups, events, config)
478
+ 5. Signals brain to merge new seed docs on next boot (append, not replace)
479
+ 6. Auto-check: 30s after startup, then every 5 minutes (configurable)
480
+
481
+ **Never run raw `npm install` or `git pull`** — they overwrite server code and brain memories. Always use `wolverine --update` or let auto-update handle it.
482
+
470
483
  - **Disable:** `"autoUpdate": { "enabled": false }` or `WOLVERINE_AUTO_UPDATE=false`
471
484
 
472
485
  ---
package/bin/wolverine.js CHANGED
@@ -54,6 +54,39 @@ if (args.includes("--info")) {
54
54
  process.exit(0);
55
55
  }
56
56
 
57
+ // --update: safe framework update
58
+ if (args.includes("--update")) {
59
+ const { safeUpdate } = require("../src/skills/update");
60
+ const dryRun = args.includes("--dry-run");
61
+ const result = safeUpdate(process.cwd(), { dryRun });
62
+ process.exit(result.success ? 0 : 1);
63
+ }
64
+
65
+ // --restore: restore from safe backup
66
+ if (args.includes("--restore")) {
67
+ const backupName = args[args.indexOf("--restore") + 1];
68
+ if (!backupName) { console.log("Usage: wolverine --restore <backup-name>"); process.exit(1); }
69
+ const { restoreFromSafeBackup } = require("../src/skills/update");
70
+ const result = restoreFromSafeBackup(process.cwd(), backupName);
71
+ console.log(chalk.green(` ✅ Restored ${result.restored} files from ${backupName}`));
72
+ process.exit(0);
73
+ }
74
+
75
+ // --backups: list safe backups
76
+ if (args.includes("--backups")) {
77
+ const { listSafeBackups } = require("../src/skills/update");
78
+ const backups = listSafeBackups();
79
+ if (backups.length === 0) { console.log("No safe backups found."); }
80
+ else {
81
+ console.log(chalk.bold("\n Safe Backups (~/.wolverine-safe-backups/):\n"));
82
+ for (const b of backups) {
83
+ console.log(` ${b.dir} v${b.version || "?"} ${b.fileCount || "?"} files ${b.iso || ""}`);
84
+ }
85
+ console.log("");
86
+ }
87
+ process.exit(0);
88
+ }
89
+
57
90
  const scriptPath = args.find(a => !a.startsWith("--")) || "server/index.js";
58
91
 
59
92
  // System detection (for analytics + dashboard, NOT for forking)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wolverine-ai",
3
- "version": "2.5.2",
3
+ "version": "2.6.0",
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": {
@@ -1,3 +1,5 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
1
3
  const chalk = require("chalk");
2
4
  const { VectorStore } = require("./vector-store");
3
5
  const { embed, embedBatch, compactAndEmbed } = require("./embedder");
@@ -235,6 +237,10 @@ const SEED_DOCS = [
235
237
  text: "Dependency manager skill (src/skills/deps.js): structured npm dependency analysis + repair. diagnose(errorMessage, cwd) returns {diagnosed, category, summary, fixes} — categories: missing_install, missing_package, version_conflict, outdated_api, corrupted_modules. healthReport(cwd) returns full health check: npm audit (vulnerabilities), outdated packages, peer dep conflicts, unused packages, lock file status, health score 0-100. getMigration(packageName) returns known upgrade paths: express→fastify (5.6x faster), moment→dayjs (2KB vs 70KB), request→node-fetch (deprecated), body-parser→built-in, callbacks→async/await. Agent tools: audit_deps (full health check), check_migration (upgrade paths). Heal pipeline uses diagnose() in tryOperationalFix before AI — zero tokens for dependency issues.",
236
238
  metadata: { topic: "skill-deps" },
237
239
  },
240
+ {
241
+ text: "CRITICAL: Never run raw 'npm install wolverine-ai' or 'git pull' to update — these OVERWRITE server/, .wolverine/ (brain, backups, events), and .env.local. Always use the safe update skill: wolverine --update (CLI), safeUpdate(cwd) (programmatic), or let auto-update handle it. The update skill: (1) creates safe backup in ~/.wolverine-safe-backups/ (outside project, survives everything), (2) backs up server/ + .wolverine/ + .env to memory, (3) selectively updates ONLY src/ bin/ package.json via git checkout, (4) restores all user files, (5) signals brain to merge new seed docs. Restore with: wolverine --restore <backup-name>. List backups: wolverine --backups.",
242
+ metadata: { topic: "safe-update-warning" },
243
+ },
238
244
  {
239
245
  text: "Auto-update: wolverine checks npm registry hourly for new versions. When found, runs npm install wolverine-ai@latest, backs up settings.json/.env.local before update and restores after. Config: autoUpdate.enabled (default true) in settings.json. Disable with WOLVERINE_AUTO_UPDATE=false env var. On successful update, signals runner to restart. Protected files never overwritten: settings.json, .env.local, .env, db.js. Update check runs 30s after startup then every hour (configurable via autoUpdate.intervalMs).",
240
246
  metadata: { topic: "auto-update" },
package/src/index.js CHANGED
@@ -36,6 +36,7 @@ const { loadConfig, getConfig } = require("./core/config");
36
36
  const { sqlGuard, SafeDB, scanForInjection, idempotencyGuard, idempotencyAfterHook } = require("./skills/sql");
37
37
  const { diagnose: diagnoseDeps, healthReport: depsHealthReport, getMigration } = require("./skills/deps");
38
38
  const { checkForUpdate, upgrade: upgradeWolverine, getCurrentVersion } = require("./platform/auto-update");
39
+ const { safeUpdate, createSafeBackup, listSafeBackups, restoreFromSafeBackup } = require("./skills/update");
39
40
 
40
41
  module.exports = {
41
42
  // Core
@@ -103,4 +104,8 @@ module.exports = {
103
104
  checkForUpdate,
104
105
  upgradeWolverine,
105
106
  getCurrentVersion,
107
+ safeUpdate,
108
+ createSafeBackup,
109
+ listSafeBackups,
110
+ restoreFromSafeBackup,
106
111
  };
@@ -186,60 +186,10 @@ function upgrade(cwd, logger) {
186
186
  return { success: false, from: current, to: latest, error: "Already up to date" };
187
187
  }
188
188
 
189
- console.log(chalk.blue(`\n 🔄 Wolverine update available: ${current} ${latest}`));
190
- if (logger) logger.info("update.start", `Upgrading ${current} ${latest}`, { from: current, to: latest });
191
-
192
- // Back up ALL user files (server/, .env, configs)
193
- const userBackups = backupUserFiles(cwd);
194
- console.log(chalk.gray(` 🔒 Backed up ${Object.keys(userBackups).length} user files (server/ protected)`));
195
-
196
- try {
197
- const useGit = isGitRepo(cwd);
198
-
199
- if (useGit) {
200
- // Git-cloned: ONLY update framework files, NEVER touch server/
201
- // Fetch latest, then selectively checkout only framework dirs
202
- console.log(chalk.blue(` 📦 Git repo — selective framework update (server/ untouched)`));
203
- execSync("git fetch origin master", { cwd, stdio: "pipe", timeout: 30000 });
204
- // Only update: src/, bin/, package.json, examples/, tests/, CLAUDE.md, README.md, CHANGELOG.md
205
- const frameworkPaths = "src/ bin/ package.json package-lock.json examples/ tests/ CLAUDE.md README.md CHANGELOG.md .npmignore";
206
- execSync(`git checkout origin/master -- ${frameworkPaths}`, { cwd, stdio: "pipe", timeout: 30000 });
207
- execSync("npm install", { cwd, stdio: "pipe", timeout: 120000 });
208
- } else {
209
- // npm-installed: update the package
210
- const isGlobal = __dirname.includes("node_modules") && !cwd.includes("node_modules");
211
- const cmd = isGlobal
212
- ? `npm install -g ${PACKAGE_NAME}@${latest}`
213
- : `npm install ${PACKAGE_NAME}@${latest}`;
214
- console.log(chalk.blue(` 📦 Running: ${cmd}`));
215
- execSync(cmd, { cwd, stdio: "pipe", timeout: 120000 });
216
- }
217
-
218
- // Restore ALL user files (server/, .env, .wolverine/) — belt AND suspenders
219
- restoreUserFiles(cwd, userBackups);
220
- console.log(chalk.gray(` 🔒 Restored ${Object.keys(userBackups).length} user files`));
221
-
222
- // Merge new brain seed docs into existing brain (append, don't replace)
223
- try {
224
- const brainMerged = _mergeBrainSeeds(cwd);
225
- if (brainMerged > 0) console.log(chalk.gray(` 🧠 Merged ${brainMerged} new seed docs into brain`));
226
- } catch {}
227
-
228
- // Clear version cache
229
- _currentVersion = null;
230
-
231
- console.log(chalk.green(` ✅ Updated to ${latest}`));
232
- if (logger) logger.info("update.success", `Upgraded to ${latest}`, { from: current, to: latest });
233
-
234
- return { success: true, from: current, to: latest };
235
- } catch (err) {
236
- // Restore user files on failure
237
- restoreUserFiles(cwd, userBackups);
238
- const errMsg = (err.message || "").slice(0, 100);
239
- console.log(chalk.yellow(` ⚠️ Update failed: ${errMsg}`));
240
- if (logger) logger.warn("update.failed", `Upgrade failed: ${errMsg}`, { from: current, to: latest });
241
- return { success: false, from: current, to: latest, error: errMsg };
242
- }
189
+ // Delegate to the update skill for the full safe upgrade routine
190
+ const { safeUpdate } = require("../skills/update");
191
+ _currentVersion = null; // clear cache so next check sees new version
192
+ return safeUpdate(cwd, { logger });
243
193
  }
244
194
 
245
195
  /**
@@ -0,0 +1,316 @@
1
+ /**
2
+ * Update Skill — safe self-updating for the wolverine framework.
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)
8
+ *
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
16
+ *
17
+ * Callable as:
18
+ * wolverine --update (CLI)
19
+ * npx wolverine-update (npm)
20
+ * require("wolverine-ai").safeUpdate(cwd) (programmatic)
21
+ */
22
+
23
+ const { execSync } = require("child_process");
24
+ const fs = require("fs");
25
+ const path = require("path");
26
+ const chalk = require("chalk");
27
+
28
+ const PACKAGE_NAME = "wolverine-ai";
29
+ const SAFE_BACKUP_DIR = path.join(require("os").homedir(), ".wolverine-safe-backups");
30
+
31
+ /**
32
+ * Create a safe backup snapshot outside the project directory.
33
+ * These survive git clean, rm -rf node_modules, even rm -rf .wolverine.
34
+ */
35
+ function createSafeBackup(cwd) {
36
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
37
+ const backupDir = path.join(SAFE_BACKUP_DIR, timestamp);
38
+ fs.mkdirSync(backupDir, { recursive: true });
39
+
40
+ const dirsToBackup = [
41
+ { src: ".wolverine", label: "brain/backups/events/usage" },
42
+ { src: "server", label: "server code" },
43
+ ];
44
+ const filesToBackup = [".env.local", ".env"];
45
+
46
+ let fileCount = 0;
47
+
48
+ for (const { src } of dirsToBackup) {
49
+ const srcPath = path.join(cwd, src);
50
+ if (!fs.existsSync(srcPath)) continue;
51
+ const destPath = path.join(backupDir, src);
52
+ _copyDirRecursive(srcPath, destPath);
53
+ fileCount += _countFiles(destPath);
54
+ }
55
+
56
+ for (const file of filesToBackup) {
57
+ const srcPath = path.join(cwd, file);
58
+ if (!fs.existsSync(srcPath)) continue;
59
+ fs.copyFileSync(srcPath, path.join(backupDir, file));
60
+ fileCount++;
61
+ }
62
+
63
+ // Write manifest
64
+ fs.writeFileSync(path.join(backupDir, "manifest.json"), JSON.stringify({
65
+ timestamp: Date.now(),
66
+ iso: new Date().toISOString(),
67
+ cwd,
68
+ version: _getCurrentVersion(cwd),
69
+ fileCount,
70
+ }, null, 2), "utf-8");
71
+
72
+ return { dir: backupDir, fileCount, timestamp };
73
+ }
74
+
75
+ /**
76
+ * List available safe backups.
77
+ */
78
+ function listSafeBackups() {
79
+ if (!fs.existsSync(SAFE_BACKUP_DIR)) return [];
80
+ return fs.readdirSync(SAFE_BACKUP_DIR)
81
+ .filter(d => fs.statSync(path.join(SAFE_BACKUP_DIR, d)).isDirectory())
82
+ .map(d => {
83
+ try {
84
+ const manifest = JSON.parse(fs.readFileSync(path.join(SAFE_BACKUP_DIR, d, "manifest.json"), "utf-8"));
85
+ return { dir: d, ...manifest };
86
+ } catch { return { dir: d }; }
87
+ })
88
+ .sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));
89
+ }
90
+
91
+ /**
92
+ * Restore from a safe backup.
93
+ */
94
+ function restoreFromSafeBackup(cwd, backupName) {
95
+ const backupDir = path.join(SAFE_BACKUP_DIR, backupName);
96
+ if (!fs.existsSync(backupDir)) throw new Error(`Backup not found: ${backupName}`);
97
+
98
+ const dirsToRestore = [".wolverine", "server"];
99
+ const filesToRestore = [".env.local", ".env"];
100
+
101
+ let restored = 0;
102
+ for (const dir of dirsToRestore) {
103
+ const srcPath = path.join(backupDir, dir);
104
+ if (!fs.existsSync(srcPath)) continue;
105
+ const destPath = path.join(cwd, dir);
106
+ _copyDirRecursive(srcPath, destPath);
107
+ restored += _countFiles(srcPath);
108
+ }
109
+ for (const file of filesToRestore) {
110
+ const srcPath = path.join(backupDir, file);
111
+ if (!fs.existsSync(srcPath)) continue;
112
+ fs.copyFileSync(srcPath, path.join(cwd, file));
113
+ restored++;
114
+ }
115
+ return { restored, backupDir };
116
+ }
117
+
118
+ /**
119
+ * Safe update — the main entry point.
120
+ * Call this instead of raw npm install or git pull.
121
+ *
122
+ * @param {string} cwd — project root
123
+ * @param {object} options — { logger, dryRun }
124
+ * @returns {{ success, from, to, backupDir, error? }}
125
+ */
126
+ function safeUpdate(cwd, options = {}) {
127
+ const { logger, dryRun } = options;
128
+ const currentVersion = _getCurrentVersion(cwd);
129
+
130
+ console.log(chalk.blue("\n 🔄 Wolverine Safe Update"));
131
+ console.log(chalk.gray(` Current version: ${currentVersion}`));
132
+
133
+ // 1. Check for updates
134
+ let latestVersion;
135
+ try {
136
+ latestVersion = execSync(`npm view ${PACKAGE_NAME} version 2>/dev/null`, {
137
+ encoding: "utf-8", timeout: 15000, cwd,
138
+ }).trim();
139
+ } catch {}
140
+
141
+ // Also check git remote
142
+ const isGit = _isGitRepo(cwd);
143
+ if (isGit) {
144
+ try {
145
+ execSync("git fetch origin --quiet", { cwd, stdio: "pipe", timeout: 15000 });
146
+ const remoteVersion = execSync("git show origin/master:package.json", {
147
+ cwd, encoding: "utf-8", timeout: 5000,
148
+ });
149
+ const remotePkg = JSON.parse(remoteVersion);
150
+ if (!latestVersion || _isNewer(remotePkg.version, latestVersion)) {
151
+ latestVersion = remotePkg.version;
152
+ }
153
+ } catch {}
154
+ }
155
+
156
+ if (!latestVersion || !_isNewer(latestVersion, currentVersion)) {
157
+ console.log(chalk.green(` ✅ Already up to date (${currentVersion})`));
158
+ return { success: true, from: currentVersion, to: currentVersion, upToDate: true };
159
+ }
160
+
161
+ console.log(chalk.blue(` 📦 Update available: ${currentVersion} → ${latestVersion}`));
162
+
163
+ if (dryRun) {
164
+ console.log(chalk.gray(" (dry run — no changes made)"));
165
+ return { success: true, from: currentVersion, to: latestVersion, dryRun: true };
166
+ }
167
+
168
+ // 2. Create safe backup (outside project, survives everything)
169
+ console.log(chalk.gray(" 🔒 Creating safe backup..."));
170
+ const backup = createSafeBackup(cwd);
171
+ console.log(chalk.gray(` 🔒 Backed up ${backup.fileCount} files to ${backup.dir}`));
172
+ if (logger) logger.info("update.backup", `Safe backup: ${backup.fileCount} files`, { dir: backup.dir });
173
+
174
+ // 3. Backup user files to memory (belt + suspenders)
175
+ const memoryBackup = _backupToMemory(cwd);
176
+ console.log(chalk.gray(` 🔒 Memory backup: ${Object.keys(memoryBackup).length} files`));
177
+
178
+ try {
179
+ // 4. Update framework ONLY
180
+ if (isGit) {
181
+ console.log(chalk.blue(" 📦 Selective git update (server/ + .wolverine/ untouched)"));
182
+ const frameworkPaths = "src/ bin/ package.json package-lock.json examples/ tests/ CLAUDE.md README.md CHANGELOG.md .npmignore";
183
+ execSync(`git checkout origin/master -- ${frameworkPaths}`, { cwd, stdio: "pipe", timeout: 30000 });
184
+ execSync("npm install --production", { cwd, stdio: "pipe", timeout: 120000 });
185
+ } else {
186
+ const cmd = `npm install ${PACKAGE_NAME}@${latestVersion}`;
187
+ console.log(chalk.blue(` 📦 ${cmd}`));
188
+ execSync(cmd, { cwd, stdio: "pipe", timeout: 120000 });
189
+ }
190
+
191
+ // 5. Restore user files from memory
192
+ _restoreFromMemory(cwd, memoryBackup);
193
+ console.log(chalk.gray(` 🔒 Restored ${Object.keys(memoryBackup).length} user files`));
194
+
195
+ // 6. Signal brain to merge new seeds on next boot
196
+ const seedRefreshDir = path.join(cwd, ".wolverine", "brain");
197
+ fs.mkdirSync(seedRefreshDir, { recursive: true });
198
+ fs.writeFileSync(path.join(seedRefreshDir, ".seed-refresh"), new Date().toISOString(), "utf-8");
199
+ console.log(chalk.gray(" 🧠 Brain seed merge scheduled for next boot"));
200
+
201
+ // 7. Verify
202
+ const newVersion = _getCurrentVersion(cwd);
203
+ console.log(chalk.green(` ✅ Updated: ${currentVersion} → ${newVersion}`));
204
+ console.log(chalk.gray(` 🔒 Safe backup at: ${backup.dir}`));
205
+ if (logger) logger.info("update.success", `Updated ${currentVersion} → ${newVersion}`, { from: currentVersion, to: newVersion });
206
+
207
+ return { success: true, from: currentVersion, to: newVersion, backupDir: backup.dir };
208
+ } catch (err) {
209
+ // Restore from memory on failure
210
+ _restoreFromMemory(cwd, memoryBackup);
211
+ const errMsg = (err.message || "").slice(0, 100);
212
+ console.log(chalk.red(` ❌ Update failed: ${errMsg}`));
213
+ console.log(chalk.yellow(` 🔒 Restore from safe backup: wolverine --restore ${backup.timestamp}`));
214
+ if (logger) logger.warn("update.failed", `Update failed: ${errMsg}`, { from: currentVersion });
215
+ return { success: false, from: currentVersion, to: latestVersion, error: errMsg, backupDir: backup.dir };
216
+ }
217
+ }
218
+
219
+ // ── Helpers ──
220
+
221
+ function _getCurrentVersion(cwd) {
222
+ try { return require(path.join(cwd, "package.json")).version; } catch { return "0.0.0"; }
223
+ }
224
+
225
+ function _isGitRepo(cwd) {
226
+ try { execSync("git rev-parse --is-inside-work-tree", { cwd, stdio: "pipe", timeout: 3000 }); return true; } catch { return false; }
227
+ }
228
+
229
+ function _isNewer(a, b) {
230
+ if (!a || !b) return false;
231
+ const av = a.split(".").map(Number), bv = b.split(".").map(Number);
232
+ for (let i = 0; i < 3; i++) { if ((av[i]||0) > (bv[i]||0)) return true; if ((av[i]||0) < (bv[i]||0)) return false; }
233
+ return false;
234
+ }
235
+
236
+ function _copyDirRecursive(src, dest) {
237
+ fs.mkdirSync(dest, { recursive: true });
238
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
239
+ if (entry.name === "node_modules") continue;
240
+ const s = path.join(src, entry.name), d = path.join(dest, entry.name);
241
+ if (entry.isDirectory()) _copyDirRecursive(s, d);
242
+ else { try { fs.copyFileSync(s, d); } catch {} }
243
+ }
244
+ }
245
+
246
+ function _countFiles(dir) {
247
+ let count = 0;
248
+ try {
249
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
250
+ if (entry.isDirectory()) count += _countFiles(path.join(dir, entry.name));
251
+ else count++;
252
+ }
253
+ } catch {}
254
+ return count;
255
+ }
256
+
257
+ function _backupToMemory(cwd) {
258
+ const backups = {};
259
+ const protect = ["server", ".wolverine"];
260
+ const protectFiles = [".env.local", ".env"];
261
+
262
+ for (const dir of protect) {
263
+ const dirPath = path.join(cwd, dir);
264
+ if (!fs.existsSync(dirPath)) continue;
265
+ const walk = (d, base) => {
266
+ try {
267
+ for (const entry of fs.readdirSync(d, { withFileTypes: true })) {
268
+ if (entry.name === "node_modules") continue;
269
+ const full = path.join(d, entry.name), rel = path.join(base, entry.name).replace(/\\/g, "/");
270
+ if (entry.isDirectory()) walk(full, rel);
271
+ else { try { const s = fs.statSync(full); if (s.size <= 10*1024*1024) backups[rel] = fs.readFileSync(full); } catch {} }
272
+ }
273
+ } catch {}
274
+ };
275
+ walk(dirPath, dir);
276
+ }
277
+ for (const f of protectFiles) {
278
+ const fp = path.join(cwd, f);
279
+ if (fs.existsSync(fp)) backups[f] = fs.readFileSync(fp);
280
+ }
281
+ return backups;
282
+ }
283
+
284
+ function _restoreFromMemory(cwd, backups) {
285
+ for (const [rel, content] of Object.entries(backups)) {
286
+ const fp = path.join(cwd, rel);
287
+ try { fs.mkdirSync(path.dirname(fp), { recursive: true }); fs.writeFileSync(fp, content); } catch {}
288
+ }
289
+ }
290
+
291
+ // ── Skill Metadata ──
292
+
293
+ const SKILL_NAME = "update";
294
+ 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.";
295
+ const SKILL_KEYWORDS = ["update", "upgrade", "version", "install", "pull", "self-update", "auto-update", "framework", "safe"];
296
+ const SKILL_USAGE = `// Safe update (programmatic)
297
+ const { safeUpdate } = require("wolverine-ai");
298
+ const result = await safeUpdate(process.cwd());
299
+ // { success: true, from: "2.5.3", to: "2.6.0", backupDir: "~/.wolverine-safe-backups/..." }
300
+
301
+ // List safe backups
302
+ const { listSafeBackups } = require("wolverine-ai");
303
+ const backups = listSafeBackups();
304
+
305
+ // Restore from safe backup
306
+ const { restoreFromSafeBackup } = require("wolverine-ai");
307
+ restoreFromSafeBackup(process.cwd(), "2026-04-02T21-15-00");
308
+
309
+ // CLI: wolverine --update
310
+ // CLI: wolverine --update --dry-run
311
+ // CLI: wolverine --restore 2026-04-02T21-15-00`;
312
+
313
+ module.exports = {
314
+ SKILL_NAME, SKILL_DESCRIPTION, SKILL_KEYWORDS, SKILL_USAGE,
315
+ safeUpdate, createSafeBackup, listSafeBackups, restoreFromSafeBackup,
316
+ };