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,6 +0,0 @@
1
- """
2
- Core Infrastructure Module
3
- """
4
-
5
- from .tool_registration_impl import configure_tiered_tool_registration as configure_tiered_tool_registration
6
- from .orchestrator import get_orchestrator as get_orchestrator
@@ -1,9 +0,0 @@
1
- """
2
- Orchestrator Initialization Module
3
- """
4
-
5
- # Lazy-loaded singleton for orchestrator access
6
- def get_orchestrator():
7
- """Get the orchestrator singleton (Unified)."""
8
- from .orchestrator_unified import get_orchestrator
9
- return get_orchestrator()
@@ -1,99 +0,0 @@
1
- # Auto-generated from monolith decomposition
2
- # =============================================================================
3
- # v0.6.0 PROTOCOL COUPLING FIX - Tiered Tool Registration
4
- # =============================================================================
5
- # This wrapper ensures only tier-appropriate tools are registered with FastMCP.
6
- # Without this, ALL tools would be registered regardless of NUCLEUS_TOOL_TIER.
7
- # See: TITAN_HANDOVER_PROTOCOL.md Section 0 (Registry Bloat Solution)
8
-
9
- from ..tool_tiers import is_tool_allowed, tier_manager
10
- import sys
11
-
12
- # State flag to prevent recursion when FastMCP internally calls mcp.tool
13
- _REGISTERING_TOOL = False
14
- _original_mcp_tool = None
15
-
16
- def _tiered_tool_wrapper(*args, **kwargs):
17
- """
18
- Wrapper around mcp.tool() that checks tier before registration.
19
-
20
- Handles both decorator styles:
21
- - @mcp.tool (func passed directly)
22
- - @mcp.tool() (func is None, returns decorator)
23
- """
24
- global _REGISTERING_TOOL, _original_mcp_tool
25
-
26
- if _original_mcp_tool is None:
27
- raise RuntimeError("Tiered tool registration not configured. Call configure_tiered_tool_registration(mcp) first.")
28
-
29
- # If we are already in the middle of a registration, use the original method
30
- if _REGISTERING_TOOL:
31
- return _original_mcp_tool(*args, **kwargs)
32
-
33
- func = None
34
- if len(args) == 1 and callable(args[0]):
35
- func = args[0]
36
- args = args[1:]
37
-
38
- def decorator(fn):
39
- global _REGISTERING_TOOL
40
- tool_name = fn.__name__
41
- allowed = is_tool_allowed(tool_name)
42
- if allowed:
43
- tier_manager.registered_tools.add(tool_name)
44
-
45
- # Set flag and call original method
46
- _REGISTERING_TOOL = True
47
- try:
48
- # Register and capture the FunctionTool return
49
- tool = _original_mcp_tool(*args, **kwargs)(fn)
50
-
51
- # Make the FunctionTool object callable by proxying to the original function
52
- if not callable(tool):
53
- class CallableTool:
54
- def __init__(self, tool, original_fn):
55
- self._tool = tool
56
- self._fn = original_fn
57
- # Copy metadata
58
- self.__name__ = original_fn.__name__
59
- self.__doc__ = original_fn.__doc__
60
- self.__module__ = original_fn.__module__
61
-
62
- def __call__(self, *args, **kwargs):
63
- print(f"[NUCLEUS] Executing {self.__name__}...", file=sys.stderr)
64
- return self._fn(*args, **kwargs)
65
-
66
- def __getattr__(self, name):
67
- return getattr(self._tool, name)
68
-
69
- # Pydantic serialization helpers
70
- def model_dump(self, *args, **kwargs):
71
- return self._tool.model_dump(*args, **kwargs)
72
-
73
- def model_dump_json(self, *args, **kwargs):
74
- return self._tool.model_dump_json(*args, **kwargs)
75
-
76
- return CallableTool(tool, fn)
77
-
78
- return tool
79
- except Exception as e:
80
- print(f"[NUCLEUS] ERROR registering {tool_name}: {e}", file=sys.stderr)
81
- raise e
82
- finally:
83
- _REGISTERING_TOOL = False
84
- else:
85
- tier_manager.filtered_tools.add(tool_name)
86
- # Return plain function - NOT registered with MCP
87
- return fn
88
-
89
- if func is not None:
90
- return decorator(func)
91
-
92
- return decorator
93
-
94
- def configure_tiered_tool_registration(mcp_instance):
95
- """Initializes the tiered tool registration system for the given MCP instance."""
96
- global _original_mcp_tool
97
- _original_mcp_tool = mcp_instance.tool
98
- mcp_instance.tool = _tiered_tool_wrapper
99
- return mcp_instance
@@ -1,121 +0,0 @@
1
- import fcntl
2
- import os
3
- import time
4
- import errno
5
- import logging
6
- from contextlib import contextmanager
7
-
8
- logger = logging.getLogger(__name__)
9
-
10
- class BrainLock:
11
- """
12
- The Atomic Guard for the Sovereign Brain.
13
-
14
- Implements cross-process file locking using fcntl.
15
- Prevents race conditions between the MCP Server (User Interface)
16
- and the Daemon (Background Worker).
17
-
18
- Principles:
19
- 1. Safety: Never corrupt the Graph.
20
- 2. Liveness: Auto-break stale locks (App crashes).
21
- 3. Atomicity: All or nothing.
22
- """
23
-
24
- def __init__(self, lock_file: str, timeout: int = 5):
25
- """
26
- Initialize the BrainLock.
27
-
28
- Args:
29
- lock_file: Path to the lock file (e.g., .brain/lock/state.lock)
30
- timeout: Seconds to wait for lock acquisition before giving up.
31
- """
32
- self.lock_file = lock_file
33
- self.timeout = timeout
34
- self.fd = None
35
- self._ensure_dir()
36
-
37
- def _ensure_dir(self):
38
- os.makedirs(os.path.dirname(self.lock_file), exist_ok=True)
39
-
40
- def acquire(self) -> bool:
41
- """
42
- Acquire egg-lock. Blocking with timeout.
43
- """
44
- start_time = time.time()
45
- while True:
46
- try:
47
- self.fd = open(self.lock_file, 'w')
48
- # LOCK_EX: Exclusive Lock
49
- # LOCK_NB: Non-blocking (fail immediately if locked)
50
- fcntl.flock(self.fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
51
-
52
- # Write PID for debugging / stale detection
53
- self.fd.write(str(os.getpid()))
54
- self.fd.flush()
55
- return True
56
-
57
- except IOError as e:
58
- if self.fd:
59
- self.fd.close()
60
- self.fd = None
61
-
62
- if e.errno != errno.EAGAIN:
63
- # Unexpected error
64
- logger.error(f"BrainLock Error: {e}")
65
- raise
66
-
67
- # Locked by another process
68
- if time.time() - start_time >= self.timeout:
69
- # Timeout reached
70
- logger.warning(f"BrainLock Acquisition Timeout: {self.lock_file}")
71
- return False
72
-
73
- # Wait and retry
74
- time.sleep(0.1)
75
-
76
- def release(self):
77
- """
78
- Release the lock.
79
- """
80
- if self.fd:
81
- try:
82
- # Remove the lock file content (optional, but clean)
83
- self.fd.truncate(0)
84
- fcntl.flock(self.fd, fcntl.LOCK_UN)
85
- except Exception as e:
86
- logger.error(f"BrainLock Release Error: {e}")
87
- finally:
88
- self.fd.close()
89
- self.fd = None
90
-
91
- @contextmanager
92
- def guard(self):
93
- """
94
- Context Manager for 'with BrainLock(...):' syntax.
95
- """
96
- if not self.acquire():
97
- raise TimeoutError(f"Could not acquire BrainLock on {self.lock_file}")
98
- try:
99
- yield
100
- finally:
101
- self.release()
102
-
103
- @staticmethod
104
- def is_locked(lock_file: str) -> bool:
105
- """
106
- Check if a file is currently locked without waiting.
107
- """
108
- if not os.path.exists(lock_file):
109
- return False
110
-
111
- try:
112
- fd = open(lock_file, 'r')
113
- fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
114
- # If we got here, it wasn't locked. Unlock and close.
115
- fcntl.flock(fd, fcntl.LOCK_UN)
116
- fd.close()
117
- return False
118
- except IOError as e:
119
- if e.errno == errno.EAGAIN:
120
- return True
121
- raise
@@ -1,4 +0,0 @@
1
- # Auto-generated from monolith decomposition
2
- # ============================================================
3
- # RENDER POLLER (Deploy monitoring)
4
- # ============================================================
File without changes
@@ -1,83 +0,0 @@
1
-
2
- import json
3
- import logging
4
- from pathlib import Path
5
- from typing import Dict, Any
6
-
7
- logger = logging.getLogger(__name__)
8
-
9
- class Injector:
10
- """
11
- The Nucleus Hypervisor Injection Primitive (Layer 2).
12
- Dynamically injects context and identity into running IDEs by manipulating
13
- configuration files that support hot-reloading (e.g., .vscode/settings.json).
14
- """
15
-
16
- def __init__(self, workspace_root: str):
17
- self.workspace_root = Path(workspace_root)
18
- self.vscode_settings_path = self.workspace_root / ".vscode" / "settings.json"
19
-
20
- def _ensure_vscode_dir(self):
21
- if not self.vscode_settings_path.parent.exists():
22
- self.vscode_settings_path.parent.mkdir(parents=True, exist_ok=True)
23
-
24
- def _read_settings(self) -> Dict[str, Any]:
25
- if not self.vscode_settings_path.exists():
26
- return {}
27
- try:
28
- # Handle comments in JSON if necessary (standard json lib doesn't, but VSCode allows them)
29
- # For now, assuming standard JSON or we overwrite.
30
- # Ideally use a comment-preserving parser, but simple JSON is safer for machine-control.
31
- return json.loads(self.vscode_settings_path.read_text())
32
- except Exception as e:
33
- logger.warning(f"Failed to read settings.json: {e}")
34
- return {}
35
-
36
- def _write_settings(self, settings: Dict[str, Any]) -> bool:
37
- try:
38
- self._ensure_vscode_dir()
39
- self.vscode_settings_path.write_text(json.dumps(settings, indent=4))
40
- return True
41
- except Exception as e:
42
- logger.error(f"Failed to write settings.json: {e}")
43
- return False
44
-
45
- def inject_identity(self, identity: str, color_hex: str) -> bool:
46
- """
47
- Injects visual identity markers into VSCode settings.
48
- """
49
- settings = self._read_settings()
50
-
51
- # Inject standard visual cues
52
- settings["workbench.colorCustomizations"] = settings.get("workbench.colorCustomizations", {})
53
- settings["workbench.colorCustomizations"]["titleBar.activeBackground"] = color_hex
54
- settings["workbench.colorCustomizations"]["titleBar.activeForeground"] = "#ffffff"
55
- settings["workbench.colorCustomizations"]["activityBar.background"] = color_hex
56
-
57
- # Inject status bar message (if possible via extension, or just window title)
58
- settings["window.title"] = f"[{identity.upper()}] ${{rootName}}"
59
-
60
- # Inject context-specific file exclusions if needed
61
- # e.g., if Red Team, hide 'build' folder?
62
-
63
- return self._write_settings(settings)
64
-
65
- def reset_identity(self) -> bool:
66
- """
67
- Removes injected identity markers.
68
- """
69
- settings = self._read_settings()
70
-
71
- if "workbench.colorCustomizations" in settings:
72
- # Remove our specific keys
73
- for key in ["titleBar.activeBackground", "titleBar.activeForeground", "activityBar.background"]:
74
- settings["workbench.colorCustomizations"].pop(key, None)
75
-
76
- # If empty, remove the whole object
77
- if not settings["workbench.colorCustomizations"]:
78
- del settings["workbench.colorCustomizations"]
79
-
80
- if "window.title" in settings:
81
- del settings["window.title"]
82
-
83
- return self._write_settings(settings)
@@ -1,130 +0,0 @@
1
-
2
- import subprocess
3
- import os
4
- import logging
5
- from typing import List
6
-
7
- logger = logging.getLogger(__name__)
8
-
9
- class Locker:
10
- """
11
- The Nucleus Hypervisor Locking Primitive (Layer 4).
12
- Uses macOS 'chflags' (immutable flags) to enforce file-system level write protection.
13
- This prevents even root/sudo users (and other agents) from modifying locked files
14
- until the lock is explicitly released.
15
- """
16
-
17
- def __init__(self):
18
- pass
19
-
20
- def _run_cmd(self, cmd: List[str]) -> bool:
21
- try:
22
- subprocess.run(
23
- cmd,
24
- capture_output=True,
25
- text=True,
26
- check=True
27
- )
28
- return True
29
- except subprocess.CalledProcessError as e:
30
- logger.error(f"Locker Command Failed: {e.stderr}")
31
- return False
32
-
33
- def lock(self, path: str, metadata: dict = None) -> bool:
34
- """
35
- Locks a file or directory using 'chflags uchg'.
36
- If path is a directory, it locks it recursively?
37
- Current implementation: Single file/folder lock (non-recursive by default for safety).
38
- To do recursive: chflags -R uchg
39
- """
40
- if not os.path.exists(path):
41
- logger.error(f"Cannot lock non-existent path: {path}")
42
- return False
43
-
44
- logger.info(f"🔒 Locking: {path}")
45
-
46
- # Apply metadata BEFORE making immutable
47
- if metadata:
48
- import time
49
- if "timestamp" not in metadata:
50
- metadata["timestamp"] = time.strftime("%Y-%m-%dT%H:%M:%S%z")
51
-
52
- for k, v in metadata.items():
53
- key = k if k.startswith("nucleus.lock.") else f"nucleus.lock.{k}"
54
- # If directory, apply only to the directory itself for now (recursive xattr is tricky)
55
- self._set_xattr(path, key, v)
56
-
57
- # Using -R (recursive) to lock entire directories if needed
58
- if os.path.isdir(path):
59
- return self._run_cmd(["chflags", "-R", "uchg", path])
60
- else:
61
- return self._run_cmd(["chflags", "uchg", path])
62
-
63
- def unlock(self, path: str) -> bool:
64
- """
65
- Unlocks a file or directory using 'chflags nouchg'.
66
- """
67
- if not os.path.exists(path):
68
- logger.error(f"Cannot unlock non-existent path: {path}")
69
- return False
70
-
71
- logger.info(f"🔓 Unlocking: {path}")
72
- if os.path.isdir(path):
73
- return self._run_cmd(["chflags", "-R", "nouchg", path])
74
- else:
75
- return self._run_cmd(["chflags", "nouchg", path])
76
-
77
- def is_locked(self, path: str) -> bool:
78
- """
79
- Checks if the file has the 'uchg' flag set.
80
- """
81
- if not os.path.exists(path):
82
- return False
83
-
84
- # 'ls -lz' shows flags on macOS, or 'ls -MdO'
85
- # simpler: stat
86
- try:
87
- st = os.stat(path)
88
- # 0x2 is UF_IMMUTABLE (uchg)
89
- return (st.st_flags & 0x2) != 0
90
- except Exception:
91
- return False
92
-
93
- # --- METADATA (Phase 28) ---
94
- def _set_xattr(self, path: str, key: str, value: str):
95
- try:
96
- subprocess.run(
97
- ["xattr", "-w", key, str(value), path],
98
- check=True,
99
- capture_output=True
100
- )
101
- except subprocess.CalledProcessError as e:
102
- logger.warning(f"Failed to set xattr {key}: {e}")
103
-
104
- def get_metadata(self, path: str) -> dict:
105
- """Retrieves lock metadata from xattrs."""
106
- if not os.path.exists(path):
107
- return {}
108
-
109
- try:
110
- result = subprocess.run(
111
- ["xattr", path],
112
- check=True,
113
- capture_output=True,
114
- text=True
115
- )
116
- keys = result.stdout.strip().split("\n")
117
- data = {}
118
- for key in keys:
119
- if key.startswith("nucleus.lock."):
120
- val = subprocess.run(
121
- ["xattr", "-p", key, path],
122
- check=True,
123
- capture_output=True,
124
- text=True
125
- ).stdout.strip()
126
- clean_key = key.replace("nucleus.lock.", "")
127
- data[clean_key] = val
128
- return data
129
- except Exception:
130
- return {}
@@ -1,71 +0,0 @@
1
-
2
- import logging
3
- from pathlib import Path
4
- from watchdog.observers import Observer
5
- from watchdog.events import FileSystemEventHandler
6
-
7
- from .locker import Locker
8
-
9
- logger = logging.getLogger(__name__)
10
-
11
- class SecurityEventHandler(FileSystemEventHandler):
12
- """
13
- Handles file events for the Hypervisor Watchdog.
14
- Detects attempts to modify locked files.
15
- """
16
- def __init__(self, watchdog_instance, locker: Locker):
17
- self.watchdog = watchdog_instance
18
- self.locker = locker
19
-
20
- def on_modified(self, event):
21
- if event.is_directory:
22
- return
23
-
24
- path = str(Path(event.src_path).resolve())
25
- is_protected = False
26
-
27
- # Access live list from watchdog instance
28
- for p_path in self.watchdog.protected_paths:
29
- if path == p_path or path.startswith(p_path + "/"):
30
- is_protected = True
31
- break
32
-
33
- if is_protected:
34
- # Breach Detected!
35
- logger.warning(f"🚨 SECURITY BREACH: Locked file modified: {path}")
36
-
37
- # 1. Immediate Re-Lock (in case it was unlocked)
38
- self.locker.lock(path)
39
-
40
- # 2. (Future) Revert from Shadow Copy
41
- # self.revert(path)
42
-
43
- class Watchdog:
44
- """
45
- The Nucleus Hypervisor Sentinel (Layer 1).
46
- Monitors critical files and reinforces locks.
47
- """
48
- def __init__(self, workspace_root: str):
49
- self.workspace_root = Path(workspace_root)
50
- self.locker = Locker()
51
- self.observer = Observer()
52
- self.protected_paths = []
53
-
54
- def protect(self, path: str):
55
- """Adds a path to the protected list and starts watching it."""
56
- abs_path = str((self.workspace_root / path).resolve())
57
- if abs_path not in self.protected_paths:
58
- self.protected_paths.append(abs_path)
59
- # Ensure it is physically locked
60
- self.locker.lock(abs_path)
61
-
62
- def start(self):
63
- """Starts the background monitoring thread."""
64
- handler = SecurityEventHandler(self, self.locker)
65
- self.observer.schedule(handler, str(self.workspace_root), recursive=True)
66
- self.observer.start()
67
- logger.info(f"👁️ Hypervisor Watchdog started on {self.workspace_root}")
68
-
69
- def stop(self):
70
- self.observer.stop()
71
- self.observer.join()
@@ -1,58 +0,0 @@
1
- """
2
- Nucleus Agent Runtime - Runtime Package
3
-
4
- v0.6.0 DSoR: Added context_manager and ipc_auth for decision provenance.
5
- """
6
-
7
- from .factory import ContextFactory
8
- from .agent import EphemeralAgent, DecisionMade, ActionRequested
9
- from .event_stream import EventSeverity, EventTypes, emit_event, read_events
10
- from .triggers import match_triggers, get_agents_for_event
11
- from .profiling import (
12
- timed, timed_io, timed_compute, timed_llm,
13
- get_metrics, get_metrics_summary, reset_metrics, export_metrics_to_file
14
- )
15
- # v0.6.0 DSoR Components
16
- from .context_manager import (
17
- ContextManager, ContextSnapshot, StateVerificationResult,
18
- get_context_manager, compute_context_hash, verify_turn_integrity
19
- )
20
- from .ipc_auth import (
21
- IPCAuthManager, IPCToken, TokenMeterEntry,
22
- get_ipc_auth_manager, require_ipc_token
23
- )
24
-
25
- __all__ = [
26
- "ContextFactory",
27
- "EphemeralAgent",
28
- "DecisionMade",
29
- "ActionRequested",
30
- "EventSeverity",
31
- "EventTypes",
32
- "emit_event",
33
- "read_events",
34
- "match_triggers",
35
- "get_agents_for_event",
36
- # Profiling (AG-014)
37
- "timed",
38
- "timed_io",
39
- "timed_compute",
40
- "timed_llm",
41
- "get_metrics",
42
- "get_metrics_summary",
43
- "reset_metrics",
44
- "export_metrics_to_file",
45
- # v0.6.0 DSoR - Context Manager
46
- "ContextManager",
47
- "ContextSnapshot",
48
- "StateVerificationResult",
49
- "get_context_manager",
50
- "compute_context_hash",
51
- "verify_turn_integrity",
52
- # v0.6.0 DSoR - IPC Auth
53
- "IPCAuthManager",
54
- "IPCToken",
55
- "TokenMeterEntry",
56
- "get_ipc_auth_manager",
57
- "require_ipc_token"
58
- ]