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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wolverine-ai",
3
- "version": "2.4.5",
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": {
@@ -81,7 +81,7 @@
81
81
 
82
82
  "autoUpdate": {
83
83
  "enabled": true,
84
- "intervalMs": 3600000
84
+ "intervalMs": 300000
85
85
  },
86
86
 
87
87
  "dashboard": {},
@@ -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 individual config files
102
- const protectedFiles = [".env.local", ".env", ".wolverine/mcp.json", ".wolverine/pricing.json"];
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, configs) — belt AND suspenders
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 configs on failure
207
- restoreConfigs(cwd, configBackups);
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.