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,192 +0,0 @@
|
|
|
1
|
-
from typing import List, Dict, Any
|
|
2
|
-
import os
|
|
3
|
-
import subprocess
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from .base import Capability
|
|
6
|
-
|
|
7
|
-
class CodeOps(Capability):
|
|
8
|
-
@property
|
|
9
|
-
def name(self) -> str:
|
|
10
|
-
return "code_ops"
|
|
11
|
-
|
|
12
|
-
@property
|
|
13
|
-
def description(self) -> str:
|
|
14
|
-
return "FileSystem and Shell Access for Coding Agents."
|
|
15
|
-
|
|
16
|
-
def get_tools(self) -> List[Dict[str, Any]]:
|
|
17
|
-
return [
|
|
18
|
-
{
|
|
19
|
-
"name": "code_read_file",
|
|
20
|
-
"description": "Read file contents.",
|
|
21
|
-
"parameters": {
|
|
22
|
-
"type": "object",
|
|
23
|
-
"properties": {
|
|
24
|
-
"path": {"type": "string", "description": "Absolute or relative path"}
|
|
25
|
-
},
|
|
26
|
-
"required": ["path"]
|
|
27
|
-
}
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
"name": "code_write_file",
|
|
31
|
-
"description": "Write or overwrite file contents. Creates directories if needed.",
|
|
32
|
-
"parameters": {
|
|
33
|
-
"type": "object",
|
|
34
|
-
"properties": {
|
|
35
|
-
"path": {"type": "string", "description": "Absolute or relative path"},
|
|
36
|
-
"content": {"type": "string"}
|
|
37
|
-
},
|
|
38
|
-
"required": ["path", "content"]
|
|
39
|
-
}
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
"name": "code_run_command",
|
|
43
|
-
"description": "Execute a shell command.",
|
|
44
|
-
"parameters": {
|
|
45
|
-
"type": "object",
|
|
46
|
-
"properties": {
|
|
47
|
-
"command": {"type": "string"},
|
|
48
|
-
"timeout": {"type": "integer", "default": 30}
|
|
49
|
-
},
|
|
50
|
-
"required": ["command"]
|
|
51
|
-
}
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
"name": "code_list_files",
|
|
55
|
-
"description": "List files in directory.",
|
|
56
|
-
"parameters": {
|
|
57
|
-
"type": "object",
|
|
58
|
-
"properties": {
|
|
59
|
-
"path": {"type": "string", "default": "."}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
]
|
|
64
|
-
|
|
65
|
-
def _resolve_path(self, path_str: str) -> Path:
|
|
66
|
-
"""
|
|
67
|
-
Self-Healing Path Resolver.
|
|
68
|
-
"""
|
|
69
|
-
cwd = Path(os.getcwd())
|
|
70
|
-
path = Path(path_str)
|
|
71
|
-
|
|
72
|
-
# 1. Try path as-is
|
|
73
|
-
if path.exists():
|
|
74
|
-
return path
|
|
75
|
-
|
|
76
|
-
# 2. Try relative to CWD
|
|
77
|
-
if not path.is_absolute():
|
|
78
|
-
candidate = cwd / path
|
|
79
|
-
if candidate.exists():
|
|
80
|
-
return candidate
|
|
81
|
-
|
|
82
|
-
# 3. Try relative to Project Root (BRAIN_PATH.parent)
|
|
83
|
-
brain_path = Path(os.environ.get("NUCLEUS_BRAIN_PATH", "/Users/lokeshgarg/ai-mvp-backend/.brain"))
|
|
84
|
-
project_root = brain_path.parent
|
|
85
|
-
|
|
86
|
-
if not path.is_absolute():
|
|
87
|
-
candidate = project_root / path
|
|
88
|
-
if candidate.exists():
|
|
89
|
-
return candidate
|
|
90
|
-
else:
|
|
91
|
-
# If absolute but invalid, try re-rooting
|
|
92
|
-
# e.g. /gentlequest-blog/... -> PROJECT_ROOT/gentlequest-blog/...
|
|
93
|
-
if 'ai-mvp-backend' in path_str:
|
|
94
|
-
relative = path_str.split('ai-mvp-backend')[-1].lstrip('/')
|
|
95
|
-
candidate = project_root / relative
|
|
96
|
-
if candidate.exists():
|
|
97
|
-
return candidate
|
|
98
|
-
else:
|
|
99
|
-
# Try just the relative part of the absolute path
|
|
100
|
-
# e.g. /foo/bar -> PROJECT_ROOT/foo/bar
|
|
101
|
-
relative = path_str.lstrip('/')
|
|
102
|
-
candidate = project_root / relative
|
|
103
|
-
if candidate.exists():
|
|
104
|
-
return candidate
|
|
105
|
-
|
|
106
|
-
return path
|
|
107
|
-
|
|
108
|
-
def execute_tool(self, tool_name: str, args: Dict) -> str:
|
|
109
|
-
"""Execute local filesystem/shell operations."""
|
|
110
|
-
cwd = os.getcwd()
|
|
111
|
-
|
|
112
|
-
if tool_name == "code_read_file":
|
|
113
|
-
path = self._resolve_path(args['path'])
|
|
114
|
-
|
|
115
|
-
if not path.exists():
|
|
116
|
-
return f"Error: File not found: {path} (in {cwd})"
|
|
117
|
-
return path.read_text(encoding='utf-8')
|
|
118
|
-
|
|
119
|
-
elif tool_name == "code_write_file":
|
|
120
|
-
path_str = args['path']
|
|
121
|
-
path = Path(path_str)
|
|
122
|
-
|
|
123
|
-
# ============================================================
|
|
124
|
-
# ENTERPRISE PATH RESOLUTION (Fix for Read-Only Filesystem Bug)
|
|
125
|
-
# ============================================================
|
|
126
|
-
# Problem: Agent subprocess may not have NUCLEUS_BRAIN_PATH set,
|
|
127
|
-
# causing relative paths like 'gentlequest-blog/...' to resolve to '/'
|
|
128
|
-
# Solution: Robust fallback chain with explicit validation
|
|
129
|
-
|
|
130
|
-
PROJECT_ROOT = Path("/Users/lokeshgarg/ai-mvp-backend")
|
|
131
|
-
|
|
132
|
-
if not path.is_absolute():
|
|
133
|
-
resolved_root = None
|
|
134
|
-
|
|
135
|
-
# Option 1: Use NUCLEUS_BRAIN_PATH if valid
|
|
136
|
-
brain_path_str = os.environ.get("NUCLEUS_BRAIN_PATH", "")
|
|
137
|
-
if brain_path_str and ".brain" in brain_path_str:
|
|
138
|
-
brain_path = Path(brain_path_str)
|
|
139
|
-
if brain_path.exists():
|
|
140
|
-
resolved_root = brain_path.parent
|
|
141
|
-
|
|
142
|
-
# Option 2: Fallback to hardcoded PROJECT_ROOT
|
|
143
|
-
if not resolved_root:
|
|
144
|
-
resolved_root = PROJECT_ROOT
|
|
145
|
-
|
|
146
|
-
path = resolved_root / path
|
|
147
|
-
# Log resolution for debugging
|
|
148
|
-
print(f"[CodeOps] Path resolved: {path_str} → {path}")
|
|
149
|
-
|
|
150
|
-
# Ensure parent directory exists
|
|
151
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
|
152
|
-
path.write_text(args['content'], encoding='utf-8')
|
|
153
|
-
return f"✅ Wrote {len(args['content'])} bytes to {path}"
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
elif tool_name == "code_run_command":
|
|
157
|
-
cmd = args['command']
|
|
158
|
-
timeout = args.get('timeout', 30)
|
|
159
|
-
try:
|
|
160
|
-
result = subprocess.run(
|
|
161
|
-
cmd,
|
|
162
|
-
shell=True,
|
|
163
|
-
capture_output=True,
|
|
164
|
-
text=True,
|
|
165
|
-
cwd=cwd,
|
|
166
|
-
timeout=timeout
|
|
167
|
-
)
|
|
168
|
-
return f"Exit Code: {result.returncode}\nstdout:\n{result.stdout}\nstderr:\n{result.stderr}"
|
|
169
|
-
except subprocess.TimeoutExpired:
|
|
170
|
-
return f"Error: Command timed out after {timeout}s"
|
|
171
|
-
except Exception as e:
|
|
172
|
-
return f"Error: {str(e)}"
|
|
173
|
-
|
|
174
|
-
elif tool_name == "code_list_files":
|
|
175
|
-
path = Path(args.get('path', '.'))
|
|
176
|
-
if not path.is_absolute():
|
|
177
|
-
path = Path(cwd) / path
|
|
178
|
-
|
|
179
|
-
if not path.exists():
|
|
180
|
-
return f"Error: Path not found: {path}"
|
|
181
|
-
|
|
182
|
-
try:
|
|
183
|
-
# Use 'ls -F' style output for simplicity
|
|
184
|
-
entries = []
|
|
185
|
-
for p in path.iterdir():
|
|
186
|
-
kind = "/" if p.is_dir() else ""
|
|
187
|
-
entries.append(f"{p.name}{kind}")
|
|
188
|
-
return "\n".join(sorted(entries))
|
|
189
|
-
except Exception as e:
|
|
190
|
-
return f"Error listing files: {str(e)}"
|
|
191
|
-
|
|
192
|
-
return f"Tool {tool_name} not found in CodeOps."
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
from typing import List, Dict, Any
|
|
2
|
-
import os
|
|
3
|
-
from .base import Capability
|
|
4
|
-
# Import core logic from top-level package
|
|
5
|
-
# Import core logic lazily inside methods to avoid circular dependency
|
|
6
|
-
# from ... import _depth_push, _depth_pop, _depth_show, _depth_reset
|
|
7
|
-
|
|
8
|
-
class DepthTracker(Capability):
|
|
9
|
-
def __init__(self):
|
|
10
|
-
# Determine brain path (fallback for agent runtime if env not set)
|
|
11
|
-
if not os.environ.get("NUCLEAR_BRAIN_PATH"):
|
|
12
|
-
# Default used in verification/dev
|
|
13
|
-
os.environ["NUCLEAR_BRAIN_PATH"] = "/Users/lokeshgarg/.gemini/antigravity/brain/7c654df4-b83e-43f9-8620-f15868ec39d1"
|
|
14
|
-
|
|
15
|
-
@property
|
|
16
|
-
def name(self) -> str:
|
|
17
|
-
return "depth_tracker"
|
|
18
|
-
|
|
19
|
-
@property
|
|
20
|
-
def description(self) -> str:
|
|
21
|
-
return "Tools for tracking conversation depth and preventing rabbit holes."
|
|
22
|
-
|
|
23
|
-
def get_tools(self) -> List[Dict[str, Any]]:
|
|
24
|
-
return [
|
|
25
|
-
{
|
|
26
|
-
"name": "brain_depth_push",
|
|
27
|
-
"description": "Go deeper into a subtopic. Tracks position in conversation tree.",
|
|
28
|
-
"parameters": {
|
|
29
|
-
"type": "object",
|
|
30
|
-
"properties": {
|
|
31
|
-
"topic": {"type": "string", "description": "What you're diving into"}
|
|
32
|
-
},
|
|
33
|
-
"required": ["topic"]
|
|
34
|
-
}
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
"name": "brain_depth_pop",
|
|
38
|
-
"description": "Come back up one level in the conversation tree.",
|
|
39
|
-
"parameters": {"type": "object", "properties": {}}
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
"name": "brain_depth_show",
|
|
43
|
-
"description": "Show current depth state with visual indicator and tree.",
|
|
44
|
-
"parameters": {"type": "object", "properties": {}}
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
"name": "brain_depth_reset",
|
|
48
|
-
"description": "Reset depth to root level (0). Clears all levels.",
|
|
49
|
-
"parameters": {"type": "object", "properties": {}}
|
|
50
|
-
}
|
|
51
|
-
]
|
|
52
|
-
|
|
53
|
-
def execute_tool(self, tool_name: str, args: Dict) -> str:
|
|
54
|
-
# Lazy import to avoid circular dependency
|
|
55
|
-
from ... import _depth_push, _depth_pop, _depth_show, _depth_reset
|
|
56
|
-
|
|
57
|
-
if tool_name == "brain_depth_push":
|
|
58
|
-
result = _depth_push(args.get("topic"))
|
|
59
|
-
if "error" in result:
|
|
60
|
-
return f"Error: {result['error']}"
|
|
61
|
-
return f"Descended to Level {result.get('current_depth')}: '{result.get('topic')}'.\n{result.get('warning', '')}\n{result.get('indicator')}"
|
|
62
|
-
|
|
63
|
-
elif tool_name == "brain_depth_pop":
|
|
64
|
-
result = _depth_pop()
|
|
65
|
-
if "error" in result:
|
|
66
|
-
return f"Error: {result['error']}"
|
|
67
|
-
return f"{result.get('message')}\n{result.get('indicator')}"
|
|
68
|
-
|
|
69
|
-
elif tool_name == "brain_depth_show":
|
|
70
|
-
result = _depth_show()
|
|
71
|
-
if "error" in result:
|
|
72
|
-
return f"Error: {result['error']}"
|
|
73
|
-
return f"{result.get('indicator')}\nStatus: {result.get('status')}\nPath: {result.get('breadcrumbs')}\n\n{result.get('tree')}"
|
|
74
|
-
|
|
75
|
-
elif tool_name == "brain_depth_reset":
|
|
76
|
-
result = _depth_reset()
|
|
77
|
-
if "error" in result:
|
|
78
|
-
return f"Error: {result['error']}"
|
|
79
|
-
return result.get('message', 'Reset complete')
|
|
80
|
-
|
|
81
|
-
return f"Tool {tool_name} not found in DepthTracker."
|
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
from typing import List, Dict, Any, Optional
|
|
2
|
-
import json
|
|
3
|
-
import os
|
|
4
|
-
import time
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
from .base import Capability
|
|
7
|
-
|
|
8
|
-
class FeatureMap(Capability):
|
|
9
|
-
def __init__(self):
|
|
10
|
-
# Determine brain path
|
|
11
|
-
brain_path_str = os.environ.get("NUCLEAR_BRAIN_PATH")
|
|
12
|
-
if not brain_path_str:
|
|
13
|
-
# Fallback for dev/verification if not set (though highly recommended)
|
|
14
|
-
brain_path_str = "/Users/lokeshgarg/.gemini/antigravity/brain/7c654df4-b83e-43f9-8620-f15868ec39d1"
|
|
15
|
-
|
|
16
|
-
self.brain_path = Path(brain_path_str)
|
|
17
|
-
self.features_dir = self.brain_path / "features"
|
|
18
|
-
|
|
19
|
-
@property
|
|
20
|
-
def name(self) -> str:
|
|
21
|
-
return "feature_map"
|
|
22
|
-
|
|
23
|
-
@property
|
|
24
|
-
def description(self) -> str:
|
|
25
|
-
return "Manage the feature inventory to prevent 'feature amnesia'. Track what exists, status, and how to test."
|
|
26
|
-
|
|
27
|
-
def get_tools(self) -> List[Dict[str, Any]]:
|
|
28
|
-
return [
|
|
29
|
-
{
|
|
30
|
-
"name": "brain_add_feature",
|
|
31
|
-
"description": "Add a new feature to the product's feature map.",
|
|
32
|
-
"parameters": {
|
|
33
|
-
"type": "object",
|
|
34
|
-
"properties": {
|
|
35
|
-
"product": {"type": "string", "description": "'gentlequest' or 'nucleus'"},
|
|
36
|
-
"name": {"type": "string", "description": "Human-readable feature name"},
|
|
37
|
-
"description": {"type": "string", "description": "What the feature does"},
|
|
38
|
-
"source": {"type": "string", "description": "Where it lives (e.g., 'gentlequest_app', 'pypi_mcp')"},
|
|
39
|
-
"version": {"type": "string", "description": "Which version it shipped in"},
|
|
40
|
-
"status": {"type": "string", "description": "development/staged/production/released"},
|
|
41
|
-
"how_to_test": {"type": "array", "items": {"type": "string"}, "description": "List of test steps"},
|
|
42
|
-
"expected_result": {"type": "string", "description": "What should happen when testing"},
|
|
43
|
-
"tags": {"type": "array", "items": {"type": "string"}, "description": "Searchable tags"}
|
|
44
|
-
},
|
|
45
|
-
"required": ["product", "name", "description", "source", "version", "how_to_test", "expected_result"]
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
"name": "brain_list_features",
|
|
50
|
-
"description": "List all features, optionally filtered.",
|
|
51
|
-
"parameters": {
|
|
52
|
-
"type": "object",
|
|
53
|
-
"properties": {
|
|
54
|
-
"product": {"type": "string", "description": "Filter by product"},
|
|
55
|
-
"status": {"type": "string", "description": "Filter by status"},
|
|
56
|
-
"tag": {"type": "string", "description": "Filter by tag"}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
"name": "brain_get_feature",
|
|
62
|
-
"description": "Get a specific feature by ID.",
|
|
63
|
-
"parameters": {
|
|
64
|
-
"type": "object",
|
|
65
|
-
"properties": {
|
|
66
|
-
"feature_id": {"type": "string", "description": "The feature ID (snake_case)"}
|
|
67
|
-
},
|
|
68
|
-
"required": ["feature_id"]
|
|
69
|
-
}
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
"name": "brain_update_feature",
|
|
73
|
-
"description": "Update a feature's fields.",
|
|
74
|
-
"parameters": {
|
|
75
|
-
"type": "object",
|
|
76
|
-
"properties": {
|
|
77
|
-
"feature_id": {"type": "string", "description": "Feature to update"},
|
|
78
|
-
"status": {"type": "string", "description": "New status"},
|
|
79
|
-
"description": {"type": "string", "description": "New description"},
|
|
80
|
-
"version": {"type": "string", "description": "New version"}
|
|
81
|
-
},
|
|
82
|
-
"required": ["feature_id"]
|
|
83
|
-
}
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
"name": "brain_mark_validated",
|
|
87
|
-
"description": "Mark a feature as validated after testing.",
|
|
88
|
-
"parameters": {
|
|
89
|
-
"type": "object",
|
|
90
|
-
"properties": {
|
|
91
|
-
"feature_id": {"type": "string", "description": "Feature that was tested"},
|
|
92
|
-
"result": {"type": "string", "description": "'passed' or 'failed'"}
|
|
93
|
-
},
|
|
94
|
-
"required": ["feature_id", "result"]
|
|
95
|
-
}
|
|
96
|
-
},
|
|
97
|
-
{
|
|
98
|
-
"name": "brain_search_features",
|
|
99
|
-
"description": "Search features by name, description, or tags.",
|
|
100
|
-
"parameters": {
|
|
101
|
-
"type": "object",
|
|
102
|
-
"properties": {
|
|
103
|
-
"query": {"type": "string", "description": "Search query"}
|
|
104
|
-
},
|
|
105
|
-
"required": ["query"]
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
]
|
|
109
|
-
|
|
110
|
-
def execute_tool(self, tool_name: str, args: Dict) -> Any:
|
|
111
|
-
try:
|
|
112
|
-
if tool_name == "brain_add_feature":
|
|
113
|
-
return self._add_feature(args)
|
|
114
|
-
elif tool_name == "brain_list_features":
|
|
115
|
-
return self._list_features(args)
|
|
116
|
-
elif tool_name == "brain_get_feature":
|
|
117
|
-
return self._get_feature(args.get("feature_id"))
|
|
118
|
-
elif tool_name == "brain_update_feature":
|
|
119
|
-
return self._update_feature(args)
|
|
120
|
-
elif tool_name == "brain_mark_validated":
|
|
121
|
-
return self._mark_validated(args.get("feature_id"), args.get("result"))
|
|
122
|
-
elif tool_name == "brain_search_features":
|
|
123
|
-
return self._search_features(args.get("query"))
|
|
124
|
-
return f"Tool {tool_name} not found"
|
|
125
|
-
except Exception as e:
|
|
126
|
-
return f"Error executing {tool_name}: {str(e)}"
|
|
127
|
-
|
|
128
|
-
# --- Internal Helpers ---
|
|
129
|
-
|
|
130
|
-
def _get_store_path(self, product: str) -> Path:
|
|
131
|
-
return self.features_dir / f"{product}.json"
|
|
132
|
-
|
|
133
|
-
def _load_store(self, product: str) -> Dict:
|
|
134
|
-
path = self._get_store_path(product)
|
|
135
|
-
if not path.exists():
|
|
136
|
-
return {"product": product, "features": []}
|
|
137
|
-
try:
|
|
138
|
-
return json.loads(path.read_text())
|
|
139
|
-
except Exception:
|
|
140
|
-
return {"product": product, "features": []}
|
|
141
|
-
|
|
142
|
-
def _save_store(self, product: str, data: Dict):
|
|
143
|
-
path = self._get_store_path(product)
|
|
144
|
-
# atomic write
|
|
145
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
|
146
|
-
path.write_text(json.dumps(data, indent=2))
|
|
147
|
-
|
|
148
|
-
def _generate_id(self, name: str) -> str:
|
|
149
|
-
# Simple snake_case conversion
|
|
150
|
-
return name.lower().replace(" ", "_").replace("-", "_")
|
|
151
|
-
|
|
152
|
-
# --- Core Logic ---
|
|
153
|
-
|
|
154
|
-
def _add_feature(self, args: Dict) -> Dict:
|
|
155
|
-
product = args.get("product").lower()
|
|
156
|
-
if product not in ["gentlequest", "nucleus"]:
|
|
157
|
-
# Default to gentlequest if unknown product?
|
|
158
|
-
# Or enforce schema. Let's strictly enforce supported products for MVP
|
|
159
|
-
if product not in ["gentlequest", "nucleus"]:
|
|
160
|
-
# Just treat it as a new product file
|
|
161
|
-
pass
|
|
162
|
-
|
|
163
|
-
store = self._load_store(product)
|
|
164
|
-
|
|
165
|
-
feature_id = self._generate_id(args.get("name"))
|
|
166
|
-
|
|
167
|
-
# Check if exists
|
|
168
|
-
for f in store["features"]:
|
|
169
|
-
if f["id"] == feature_id:
|
|
170
|
-
return {"error": f"Feature '{feature_id}' already exists. Use update."}
|
|
171
|
-
|
|
172
|
-
new_feature = {
|
|
173
|
-
"id": feature_id,
|
|
174
|
-
"name": args.get("name"),
|
|
175
|
-
"description": args.get("description"),
|
|
176
|
-
"product": product,
|
|
177
|
-
"source": args.get("source"),
|
|
178
|
-
"version": args.get("version"),
|
|
179
|
-
"status": args.get("status", "development"),
|
|
180
|
-
"how_to_test": args.get("how_to_test", []),
|
|
181
|
-
"expected_result": args.get("expected_result", ""),
|
|
182
|
-
"tags": args.get("tags", []),
|
|
183
|
-
"created_at": time.strftime("%Y-%m-%dT%H:%M:%S%z"),
|
|
184
|
-
"last_validated": None,
|
|
185
|
-
"validation_result": None
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
store["features"].append(new_feature)
|
|
189
|
-
store["total_features"] = len(store["features"])
|
|
190
|
-
store["last_updated"] = time.strftime("%Y-%m-%dT%H:%M:%S%z")
|
|
191
|
-
|
|
192
|
-
self._save_store(product, store)
|
|
193
|
-
return {"success": True, "feature": new_feature}
|
|
194
|
-
|
|
195
|
-
def _list_features(self, args: Dict) -> List[Dict]:
|
|
196
|
-
products = ["gentlequest", "nucleus"]
|
|
197
|
-
target_product = args.get("product")
|
|
198
|
-
if target_product:
|
|
199
|
-
products = [target_product]
|
|
200
|
-
|
|
201
|
-
all_features = []
|
|
202
|
-
for p in products:
|
|
203
|
-
store = self._load_store(p)
|
|
204
|
-
all_features.extend(store.get("features", []))
|
|
205
|
-
|
|
206
|
-
# Filters
|
|
207
|
-
status = args.get("status")
|
|
208
|
-
tag = args.get("tag")
|
|
209
|
-
|
|
210
|
-
if status:
|
|
211
|
-
all_features = [f for f in all_features if f.get("status") == status]
|
|
212
|
-
if tag:
|
|
213
|
-
all_features = [f for f in all_features if tag in f.get("tags", [])]
|
|
214
|
-
|
|
215
|
-
return all_features[:50] # Limit output
|
|
216
|
-
|
|
217
|
-
def _get_find_feature(self, feature_id: str) -> Optional[Dict]:
|
|
218
|
-
# Helper to search across all products
|
|
219
|
-
for p in ["gentlequest", "nucleus"]:
|
|
220
|
-
store = self._load_store(p)
|
|
221
|
-
for f in store.get("features", []):
|
|
222
|
-
if f["id"] == feature_id:
|
|
223
|
-
return f, p, store
|
|
224
|
-
return None, None, None
|
|
225
|
-
|
|
226
|
-
def _get_feature(self, feature_id: str) -> Any:
|
|
227
|
-
f, _, _ = self._get_find_feature(feature_id)
|
|
228
|
-
if f:
|
|
229
|
-
return f
|
|
230
|
-
return {"error": f"Feature '{feature_id}' not found"}
|
|
231
|
-
|
|
232
|
-
def _update_feature(self, args: Dict) -> Any:
|
|
233
|
-
feature_id = args.get("feature_id")
|
|
234
|
-
f, product, store = self._get_find_feature(feature_id)
|
|
235
|
-
|
|
236
|
-
if not f:
|
|
237
|
-
return {"error": f"Feature '{feature_id}' not found"}
|
|
238
|
-
|
|
239
|
-
# Update fields
|
|
240
|
-
valid_updates = ["status", "description", "version"]
|
|
241
|
-
updated = False
|
|
242
|
-
for key in valid_updates:
|
|
243
|
-
if key in args:
|
|
244
|
-
f[key] = args[key]
|
|
245
|
-
updated = True
|
|
246
|
-
|
|
247
|
-
if updated:
|
|
248
|
-
self._save_store(product, store)
|
|
249
|
-
return {"success": True, "feature": f}
|
|
250
|
-
return {"success": False, "message": "No changes made"}
|
|
251
|
-
|
|
252
|
-
def _mark_validated(self, feature_id: str, result: str) -> Any:
|
|
253
|
-
f, product, store = self._get_find_feature(feature_id)
|
|
254
|
-
if not f:
|
|
255
|
-
return {"error": f"Feature '{feature_id}' not found"}
|
|
256
|
-
|
|
257
|
-
f["last_validated"] = time.strftime("%Y-%m-%dT%H:%M:%S%z")
|
|
258
|
-
f["validation_result"] = result
|
|
259
|
-
|
|
260
|
-
self._save_store(product, store)
|
|
261
|
-
return {"success": True, "feature_id": feature_id, "result": result, "timestamp": f["last_validated"]}
|
|
262
|
-
|
|
263
|
-
def _search_features(self, query: str) -> List[Dict]:
|
|
264
|
-
query = query.lower()
|
|
265
|
-
all_features = self._list_features({})
|
|
266
|
-
|
|
267
|
-
matches = []
|
|
268
|
-
for f in all_features:
|
|
269
|
-
text = f"{f['name']} {f['description']} {' '.join(f.get('tags', []))}".lower()
|
|
270
|
-
if query in text:
|
|
271
|
-
matches.append(f)
|
|
272
|
-
|
|
273
|
-
# Sort by relevance? For now just return matches
|
|
274
|
-
return matches[:20]
|