wolverine-ai 2.6.0 → 2.6.2

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
@@ -535,14 +535,22 @@ All demos use the `server/` directory pattern. Each demo:
535
535
 
536
536
  ## Backup System
537
537
 
538
- Full `server/` directory snapshots with lifecycle management:
538
+ All backups stored in **`~/.wolverine-safe-backups/`** — outside the project directory. Survives `git pull`, `npm install`, `rm -rf .wolverine`, even deleting the project entirely.
539
539
 
540
- - Created before every repair attempt and every smart edit (with reason string)
540
+ ```
541
+ ~/.wolverine-safe-backups/
542
+ manifest.json ← backup registry
543
+ snapshots/ ← heal snapshots (per fix attempt)
544
+ updates/ ← pre-update snapshots (before framework upgrades)
545
+ ```
546
+
547
+ - Created before every repair attempt and every framework update (with reason string)
541
548
  - Created on graceful shutdown (`createShutdownBackup()`)
542
549
  - Includes all files: `.js`, `.json`, `.sql`, `.db`, `.yaml`, configs
550
+ - Old `.wolverine/backups/` auto-migrated to safe location on first run
543
551
  - **Status lifecycle**: UNSTABLE → VERIFIED (fix passed) → STABLE (30min+ uptime)
544
552
  - **Retention**: unstable/verified pruned after 7 days, stable keeps 1/day after 7 days
545
- - Atomic writes prevent corruption on kill
553
+ - Protected files never overwritten during rollback: `settings.json`, `db.js`, `.env.local`
546
554
 
547
555
  **Rollback & Recovery:**
548
556
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wolverine-ai",
3
- "version": "2.6.0",
3
+ "version": "2.6.2",
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": {
@@ -14,9 +14,13 @@ const { redact } = require("../security/secret-redactor");
14
14
  * Stable backups older than 7 days → keep 1 per day (most recent).
15
15
  */
16
16
 
17
- const WOLVERINE_DIR = ".wolverine";
18
- const BACKUPS_DIR = path.join(WOLVERINE_DIR, "backups");
19
- const MANIFEST_FILE = path.join(BACKUPS_DIR, "manifest.json");
17
+ const os = require("os");
18
+
19
+ // Safe backup location — OUTSIDE the project directory.
20
+ // Survives git pull, npm install, rm -rf .wolverine, project deletion.
21
+ // All backup infrastructure (heal snapshots, update snapshots, rollbacks) uses this.
22
+ const SAFE_BACKUP_ROOT = path.join(os.homedir(), ".wolverine-safe-backups");
23
+ const MANIFEST_FILE_NAME = "manifest.json";
20
24
  const STABILITY_THRESHOLD_MS = 30 * 60 * 1000;
21
25
  const RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
22
26
 
