nucleus-mcp 1.0.7 → 1.2.0
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 +2 -4
- package/.github/workflows/ci.yml +0 -72
- package/.smithery/shttp/module.js +0 -3
- package/.smithery/shttp/module.js.map +0 -7
- package/.smithery/stdio/index.cjs +0 -74
- package/.smithery/stdio/index.cjs.map +0 -7
- package/CHANGELOG.md +0 -65
- package/CODE_OF_CONDUCT.md +0 -34
- package/COMPARISON.md +0 -20
- package/CONTRIBUTING.md +0 -137
- package/DEVELOPMENT.md +0 -109
- package/LICENSE +0 -21
- package/README.md +0 -319
- package/ROADMAP.md +0 -24
- package/SECURITY.md +0 -88
- package/docs/COMMUNITY_FAQ.md +0 -48
- package/docs/COMPETITIVE_ANALYSIS.md +0 -90
- package/docs/DEMO_SCRIPT.md +0 -86
- package/docs/ENTERPRISE.md +0 -169
- package/docs/FAQ.md +0 -228
- package/docs/QUICK_START.md +0 -155
- package/docs/README.md +0 -67
- package/docs/RECURSIVE_SNAP_DEMO.gif +0 -0
- package/docs/social-preview-hq.png +0 -0
- package/docs/social-preview.png +0 -0
- package/examples/basic_usage.py +0 -84
- package/package.json.real +0 -24
- package/pyproject.toml +0 -50
- package/registry-server.json +0 -20
- package/scripts/demo_governance.py +0 -109
- package/scripts/sse_bridge.py +0 -87
- package/scripts/verify_windows_paths.py +0 -48
- package/server.json +0 -20
- package/src/mcp_server_nucleus/__init__.py +0 -8583
- package/src/mcp_server_nucleus/__main__.py +0 -9
- package/src/mcp_server_nucleus/cli.py +0 -1225
- package/src/mcp_server_nucleus/commitment_core.py +0 -146
- package/src/mcp_server_nucleus/commitment_ledger.py +0 -841
- package/src/mcp_server_nucleus/core/__init__.py +0 -6
- package/src/mcp_server_nucleus/core/orchestrator.py +0 -9
- package/src/mcp_server_nucleus/core/tool_registration_impl.py +0 -99
- package/src/mcp_server_nucleus/daemon/safety/lock.py +0 -121
- package/src/mcp_server_nucleus/deployment/render_integration.py +0 -4
- package/src/mcp_server_nucleus/hypervisor/__init__.py +0 -0
- package/src/mcp_server_nucleus/hypervisor/injector.py +0 -83
- package/src/mcp_server_nucleus/hypervisor/locker.py +0 -130
- package/src/mcp_server_nucleus/hypervisor/watchdog.py +0 -71
- package/src/mcp_server_nucleus/runtime/__init__.py +0 -58
- package/src/mcp_server_nucleus/runtime/agent.py +0 -517
- package/src/mcp_server_nucleus/runtime/agent_pool.py +0 -797
- package/src/mcp_server_nucleus/runtime/agents/base.py +0 -34
- package/src/mcp_server_nucleus/runtime/agents/oracle/manifest.json +0 -28
- package/src/mcp_server_nucleus/runtime/auth.py +0 -98
- package/src/mcp_server_nucleus/runtime/autopilot.py +0 -1057
- package/src/mcp_server_nucleus/runtime/broker.py +0 -126
- package/src/mcp_server_nucleus/runtime/budget.py +0 -153
- package/src/mcp_server_nucleus/runtime/capabilities/base.py +0 -20
- package/src/mcp_server_nucleus/runtime/capabilities/brain_ops.py +0 -253
- package/src/mcp_server_nucleus/runtime/capabilities/code_ops.py +0 -192
- package/src/mcp_server_nucleus/runtime/capabilities/depth_tracker.py +0 -81
- package/src/mcp_server_nucleus/runtime/capabilities/feature_map.py +0 -274
- package/src/mcp_server_nucleus/runtime/capabilities/marketing_engine.py +0 -205
- package/src/mcp_server_nucleus/runtime/capabilities/memory_ops.py +0 -79
- package/src/mcp_server_nucleus/runtime/capabilities/proof_system.py +0 -127
- package/src/mcp_server_nucleus/runtime/capabilities/render_ops.py +0 -80
- package/src/mcp_server_nucleus/runtime/capabilities/render_poller_cap.py +0 -266
- package/src/mcp_server_nucleus/runtime/capabilities/self_healing.py +0 -110
- package/src/mcp_server_nucleus/runtime/capabilities/strategy.py +0 -138
- package/src/mcp_server_nucleus/runtime/capabilities/synthesizer.py +0 -134
- package/src/mcp_server_nucleus/runtime/capabilities/web_ops.py +0 -94
- package/src/mcp_server_nucleus/runtime/common.py +0 -134
- package/src/mcp_server_nucleus/runtime/context_manager.py +0 -342
- package/src/mcp_server_nucleus/runtime/context_rules.json +0 -86
- package/src/mcp_server_nucleus/runtime/crdt_task_store.py +0 -349
- package/src/mcp_server_nucleus/runtime/critic.py +0 -150
- package/src/mcp_server_nucleus/runtime/daemon.py +0 -142
- package/src/mcp_server_nucleus/runtime/dashboard.py +0 -1087
- package/src/mcp_server_nucleus/runtime/depth_ops.py +0 -339
- package/src/mcp_server_nucleus/runtime/event_ops.py +0 -103
- package/src/mcp_server_nucleus/runtime/event_stream.py +0 -213
- package/src/mcp_server_nucleus/runtime/export.py +0 -64
- package/src/mcp_server_nucleus/runtime/factory.py +0 -594
- package/src/mcp_server_nucleus/runtime/federation.py +0 -1040
- package/src/mcp_server_nucleus/runtime/fetcher.py +0 -92
- package/src/mcp_server_nucleus/runtime/file_monitor.py +0 -208
- package/src/mcp_server_nucleus/runtime/firestore_bridge.py +0 -91
- package/src/mcp_server_nucleus/runtime/gcloud_ops.py +0 -186
- package/src/mcp_server_nucleus/runtime/hooks.py +0 -246
- package/src/mcp_server_nucleus/runtime/identity/gatekeeper.py +0 -106
- package/src/mcp_server_nucleus/runtime/identity/keygen.py +0 -147
- package/src/mcp_server_nucleus/runtime/identity/manifest.py +0 -62
- package/src/mcp_server_nucleus/runtime/identity/trust.py +0 -27
- package/src/mcp_server_nucleus/runtime/inspector.py +0 -74
- package/src/mcp_server_nucleus/runtime/installer.py +0 -81
- package/src/mcp_server_nucleus/runtime/ipc_auth.py +0 -377
- package/src/mcp_server_nucleus/runtime/lifecycle.py +0 -129
- package/src/mcp_server_nucleus/runtime/llm_client.py +0 -408
- package/src/mcp_server_nucleus/runtime/llm_tiers.json +0 -88
- package/src/mcp_server_nucleus/runtime/locking.py +0 -228
- package/src/mcp_server_nucleus/runtime/loops/fixer.py +0 -110
- package/src/mcp_server_nucleus/runtime/memory.py +0 -158
- package/src/mcp_server_nucleus/runtime/mounter.py +0 -210
- package/src/mcp_server_nucleus/runtime/mounter_ops.py +0 -250
- package/src/mcp_server_nucleus/runtime/nuke_protocol.py +0 -147
- package/src/mcp_server_nucleus/runtime/orchestrator.py +0 -370
- package/src/mcp_server_nucleus/runtime/orchestrator_unified.py +0 -263
- package/src/mcp_server_nucleus/runtime/orchestrator_v3.py +0 -617
- package/src/mcp_server_nucleus/runtime/plugin_loader.py +0 -84
- package/src/mcp_server_nucleus/runtime/policy.py +0 -117
- package/src/mcp_server_nucleus/runtime/profiling.py +0 -219
- package/src/mcp_server_nucleus/runtime/prometheus.py +0 -286
- package/src/mcp_server_nucleus/runtime/proposals.py +0 -101
- package/src/mcp_server_nucleus/runtime/publisher.py +0 -109
- package/src/mcp_server_nucleus/runtime/pulse.py +0 -516
- package/src/mcp_server_nucleus/runtime/registry.py +0 -82
- package/src/mcp_server_nucleus/runtime/render_ops.py +0 -83
- package/src/mcp_server_nucleus/runtime/schema_gen.py +0 -140
- package/src/mcp_server_nucleus/runtime/session_ops.py +0 -224
- package/src/mcp_server_nucleus/runtime/stdio_server.py +0 -633
- package/src/mcp_server_nucleus/runtime/storage.py +0 -83
- package/src/mcp_server_nucleus/runtime/strategy.py +0 -91
- package/src/mcp_server_nucleus/runtime/swarm.py +0 -170
- package/src/mcp_server_nucleus/runtime/sync_ops.py +0 -878
- package/src/mcp_server_nucleus/runtime/task_ingestion.py +0 -1020
- package/src/mcp_server_nucleus/runtime/task_ops.py +0 -449
- package/src/mcp_server_nucleus/runtime/task_scheduler.py +0 -488
- package/src/mcp_server_nucleus/runtime/team.py +0 -59
- package/src/mcp_server_nucleus/runtime/triggers.py +0 -237
- package/src/mcp_server_nucleus/runtime/vector_store.py +0 -125
- package/src/mcp_server_nucleus/runtime/watcher.py +0 -112
- package/src/mcp_server_nucleus/tool_tiers.py +0 -243
- package/tests/__init__.py +0 -1
- package/tests/conftest.py +0 -42
- package/tests/test_core.py +0 -235
- package/tests/test_search.py +0 -69
|
@@ -1,349 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
CRDTTaskStore - Conflict-free Distributed Task Store
|
|
3
|
-
Implements Last-Writer-Wins (LWW) + Vector Clocks
|
|
4
|
-
|
|
5
|
-
Path B: Simple, scalable, production-ready
|
|
6
|
-
- Handles concurrent writes without locks
|
|
7
|
-
- Guarantees zero data loss (CRDT invariant)
|
|
8
|
-
- Auto-resolves conflicts with LWW + vector clocks
|
|
9
|
-
- Scales 1→100→10K users with same code
|
|
10
|
-
- Single-process MVP, multi-process ready
|
|
11
|
-
|
|
12
|
-
Author: NOP V3 - January 2026
|
|
13
|
-
"""
|
|
14
|
-
|
|
15
|
-
import json
|
|
16
|
-
import time
|
|
17
|
-
import threading
|
|
18
|
-
from typing import Dict, List, Optional, Set
|
|
19
|
-
from collections import defaultdict
|
|
20
|
-
from copy import deepcopy
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class CRDTTaskStore:
|
|
24
|
-
"""
|
|
25
|
-
Conflict-free replicated data type for task management.
|
|
26
|
-
|
|
27
|
-
Key guarantees:
|
|
28
|
-
- Zero data loss: Every write is persisted
|
|
29
|
-
- Causality: Vector clocks track dependencies
|
|
30
|
-
- Conflict resolution: LWW (Last-Writer-Wins) with timestamps
|
|
31
|
-
- Convergence: All replicas eventually see same state
|
|
32
|
-
- Idempotency: Same write applied multiple times = same result
|
|
33
|
-
|
|
34
|
-
Single-process implementation (thread-safe for concurrent writes).
|
|
35
|
-
Multi-replica merge() support for future distributed sync.
|
|
36
|
-
"""
|
|
37
|
-
|
|
38
|
-
def __init__(self, replica_id: str = "default"):
|
|
39
|
-
"""
|
|
40
|
-
Initialize CRDT task store.
|
|
41
|
-
|
|
42
|
-
Args:
|
|
43
|
-
replica_id: Unique identifier for this replica (used for multi-replica merge)
|
|
44
|
-
"""
|
|
45
|
-
self.replica_id = replica_id
|
|
46
|
-
self._tasks: Dict[str, Dict] = {} # task_id -> Task
|
|
47
|
-
self._clocks: Dict[str, int] = defaultdict(int) # replica_id -> clock value (for vector clocks)
|
|
48
|
-
self._timestamps: Dict[str, int] = {} # task_id -> LWW timestamp
|
|
49
|
-
self._tombstones: Set[str] = set() # Deleted task IDs (for CRDT consistency)
|
|
50
|
-
self._lock = threading.RLock() # Thread-safe concurrent writes
|
|
51
|
-
|
|
52
|
-
def _increment_clock(self) -> None:
|
|
53
|
-
"""Increment vector clock for this replica."""
|
|
54
|
-
self._clocks[self.replica_id] += 1
|
|
55
|
-
|
|
56
|
-
def _get_vector_clock(self) -> Dict[str, int]:
|
|
57
|
-
"""Get current vector clock state."""
|
|
58
|
-
return dict(self._clocks)
|
|
59
|
-
|
|
60
|
-
def _merge_vector_clocks(self, incoming: Dict[str, int]) -> Dict[str, int]:
|
|
61
|
-
"""Merge incoming vector clock with local clock (element-wise max)."""
|
|
62
|
-
merged = dict(self._clocks)
|
|
63
|
-
for replica_id, clock_value in incoming.items():
|
|
64
|
-
merged[replica_id] = max(merged.get(replica_id, 0), clock_value)
|
|
65
|
-
return merged
|
|
66
|
-
|
|
67
|
-
def add_task(self, task: Dict) -> Dict:
|
|
68
|
-
"""
|
|
69
|
-
Add a new task to the store.
|
|
70
|
-
|
|
71
|
-
Args:
|
|
72
|
-
task: Task dict with keys: id, title, status, tier, created_at, assigned_to (optional)
|
|
73
|
-
|
|
74
|
-
Returns:
|
|
75
|
-
Task with CRDT metadata added (updated_at, replica_id, vector_clock)
|
|
76
|
-
|
|
77
|
-
Guarantees:
|
|
78
|
-
- Task is immediately visible to all readers
|
|
79
|
-
- Timestamp ensures consistent ordering
|
|
80
|
-
- Vector clock tracks causality
|
|
81
|
-
"""
|
|
82
|
-
with self._lock:
|
|
83
|
-
task_id = task.get("id")
|
|
84
|
-
if not task_id:
|
|
85
|
-
raise ValueError("Task must have 'id' field")
|
|
86
|
-
|
|
87
|
-
current_time = int(time.time() * 1000) # Epoch milliseconds
|
|
88
|
-
|
|
89
|
-
# Add CRDT metadata
|
|
90
|
-
self._increment_clock()
|
|
91
|
-
enriched_task = {
|
|
92
|
-
**task,
|
|
93
|
-
"updated_at": current_time,
|
|
94
|
-
"replica_id": self.replica_id,
|
|
95
|
-
"vector_clock": self._get_vector_clock(),
|
|
96
|
-
"created_at": task.get("created_at", current_time),
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
self._tasks[task_id] = enriched_task
|
|
100
|
-
self._timestamps[task_id] = current_time
|
|
101
|
-
|
|
102
|
-
# Remove from tombstones if previously deleted
|
|
103
|
-
self._tombstones.discard(task_id)
|
|
104
|
-
|
|
105
|
-
return deepcopy(enriched_task)
|
|
106
|
-
|
|
107
|
-
def update_task(self, task_id: str, updates: Dict) -> Dict:
|
|
108
|
-
"""
|
|
109
|
-
Update an existing task with LWW conflict resolution.
|
|
110
|
-
|
|
111
|
-
Args:
|
|
112
|
-
task_id: ID of task to update
|
|
113
|
-
updates: Dict of fields to update (status, assigned_to, title, etc.)
|
|
114
|
-
|
|
115
|
-
Returns:
|
|
116
|
-
Updated task
|
|
117
|
-
|
|
118
|
-
Raises:
|
|
119
|
-
KeyError: If task doesn't exist
|
|
120
|
-
|
|
121
|
-
Conflict Resolution:
|
|
122
|
-
- Incoming update has new timestamp (LWW)
|
|
123
|
-
- Only applied if newer than existing
|
|
124
|
-
- Vector clock updated to track causality
|
|
125
|
-
"""
|
|
126
|
-
with self._lock:
|
|
127
|
-
if task_id not in self._tasks:
|
|
128
|
-
raise KeyError(f"Task {task_id} not found")
|
|
129
|
-
|
|
130
|
-
if task_id in self._tombstones:
|
|
131
|
-
raise KeyError(f"Task {task_id} has been deleted")
|
|
132
|
-
|
|
133
|
-
current_time = int(time.time() * 1000)
|
|
134
|
-
self._increment_clock()
|
|
135
|
-
|
|
136
|
-
existing = self._tasks[task_id]
|
|
137
|
-
|
|
138
|
-
# LWW: Use new timestamp (later write wins)
|
|
139
|
-
updated_task = {
|
|
140
|
-
**existing,
|
|
141
|
-
**updates,
|
|
142
|
-
"updated_at": current_time,
|
|
143
|
-
"replica_id": self.replica_id,
|
|
144
|
-
"vector_clock": self._get_vector_clock(),
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
self._tasks[task_id] = updated_task
|
|
148
|
-
self._timestamps[task_id] = current_time
|
|
149
|
-
|
|
150
|
-
return deepcopy(updated_task)
|
|
151
|
-
|
|
152
|
-
def delete_task(self, task_id: str) -> bool:
|
|
153
|
-
"""
|
|
154
|
-
Delete a task (tombstone-based deletion for CRDT safety).
|
|
155
|
-
|
|
156
|
-
Args:
|
|
157
|
-
task_id: ID of task to delete
|
|
158
|
-
|
|
159
|
-
Returns:
|
|
160
|
-
True if deleted, False if not found
|
|
161
|
-
|
|
162
|
-
Note:
|
|
163
|
-
Uses tombstones to track deleted tasks. This prevents
|
|
164
|
-
re-insertion of deleted data during multi-replica merges.
|
|
165
|
-
"""
|
|
166
|
-
with self._lock:
|
|
167
|
-
if task_id not in self._tasks:
|
|
168
|
-
return False
|
|
169
|
-
|
|
170
|
-
del self._tasks[task_id]
|
|
171
|
-
self._tombstones.add(task_id)
|
|
172
|
-
if task_id in self._timestamps:
|
|
173
|
-
del self._timestamps[task_id]
|
|
174
|
-
|
|
175
|
-
return True
|
|
176
|
-
|
|
177
|
-
def get_task(self, task_id: str) -> Optional[Dict]:
|
|
178
|
-
"""
|
|
179
|
-
Retrieve a single task by ID.
|
|
180
|
-
|
|
181
|
-
Args:
|
|
182
|
-
task_id: ID of task
|
|
183
|
-
|
|
184
|
-
Returns:
|
|
185
|
-
Task dict or None if not found
|
|
186
|
-
"""
|
|
187
|
-
with self._lock:
|
|
188
|
-
task = self._tasks.get(task_id)
|
|
189
|
-
return deepcopy(task) if task else None
|
|
190
|
-
|
|
191
|
-
def get_all_tasks(self) -> List[Dict]:
|
|
192
|
-
"""
|
|
193
|
-
Retrieve all tasks (consistent snapshot).
|
|
194
|
-
|
|
195
|
-
Returns:
|
|
196
|
-
List of all tasks, sorted by updated_at (newest first)
|
|
197
|
-
|
|
198
|
-
Consistency:
|
|
199
|
-
- Snapshot is taken within lock
|
|
200
|
-
- No tasks added/removed during read
|
|
201
|
-
- All timestamps consistent
|
|
202
|
-
"""
|
|
203
|
-
with self._lock:
|
|
204
|
-
tasks = [deepcopy(t) for t in self._tasks.values()]
|
|
205
|
-
# Sort by updated_at descending (newest first)
|
|
206
|
-
tasks.sort(key=lambda t: t.get("updated_at", 0), reverse=True)
|
|
207
|
-
return tasks
|
|
208
|
-
|
|
209
|
-
def get_tasks_by_tier(self, tier: str) -> List[Dict]:
|
|
210
|
-
"""Get all tasks for a specific tier."""
|
|
211
|
-
with self._lock:
|
|
212
|
-
tasks = [
|
|
213
|
-
deepcopy(t) for t in self._tasks.values()
|
|
214
|
-
if t.get("tier") == tier
|
|
215
|
-
]
|
|
216
|
-
tasks.sort(key=lambda t: t.get("updated_at", 0), reverse=True)
|
|
217
|
-
return tasks
|
|
218
|
-
|
|
219
|
-
def get_tasks_by_status(self, status: str) -> List[Dict]:
|
|
220
|
-
"""Get all tasks with a specific status."""
|
|
221
|
-
with self._lock:
|
|
222
|
-
tasks = [
|
|
223
|
-
deepcopy(t) for t in self._tasks.values()
|
|
224
|
-
if t.get("status") == status
|
|
225
|
-
]
|
|
226
|
-
tasks.sort(key=lambda t: t.get("updated_at", 0), reverse=True)
|
|
227
|
-
return tasks
|
|
228
|
-
|
|
229
|
-
def merge(self, remote_store: "CRDTTaskStore") -> None:
|
|
230
|
-
"""
|
|
231
|
-
Merge this store with a remote store using LWW + vector clocks.
|
|
232
|
-
|
|
233
|
-
Args:
|
|
234
|
-
remote_store: Another CRDTTaskStore to merge with
|
|
235
|
-
|
|
236
|
-
Algorithm:
|
|
237
|
-
1. For each task in remote:
|
|
238
|
-
- If not in local: add it (new task)
|
|
239
|
-
- If in local: compare timestamps (LWW wins)
|
|
240
|
-
- Merge vector clocks (element-wise max)
|
|
241
|
-
2. Update local vector clock with remote state
|
|
242
|
-
|
|
243
|
-
Result:
|
|
244
|
-
- All replicas converge to same state
|
|
245
|
-
- No data loss
|
|
246
|
-
- Conflicts auto-resolved by timestamp
|
|
247
|
-
"""
|
|
248
|
-
with self._lock:
|
|
249
|
-
remote_tasks = remote_store.get_all_tasks()
|
|
250
|
-
|
|
251
|
-
for remote_task in remote_tasks:
|
|
252
|
-
task_id = remote_task.get("id")
|
|
253
|
-
remote_timestamp = remote_task.get("updated_at", 0)
|
|
254
|
-
|
|
255
|
-
if task_id in self._tombstones:
|
|
256
|
-
# Local delete wins - skip remote version
|
|
257
|
-
continue
|
|
258
|
-
|
|
259
|
-
if task_id not in self._tasks:
|
|
260
|
-
# New task from remote
|
|
261
|
-
self._tasks[task_id] = deepcopy(remote_task)
|
|
262
|
-
self._timestamps[task_id] = remote_timestamp
|
|
263
|
-
else:
|
|
264
|
-
# Task exists locally - LWW: newer wins
|
|
265
|
-
local_timestamp = self._timestamps.get(task_id, 0)
|
|
266
|
-
|
|
267
|
-
if remote_timestamp > local_timestamp:
|
|
268
|
-
# Remote is newer, use it
|
|
269
|
-
self._tasks[task_id] = deepcopy(remote_task)
|
|
270
|
-
self._timestamps[task_id] = remote_timestamp
|
|
271
|
-
# else: local is newer, keep local version
|
|
272
|
-
|
|
273
|
-
# Merge vector clocks
|
|
274
|
-
local_vc = self._tasks[task_id].get("vector_clock", {})
|
|
275
|
-
remote_vc = remote_task.get("vector_clock", {})
|
|
276
|
-
merged_vc = self._merge_vector_clocks({**local_vc, **remote_vc})
|
|
277
|
-
self._tasks[task_id]["vector_clock"] = merged_vc
|
|
278
|
-
|
|
279
|
-
# Merge remote tombstones
|
|
280
|
-
remote_tombstones = remote_store._tombstones
|
|
281
|
-
for tombstone_id in remote_tombstones:
|
|
282
|
-
if tombstone_id in self._tasks:
|
|
283
|
-
del self._tasks[tombstone_id]
|
|
284
|
-
self._tombstones.add(tombstone_id)
|
|
285
|
-
|
|
286
|
-
# Merge vector clocks globally
|
|
287
|
-
remote_clocks = remote_store._clocks
|
|
288
|
-
for replica_id, clock_value in remote_clocks.items():
|
|
289
|
-
self._clocks[replica_id] = max(
|
|
290
|
-
self._clocks.get(replica_id, 0),
|
|
291
|
-
clock_value
|
|
292
|
-
)
|
|
293
|
-
|
|
294
|
-
def to_json(self) -> str:
|
|
295
|
-
"""
|
|
296
|
-
Export store to JSON for persistence.
|
|
297
|
-
|
|
298
|
-
Returns:
|
|
299
|
-
JSON string with all tasks, tombstones, and vector clocks
|
|
300
|
-
|
|
301
|
-
Format:
|
|
302
|
-
{
|
|
303
|
-
"replica_id": "...",
|
|
304
|
-
"tasks": [...],
|
|
305
|
-
"tombstones": [...],
|
|
306
|
-
"vector_clocks": {...}
|
|
307
|
-
}
|
|
308
|
-
"""
|
|
309
|
-
with self._lock:
|
|
310
|
-
export_data = {
|
|
311
|
-
"replica_id": self.replica_id,
|
|
312
|
-
"tasks": list(self._tasks.values()),
|
|
313
|
-
"tombstones": list(self._tombstones),
|
|
314
|
-
"vector_clocks": dict(self._clocks),
|
|
315
|
-
}
|
|
316
|
-
return json.dumps(export_data, indent=2, default=str)
|
|
317
|
-
|
|
318
|
-
def from_json(self, json_str: str) -> None:
|
|
319
|
-
"""
|
|
320
|
-
Import store from JSON (e.g., for recovery from persistence).
|
|
321
|
-
|
|
322
|
-
Args:
|
|
323
|
-
json_str: JSON string from to_json()
|
|
324
|
-
|
|
325
|
-
Note:
|
|
326
|
-
This overwrites current state. Use for recovery only.
|
|
327
|
-
"""
|
|
328
|
-
with self._lock:
|
|
329
|
-
data = json.loads(json_str)
|
|
330
|
-
|
|
331
|
-
self.replica_id = data.get("replica_id", "default")
|
|
332
|
-
self._tasks = {t["id"]: t for t in data.get("tasks", [])}
|
|
333
|
-
self._tombstones = set(data.get("tombstones", []))
|
|
334
|
-
self._clocks = defaultdict(int, data.get("vector_clocks", {}))
|
|
335
|
-
self._timestamps = {
|
|
336
|
-
t["id"]: t.get("updated_at", 0)
|
|
337
|
-
for t in data.get("tasks", [])
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
def get_stats(self) -> Dict:
|
|
341
|
-
"""Get store statistics for monitoring."""
|
|
342
|
-
with self._lock:
|
|
343
|
-
return {
|
|
344
|
-
"replica_id": self.replica_id,
|
|
345
|
-
"total_tasks": len(self._tasks),
|
|
346
|
-
"tombstones": len(self._tombstones),
|
|
347
|
-
"vector_clock": dict(self._clocks),
|
|
348
|
-
"memory_estimate_kb": len(self.to_json()) / 1024,
|
|
349
|
-
}
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import os
|
|
3
|
-
import json
|
|
4
|
-
import time
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
from typing import Dict
|
|
7
|
-
import logging
|
|
8
|
-
|
|
9
|
-
# Configure logger
|
|
10
|
-
logger = logging.getLogger("nucleus.critic")
|
|
11
|
-
|
|
12
|
-
try:
|
|
13
|
-
from mcp_server_nucleus.runtime.llm_client import DualEngineLLM
|
|
14
|
-
except ImportError:
|
|
15
|
-
# Fallback for when running in simpler contexts
|
|
16
|
-
DualEngineLLM = None
|
|
17
|
-
|
|
18
|
-
def get_brain_path() -> Path:
|
|
19
|
-
"""Get the brain path from environment variable."""
|
|
20
|
-
brain_path = os.environ.get("NUCLEAR_BRAIN_PATH")
|
|
21
|
-
if not brain_path:
|
|
22
|
-
# Fallback for dev/test if env not set, check common locations
|
|
23
|
-
cwd = Path.cwd()
|
|
24
|
-
if (cwd / ".brain").exists():
|
|
25
|
-
return cwd / ".brain"
|
|
26
|
-
# Try finding in parent directories
|
|
27
|
-
for parent in cwd.parents:
|
|
28
|
-
if (parent / ".brain").exists():
|
|
29
|
-
return parent / ".brain"
|
|
30
|
-
raise ValueError("NUCLEAR_BRAIN_PATH environment variable not set and .brain not found")
|
|
31
|
-
|
|
32
|
-
path = Path(brain_path)
|
|
33
|
-
if not path.exists():
|
|
34
|
-
raise ValueError(f"Brain path does not exist: {brain_path}")
|
|
35
|
-
return path
|
|
36
|
-
|
|
37
|
-
def _critique_code(file_path: str) -> Dict:
|
|
38
|
-
"""
|
|
39
|
-
Critique a specific file using the Critic Agent persona.
|
|
40
|
-
"""
|
|
41
|
-
try:
|
|
42
|
-
brain = get_brain_path()
|
|
43
|
-
|
|
44
|
-
# 1. Resolve Target File
|
|
45
|
-
# Handle absolute or relative paths
|
|
46
|
-
if file_path.startswith("/"):
|
|
47
|
-
target_path = Path(file_path)
|
|
48
|
-
else:
|
|
49
|
-
# Assume relative to project root (parent of .brain)
|
|
50
|
-
target_path = brain.parent / file_path
|
|
51
|
-
|
|
52
|
-
if not target_path.exists():
|
|
53
|
-
return {"success": False, "error": f"File not found: {file_path}"}
|
|
54
|
-
|
|
55
|
-
code_content = target_path.read_text()
|
|
56
|
-
|
|
57
|
-
# 2. Read System Prompt
|
|
58
|
-
critic_prompt_path = brain / "agents" / "critic.md"
|
|
59
|
-
if not critic_prompt_path.exists():
|
|
60
|
-
return {"success": False, "error": "Critic persona not found in .brain/agents/critic.md"}
|
|
61
|
-
|
|
62
|
-
system_prompt = critic_prompt_path.read_text()
|
|
63
|
-
|
|
64
|
-
# 3. Initialize LLM
|
|
65
|
-
if not DualEngineLLM:
|
|
66
|
-
return {"success": False, "error": "LLM Client not available (ImportError)"}
|
|
67
|
-
|
|
68
|
-
try:
|
|
69
|
-
llm = DualEngineLLM(
|
|
70
|
-
model_name="gemini-2.0-flash-exp",
|
|
71
|
-
system_instruction=system_prompt
|
|
72
|
-
)
|
|
73
|
-
except Exception as e:
|
|
74
|
-
return {"success": False, "error": f"LLM Init failed: {e}"}
|
|
75
|
-
|
|
76
|
-
# 4. Construct Prompt
|
|
77
|
-
user_prompt = f"""
|
|
78
|
-
ACTION: Review the following code file.
|
|
79
|
-
|
|
80
|
-
TARGET: {file_path}
|
|
81
|
-
|
|
82
|
-
CONTENT:
|
|
83
|
-
```
|
|
84
|
-
{code_content}
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
INSTRUCTIONS:
|
|
88
|
-
1. Analyze for bugs, security flaws, style violations, and adherence to best practices.
|
|
89
|
-
2. Check against the Prime Directives.
|
|
90
|
-
3. Output your critique in valid JSON format ONLY.
|
|
91
|
-
Do not include markdown formatting (like ```json), just the raw JSON string.
|
|
92
|
-
Use the schema defined in 'Completion Events' (e.g., event_type="review_approved" or "review_blocked").
|
|
93
|
-
|
|
94
|
-
Example Success:
|
|
95
|
-
{{
|
|
96
|
-
"event_type": "review_approved",
|
|
97
|
-
"severity": "ROUTINE",
|
|
98
|
-
"payload": {{ "target": "...", "notes": "LGTM" }}
|
|
99
|
-
}}
|
|
100
|
-
|
|
101
|
-
Example Failure:
|
|
102
|
-
{{
|
|
103
|
-
"event_type": "review_blocked",
|
|
104
|
-
"severity": "HIGH",
|
|
105
|
-
"payload": {{ "issues": [...] }}
|
|
106
|
-
}}
|
|
107
|
-
"""
|
|
108
|
-
|
|
109
|
-
# 5. Call LLM
|
|
110
|
-
logger.info(f"Invoking Critic on {file_path}")
|
|
111
|
-
response = llm.generate_content(user_prompt)
|
|
112
|
-
|
|
113
|
-
if not response or not hasattr(response, 'text'):
|
|
114
|
-
return {"success": False, "error": "LLM returned empty response"}
|
|
115
|
-
|
|
116
|
-
raw_text = response.text
|
|
117
|
-
|
|
118
|
-
# Naive JSON extraction
|
|
119
|
-
start = raw_text.find("{")
|
|
120
|
-
end = raw_text.rfind("}") + 1
|
|
121
|
-
|
|
122
|
-
if start != -1 and end != -1:
|
|
123
|
-
json_str = raw_text[start:end]
|
|
124
|
-
try:
|
|
125
|
-
critique_data = json.loads(json_str)
|
|
126
|
-
except json.JSONDecodeError:
|
|
127
|
-
return {"success": False, "error": "Invalid JSON from Critic", "raw": raw_text}
|
|
128
|
-
else:
|
|
129
|
-
return {"success": False, "error": "No JSON found in Critic response", "raw": raw_text}
|
|
130
|
-
|
|
131
|
-
# 6. Save Artifact
|
|
132
|
-
timestamp = int(time.time())
|
|
133
|
-
filename = f"code_review_{target_path.name}_{timestamp}.json"
|
|
134
|
-
review_path = brain / "artifacts" / "reviews" / filename
|
|
135
|
-
review_path.parent.mkdir(parents=True, exist_ok=True)
|
|
136
|
-
review_path.write_text(json.dumps(critique_data, indent=2))
|
|
137
|
-
|
|
138
|
-
# 7. Extract status for easy consumption
|
|
139
|
-
status = "APPROVED" if critique_data.get("event_type") == "review_approved" else "BLOCKED"
|
|
140
|
-
|
|
141
|
-
return {
|
|
142
|
-
"success": True,
|
|
143
|
-
"status": status,
|
|
144
|
-
"review_path": str(review_path),
|
|
145
|
-
"critique": critique_data
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
except Exception as e:
|
|
149
|
-
logger.error(f"Critique failed: {e}")
|
|
150
|
-
return {"success": False, "error": f"Critique generation failed: {str(e)}"}
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
"""
|
|
3
|
-
Nucleus Daemon Manager.
|
|
4
|
-
The Kernel service that runs the persistent background agent.
|
|
5
|
-
|
|
6
|
-
Strategic Role:
|
|
7
|
-
- Lifecycle Management: Startup, Shutdown, Signal Handling.
|
|
8
|
-
- process Safety: Enforces BrainLock to ensure only one Daemon runs.
|
|
9
|
-
- Heartbeat: Updates status for the HUD.
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
import asyncio
|
|
13
|
-
import signal
|
|
14
|
-
import sys
|
|
15
|
-
import logging
|
|
16
|
-
from pathlib import Path
|
|
17
|
-
from typing import Optional
|
|
18
|
-
|
|
19
|
-
from .locking import get_lock, BrainLock
|
|
20
|
-
from .policy import DirectivesLoader
|
|
21
|
-
from .orchestrator import SwarmsOrchestrator
|
|
22
|
-
from .hooks import IdentityKey, AmbientTelemetry, InsightExchange, RemoteExecutionProtocol
|
|
23
|
-
|
|
24
|
-
# Configure logging
|
|
25
|
-
logging.basicConfig(
|
|
26
|
-
level=logging.INFO,
|
|
27
|
-
format='%(asctime)s [%(name)s] %(levelname)s: %(message)s',
|
|
28
|
-
handlers=[
|
|
29
|
-
logging.StreamHandler(sys.stdout),
|
|
30
|
-
logging.FileHandler("daemon.log")
|
|
31
|
-
]
|
|
32
|
-
)
|
|
33
|
-
logger = logging.getLogger("NucleusDaemon")
|
|
34
|
-
|
|
35
|
-
class DaemonManager:
|
|
36
|
-
def __init__(self, brain_path: Path):
|
|
37
|
-
self.brain_path = brain_path
|
|
38
|
-
self.lock: Optional[BrainLock] = None
|
|
39
|
-
self.running = False
|
|
40
|
-
self.policy_engine = DirectivesLoader(brain_path)
|
|
41
|
-
self.orchestrator = SwarmsOrchestrator(brain_path)
|
|
42
|
-
|
|
43
|
-
# Strategic Hooks (The Billion Dollar Extensions)
|
|
44
|
-
self.identity = IdentityKey(brain_path)
|
|
45
|
-
self.pulse = AmbientTelemetry(brain_path, identity=self.identity)
|
|
46
|
-
self.insight_exchange = InsightExchange(brain_path)
|
|
47
|
-
self.grid = RemoteExecutionProtocol(brain_path)
|
|
48
|
-
|
|
49
|
-
async def start(self):
|
|
50
|
-
"""Start the Daemon sequence"""
|
|
51
|
-
logger.info("Nucleus Daemon Starting...")
|
|
52
|
-
|
|
53
|
-
# 1. Acquire Lock
|
|
54
|
-
lock_dir = self.brain_path / ".locks"
|
|
55
|
-
self.lock = get_lock("daemon_process", base_dir=lock_dir)
|
|
56
|
-
|
|
57
|
-
if not self.lock.acquire(timeout=1.0):
|
|
58
|
-
logger.error("Could not acquire daemon lock. Is another instance running?")
|
|
59
|
-
sys.exit(1)
|
|
60
|
-
|
|
61
|
-
logger.info("🔒 BrainLock Acquired. I am the Sovereign Process.")
|
|
62
|
-
self.running = True
|
|
63
|
-
|
|
64
|
-
# 2. Setup Signals
|
|
65
|
-
loop = asyncio.get_running_loop()
|
|
66
|
-
for sig in (signal.SIGINT, signal.SIGTERM):
|
|
67
|
-
loop.add_signal_handler(sig, lambda: asyncio.create_task(self.shutdown()))
|
|
68
|
-
|
|
69
|
-
# 3. Load Policy
|
|
70
|
-
policy = self.policy_engine.load_policy()
|
|
71
|
-
logger.info(f"📜 Policy Loaded: Mode={policy.get('mode')}, Safety={policy.get('safety_level')}")
|
|
72
|
-
|
|
73
|
-
# 4. Main Event Loop
|
|
74
|
-
try:
|
|
75
|
-
await self.main_loop()
|
|
76
|
-
except asyncio.CancelledError:
|
|
77
|
-
logger.info("Daemon cancelled.")
|
|
78
|
-
except Exception as e:
|
|
79
|
-
logger.exception(f"Daemon crashed: {e}")
|
|
80
|
-
finally:
|
|
81
|
-
await self.shutdown()
|
|
82
|
-
|
|
83
|
-
async def main_loop(self):
|
|
84
|
-
"""The Core Preemptive Loop"""
|
|
85
|
-
logger.info("🚀 Daemon is active. Entering Main Loop.")
|
|
86
|
-
|
|
87
|
-
while self.running:
|
|
88
|
-
# Heartbeat (The Ambient Pulse)
|
|
89
|
-
status = "busy" if self.orchestrator._active_missions else "idle"
|
|
90
|
-
self.pulse.beat(status, len(self.orchestrator._active_missions))
|
|
91
|
-
logger.debug(f"❤️ Heartbeat ({status})")
|
|
92
|
-
|
|
93
|
-
# Monitor Swarms
|
|
94
|
-
active_count = len(self.orchestrator._active_missions)
|
|
95
|
-
if active_count > 0:
|
|
96
|
-
logger.info(f"🦾 Active Swarms: {active_count}")
|
|
97
|
-
|
|
98
|
-
# TODO: Phase A Components
|
|
99
|
-
# - Check AsyncFileWatcher
|
|
100
|
-
# - Check Pending Commitments (SwarmsOrchestrator)
|
|
101
|
-
|
|
102
|
-
# Simulation of work (Polling interval)
|
|
103
|
-
# In future, this is replaced by Event triggers to avoid busy wait
|
|
104
|
-
await asyncio.sleep(5)
|
|
105
|
-
|
|
106
|
-
async def shutdown(self):
|
|
107
|
-
"""Graceful Shutdown"""
|
|
108
|
-
if not self.running:
|
|
109
|
-
return
|
|
110
|
-
|
|
111
|
-
logger.info("🛑 Shutdown initiated...")
|
|
112
|
-
self.running = False
|
|
113
|
-
|
|
114
|
-
# Release resources
|
|
115
|
-
if self.lock:
|
|
116
|
-
self.lock.release()
|
|
117
|
-
logger.info("🔓 BrainLock Released.")
|
|
118
|
-
|
|
119
|
-
# TODO: Close sockets, database connections
|
|
120
|
-
|
|
121
|
-
logger.info("Daemon Stopped.")
|
|
122
|
-
sys.exit(0)
|
|
123
|
-
|
|
124
|
-
def run_daemon():
|
|
125
|
-
"""Entry point for the daemon service"""
|
|
126
|
-
# Determine Brain Path (Environment or Default)
|
|
127
|
-
import os
|
|
128
|
-
brain_path_str = os.environ.get("NUCLEUS_BRAIN_PATH")
|
|
129
|
-
if not brain_path_str:
|
|
130
|
-
# Dev fallback
|
|
131
|
-
brain_path = Path.cwd() / ".brain" # simplistic
|
|
132
|
-
else:
|
|
133
|
-
brain_path = Path(brain_path_str)
|
|
134
|
-
|
|
135
|
-
daemon = DaemonManager(brain_path)
|
|
136
|
-
try:
|
|
137
|
-
asyncio.run(daemon.start())
|
|
138
|
-
except KeyboardInterrupt:
|
|
139
|
-
pass # Handled in handler
|
|
140
|
-
|
|
141
|
-
if __name__ == "__main__":
|
|
142
|
-
run_daemon()
|