wolverine-ai 2.6.0 → 2.6.1

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.0",
3
+ "version": "2.6.1",
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 };
@@ -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"];