@@ -33,9 +37,11 @@ const NEVER_ROLLBACK = [
33
37
  class BackupManager {
34
38
  constructor(projectRoot) {
35
39
  this.projectRoot = path.resolve(projectRoot);
36
- this.backupsDir = path.join(this.projectRoot, BACKUPS_DIR);
37
- this.manifestPath = path.join(this.projectRoot, MANIFEST_FILE);
40
+ // All backups in safe home directory — never inside the project
41
+ this.backupsDir = path.join(SAFE_BACKUP_ROOT, "snapshots");
42
+ this.manifestPath = path.join(SAFE_BACKUP_ROOT, MANIFEST_FILE_NAME);
38
43
  this._ensureDirs();
44
+ this._migrateOldBackups(); // one-time migration from .wolverine/backups/
39
45
  this.manifest = this._loadManifest();
40
46
  }
41
47
 
@@ -276,7 +282,37 @@ class BackupManager {
276
282
 
277
283
  // -- Private --
278
284
 
279
- _ensureDirs() { fs.mkdirSync(this.backupsDir, { recursive: true }); }
285
+ _ensureDirs() {
286
+ fs.mkdirSync(this.backupsDir, { recursive: true });
287
+ fs.mkdirSync(SAFE_BACKUP_ROOT, { recursive: true });
288
+ }
289
+
290
+ /**
291
+ * One-time migration: move backups from old .wolverine/backups/ to safe location.
292
+ */
293
+ _migrateOldBackups() {
294
+ const oldDir = path.join(this.projectRoot, ".wolverine", "backups");
295
+ const oldManifest = path.join(oldDir, "manifest.json");
296
+ if (!fs.existsSync(oldManifest) || fs.existsSync(this.manifestPath)) return;
297
+
298
+ try {
299
+ // Copy manifest
300
+ fs.copyFileSync(oldManifest, this.manifestPath);
301
+ // Copy backup dirs
302
+ for (const entry of fs.readdirSync(oldDir, { withFileTypes: true })) {
303
+ if (!entry.isDirectory()) continue;
304
+ const src = path.join(oldDir, entry.name);
305
+ const dest = path.join(this.backupsDir, entry.name);
306
+ if (!fs.existsSync(dest)) {
307
+ fs.mkdirSync(dest, { recursive: true });
308
+ for (const file of fs.readdirSync(src)) {
309
+ fs.copyFileSync(path.join(src, file), path.join(dest, file));
310
+ }
311
+ }
312
+ }
313
+ console.log(chalk.gray(` 📦 Migrated backups to safe location: ${SAFE_BACKUP_ROOT}`));
314
+ } catch {}
315
+ }
280
316
 
281
317
  _loadManifest() {
282
318
  if (fs.existsSync(this.manifestPath)) {
@@ -323,4 +359,4 @@ class BackupManager {
323
359
  }
324
360
  }
325
361
 
326
- module.exports = { BackupManager, STABILITY_THRESHOLD_MS };
362
+ module.exports = { BackupManager, STABILITY_THRESHOLD_MS, SAFE_BACKUP_ROOT };
@@ -38,7 +38,7 @@ const SEED_DOCS = [
38
38
  metadata: { topic: "heal-pipeline" },
39
39
  },
40
40
  {
41
- text: "Wolverine backup system: full server/ directory snapshots with lifecycle management. Every fix creates a backup with a reason string before patching. Status lifecycle: UNSTABLE (just created) → VERIFIED (fix passed boot probe) → STABLE (server ran 30min+ without crash). Features: rollbackTo(backupId) creates pre-rollback backup then restores files and restarts server. undoRollback() restores pre-rollback state. Hot-load: admin can load any backup as current server state from dashboard. Shutdown backup on graceful exit. Retention: unstable/verified pruned after 7 days. Stable backups older than 7 days keep 1 per day. Rollback log tracks all rollback/undo operations with timestamps and success status. Dashboard endpoints: POST /api/backups/:id/rollback, POST /api/backups/undo, POST /api/backups/:id/hotload (all require admin auth).",
41
+ text: "Wolverine backup system: ALL backups stored in ~/.wolverine-safe-backups/ (OUTSIDE project, survives git pull/npm install/rm -rf). Structure: snapshots/ (heal backups per fix attempt), updates/ (pre-update snapshots), manifest.json (backup registry). Old .wolverine/backups/ auto-migrated on first run. Full server/ directory snapshots with lifecycle management. Every fix creates a backup with a reason string before patching. Status lifecycle: UNSTABLE (just created) → VERIFIED (fix passed boot probe) → STABLE (server ran 30min+ without crash). Features: rollbackTo(backupId) creates pre-rollback backup then restores files and restarts server. undoRollback() restores pre-rollback state. Hot-load: admin can load any backup as current server state from dashboard. Shutdown backup on graceful exit. Retention: unstable/verified pruned after 7 days. Stable backups older than 7 days keep 1 per day. Rollback log tracks all rollback/undo operations with timestamps and success status. Dashboard endpoints: POST /api/backups/:id/rollback, POST /api/backups/undo, POST /api/backups/:id/hotload (all require admin auth).",
42
42
  metadata: { topic: "backup-system" },
43
43
  },
44
44
  {
@@ -238,7 +238,7 @@ const SEED_DOCS = [
238
238
  metadata: { topic: "skill-deps" },
239
239
  },
240
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.",
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. ALL backups (heal snapshots + update snapshots) stored in ~/.wolverine-safe-backups/ (OUTSIDE project, survives git clean, rm -rf, project deletion). Structure: ~/.wolverine-safe-backups/snapshots/ (heal backups), ~/.wolverine-safe-backups/updates/ (pre-update snapshots), ~/.wolverine-safe-backups/manifest.json (backup registry). Old .wolverine/backups/ auto-migrated on first run. Restore with: wolverine --restore <name>. List: wolverine --backups.",
242
242
  metadata: { topic: "safe-update-warning" },
243
243
  },
244
244
  {
@@ -27,6 +27,7 @@ const chalk = require("chalk");
27
27
 
28
28
  const PACKAGE_NAME = "wolverine-ai";
29
29
  const SAFE_BACKUP_DIR = path.join(require("os").homedir(), ".wolverine-safe-backups");
30
+ const SAFE_SNAPSHOTS_DIR = path.join(SAFE_BACKUP_DIR, "snapshots");
30
31
 
31
32
  /**
32
33
  * Create a safe backup snapshot outside the project directory.
@@ -34,7 +35,7 @@ const SAFE_BACKUP_DIR = path.join(require("os").homedir(), ".wolverine-safe-back
34
35
  */
35
36
  function createSafeBackup(cwd) {
36
37
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
37
- const backupDir = path.join(SAFE_BACKUP_DIR, timestamp);
38
+ const backupDir = path.join(SAFE_BACKUP_DIR, "updates", timestamp);
38
39
  fs.mkdirSync(backupDir, { recursive: true });
39
40
 
40
41
  const dirsToBackup = [
@@ -76,12 +77,13 @@ function createSafeBackup(cwd) {
76
77
  * List available safe backups.
77
78
  */
78
79
  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())
80
+ const updatesDir = path.join(SAFE_BACKUP_DIR, "updates");
81
+ if (!fs.existsSync(updatesDir)) return [];
82
+ return fs.readdirSync(updatesDir)
83
+ .filter(d => fs.statSync(path.join(updatesDir, d)).isDirectory())
82
84
  .map(d => {
83
85
  try {
84
- const manifest = JSON.parse(fs.readFileSync(path.join(SAFE_BACKUP_DIR, d, "manifest.json"), "utf-8"));
86
+ const manifest = JSON.parse(fs.readFileSync(path.join(SAFE_BACKUP_DIR, "updates", d, "manifest.json"), "utf-8"));
85
87
  return { dir: d, ...manifest };
86
88
  } catch { return { dir: d }; }
87
89
  })
@@ -92,7 +94,7 @@ function listSafeBackups() {
92
94
  * Restore from a safe backup.
93
95
  */
94
96
  function restoreFromSafeBackup(cwd, backupName) {
95
- const backupDir = path.join(SAFE_BACKUP_DIR, backupName);
97
+ const backupDir = path.join(SAFE_BACKUP_DIR, "updates", backupName);
96
98
  if (!fs.existsSync(backupDir)) throw new Error(`Backup not found: ${backupName}`);
97
99
 
98
100
  const dirsToRestore = [".wolverine", "server"];