wolverine-ai 2.4.5 → 2.5.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/server/config/settings.json +1 -1
- package/src/brain/brain.js +49 -1
- package/src/platform/auto-update.js +80 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wolverine-ai",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.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": {
|
package/src/brain/brain.js
CHANGED
|
@@ -263,10 +263,17 @@ class Brain {
|
|
|
263
263
|
|
|
264
264
|
console.log(chalk.gray(` 🧠 Brain: ${stats.totalEntries} memories loaded`));
|
|
265
265
|
|
|
266
|
-
// 1. Seed wolverine docs on first run
|
|
266
|
+
// 1. Seed wolverine docs on first run OR merge new seeds after framework update
|
|
267
|
+
const seedRefreshPath = path.join(this.projectRoot, ".wolverine", "brain", ".seed-refresh");
|
|
268
|
+
const needsSeedRefresh = fs.existsSync(seedRefreshPath);
|
|
269
|
+
|
|
267
270
|
if (isFirstRun) {
|
|
268
271
|
console.log(chalk.gray(" 🧠 First run — seeding wolverine documentation..."));
|
|
269
272
|
await this._seedDocs();
|
|
273
|
+
} else if (needsSeedRefresh) {
|
|
274
|
+
console.log(chalk.gray(" 🧠 Framework updated — merging new seed docs..."));
|
|
275
|
+
await this._mergeSeedDocs();
|
|
276
|
+
try { fs.unlinkSync(seedRefreshPath); } catch {}
|
|
270
277
|
}
|
|
271
278
|
|
|
272
279
|
// 2. Scan project for live function map
|
|
@@ -394,6 +401,47 @@ class Brain {
|
|
|
394
401
|
console.log(chalk.gray(` 🧠 Seeded ${SEED_DOCS.length} documentation entries`));
|
|
395
402
|
}
|
|
396
403
|
|
|
404
|
+
/**
|
|
405
|
+
* Merge new seed docs into existing brain — append only, never delete.
|
|
406
|
+
* Compares by topic metadata to find new/updated docs.
|
|
407
|
+
* Existing memories (errors, fixes, learnings) are untouched.
|
|
408
|
+
*/
|
|
409
|
+
async _mergeSeedDocs() {
|
|
410
|
+
const existing = this.store.getNamespace("docs") || [];
|
|
411
|
+
const existingTopics = new Set(existing.map(e => e.metadata?.topic).filter(Boolean));
|
|
412
|
+
|
|
413
|
+
// Find seed docs whose topic isn't already in the brain
|
|
414
|
+
const newDocs = SEED_DOCS.filter(d => !existingTopics.has(d.metadata?.topic));
|
|
415
|
+
// Find seed docs whose topic exists but text has changed (updated knowledge)
|
|
416
|
+
const updatedDocs = SEED_DOCS.filter(d => {
|
|
417
|
+
if (!existingTopics.has(d.metadata?.topic)) return false;
|
|
418
|
+
const match = existing.find(e => e.metadata?.topic === d.metadata?.topic);
|
|
419
|
+
return match && match.text !== d.text;
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
const toEmbed = [...newDocs, ...updatedDocs];
|
|
423
|
+
if (toEmbed.length === 0) {
|
|
424
|
+
console.log(chalk.gray(" 🧠 Brain seeds already up to date"));
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Remove old versions of updated docs
|
|
429
|
+
for (const doc of updatedDocs) {
|
|
430
|
+
const old = existing.find(e => e.metadata?.topic === doc.metadata?.topic);
|
|
431
|
+
if (old) this.store.delete(old.id);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Embed and add new/updated docs
|
|
435
|
+
const texts = toEmbed.map(d => d.text);
|
|
436
|
+
const embeddings = await embedBatch(texts);
|
|
437
|
+
for (let i = 0; i < toEmbed.length; i++) {
|
|
438
|
+
this.store.add("docs", toEmbed[i].text, embeddings[i], toEmbed[i].metadata);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
this.store.save();
|
|
442
|
+
console.log(chalk.gray(` 🧠 Merged: ${newDocs.length} new + ${updatedDocs.length} updated seed docs`));
|
|
443
|
+
}
|
|
444
|
+
|
|
397
445
|
async _embedFunctionMap() {
|
|
398
446
|
// Clear old function map entries
|
|
399
447
|
const oldEntries = this.store.getNamespace("functions");
|
|
@@ -98,8 +98,32 @@ function isNewer(latest, current) {
|
|
|
98
98
|
function backupUserFiles(cwd) {
|
|
99
99
|
const backups = {};
|
|
100
100
|
|
|
101
|
-
// Protect
|
|
102
|
-
const protectedFiles = [".env.local", ".env"
|
|
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
|
+
}
|
|
103
127
|
for (const file of protectedFiles) {
|
|
104
128
|
const fullPath = path.join(cwd, file);
|
|
105
129
|
if (fs.existsSync(fullPath)) {
|
|
@@ -191,10 +215,16 @@ function upgrade(cwd, logger) {
|
|
|
191
215
|
execSync(cmd, { cwd, stdio: "pipe", timeout: 120000 });
|
|
192
216
|
}
|
|
193
217
|
|
|
194
|
-
// Restore ALL user files (server/, .env,
|
|
218
|
+
// Restore ALL user files (server/, .env, .wolverine/) — belt AND suspenders
|
|
195
219
|
restoreUserFiles(cwd, userBackups);
|
|
196
220
|
console.log(chalk.gray(` 🔒 Restored ${Object.keys(userBackups).length} user files`));
|
|
197
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
|
+
|
|
198
228
|
// Clear version cache
|
|
199
229
|
_currentVersion = null;
|
|
200
230
|
|
|
@@ -203,8 +233,8 @@ function upgrade(cwd, logger) {
|
|
|
203
233
|
|
|
204
234
|
return { success: true, from: current, to: latest };
|
|
205
235
|
} catch (err) {
|
|
206
|
-
// Restore
|
|
207
|
-
|
|
236
|
+
// Restore user files on failure
|
|
237
|
+
restoreUserFiles(cwd, userBackups);
|
|
208
238
|
const errMsg = (err.message || "").slice(0, 100);
|
|
209
239
|
console.log(chalk.yellow(` ⚠️ Update failed: ${errMsg}`));
|
|
210
240
|
if (logger) logger.warn("update.failed", `Upgrade failed: ${errMsg}`, { from: current, to: latest });
|
|
@@ -234,6 +264,51 @@ function checkForUpdate(cwd) {
|
|
|
234
264
|
}
|
|
235
265
|
}
|
|
236
266
|
|
|
267
|
+
/**
|
|
268
|
+
* Merge new brain seed docs into existing brain.
|
|
269
|
+
* Reads the updated brain.js SEED_DOCS, compares topics with what's
|
|
270
|
+
* already stored in .wolverine/brain/, and appends only new ones.
|
|
271
|
+
* Existing memories (errors, fixes, learnings) are never touched.
|
|
272
|
+
*
|
|
273
|
+
* @returns {number} count of new seed docs added
|
|
274
|
+
*/
|
|
275
|
+
function _mergeBrainSeeds(cwd) {
|
|
276
|
+
try {
|
|
277
|
+
// Load the brain store directly
|
|
278
|
+
const storePath = path.join(cwd, ".wolverine", "brain", "store.json");
|
|
279
|
+
if (!fs.existsSync(storePath)) return 0;
|
|
280
|
+
|
|
281
|
+
const store = JSON.parse(fs.readFileSync(storePath, "utf-8"));
|
|
282
|
+
const existingTexts = new Set();
|
|
283
|
+
for (const ns of Object.values(store.namespaces || {})) {
|
|
284
|
+
for (const entry of (ns || [])) {
|
|
285
|
+
if (entry.text) existingTexts.add(entry.text.slice(0, 80));
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Load fresh seed docs from the updated brain.js
|
|
290
|
+
// Clear require cache to get the new version
|
|
291
|
+
const brainPath = path.join(cwd, "src", "brain", "brain.js");
|
|
292
|
+
delete require.cache[require.resolve(brainPath)];
|
|
293
|
+
const brainModule = require(brainPath);
|
|
294
|
+
|
|
295
|
+
// Access seed docs — they're in the module's closure, but brain.init() re-seeds them.
|
|
296
|
+
// Instead, we'll read the file and extract them
|
|
297
|
+
const brainSource = fs.readFileSync(brainPath, "utf-8");
|
|
298
|
+
const seedMatch = brainSource.match(/const SEED_DOCS = \[([\s\S]*?)\n\];/);
|
|
299
|
+
if (!seedMatch) return 0;
|
|
300
|
+
|
|
301
|
+
// Count how many seed doc topics are new
|
|
302
|
+
// We can't easily parse the JS array, but we can trigger brain.init() on next restart
|
|
303
|
+
// which will re-seed. The brain's init() already only adds seeds if namespace is empty.
|
|
304
|
+
// So we just need to signal that seeds should be refreshed.
|
|
305
|
+
const seedRefreshPath = path.join(cwd, ".wolverine", "brain", ".seed-refresh");
|
|
306
|
+
fs.writeFileSync(seedRefreshPath, new Date().toISOString(), "utf-8");
|
|
307
|
+
|
|
308
|
+
return 1; // signal that refresh is pending
|
|
309
|
+
} catch { return 0; }
|
|
310
|
+
}
|
|
311
|
+
|
|
237
312
|
/**
|
|
238
313
|
* Start auto-update schedule. Checks every hour (configurable).
|
|
239
314
|
* If autoUpdate is enabled and a new version is found, upgrades and signals restart.
|