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.
Files changed (135) hide show
  1. package/package.json +2 -4
  2. package/.github/workflows/ci.yml +0 -72
  3. package/.smithery/shttp/module.js +0 -3
  4. package/.smithery/shttp/module.js.map +0 -7
  5. package/.smithery/stdio/index.cjs +0 -74
  6. package/.smithery/stdio/index.cjs.map +0 -7
  7. package/CHANGELOG.md +0 -65
  8. package/CODE_OF_CONDUCT.md +0 -34
  9. package/COMPARISON.md +0 -20
  10. package/CONTRIBUTING.md +0 -137
  11. package/DEVELOPMENT.md +0 -109
  12. package/LICENSE +0 -21
  13. package/README.md +0 -319
  14. package/ROADMAP.md +0 -24
  15. package/SECURITY.md +0 -88
  16. package/docs/COMMUNITY_FAQ.md +0 -48
  17. package/docs/COMPETITIVE_ANALYSIS.md +0 -90
  18. package/docs/DEMO_SCRIPT.md +0 -86
  19. package/docs/ENTERPRISE.md +0 -169
  20. package/docs/FAQ.md +0 -228
  21. package/docs/QUICK_START.md +0 -155
  22. package/docs/README.md +0 -67
  23. package/docs/RECURSIVE_SNAP_DEMO.gif +0 -0
  24. package/docs/social-preview-hq.png +0 -0
  25. package/docs/social-preview.png +0 -0
  26. package/examples/basic_usage.py +0 -84
  27. package/package.json.real +0 -24
  28. package/pyproject.toml +0 -50
  29. package/registry-server.json +0 -20
  30. package/scripts/demo_governance.py +0 -109
  31. package/scripts/sse_bridge.py +0 -87
  32. package/scripts/verify_windows_paths.py +0 -48
  33. package/server.json +0 -20
  34. package/src/mcp_server_nucleus/__init__.py +0 -8583
  35. package/src/mcp_server_nucleus/__main__.py +0 -9
  36. package/src/mcp_server_nucleus/cli.py +0 -1225
  37. package/src/mcp_server_nucleus/commitment_core.py +0 -146
  38. package/src/mcp_server_nucleus/commitment_ledger.py +0 -841
  39. package/src/mcp_server_nucleus/core/__init__.py +0 -6
  40. package/src/mcp_server_nucleus/core/orchestrator.py +0 -9
  41. package/src/mcp_server_nucleus/core/tool_registration_impl.py +0 -99
  42. package/src/mcp_server_nucleus/daemon/safety/lock.py +0 -121
  43. package/src/mcp_server_nucleus/deployment/render_integration.py +0 -4
  44. package/src/mcp_server_nucleus/hypervisor/__init__.py +0 -0
  45. package/src/mcp_server_nucleus/hypervisor/injector.py +0 -83
  46. package/src/mcp_server_nucleus/hypervisor/locker.py +0 -130
  47. package/src/mcp_server_nucleus/hypervisor/watchdog.py +0 -71
  48. package/src/mcp_server_nucleus/runtime/__init__.py +0 -58
  49. package/src/mcp_server_nucleus/runtime/agent.py +0 -517
  50. package/src/mcp_server_nucleus/runtime/agent_pool.py +0 -797
  51. package/src/mcp_server_nucleus/runtime/agents/base.py +0 -34
  52. package/src/mcp_server_nucleus/runtime/agents/oracle/manifest.json +0 -28
  53. package/src/mcp_server_nucleus/runtime/auth.py +0 -98
  54. package/src/mcp_server_nucleus/runtime/autopilot.py +0 -1057
  55. package/src/mcp_server_nucleus/runtime/broker.py +0 -126
  56. package/src/mcp_server_nucleus/runtime/budget.py +0 -153
  57. package/src/mcp_server_nucleus/runtime/capabilities/base.py +0 -20
  58. package/src/mcp_server_nucleus/runtime/capabilities/brain_ops.py +0 -253
  59. package/src/mcp_server_nucleus/runtime/capabilities/code_ops.py +0 -192
  60. package/src/mcp_server_nucleus/runtime/capabilities/depth_tracker.py +0 -81
  61. package/src/mcp_server_nucleus/runtime/capabilities/feature_map.py +0 -274
  62. package/src/mcp_server_nucleus/runtime/capabilities/marketing_engine.py +0 -205
  63. package/src/mcp_server_nucleus/runtime/capabilities/memory_ops.py +0 -79
  64. package/src/mcp_server_nucleus/runtime/capabilities/proof_system.py +0 -127
  65. package/src/mcp_server_nucleus/runtime/capabilities/render_ops.py +0 -80
  66. package/src/mcp_server_nucleus/runtime/capabilities/render_poller_cap.py +0 -266
  67. package/src/mcp_server_nucleus/runtime/capabilities/self_healing.py +0 -110
  68. package/src/mcp_server_nucleus/runtime/capabilities/strategy.py +0 -138
  69. package/src/mcp_server_nucleus/runtime/capabilities/synthesizer.py +0 -134
  70. package/src/mcp_server_nucleus/runtime/capabilities/web_ops.py +0 -94
  71. package/src/mcp_server_nucleus/runtime/common.py +0 -134
  72. package/src/mcp_server_nucleus/runtime/context_manager.py +0 -342
  73. package/src/mcp_server_nucleus/runtime/context_rules.json +0 -86
  74. package/src/mcp_server_nucleus/runtime/crdt_task_store.py +0 -349
  75. package/src/mcp_server_nucleus/runtime/critic.py +0 -150
  76. package/src/mcp_server_nucleus/runtime/daemon.py +0 -142
  77. package/src/mcp_server_nucleus/runtime/dashboard.py +0 -1087
  78. package/src/mcp_server_nucleus/runtime/depth_ops.py +0 -339
  79. package/src/mcp_server_nucleus/runtime/event_ops.py +0 -103
  80. package/src/mcp_server_nucleus/runtime/event_stream.py +0 -213
  81. package/src/mcp_server_nucleus/runtime/export.py +0 -64
  82. package/src/mcp_server_nucleus/runtime/factory.py +0 -594
  83. package/src/mcp_server_nucleus/runtime/federation.py +0 -1040
  84. package/src/mcp_server_nucleus/runtime/fetcher.py +0 -92
  85. package/src/mcp_server_nucleus/runtime/file_monitor.py +0 -208
  86. package/src/mcp_server_nucleus/runtime/firestore_bridge.py +0 -91
  87. package/src/mcp_server_nucleus/runtime/gcloud_ops.py +0 -186
  88. package/src/mcp_server_nucleus/runtime/hooks.py +0 -246
  89. package/src/mcp_server_nucleus/runtime/identity/gatekeeper.py +0 -106
  90. package/src/mcp_server_nucleus/runtime/identity/keygen.py +0 -147
  91. package/src/mcp_server_nucleus/runtime/identity/manifest.py +0 -62
  92. package/src/mcp_server_nucleus/runtime/identity/trust.py +0 -27
  93. package/src/mcp_server_nucleus/runtime/inspector.py +0 -74
  94. package/src/mcp_server_nucleus/runtime/installer.py +0 -81
  95. package/src/mcp_server_nucleus/runtime/ipc_auth.py +0 -377
  96. package/src/mcp_server_nucleus/runtime/lifecycle.py +0 -129
  97. package/src/mcp_server_nucleus/runtime/llm_client.py +0 -408
  98. package/src/mcp_server_nucleus/runtime/llm_tiers.json +0 -88
  99. package/src/mcp_server_nucleus/runtime/locking.py +0 -228
  100. package/src/mcp_server_nucleus/runtime/loops/fixer.py +0 -110
  101. package/src/mcp_server_nucleus/runtime/memory.py +0 -158
  102. package/src/mcp_server_nucleus/runtime/mounter.py +0 -210
  103. package/src/mcp_server_nucleus/runtime/mounter_ops.py +0 -250
  104. package/src/mcp_server_nucleus/runtime/nuke_protocol.py +0 -147
  105. package/src/mcp_server_nucleus/runtime/orchestrator.py +0 -370
  106. package/src/mcp_server_nucleus/runtime/orchestrator_unified.py +0 -263
  107. package/src/mcp_server_nucleus/runtime/orchestrator_v3.py +0 -617
  108. package/src/mcp_server_nucleus/runtime/plugin_loader.py +0 -84
  109. package/src/mcp_server_nucleus/runtime/policy.py +0 -117
  110. package/src/mcp_server_nucleus/runtime/profiling.py +0 -219
  111. package/src/mcp_server_nucleus/runtime/prometheus.py +0 -286
  112. package/src/mcp_server_nucleus/runtime/proposals.py +0 -101
  113. package/src/mcp_server_nucleus/runtime/publisher.py +0 -109
  114. package/src/mcp_server_nucleus/runtime/pulse.py +0 -516
  115. package/src/mcp_server_nucleus/runtime/registry.py +0 -82
  116. package/src/mcp_server_nucleus/runtime/render_ops.py +0 -83
  117. package/src/mcp_server_nucleus/runtime/schema_gen.py +0 -140
  118. package/src/mcp_server_nucleus/runtime/session_ops.py +0 -224
  119. package/src/mcp_server_nucleus/runtime/stdio_server.py +0 -633
  120. package/src/mcp_server_nucleus/runtime/storage.py +0 -83
  121. package/src/mcp_server_nucleus/runtime/strategy.py +0 -91
  122. package/src/mcp_server_nucleus/runtime/swarm.py +0 -170
  123. package/src/mcp_server_nucleus/runtime/sync_ops.py +0 -878
  124. package/src/mcp_server_nucleus/runtime/task_ingestion.py +0 -1020
  125. package/src/mcp_server_nucleus/runtime/task_ops.py +0 -449
  126. package/src/mcp_server_nucleus/runtime/task_scheduler.py +0 -488
  127. package/src/mcp_server_nucleus/runtime/team.py +0 -59
  128. package/src/mcp_server_nucleus/runtime/triggers.py +0 -237
  129. package/src/mcp_server_nucleus/runtime/vector_store.py +0 -125
  130. package/src/mcp_server_nucleus/runtime/watcher.py +0 -112
  131. package/src/mcp_server_nucleus/tool_tiers.py +0 -243
  132. package/tests/__init__.py +0 -1
  133. package/tests/conftest.py +0 -42
  134. package/tests/test_core.py +0 -235
  135. 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()