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,449 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Nucleus Runtime - Task Operations (V2)
|
|
3
|
-
=====================================
|
|
4
|
-
Core logic for task management (CRUD, Claiming, Importing).
|
|
5
|
-
Moves task orchestration out of the monolith.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import json
|
|
9
|
-
import time
|
|
10
|
-
import uuid
|
|
11
|
-
import logging
|
|
12
|
-
from typing import Dict, Any, List, Optional
|
|
13
|
-
from pathlib import Path
|
|
14
|
-
|
|
15
|
-
# Relative imports assuming this is in mcp_server_nucleus.runtime
|
|
16
|
-
from .common import get_brain_path, _get_state
|
|
17
|
-
from .event_ops import _emit_event
|
|
18
|
-
|
|
19
|
-
logger = logging.getLogger("nucleus.task_ops")
|
|
20
|
-
|
|
21
|
-
def _get_tasks_list() -> List[Dict]:
|
|
22
|
-
"""Get the tasks array from tasks.json (V2) or fallback to state.json (V1)."""
|
|
23
|
-
try:
|
|
24
|
-
brain = get_brain_path()
|
|
25
|
-
tasks_path = brain / "ledger" / "tasks.json"
|
|
26
|
-
|
|
27
|
-
# Priority 1: Read V2 tasks.json
|
|
28
|
-
if tasks_path.exists():
|
|
29
|
-
with open(tasks_path, "r") as f:
|
|
30
|
-
data = json.load(f)
|
|
31
|
-
if isinstance(data, dict) and "tasks" in data:
|
|
32
|
-
return data["tasks"]
|
|
33
|
-
return data
|
|
34
|
-
|
|
35
|
-
# Priority 2: Fallback to V1 state.json
|
|
36
|
-
state = _get_state()
|
|
37
|
-
current_sprint = state.get("current_sprint", {})
|
|
38
|
-
return current_sprint.get("tasks", [])
|
|
39
|
-
except Exception as e:
|
|
40
|
-
logger.error(f"Error getting tasks list: {e}")
|
|
41
|
-
return []
|
|
42
|
-
|
|
43
|
-
def _save_tasks_list(tasks: List[Dict]) -> str:
|
|
44
|
-
"""Save the tasks array (prefers V2 tasks.json if it exists)."""
|
|
45
|
-
try:
|
|
46
|
-
brain = get_brain_path()
|
|
47
|
-
tasks_path = brain / "ledger" / "tasks.json"
|
|
48
|
-
|
|
49
|
-
# Priority 1: Write to V2 tasks.json if it exists (or create it)
|
|
50
|
-
if not tasks_path.exists():
|
|
51
|
-
tasks_path.parent.mkdir(parents=True, exist_ok=True)
|
|
52
|
-
|
|
53
|
-
with open(tasks_path, "w") as f:
|
|
54
|
-
json.dump(tasks, f, indent=2)
|
|
55
|
-
return "Tasks saved (V2)"
|
|
56
|
-
|
|
57
|
-
except Exception as e:
|
|
58
|
-
logger.error(f"Error saving tasks list: {e}")
|
|
59
|
-
return f"Error saving tasks: {str(e)}"
|
|
60
|
-
|
|
61
|
-
def _list_tasks(
|
|
62
|
-
status: Optional[str] = None,
|
|
63
|
-
priority: Optional[int] = None,
|
|
64
|
-
skill: Optional[str] = None,
|
|
65
|
-
claimed_by: Optional[str] = None
|
|
66
|
-
) -> List[Dict]:
|
|
67
|
-
"""List tasks with optional filters and external provider merging."""
|
|
68
|
-
try:
|
|
69
|
-
tasks = _get_tasks_list()
|
|
70
|
-
|
|
71
|
-
# Ensure all tasks have V2 fields (backward compat)
|
|
72
|
-
for task in tasks:
|
|
73
|
-
if "id" not in task:
|
|
74
|
-
task["id"] = f"task-{str(uuid.uuid4())[:8]}"
|
|
75
|
-
if "priority" not in task:
|
|
76
|
-
task["priority"] = 3 # Default medium
|
|
77
|
-
if "blocked_by" not in task:
|
|
78
|
-
task["blocked_by"] = []
|
|
79
|
-
if "required_skills" not in task:
|
|
80
|
-
# Migrate from preferred_role
|
|
81
|
-
if "preferred_role" in task:
|
|
82
|
-
task["required_skills"] = [task["preferred_role"].lower()]
|
|
83
|
-
else:
|
|
84
|
-
task["required_skills"] = []
|
|
85
|
-
if "source" not in task:
|
|
86
|
-
task["source"] = "synthesizer"
|
|
87
|
-
if "created_at" not in task:
|
|
88
|
-
task["created_at"] = time.strftime("%Y-%m-%dT%H:%M:%S%z")
|
|
89
|
-
if "updated_at" not in task:
|
|
90
|
-
task["updated_at"] = task["created_at"]
|
|
91
|
-
|
|
92
|
-
# Merge with Commitment Ledger (PEFS)
|
|
93
|
-
try:
|
|
94
|
-
from mcp_server_nucleus import commitment_ledger
|
|
95
|
-
brain = get_brain_path()
|
|
96
|
-
ledger = commitment_ledger.load_ledger(brain)
|
|
97
|
-
|
|
98
|
-
for comm in ledger.get("commitments", []):
|
|
99
|
-
comm_status = comm.get("status", "open").lower()
|
|
100
|
-
task_status = "PENDING"
|
|
101
|
-
if comm_status == "closed":
|
|
102
|
-
task_status = "DONE"
|
|
103
|
-
|
|
104
|
-
cm_task = {
|
|
105
|
-
"id": comm["id"],
|
|
106
|
-
"description": comm["description"],
|
|
107
|
-
"status": task_status,
|
|
108
|
-
"priority": comm.get("priority", 3),
|
|
109
|
-
"blocked_by": [],
|
|
110
|
-
"required_skills": comm.get("required_skills", []),
|
|
111
|
-
"source": f"ledger:{comm.get('source', 'unknown')}",
|
|
112
|
-
"created_at": comm.get("created"),
|
|
113
|
-
"claimed_by": None
|
|
114
|
-
}
|
|
115
|
-
tasks.append(cm_task)
|
|
116
|
-
except Exception as e:
|
|
117
|
-
logger.warning(f"Failed to merge commitment ledger: {e}")
|
|
118
|
-
|
|
119
|
-
# Apply filters
|
|
120
|
-
filtered = tasks
|
|
121
|
-
|
|
122
|
-
if status:
|
|
123
|
-
status_map = {"TODO": "PENDING", "COMPLETE": "DONE"}
|
|
124
|
-
target_status = status_map.get(status, status)
|
|
125
|
-
filtered = [t for t in filtered if t.get("status", "").upper() == target_status.upper()
|
|
126
|
-
or t.get("status", "").upper() == status.upper()]
|
|
127
|
-
|
|
128
|
-
if priority is not None:
|
|
129
|
-
filtered = [t for t in filtered if t.get("priority") == priority]
|
|
130
|
-
|
|
131
|
-
if skill:
|
|
132
|
-
filtered = [t for t in filtered if skill.lower() in
|
|
133
|
-
[s.lower() for s in t.get("required_skills", [])]]
|
|
134
|
-
|
|
135
|
-
if claimed_by:
|
|
136
|
-
filtered = [t for t in filtered if claimed_by in str(t.get("claimed_by", ""))]
|
|
137
|
-
|
|
138
|
-
# Merge with Cloud Tasks (if available)
|
|
139
|
-
try:
|
|
140
|
-
from mcp_server_nucleus.runtime.firestore_bridge import get_bridge
|
|
141
|
-
cloud_tasks = get_bridge().list_cloud_tasks()
|
|
142
|
-
if cloud_tasks:
|
|
143
|
-
local_ids = {t["id"] for t in filtered}
|
|
144
|
-
for ct in cloud_tasks:
|
|
145
|
-
if ct["id"] not in local_ids:
|
|
146
|
-
filtered.append(ct)
|
|
147
|
-
except Exception as e:
|
|
148
|
-
logger.warning(f"Failed to merge cloud tasks: {e}")
|
|
149
|
-
pass
|
|
150
|
-
|
|
151
|
-
# Sort by priority (asc)
|
|
152
|
-
filtered.sort(key=lambda x: x.get("priority", 3))
|
|
153
|
-
|
|
154
|
-
return filtered
|
|
155
|
-
except Exception as e:
|
|
156
|
-
logger.error(f"Error listing tasks: {e}")
|
|
157
|
-
return []
|
|
158
|
-
|
|
159
|
-
def _add_task(
|
|
160
|
-
description: str,
|
|
161
|
-
priority: int = 3,
|
|
162
|
-
blocked_by: Optional[List[str]] = None,
|
|
163
|
-
required_skills: Optional[List[str]] = None,
|
|
164
|
-
source: str = "synthesizer",
|
|
165
|
-
task_id: Optional[str] = None,
|
|
166
|
-
skip_dep_check: bool = False
|
|
167
|
-
) -> Dict[str, Any]:
|
|
168
|
-
"""Create a new task."""
|
|
169
|
-
try:
|
|
170
|
-
tasks = _get_tasks_list()
|
|
171
|
-
task_ids = {t.get("id") for t in tasks if t.get("id")}
|
|
172
|
-
|
|
173
|
-
if blocked_by and not skip_dep_check:
|
|
174
|
-
for dep_id in blocked_by:
|
|
175
|
-
if dep_id not in task_ids:
|
|
176
|
-
return {
|
|
177
|
-
"success": False,
|
|
178
|
-
"error": f"Referential integrity violation: dependency '{dep_id}' does not exist"
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
now = time.strftime("%Y-%m-%dT%H:%M:%S%z")
|
|
182
|
-
new_task_id = task_id if task_id else f"task-{str(uuid.uuid4())[:8]}"
|
|
183
|
-
|
|
184
|
-
if new_task_id in task_ids:
|
|
185
|
-
return {"success": False, "error": f"Task ID '{new_task_id}' already exists"}
|
|
186
|
-
|
|
187
|
-
if blocked_by and new_task_id in blocked_by:
|
|
188
|
-
return {"success": False, "error": "DAG violation: task cannot block itself"}
|
|
189
|
-
|
|
190
|
-
new_task = {
|
|
191
|
-
"id": new_task_id,
|
|
192
|
-
"description": description,
|
|
193
|
-
"status": "PENDING" if not blocked_by else "BLOCKED",
|
|
194
|
-
"priority": priority,
|
|
195
|
-
"blocked_by": blocked_by or [],
|
|
196
|
-
"required_skills": required_skills or [],
|
|
197
|
-
"claimed_by": None,
|
|
198
|
-
"source": source,
|
|
199
|
-
"escalation_reason": None,
|
|
200
|
-
"created_at": now,
|
|
201
|
-
"updated_at": now
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
tasks.append(new_task)
|
|
205
|
-
_save_tasks_list(tasks)
|
|
206
|
-
|
|
207
|
-
_emit_event("task_created", source, {
|
|
208
|
-
"task_id": new_task_id,
|
|
209
|
-
"description": description,
|
|
210
|
-
"priority": priority
|
|
211
|
-
})
|
|
212
|
-
|
|
213
|
-
return {"success": True, "task": new_task}
|
|
214
|
-
except Exception as e:
|
|
215
|
-
return {"success": False, "error": str(e)}
|
|
216
|
-
|
|
217
|
-
def _update_task(task_id: str, updates: Dict[str, Any]) -> Dict:
|
|
218
|
-
"""Update task fields."""
|
|
219
|
-
try:
|
|
220
|
-
tasks = _get_tasks_list()
|
|
221
|
-
|
|
222
|
-
for task in tasks:
|
|
223
|
-
if task.get("id") == task_id or task.get("description") == task_id:
|
|
224
|
-
valid_keys = ["status", "priority", "description", "blocked_by",
|
|
225
|
-
"required_skills", "claimed_by"]
|
|
226
|
-
|
|
227
|
-
if "blocked_by" in updates:
|
|
228
|
-
all_ids = {t["id"] for t in tasks}
|
|
229
|
-
for dep_id in updates["blocked_by"]:
|
|
230
|
-
if dep_id not in all_ids:
|
|
231
|
-
raise ValueError(f"Referential integrity violation: Dependency task '{dep_id}' does not exist")
|
|
232
|
-
|
|
233
|
-
old_status = task.get("status")
|
|
234
|
-
|
|
235
|
-
for key, value in updates.items():
|
|
236
|
-
if key in valid_keys:
|
|
237
|
-
task[key] = value
|
|
238
|
-
|
|
239
|
-
task["updated_at"] = time.strftime("%Y-%m-%dT%H:%M:%S%z")
|
|
240
|
-
|
|
241
|
-
_save_tasks_list(tasks)
|
|
242
|
-
|
|
243
|
-
new_status = task.get("status")
|
|
244
|
-
if old_status != new_status and "status" in updates:
|
|
245
|
-
_emit_event(
|
|
246
|
-
"task_state_changed",
|
|
247
|
-
"brain_update_task",
|
|
248
|
-
{
|
|
249
|
-
"task_id": task.get("id"),
|
|
250
|
-
"old_status": old_status,
|
|
251
|
-
"new_status": new_status
|
|
252
|
-
}
|
|
253
|
-
)
|
|
254
|
-
|
|
255
|
-
return {"success": True, "task": task}
|
|
256
|
-
|
|
257
|
-
return {"success": False, "error": "Task not found"}
|
|
258
|
-
except Exception as e:
|
|
259
|
-
return {"success": False, "error": str(e)}
|
|
260
|
-
|
|
261
|
-
def _claim_task(task_id: str, agent_id: str) -> Dict[str, Any]:
|
|
262
|
-
"""Atomically claim a task."""
|
|
263
|
-
try:
|
|
264
|
-
tasks = _get_tasks_list()
|
|
265
|
-
|
|
266
|
-
for task in tasks:
|
|
267
|
-
if task.get("id") == task_id or task.get("description") == task_id:
|
|
268
|
-
if task.get("claimed_by"):
|
|
269
|
-
return {"success": False, "error": f"Task already claimed by {task['claimed_by']}"}
|
|
270
|
-
|
|
271
|
-
status = task.get("status", "").upper()
|
|
272
|
-
if status not in ["TODO", "PENDING", "READY"]:
|
|
273
|
-
return {"success": False, "error": f"Task status is {status}, cannot claim"}
|
|
274
|
-
|
|
275
|
-
task["claimed_by"] = agent_id
|
|
276
|
-
task["status"] = "IN_PROGRESS"
|
|
277
|
-
task["updated_at"] = time.strftime("%Y-%m-%dT%H:%M:%S%z")
|
|
278
|
-
|
|
279
|
-
_save_tasks_list(tasks)
|
|
280
|
-
|
|
281
|
-
_emit_event("task_claimed", agent_id, {
|
|
282
|
-
"task_id": task.get("id", task_id),
|
|
283
|
-
"description": task.get("description")
|
|
284
|
-
})
|
|
285
|
-
|
|
286
|
-
return {"success": True, "task": task}
|
|
287
|
-
|
|
288
|
-
return {"success": False, "error": "Task not found"}
|
|
289
|
-
except Exception as e:
|
|
290
|
-
return {"success": False, "error": str(e)}
|
|
291
|
-
|
|
292
|
-
def _get_next_task(skills: List[str]) -> Optional[Dict]:
|
|
293
|
-
"""Get highest priority unblocked task matching skills."""
|
|
294
|
-
try:
|
|
295
|
-
tasks = _list_tasks() # This handles sorting and external merging
|
|
296
|
-
|
|
297
|
-
actionable = []
|
|
298
|
-
for task in tasks:
|
|
299
|
-
status = task.get("status", "").upper()
|
|
300
|
-
if status not in ["TODO", "PENDING", "READY"]:
|
|
301
|
-
continue
|
|
302
|
-
|
|
303
|
-
if task.get("claimed_by"):
|
|
304
|
-
continue
|
|
305
|
-
|
|
306
|
-
blocked_by = task.get("blocked_by", [])
|
|
307
|
-
if blocked_by:
|
|
308
|
-
all_tasks = _get_tasks_list()
|
|
309
|
-
blocking_done = True
|
|
310
|
-
for blocker_id in blocked_by:
|
|
311
|
-
for t in all_tasks:
|
|
312
|
-
if t.get("id") == blocker_id:
|
|
313
|
-
if t.get("status", "").upper() not in ["DONE", "COMPLETE"]:
|
|
314
|
-
blocking_done = False
|
|
315
|
-
break
|
|
316
|
-
if not blocking_done:
|
|
317
|
-
continue
|
|
318
|
-
|
|
319
|
-
required = [s.lower() for s in task.get("required_skills", [])]
|
|
320
|
-
available = [s.lower() for s in skills]
|
|
321
|
-
|
|
322
|
-
if not required or any(r in available for r in required):
|
|
323
|
-
actionable.append(task)
|
|
324
|
-
|
|
325
|
-
actionable.sort(key=lambda t: t.get("priority", 3))
|
|
326
|
-
return actionable[0] if actionable else None
|
|
327
|
-
except Exception as e:
|
|
328
|
-
logger.error(f"Error getting next task: {e}")
|
|
329
|
-
return None
|
|
330
|
-
|
|
331
|
-
def _escalate_task(task_id: str, reason: str) -> Dict[str, Any]:
|
|
332
|
-
"""Escalate a task to request human help."""
|
|
333
|
-
try:
|
|
334
|
-
tasks = _get_tasks_list()
|
|
335
|
-
|
|
336
|
-
for task in tasks:
|
|
337
|
-
if task.get("id") == task_id or task.get("description") == task_id:
|
|
338
|
-
task["status"] = "ESCALATED"
|
|
339
|
-
task["escalation_reason"] = reason
|
|
340
|
-
task["claimed_by"] = None # Unclaim
|
|
341
|
-
task["updated_at"] = time.strftime("%Y-%m-%dT%H:%M:%S%z")
|
|
342
|
-
|
|
343
|
-
_save_tasks_list(tasks)
|
|
344
|
-
|
|
345
|
-
_emit_event("task_escalated", "nucleus_mcp", {
|
|
346
|
-
"task_id": task.get("id", task_id),
|
|
347
|
-
"reason": reason
|
|
348
|
-
})
|
|
349
|
-
|
|
350
|
-
return {"success": True, "task": task}
|
|
351
|
-
|
|
352
|
-
return {"success": False, "error": "Task not found"}
|
|
353
|
-
except Exception as e:
|
|
354
|
-
return {"success": False, "error": str(e)}
|
|
355
|
-
|
|
356
|
-
def _import_tasks_from_jsonl(jsonl_path: str, clear_existing: bool = False, merge_gtm_params: bool = True) -> Dict:
|
|
357
|
-
"""Import tasks from a JSONL file into the brain database."""
|
|
358
|
-
try:
|
|
359
|
-
brain = get_brain_path()
|
|
360
|
-
|
|
361
|
-
jsonl_file = Path(jsonl_path)
|
|
362
|
-
if not jsonl_file.is_absolute():
|
|
363
|
-
jsonl_file = brain / jsonl_path
|
|
364
|
-
|
|
365
|
-
if not jsonl_file.exists():
|
|
366
|
-
return {
|
|
367
|
-
"success": False,
|
|
368
|
-
"error": f"File not found: {jsonl_path}",
|
|
369
|
-
"imported": 0,
|
|
370
|
-
"skipped": 0
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
existing_tasks = _get_tasks_list()
|
|
374
|
-
existing_ids = {t.get("id") for t in existing_tasks if t.get("id")}
|
|
375
|
-
|
|
376
|
-
if clear_existing:
|
|
377
|
-
existing_tasks = []
|
|
378
|
-
existing_ids = set()
|
|
379
|
-
|
|
380
|
-
imported = 0
|
|
381
|
-
skipped = 0
|
|
382
|
-
errors = []
|
|
383
|
-
new_tasks = []
|
|
384
|
-
|
|
385
|
-
for line_num, line in enumerate(jsonl_file.read_text().splitlines(), 1):
|
|
386
|
-
if not line.strip():
|
|
387
|
-
continue
|
|
388
|
-
|
|
389
|
-
try:
|
|
390
|
-
task_data = json.loads(line)
|
|
391
|
-
except json.JSONDecodeError as e:
|
|
392
|
-
errors.append(f"Line {line_num}: Invalid JSON - {str(e)}")
|
|
393
|
-
skipped += 1
|
|
394
|
-
continue
|
|
395
|
-
|
|
396
|
-
task_id = task_data.get("id")
|
|
397
|
-
if not clear_existing and task_id in existing_ids:
|
|
398
|
-
skipped += 1
|
|
399
|
-
continue
|
|
400
|
-
|
|
401
|
-
if not task_data.get("description"):
|
|
402
|
-
errors.append(f"Line {line_num}: Missing description")
|
|
403
|
-
skipped += 1
|
|
404
|
-
continue
|
|
405
|
-
|
|
406
|
-
now = time.strftime("%Y-%m-%dT%H:%M:%S%z")
|
|
407
|
-
task = {
|
|
408
|
-
"id": task_id or f"task-{str(uuid.uuid4())[:8]}",
|
|
409
|
-
"description": task_data.get("description"),
|
|
410
|
-
"status": task_data.get("status", "PENDING").upper(),
|
|
411
|
-
"priority": int(task_data.get("priority", 3)),
|
|
412
|
-
"blocked_by": task_data.get("blocked_by", []),
|
|
413
|
-
"required_skills": task_data.get("required_skills", []),
|
|
414
|
-
"claimed_by": task_data.get("claimed_by"),
|
|
415
|
-
"source": f"import:{jsonl_file.name}",
|
|
416
|
-
"created_at": task_data.get("created_at", now),
|
|
417
|
-
"updated_at": now
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
# GTM Metadata Merging
|
|
421
|
-
if merge_gtm_params:
|
|
422
|
-
if "environment" in task_data:
|
|
423
|
-
task["environment"] = task_data["environment"]
|
|
424
|
-
if "model" in task_data:
|
|
425
|
-
task["model"] = task_data["model"]
|
|
426
|
-
if "step" in task_data:
|
|
427
|
-
task["step"] = task_data["step"]
|
|
428
|
-
|
|
429
|
-
new_tasks.append(task)
|
|
430
|
-
existing_ids.add(task["id"])
|
|
431
|
-
imported += 1
|
|
432
|
-
|
|
433
|
-
if imported > 0:
|
|
434
|
-
final_tasks = existing_tasks + new_tasks
|
|
435
|
-
_save_tasks_list(final_tasks)
|
|
436
|
-
|
|
437
|
-
_emit_event("tasks_imported", "nucleus_mcp", {
|
|
438
|
-
"source": str(jsonl_file),
|
|
439
|
-
"count": imported
|
|
440
|
-
})
|
|
441
|
-
|
|
442
|
-
return {
|
|
443
|
-
"success": True,
|
|
444
|
-
"imported": imported,
|
|
445
|
-
"skipped": skipped,
|
|
446
|
-
"errors": errors
|
|
447
|
-
}
|
|
448
|
-
except Exception as e:
|
|
449
|
-
return {"success": False, "error": str(e), "imported": 0, "skipped": 0}
|