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 +1 -1
- package/src/backup/backup-manager.js +43 -7
- package/src/brain/brain.js +1 -1
- package/src/skills/update.js +8 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wolverine-ai",
|
|
3
|
-
"version": "2.6.
|
|
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
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
37
|
-
this.
|
|
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() {
|
|
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 };
|
package/src/brain/brain.js
CHANGED
|
@@ -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.
|
|
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
|
{
|
package/src/skills/update.js
CHANGED
|
@@ -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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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"];
|