nexo-brain 5.1.0 → 5.1.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.
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.1",
|
|
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.1",
|
|
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",
|
|
@@ -2710,6 +2710,137 @@ def check_release_artifact_sync() -> DoctorCheck:
|
|
|
2710
2710
|
)
|
|
2711
2711
|
|
|
2712
2712
|
|
|
2713
|
+
def check_release_trace_hygiene() -> DoctorCheck:
|
|
2714
|
+
db_path = NEXO_HOME / "data" / "nexo.db"
|
|
2715
|
+
if not db_path.is_file():
|
|
2716
|
+
return DoctorCheck(
|
|
2717
|
+
id="runtime.release_trace_hygiene",
|
|
2718
|
+
tier="runtime",
|
|
2719
|
+
status="healthy",
|
|
2720
|
+
severity="info",
|
|
2721
|
+
summary="Release trace hygiene unavailable (no DB)",
|
|
2722
|
+
evidence=[],
|
|
2723
|
+
repair_plan=[],
|
|
2724
|
+
escalation_prompt="",
|
|
2725
|
+
)
|
|
2726
|
+
|
|
2727
|
+
try:
|
|
2728
|
+
conn = sqlite3.connect(str(db_path), timeout=2)
|
|
2729
|
+
conn.row_factory = sqlite3.Row
|
|
2730
|
+
try:
|
|
2731
|
+
tables = {
|
|
2732
|
+
row[0]
|
|
2733
|
+
for row in conn.execute(
|
|
2734
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name IN ('workflow_goals', 'workflow_runs')"
|
|
2735
|
+
).fetchall()
|
|
2736
|
+
}
|
|
2737
|
+
if "workflow_goals" not in tables or "workflow_runs" not in tables:
|
|
2738
|
+
return DoctorCheck(
|
|
2739
|
+
id="runtime.release_trace_hygiene",
|
|
2740
|
+
tier="runtime",
|
|
2741
|
+
status="healthy",
|
|
2742
|
+
severity="info",
|
|
2743
|
+
summary="Release trace hygiene unavailable (workflow tables absent)",
|
|
2744
|
+
evidence=[],
|
|
2745
|
+
repair_plan=[],
|
|
2746
|
+
escalation_prompt="",
|
|
2747
|
+
)
|
|
2748
|
+
|
|
2749
|
+
stale_run_samples: list[str] = []
|
|
2750
|
+
stale_goal_samples: list[str] = []
|
|
2751
|
+
now = dt.datetime.now(dt.timezone.utc)
|
|
2752
|
+
stale_after_hours = 6
|
|
2753
|
+
|
|
2754
|
+
run_rows = conn.execute(
|
|
2755
|
+
"""SELECT run_id, goal, updated_at
|
|
2756
|
+
FROM workflow_runs
|
|
2757
|
+
WHERE workflow_kind = 'audit-phase'
|
|
2758
|
+
AND status NOT IN ('completed', 'failed', 'cancelled')
|
|
2759
|
+
ORDER BY updated_at DESC"""
|
|
2760
|
+
).fetchall()
|
|
2761
|
+
for row in run_rows:
|
|
2762
|
+
updated_at = _parse_timestamp(row["updated_at"] or "")
|
|
2763
|
+
if updated_at is None:
|
|
2764
|
+
stale_run_samples.append(f"{row['run_id']}: unreadable updated_at")
|
|
2765
|
+
continue
|
|
2766
|
+
if updated_at.tzinfo is None:
|
|
2767
|
+
updated_at = updated_at.replace(tzinfo=dt.timezone.utc)
|
|
2768
|
+
age_hours = (now - updated_at).total_seconds() / 3600
|
|
2769
|
+
if age_hours >= stale_after_hours:
|
|
2770
|
+
stale_run_samples.append(
|
|
2771
|
+
f"{row['run_id']}: {age_hours:.1f}h stale ({str(row['goal'] or '')[:72]})"
|
|
2772
|
+
)
|
|
2773
|
+
|
|
2774
|
+
goal_rows = conn.execute(
|
|
2775
|
+
"""SELECT g.goal_id, g.title, g.updated_at,
|
|
2776
|
+
COALESCE((SELECT COUNT(*) FROM workflow_runs r WHERE r.goal_id = g.goal_id), 0) AS run_count,
|
|
2777
|
+
COALESCE((SELECT COUNT(*) FROM workflow_runs r WHERE r.goal_id = g.goal_id
|
|
2778
|
+
AND r.status NOT IN ('completed', 'failed', 'cancelled')), 0) AS open_run_count
|
|
2779
|
+
FROM workflow_goals g
|
|
2780
|
+
WHERE g.status = 'active'
|
|
2781
|
+
AND (g.goal_id LIKE 'WG-AUDIT-%' OR g.title LIKE 'NEXO-AUDIT-%')
|
|
2782
|
+
ORDER BY g.updated_at DESC"""
|
|
2783
|
+
).fetchall()
|
|
2784
|
+
for row in goal_rows:
|
|
2785
|
+
if int(row["open_run_count"] or 0) > 0:
|
|
2786
|
+
continue
|
|
2787
|
+
updated_at = _parse_timestamp(row["updated_at"] or "")
|
|
2788
|
+
if updated_at is None:
|
|
2789
|
+
stale_goal_samples.append(f"{row['goal_id']}: unreadable updated_at")
|
|
2790
|
+
continue
|
|
2791
|
+
if updated_at.tzinfo is None:
|
|
2792
|
+
updated_at = updated_at.replace(tzinfo=dt.timezone.utc)
|
|
2793
|
+
age_hours = (now - updated_at).total_seconds() / 3600
|
|
2794
|
+
if age_hours >= stale_after_hours:
|
|
2795
|
+
stale_goal_samples.append(
|
|
2796
|
+
f"{row['goal_id']}: {age_hours:.1f}h stale ({str(row['title'] or '')[:72]})"
|
|
2797
|
+
)
|
|
2798
|
+
finally:
|
|
2799
|
+
conn.close()
|
|
2800
|
+
except Exception as exc:
|
|
2801
|
+
return DoctorCheck(
|
|
2802
|
+
id="runtime.release_trace_hygiene",
|
|
2803
|
+
tier="runtime",
|
|
2804
|
+
status="degraded",
|
|
2805
|
+
severity="warn",
|
|
2806
|
+
summary="Release trace hygiene check failed",
|
|
2807
|
+
evidence=[str(exc)],
|
|
2808
|
+
repair_plan=["Inspect workflow_goals/workflow_runs state manually"],
|
|
2809
|
+
escalation_prompt="Release traces could not be audited, so stale audit artifacts may be hiding in the runtime.",
|
|
2810
|
+
)
|
|
2811
|
+
|
|
2812
|
+
evidence = [
|
|
2813
|
+
f"stale audit workflows: {len(stale_run_samples)}",
|
|
2814
|
+
f"stale audit goals: {len(stale_goal_samples)}",
|
|
2815
|
+
]
|
|
2816
|
+
evidence.extend(stale_run_samples[:3])
|
|
2817
|
+
evidence.extend(stale_goal_samples[:3])
|
|
2818
|
+
if stale_run_samples or stale_goal_samples:
|
|
2819
|
+
return DoctorCheck(
|
|
2820
|
+
id="runtime.release_trace_hygiene",
|
|
2821
|
+
tier="runtime",
|
|
2822
|
+
status="degraded",
|
|
2823
|
+
severity="warn",
|
|
2824
|
+
summary="Release trace hygiene needs cleanup",
|
|
2825
|
+
evidence=evidence,
|
|
2826
|
+
repair_plan=[
|
|
2827
|
+
"Close or complete stale audit-phase workflows and active audit goals",
|
|
2828
|
+
"Keep workflow/goal state aligned with the real shipped state after releases",
|
|
2829
|
+
],
|
|
2830
|
+
escalation_prompt="Audit/release traces drifted away from reality, which makes shipping state look ambiguous.",
|
|
2831
|
+
)
|
|
2832
|
+
return DoctorCheck(
|
|
2833
|
+
id="runtime.release_trace_hygiene",
|
|
2834
|
+
tier="runtime",
|
|
2835
|
+
status="healthy",
|
|
2836
|
+
severity="info",
|
|
2837
|
+
summary="Release trace hygiene OK",
|
|
2838
|
+
evidence=evidence,
|
|
2839
|
+
repair_plan=[],
|
|
2840
|
+
escalation_prompt="",
|
|
2841
|
+
)
|
|
2842
|
+
|
|
2843
|
+
|
|
2713
2844
|
def check_state_watchers() -> DoctorCheck:
|
|
2714
2845
|
db_path = NEXO_HOME / "data" / "nexo.db"
|
|
2715
2846
|
summary_path = NEXO_HOME / "operations" / "state-watchers-status.json"
|
|
@@ -2988,6 +3119,7 @@ def run_runtime_checks(fix: bool = False) -> list[DoctorCheck]:
|
|
|
2988
3119
|
safe_check(check_automation_telemetry),
|
|
2989
3120
|
safe_check(check_state_watchers),
|
|
2990
3121
|
safe_check(check_release_artifact_sync),
|
|
3122
|
+
safe_check(check_release_trace_hygiene),
|
|
2991
3123
|
safe_check(check_launchagent_inventory),
|
|
2992
3124
|
safe_check(check_launchagent_integrity, fix=fix),
|
|
2993
3125
|
safe_check(check_personal_script_registry, fix=fix),
|
|
@@ -229,8 +229,20 @@ def handle_session_diary_write(decisions: str, summary: str,
|
|
|
229
229
|
orphan_changes = conn.execute(
|
|
230
230
|
"SELECT COUNT(*) FROM change_log WHERE (commit_ref IS NULL OR commit_ref = '')"
|
|
231
231
|
).fetchone()[0]
|
|
232
|
+
recent_orphan_changes = conn.execute(
|
|
233
|
+
"""SELECT COUNT(*) FROM change_log
|
|
234
|
+
WHERE (commit_ref IS NULL OR commit_ref = '')
|
|
235
|
+
AND created_at >= datetime('now', '-7 days')"""
|
|
236
|
+
).fetchone()[0]
|
|
232
237
|
if orphan_changes > 0:
|
|
233
|
-
|
|
238
|
+
if recent_orphan_changes > 0 and recent_orphan_changes != orphan_changes:
|
|
239
|
+
warnings.append(
|
|
240
|
+
f"{recent_orphan_changes} changes recientes sin commit_ref ({orphan_changes} históricas total)"
|
|
241
|
+
)
|
|
242
|
+
elif recent_orphan_changes > 0:
|
|
243
|
+
warnings.append(f"{recent_orphan_changes} changes recientes sin commit_ref")
|
|
244
|
+
else:
|
|
245
|
+
warnings.append(f"{orphan_changes} changes históricas sin commit_ref")
|
|
234
246
|
orphan_decisions = conn.execute(
|
|
235
247
|
"SELECT COUNT(*) FROM decisions WHERE (outcome IS NULL OR outcome = '') AND created_at < datetime('now', '-7 days')"
|
|
236
248
|
).fetchone()[0]
|
|
@@ -78,6 +78,10 @@ CLAUDE_CLI = _resolve_claude_cli()
|
|
|
78
78
|
|
|
79
79
|
findings = []
|
|
80
80
|
|
|
81
|
+
AUDIT_GOAL_NEXT_ACTION = "Convert the recurring theme into an explicit workflow or close it as intentional noise."
|
|
82
|
+
AUDIT_GOAL_OWNER = "system:self-audit"
|
|
83
|
+
AUDIT_GOAL_STALE_HOURS = 36
|
|
84
|
+
|
|
81
85
|
|
|
82
86
|
def log(msg):
|
|
83
87
|
ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
@@ -492,7 +496,7 @@ def _upsert_workflow_goal_inline(conn: sqlite3.Connection, *, area: str, sample_
|
|
|
492
496
|
f"Recurring {area} theme detected by daily self-audit. "
|
|
493
497
|
f"The theme '{sample_goal}' appeared {count} times without a durable goal, learning, or resolved workflow."
|
|
494
498
|
)
|
|
495
|
-
next_action =
|
|
499
|
+
next_action = AUDIT_GOAL_NEXT_ACTION
|
|
496
500
|
success_signal = "The theme stops resurfacing in unresolved protocol tasks."
|
|
497
501
|
now_iso = datetime.now().isoformat(timespec="seconds")
|
|
498
502
|
if existing:
|
|
@@ -504,7 +508,7 @@ def _upsert_workflow_goal_inline(conn: sqlite3.Connection, *, area: str, sample_
|
|
|
504
508
|
if "priority" in columns:
|
|
505
509
|
updates["priority"] = "high"
|
|
506
510
|
if "owner" in columns:
|
|
507
|
-
updates["owner"] =
|
|
511
|
+
updates["owner"] = AUDIT_GOAL_OWNER
|
|
508
512
|
if "next_action" in columns:
|
|
509
513
|
updates["next_action"] = next_action
|
|
510
514
|
if "success_signal" in columns:
|
|
@@ -534,7 +538,7 @@ def _upsert_workflow_goal_inline(conn: sqlite3.Connection, *, area: str, sample_
|
|
|
534
538
|
if "priority" in columns:
|
|
535
539
|
values["priority"] = "high"
|
|
536
540
|
if "owner" in columns:
|
|
537
|
-
values["owner"] =
|
|
541
|
+
values["owner"] = AUDIT_GOAL_OWNER
|
|
538
542
|
if "next_action" in columns:
|
|
539
543
|
values["next_action"] = next_action
|
|
540
544
|
if "success_signal" in columns:
|
|
@@ -553,6 +557,75 @@ def _upsert_workflow_goal_inline(conn: sqlite3.Connection, *, area: str, sample_
|
|
|
553
557
|
return {"ok": True, "action": "created", "goal_id": goal_id}
|
|
554
558
|
|
|
555
559
|
|
|
560
|
+
def _retire_stale_audit_goals_inline(
|
|
561
|
+
conn: sqlite3.Connection, *, max_age_hours: int = AUDIT_GOAL_STALE_HOURS
|
|
562
|
+
) -> dict:
|
|
563
|
+
if not _table_exists(conn, "workflow_goals"):
|
|
564
|
+
return {"ok": False, "reason": "workflow_goals_missing"}
|
|
565
|
+
|
|
566
|
+
has_runs = _table_exists(conn, "workflow_runs")
|
|
567
|
+
if has_runs:
|
|
568
|
+
rows = conn.execute(
|
|
569
|
+
"""SELECT g.goal_id, g.title, g.status, g.owner, g.next_action, g.opened_at, g.updated_at,
|
|
570
|
+
COALESCE((SELECT COUNT(*) FROM workflow_runs r WHERE r.goal_id = g.goal_id), 0) AS run_count,
|
|
571
|
+
COALESCE((SELECT COUNT(*) FROM workflow_runs r WHERE r.goal_id = g.goal_id
|
|
572
|
+
AND r.status NOT IN ('completed', 'failed', 'cancelled')), 0) AS open_run_count
|
|
573
|
+
FROM workflow_goals g
|
|
574
|
+
WHERE g.status = 'active'
|
|
575
|
+
AND g.goal_id LIKE 'WG-AUDIT-%'
|
|
576
|
+
ORDER BY g.updated_at DESC, g.opened_at DESC"""
|
|
577
|
+
).fetchall()
|
|
578
|
+
else:
|
|
579
|
+
rows = conn.execute(
|
|
580
|
+
"""SELECT g.goal_id, g.title, g.status, g.owner, g.next_action, g.opened_at, g.updated_at,
|
|
581
|
+
0 AS run_count,
|
|
582
|
+
0 AS open_run_count
|
|
583
|
+
FROM workflow_goals g
|
|
584
|
+
WHERE g.status = 'active'
|
|
585
|
+
AND g.goal_id LIKE 'WG-AUDIT-%'
|
|
586
|
+
ORDER BY g.updated_at DESC, g.opened_at DESC"""
|
|
587
|
+
).fetchall()
|
|
588
|
+
|
|
589
|
+
if not rows:
|
|
590
|
+
return {"ok": True, "retired": 0}
|
|
591
|
+
|
|
592
|
+
now = datetime.now()
|
|
593
|
+
now_iso = now.isoformat(timespec="seconds")
|
|
594
|
+
retired = 0
|
|
595
|
+
for row in rows:
|
|
596
|
+
if str(row["next_action"] or "").strip() != AUDIT_GOAL_NEXT_ACTION:
|
|
597
|
+
continue
|
|
598
|
+
owner = str(row["owner"] or "").strip()
|
|
599
|
+
if owner and owner != AUDIT_GOAL_OWNER:
|
|
600
|
+
continue
|
|
601
|
+
if int(row["open_run_count"] or 0) > 0:
|
|
602
|
+
continue
|
|
603
|
+
updated_at = _parse_mixed_datetime(row["updated_at"]) or _parse_mixed_datetime(row["opened_at"])
|
|
604
|
+
if not updated_at:
|
|
605
|
+
continue
|
|
606
|
+
age_hours = (now - updated_at).total_seconds() / 3600
|
|
607
|
+
if age_hours < max_age_hours:
|
|
608
|
+
continue
|
|
609
|
+
conn.execute(
|
|
610
|
+
"""UPDATE workflow_goals
|
|
611
|
+
SET status = 'abandoned',
|
|
612
|
+
next_action = ?,
|
|
613
|
+
blocker_reason = ?,
|
|
614
|
+
updated_at = ?,
|
|
615
|
+
closed_at = ?
|
|
616
|
+
WHERE goal_id = ?""",
|
|
617
|
+
(
|
|
618
|
+
"Ninguna. Placeholder stale retirado automáticamente; el self-audit lo recreará si el patrón reaparece.",
|
|
619
|
+
f"Self-audit placeholder stale >{max_age_hours}h sin workflow runs abiertos.",
|
|
620
|
+
now_iso,
|
|
621
|
+
now_iso,
|
|
622
|
+
row["goal_id"],
|
|
623
|
+
),
|
|
624
|
+
)
|
|
625
|
+
retired += 1
|
|
626
|
+
return {"ok": True, "retired": retired}
|
|
627
|
+
|
|
628
|
+
|
|
556
629
|
def _queue_public_core_handoff(
|
|
557
630
|
conn: sqlite3.Connection,
|
|
558
631
|
*,
|
|
@@ -1174,6 +1247,11 @@ def check_unformalized_mentions():
|
|
|
1174
1247
|
conn.close()
|
|
1175
1248
|
return
|
|
1176
1249
|
|
|
1250
|
+
retired_result = _retire_stale_audit_goals_inline(conn)
|
|
1251
|
+
retired_count = int(retired_result.get("retired") or 0)
|
|
1252
|
+
if retired_count:
|
|
1253
|
+
finding("INFO", "formalization", f"retired {retired_count} stale self-audit workflow goals")
|
|
1254
|
+
|
|
1177
1255
|
rows = conn.execute(
|
|
1178
1256
|
"""SELECT goal, area, learning_id, followup_id
|
|
1179
1257
|
FROM protocol_tasks
|