nexo-brain 5.4.6 → 5.4.8

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexo-brain",
3
- "version": "5.4.6",
3
+ "version": "5.4.8",
4
4
  "description": "Local cognitive runtime for Claude Code \u2014 persistent memory, overnight learning, doctor diagnostics, personal scripts, recovery-aware jobs, startup preflight, and optional dashboard/power helper.",
5
5
  "author": {
6
6
  "name": "NEXO Brain",
package/README.md CHANGED
@@ -18,9 +18,9 @@
18
18
 
19
19
  [Watch the overview video](https://nexo-brain.com/watch/) · [Watch on YouTube](https://www.youtube.com/watch?v=i2lkGhKyVqI) · [Open the infographic](https://nexo-brain.com/assets/nexo-brain-infographic-v5.png)
20
20
 
21
- Version `5.4.6` is the current packaged-runtime line: runtime dependency management in `nexo update` + daily auto-update cron. `nexo update` now manages external dependencies declared in `runtimeDependencies` (starting with Claude Code).
21
+ Version `5.4.8` is the current packaged-runtime line: tool-enforcement-map v2.0 multi-dimensional enforcement with dependency chains, internal_calls, provides/requires, and 3-level enforcement (must/should/none).
22
22
 
23
- Previously in `5.4.5`: test isolation for tree_hygiene module + fake venv to prevent CI timeout.
23
+ Previously in `5.4.6`: runtime dependency management in `nexo update` + daily auto-update cron.
24
24
 
25
25
  Start here:
26
26
  - [5-minute quickstart](docs/quickstart-5-minutes.md)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexo-brain",
3
- "version": "5.4.6",
3
+ "version": "5.4.8",
4
4
  "mcpName": "io.github.wazionapps/nexo",
5
5
  "description": "NEXO Brain — Shared brain for AI agents. Persistent memory, semantic RAG, natural forgetting, metacognitive guard, trust scoring, 150+ MCP tools. Works with Claude Code, Codex, Claude Desktop & any MCP client. 100% local, free.",
6
6
  "homepage": "https://nexo-brain.com",
@@ -207,7 +207,7 @@
207
207
  {
208
208
  "id": "auto-update",
209
209
  "script": "scripts/nexo-auto-update.py",
210
- "schedule": {"hour": 3, "minute": 45},
210
+ "schedule": {"hour": 2, "minute": 0},
211
211
  "description": "Daily auto-update — Brain + runtime dependencies",
212
212
  "core": true,
213
213
  "recovery_policy": "catchup",
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
  """Update plugin — pull latest code, backup DBs, run migrations, verify."""
3
3
  import json
4
4
  import os
5
+ import re
5
6
  import shutil
6
7
  import sqlite3
7
8
  import subprocess
@@ -347,6 +348,14 @@ def _read_runtime_dependencies() -> list[dict]:
347
348
  return []
348
349
 
349
350
 
351
+ _VALID_NPM_NAME = re.compile(r'^(@[a-z0-9\-_.]+/)?[a-z0-9\-_.]+$')
352
+
353
+
354
+ def _validate_npm_name(name: str) -> bool:
355
+ """Validate that a package name follows npm naming conventions."""
356
+ return bool(name) and bool(_VALID_NPM_NAME.match(name)) and ".." not in name
357
+
358
+
350
359
  def _get_npm_global_version(package_name: str) -> str | None:
351
360
  """Return the currently installed global npm package version, or None."""
352
361
  try:
@@ -354,7 +363,9 @@ def _get_npm_global_version(package_name: str) -> str | None:
354
363
  ["npm", "list", "-g", package_name, "--json", "--depth=0"],
355
364
  capture_output=True, text=True, timeout=15,
356
365
  )
357
- if result.returncode == 0:
366
+ # npm list returns exit 1 with valid JSON for peer dep issues;
367
+ # always try to parse the output regardless of returncode.
368
+ if result.stdout.strip():
358
369
  data = json.loads(result.stdout)
359
370
  deps = data.get("dependencies", {})
360
371
  info = deps.get(package_name)
@@ -362,6 +373,8 @@ def _get_npm_global_version(package_name: str) -> str | None:
362
373
  return info.get("version")
363
374
  except subprocess.TimeoutExpired:
364
375
  pass
376
+ except FileNotFoundError:
377
+ pass # npm not installed
365
378
  except Exception:
366
379
  pass
367
380
  return None
@@ -378,6 +391,8 @@ def _get_npm_registry_version(package_name: str) -> str | None:
378
391
  return result.stdout.strip()
379
392
  except subprocess.TimeoutExpired:
380
393
  pass
394
+ except FileNotFoundError:
395
+ pass # npm not installed
381
396
  except Exception:
382
397
  pass
383
398
  return None
@@ -401,7 +416,7 @@ def _update_runtime_dependencies(progress_fn=None) -> list[dict]:
401
416
  for dep in deps:
402
417
  name = dep.get("name", "")
403
418
  dep_type = dep.get("type", "")
404
- optional = dep.get("optional", True)
419
+ optional = dep.get("optional", False)
405
420
 
406
421
  if not name or dep_type != "npm-global":
407
422
  results.append({
@@ -412,6 +427,16 @@ def _update_runtime_dependencies(progress_fn=None) -> list[dict]:
412
427
  })
413
428
  continue
414
429
 
430
+ if not _validate_npm_name(name):
431
+ results.append({
432
+ "name": name,
433
+ "old_version": None,
434
+ "new_version": None,
435
+ "status": "failed",
436
+ "error": f"invalid npm package name: {name!r}",
437
+ })
438
+ continue
439
+
415
440
  _emit_progress(progress_fn, f"Checking runtime dependency: {name}...")
416
441
 
417
442
  old_version = _get_npm_global_version(name)