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,266 +0,0 @@
1
- from typing import List, Dict, Any, Optional
2
- import os
3
- import time
4
- import asyncio
5
- import logging
6
- import urllib.request
7
- import urllib.error
8
- import json
9
- from pathlib import Path
10
- from .base import Capability
11
-
12
- logger = logging.getLogger("nucleus.render_poller")
13
-
14
- # Global dict to store active polls in memory
15
- # {service_id: {"commit_sha": str, "start_time": float, "status": str, "task": asyncio.Task}}
16
- ACTIVE_POLLS = {}
17
-
18
- class RenderPolling(Capability):
19
- def __init__(self):
20
- self._brain_path = Path(os.environ.get("NUCLEAR_BRAIN_PATH", "/Users/lokeshgarg/.gemini/antigravity/brain/7c654df4-b83e-43f9-8620-f15868ec39d1"))
21
- self._api_key = os.environ.get("RENDER_API_KEY")
22
-
23
- @property
24
- def name(self) -> str:
25
- return "render_poller"
26
-
27
- @property
28
- def description(self) -> str:
29
- return "Automated deployment monitoring for Render services."
30
-
31
- def get_tools(self) -> List[Dict[str, Any]]:
32
- return [
33
- {
34
- "name": "brain_start_deploy_poll",
35
- "description": "Start monitoring a Render deploy in the background.",
36
- "parameters": {
37
- "type": "object",
38
- "properties": {
39
- "service_id": {"type": "string", "description": "Render service ID (e.g., 'srv-abc123')"},
40
- "commit_sha": {"type": "string", "description": "Optional Git commit SHA being deployed"}
41
- },
42
- "required": ["service_id"]
43
- }
44
- },
45
- {
46
- "name": "brain_check_deploy",
47
- "description": "Check status of an active deploy poll.",
48
- "parameters": {
49
- "type": "object",
50
- "properties": {
51
- "service_id": {"type": "string", "description": "Render service ID to check"}
52
- },
53
- "required": ["service_id"]
54
- }
55
- },
56
- {
57
- "name": "brain_smoke_test",
58
- "description": "Run a smoke test on any URL.",
59
- "parameters": {
60
- "type": "object",
61
- "properties": {
62
- "url": {"type": "string", "description": "Base URL of service (e.g., 'https://myapp.onrender.com')"},
63
- "endpoint": {"type": "string", "description": "Health endpoint to hit (default: '/api/health')"}
64
- },
65
- "required": ["url"]
66
- }
67
- }
68
- ]
69
-
70
- def execute_tool(self, tool_name: str, args: Dict) -> Any:
71
- try:
72
- if tool_name == "brain_start_deploy_poll":
73
- return self._start_poll(args.get("service_id"), args.get("commit_sha"))
74
- elif tool_name == "brain_check_deploy":
75
- return self._check_poll(args.get("service_id"))
76
- elif tool_name == "brain_smoke_test":
77
- return self._smoke_test(args.get("url"), args.get("endpoint", "/api/health"))
78
- return f"Tool {tool_name} not found"
79
- except Exception as e:
80
- return f"Error executing {tool_name}: {str(e)}"
81
-
82
- def _start_poll(self, service_id: str, commit_sha: Optional[str] = None) -> Dict:
83
- """Start a background polling task."""
84
- if not service_id:
85
- return {"error": "service_id required"}
86
-
87
- # Cancel existing poll for this service
88
- if service_id in ACTIVE_POLLS:
89
- existing = ACTIVE_POLLS[service_id]
90
- if not existing["task"].done():
91
- existing["task"].cancel()
92
- logger.info(f"Cancelled existing poll for {service_id}")
93
-
94
- # Start new task
95
- task = asyncio.create_task(self._poll_loop(service_id, commit_sha))
96
-
97
- ACTIVE_POLLS[service_id] = {
98
- "commit_sha": commit_sha or "latest",
99
- "start_time": time.time(),
100
- "status": "polling",
101
- "task": task,
102
- "logs": []
103
- }
104
-
105
- # Log event
106
- try:
107
- from ... import _emit_event
108
- _emit_event("deploy_poll_started", "render_poller", {
109
- "service_id": service_id,
110
- "commit_sha": commit_sha
111
- })
112
- except ImportError:
113
- pass # Circular import or not available
114
-
115
- return {
116
- "success": True,
117
- "message": f"Started polling {service_id}",
118
- "poll_id": f"poll_{service_id}_{int(time.time())}"
119
- }
120
-
121
- def _check_poll(self, service_id: str) -> Dict:
122
- """Check status of an active poll."""
123
- if service_id not in ACTIVE_POLLS:
124
- return {"status": "not_found", "message": "No active poll for this service."}
125
-
126
- poll = ACTIVE_POLLS[service_id]
127
- uptime = int(time.time() - poll["start_time"])
128
-
129
- # Check if task is done (failed or finished)
130
- if poll["task"].done():
131
- try:
132
- result = poll["task"].result()
133
- return {
134
- "status": "complete",
135
- "result": result,
136
- "uptime_seconds": uptime
137
- }
138
- except asyncio.CancelledError:
139
- return {"status": "cancelled", "uptime_seconds": uptime}
140
- except Exception as e:
141
- return {"status": "error", "error": str(e), "uptime_seconds": uptime}
142
-
143
- return {
144
- "status": "polling",
145
- "uptime_seconds": uptime,
146
- "commit_sha": poll["commit_sha"]
147
- }
148
-
149
- async def _poll_loop(self, service_id: str, commit_sha: Optional[str]):
150
- """Async loop to check Render API."""
151
- timeout_mins = 20
152
- start_time = time.time()
153
-
154
- if not self._api_key:
155
- # Simulation mode if no key
156
- await asyncio.sleep(2)
157
- if service_id == "srv-test":
158
- await asyncio.sleep(1)
159
- return {"success": True, "url": "https://test-app.onrender.com", "simulated": True}
160
-
161
- raise ValueError("RENDER_API_KEY not set")
162
-
163
- headers = {"Authorization": f"Bearer {self._api_key}"}
164
- url = f"https://api.render.com/v1/services/{service_id}/deploys?limit=1"
165
- req = urllib.request.Request(url, headers=headers)
166
-
167
- while time.time() - start_time < (timeout_mins * 60):
168
- try:
169
- loop = asyncio.get_running_loop()
170
- # Use run_in_executor for blocking urlopen
171
- msg = await loop.run_in_executor(None, lambda: self._fetch_url(req))
172
-
173
- if "error" in msg:
174
- logger.error(f"Render API error: {msg['error']}")
175
- else:
176
- data = msg.get("json", [])
177
- if data:
178
- latest = data[0].get("deploy")
179
- if latest:
180
- status = latest.get("status")
181
- # Match commit if provided
182
- latest_commit = latest.get("commit", {}).get("id")
183
- if commit_sha and latest_commit and not latest_commit.startswith(commit_sha):
184
- # Not our commit yet
185
- pass
186
- elif status == "live":
187
- deploy_url = f"https://{service_id}.onrender.com" # Simplification
188
-
189
- # Run smoke test!
190
- smoke = self._smoke_test(deploy_url)
191
-
192
- # Log complete
193
- try:
194
- from ... import _emit_event
195
- _emit_event("deploy_complete", "render_poller", {
196
- "service_id": service_id,
197
- "status": "live",
198
- "url": deploy_url,
199
- "smoke_test": smoke
200
- })
201
- except Exception:
202
- pass
203
-
204
- return {
205
- "success": True,
206
- "url": deploy_url,
207
- "smoke_test": smoke
208
- }
209
- elif status in ["build_failed", "update_failed", "canceled"]:
210
- return {"success": False, "status": status}
211
-
212
- except Exception as e:
213
- logger.error(f"Poll error: {e}")
214
-
215
- await asyncio.sleep(30)
216
-
217
- return {"success": False, "error": "Timeout"}
218
-
219
- def _fetch_url(self, req) -> Dict:
220
- """Helper to fetch URL synchronously."""
221
- try:
222
- with urllib.request.urlopen(req, timeout=60) as response:
223
- if response.status == 200:
224
- return {"json": json.load(response)}
225
- return {"error": f"HTTP {response.status}"}
226
- except Exception as e:
227
- return {"error": str(e)}
228
-
229
- def _smoke_test(self, url: str, endpoint: str = "/api/health") -> Dict:
230
- """Run a smoke test."""
231
- try:
232
- target = f"{url.rstrip('/')}{endpoint}"
233
- start = time.time()
234
-
235
- req = urllib.request.Request(target)
236
- try:
237
- with urllib.request.urlopen(req, timeout=60) as response:
238
- latency = (time.time() - start) * 1000
239
- passed = response.status == 200
240
- # Check JSON content if valid
241
- try:
242
- data = json.load(response)
243
- if isinstance(data, dict) and data.get("status") == "healthy":
244
- passed = True
245
- except Exception:
246
- pass
247
-
248
- return {
249
- "passed": passed,
250
- "status_code": response.status,
251
- "latency_ms": latency
252
- }
253
- except urllib.error.HTTPError as e:
254
- return {
255
- "passed": False,
256
- "status_code": e.code,
257
- "error": str(e)
258
- }
259
- except Exception as e:
260
- return {
261
- "passed": False,
262
- "error": str(e)
263
- }
264
-
265
- except Exception as e:
266
- return {"passed": False, "error": str(e)}
@@ -1,110 +0,0 @@
1
-
2
- from typing import List, Dict, Any
3
- import os
4
- import subprocess
5
- from .base import Capability
6
-
7
- class SelfHealingOps(Capability):
8
- def __init__(self):
9
- # Default health command (can be overridden by env)
10
- self.health_cmd = os.environ.get("NUCLEUS_HEALTH_CMD", "echo 'No health check configured.'")
11
-
12
- @property
13
- def name(self) -> str:
14
- return "self_healing_ops"
15
-
16
- @property
17
- def description(self) -> str:
18
- return "Diagnose and repair codebase issues."
19
-
20
- def get_tools(self) -> List[Dict[str, Any]]:
21
- return [
22
- {
23
- "name": "brain_scan_health",
24
- "description": "Run the project's configured health check (tests/lints).",
25
- "parameters": {
26
- "type": "object",
27
- "properties": {},
28
- "required": []
29
- }
30
- },
31
- {
32
- "name": "brain_generate_fix_plan",
33
- "description": "Generate a markdown plan to fix a specific error.",
34
- "parameters": {
35
- "type": "object",
36
- "properties": {
37
- "error_log": {"type": "string", "description": "The error output or log to analyze"},
38
- "context_files": {"type": "array", "items": {"type": "string"}, "description": "List of files related to the error"}
39
- },
40
- "required": ["error_log"]
41
- }
42
- }
43
- ]
44
-
45
- def execute_tool(self, tool_name: str, args: Dict) -> Any:
46
- if tool_name == "brain_scan_health":
47
- return self._scan_health()
48
- elif tool_name == "brain_generate_fix_plan":
49
- return self._generate_fix_plan(args)
50
- return f"Tool {tool_name} not found"
51
-
52
- def _scan_health(self) -> Dict:
53
- """Runs the configured health check command."""
54
- try:
55
- # Security: We run the command as-is.
56
- # In a real scenario, this should be sandboxed or strictly defined.
57
- result = subprocess.run(
58
- self.health_cmd,
59
- shell=True,
60
- capture_output=True,
61
- text=True,
62
- timeout=120 # 2 minute timeout for health checks
63
- )
64
-
65
- success = result.returncode == 0
66
- return {
67
- "success": success,
68
- "output": result.stdout,
69
- "errors": result.stderr,
70
- "command": self.health_cmd
71
- }
72
- except subprocess.TimeoutExpired:
73
- return {"success": False, "error": "Health check timed out."}
74
- except Exception as e:
75
- return {"success": False, "error": str(e)}
76
-
77
- def _generate_fix_plan(self, args: Dict) -> str:
78
- """
79
- Generates a structured plan for the user (or agent) to execute.
80
- This DOES NOT Apply the fix. Safety first.
81
- """
82
- error_log = args.get("error_log", "")
83
- context_files = args.get("context_files", [])
84
-
85
- # In a full recursive agent, we would use an LLM here to analyze the code.
86
- # But since we ARE the LLM (when the tool returns), we return the prompt
87
- # or a structure that helps the calling agent 'think'.
88
-
89
- # Actually, if this tool is called by an Agent, the Agent expects a result.
90
- # Returning a template helps the Agent formulate the next step.
91
-
92
- return f"""# Fix Plan Generation
93
-
94
- **Analyzed Error:**
95
- ```
96
- {error_log[:500]}...
97
- ```
98
-
99
- **Context Files:**
100
- {', '.join(context_files)}
101
-
102
- **Recommended Strategy:**
103
- 1. Read the context files using `code_read_file`.
104
- 2. Analyze the logic causing the error.
105
- 3. Create a reproduction case if possible.
106
- 4. Apply the fix using `code_write_file`.
107
- 5. Re-run `brain_scan_health`.
108
-
109
- *System Note: The Agent receiving this output should now proceed to Step 1.*
110
- """
@@ -1,138 +0,0 @@
1
- """
2
- Strategy Capability
3
- Allows the Oracle to interact with the "Cortex" (Strategy Folder).
4
- """
5
- import json
6
- import logging
7
- from pathlib import Path
8
- from typing import Dict, Any, List, Optional
9
- from ..capabilities.base import Capability
10
-
11
- logger = logging.getLogger("cap.strategy")
12
-
13
- class StrategyTool(Capability):
14
- def __init__(self, brain_path: Path, allowed_paths: List[str]):
15
- self.brain_path = brain_path
16
- # Resolve variables in paths
17
- self.allowed_paths = [
18
- Path(p.replace("${BRAIN_PATH}", str(brain_path))).resolve()
19
- for p in allowed_paths
20
- ]
21
- self._name = "strategy_ops"
22
- self._desc = "Read and Evolve Strategic Protocols"
23
-
24
- @property
25
- def name(self): return self._name
26
-
27
- @property
28
- def description(self): return self._desc
29
-
30
- def get_tools(self) -> List[Dict[str, Any]]:
31
- return [
32
- {
33
- "name": "read_strategy",
34
- "description": "Reads a strategy document or protocol.",
35
- "parameters": {
36
- "type": "object",
37
- "properties": {
38
- "filename": {"type": "string", "description": "Relative path to brain root (e.g. 'strategy/SOVEREIGN_TESTAMENT.md')"}
39
- },
40
- "required": ["filename"]
41
- }
42
- },
43
- {
44
- "name": "evolve_protocol",
45
- "description": "Overwrites/Updates a protocol document with new evolved content.",
46
- "parameters": {
47
- "type": "object",
48
- "properties": {
49
- "filename": {"type": "string"},
50
- "content": {"type": "string", "description": "The new content"},
51
- "reason": {"type": "string", "description": "Why was this evolution necessary?"}
52
- },
53
- "required": ["filename", "content", "reason"]
54
- }
55
- }
56
- ]
57
-
58
- def _validate_path(self, filename: str) -> Optional[Path]:
59
- # Simple security verify
60
- target = (self.brain_path / filename).resolve()
61
-
62
- # Check if target is within any allowed path
63
- allowed = False
64
- for parent in self.allowed_paths:
65
- # Check exact match (File allow)
66
- if target == parent:
67
- allowed = True
68
- break
69
- # Check directory containment
70
- # We assume if it doesn't have an suffix, it might be a dir,
71
- # or we just check if it IS a dir on disk.
72
- # If it's a non-existent dir whitelisted, we can't easily distinguish from file without trailing slash convention.
73
- # For now, rely on standard pathlib behavior.
74
- try:
75
- if parent.is_dir() and str(target).startswith(str(parent)):
76
- allowed = True
77
- break
78
- except OSError:
79
- pass
80
-
81
- if not allowed:
82
- logger.warning(f"⛔ ACCESS DENIED: {target} is not in allowed strategy paths.")
83
- return None
84
-
85
- return target
86
-
87
- def execute(self, params: Dict[str, Any]) -> str:
88
- # Since I'm binding multiple tools to one capability execute,
89
- # I need a way to dispatch.
90
- # But Capability.execute usually takes 'tool_name' or we rely on the implementation wrapping it.
91
- # Wait, the Standard we mostly used was:
92
- # 'execute' takes the params of the *tool call*.
93
- # But which tool?
94
- # Standard implementation in 'tool_manager' usually passes the Tool Name AND arguments.
95
- # But 'Capability.execute' interface in this codebase usually takes just params?
96
- # Let's check 'base.py' or 'filesystem.py'.
97
- # Assuming for now we need a 'name' or 'action' in params, or we implement separate capabilities.
98
- # Let's assume the caller passes the tool name or strict params.
99
-
100
- # Hack for MVP: Check keys
101
- filename = params.get("filename")
102
- content = params.get("content")
103
- reason = params.get("reason")
104
-
105
- if content is None:
106
- # READ
107
- path = self._validate_path(filename)
108
- if not path:
109
- return "Error: Access Denied"
110
- if not path.exists():
111
- return "Error: File not found"
112
- return path.read_text()
113
- else:
114
- # WRITE (Evolve)
115
- path = self._validate_path(filename)
116
- if not path:
117
- return "Error: Access Denied"
118
-
119
- # Log the evolution
120
- log_path = self.brain_path / "decisions" / "evolution_log.jsonl"
121
- log_path.parent.mkdir(parents=True, exist_ok=True)
122
-
123
- import datetime
124
- entry = {
125
- "timestamp": datetime.datetime.now().isoformat(),
126
- "file": filename,
127
- "reason": reason,
128
- "len_delta": len(content) - (path.stat().st_size if path.exists() else 0)
129
- }
130
- with open(log_path, "a") as f:
131
- f.write(json.dumps(entry) + "\n")
132
-
133
- path.parent.mkdir(parents=True, exist_ok=True)
134
- path.write_text(content)
135
- return f"Protocol {filename} evolved. Reason: {reason}"
136
-
137
- def get_capability(brain_path: Path, config: Dict):
138
- return StrategyTool(brain_path, config.get("paths", []))
@@ -1,134 +0,0 @@
1
- """
2
- Nucleus Synthesizer
3
- ===================
4
- The 'Executive Brain' that synthesizes system status into human-readable reports.
5
-
6
- Capabilities:
7
- - brain_synthesize_status_report: Generates a 'State of the Union' roadmap.
8
- """
9
-
10
- import os
11
- from pathlib import Path
12
- from typing import Dict, Optional
13
- import logging
14
- import subprocess
15
-
16
- try:
17
- from google import genai
18
- HAS_GENAI = True
19
- except ImportError:
20
- HAS_GENAI = False
21
-
22
- logger = logging.getLogger(__name__)
23
-
24
- def _get_api_key() -> Optional[str]:
25
- return os.environ.get("GEMINI_API_KEY")
26
-
27
- def brain_synthesize_status_report(
28
- project_root: str,
29
- focus: str = "roadmap"
30
- ) -> Dict[str, str]:
31
- """
32
- Generates a 'State of the Union' report by analyzing tasks, logs, and vision.
33
-
34
- Args:
35
- project_root: Absolute path to project root
36
- focus: 'roadmap' (default) or 'technical' or 'marketing'
37
-
38
- Returns:
39
- Dict with status and output report content
40
- """
41
- if not HAS_GENAI:
42
- return {"status": "error", "message": "google-genai library not installed"}
43
-
44
- api_key = _get_api_key()
45
- if not api_key:
46
- return {"status": "error", "message": "GEMINI_API_KEY not found"}
47
-
48
- root_path = Path(project_root)
49
-
50
- # 1. Gather Context
51
- context = {}
52
-
53
- # Task List (What's pending)
54
- task_path = root_path / ".brain" / "task.md"
55
- if task_path.exists():
56
- with open(task_path, 'r') as f:
57
- context['tasks'] = f.read()[:5000] # Cap usage
58
-
59
- # Vision (Where we are going)
60
- vision_path = root_path / ".brain" / "NUCLEUS_VISION.md"
61
- if vision_path.exists():
62
- with open(vision_path, 'r') as f:
63
- context['vision'] = f.read()
64
-
65
- # System Status (invisible work)
66
- try:
67
- cron_output = subprocess.check_output(['crontab', '-l'], stderr=subprocess.STDOUT).decode('utf-8')
68
- context['cron'] = cron_output
69
- except Exception:
70
- context['cron'] = "No crontab accessible."
71
-
72
- # Logs (Last 20 lines of key logs)
73
- log_paths = [
74
- root_path / ".brain/ledger/cron.log",
75
- Path("/tmp/nucleus_nightly.log"),
76
- Path("/tmp/nucleus_orchestrator.log")
77
- ]
78
- logs_summary = ""
79
- for log_p in log_paths:
80
- if log_p.exists():
81
- try:
82
- tail = subprocess.check_output(['tail', '-n', '10', str(log_p)]).decode('utf-8')
83
- logs_summary += f"\n--- {log_p.name} ---\n{tail}\n"
84
- except Exception:
85
- pass
86
- context['logs'] = logs_summary
87
-
88
- # 2. Construct Prompt
89
- prompt = f"""
90
- You are the Nucleus Brain (Executive Function).
91
-
92
- TASK:
93
- Generate a 'State of the Union' report for the Founder (The User).
94
-
95
- CONTEXT:
96
- 1. VISION (Where we are going):
97
- {context.get('vision', 'No vision file found.')}
98
-
99
- 2. TASKS (What is pending):
100
- {context.get('tasks', 'No task file found.')}
101
-
102
- 3. INVISIBLE MACHINERY (What is running in background):
103
- CRON: {context.get('cron')}
104
- LOGS: {context.get('logs')}
105
-
106
- INSTRUCTIONS:
107
- - Write a clear, high-level Roadmap Report.
108
- - Group status into:
109
- 1. ✅ Completed Capabilities (The Iron Man Suit)
110
- 2. ⚙️ Running Machinery (Invisible Work - cite logs)
111
- 3. 🚧 Active Construction (Next Phase)
112
- - Clarify the "Mac vs Cloud" status explicitly.
113
- - End with a concrete recommendation for the NEXT move.
114
-
115
- FORMAT:
116
- Markdown. Use Emoji. Be concise.
117
- """
118
-
119
- try:
120
- client = genai.Client(api_key=api_key)
121
- response = client.models.generate_content(
122
- model="gemini-2.0-flash-exp",
123
- contents=prompt
124
- )
125
-
126
- report = response.text
127
- return {
128
- "status": "success",
129
- "report": report
130
- }
131
-
132
- except Exception as e:
133
- logger.error(f"Status Generation failed: {e}")
134
- return {"status": "error", "message": str(e)}