@mamdouh-aboammar/agentic-workflow 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 (118) hide show
  1. package/.claude-plugin/plugin.json +10 -0
  2. package/.codex-plugin/plugin.json +13 -0
  3. package/.skills.json +19 -0
  4. package/AGENTS.md +1344 -0
  5. package/CLAUDE.md +178 -0
  6. package/GEMINI.md +102 -0
  7. package/LICENSE +21 -0
  8. package/README.md +350 -0
  9. package/SKILL.md +132 -0
  10. package/bin/agentic-hooks.sh +79 -0
  11. package/bin/cli.js +1060 -0
  12. package/core/__init__.py +52 -0
  13. package/core/ai_evaluator.py +117 -0
  14. package/core/autopilot_engine.py +368 -0
  15. package/core/clean_code_guard.py +188 -0
  16. package/core/engine_py/__init__.py +29 -0
  17. package/core/engine_py/agent_worker.py +136 -0
  18. package/core/engine_py/decider.py +150 -0
  19. package/core/engine_py/energy.py +45 -0
  20. package/core/engine_py/event_bus.py +63 -0
  21. package/core/engine_py/executor.py +186 -0
  22. package/core/engine_py/models.py +193 -0
  23. package/core/engine_py/queue.py +314 -0
  24. package/core/engine_py/runner.py +116 -0
  25. package/core/engine_py/system_workers.py +70 -0
  26. package/core/engine_py/toon_adapter.py +586 -0
  27. package/core/engine_py/verification_controller.py +208 -0
  28. package/core/engine_py/worker.py +167 -0
  29. package/core/engine_spec/event_schema.json +65 -0
  30. package/core/engine_spec/example_workflow.yaml +73 -0
  31. package/core/engine_spec/workflow_schema.json +127 -0
  32. package/core/hooks/__init__.py +29 -0
  33. package/core/hooks/adapters/__init__.py +25 -0
  34. package/core/hooks/adapters/claude_adapter.py +83 -0
  35. package/core/hooks/adapters/cli_agent_adapter.py +82 -0
  36. package/core/hooks/adapters/codex_adapter.py +78 -0
  37. package/core/hooks/adapters/cursor_adapter.py +73 -0
  38. package/core/hooks/adapters/gemini_adapter.py +93 -0
  39. package/core/hooks/adapters/homebrew_adapter.py +69 -0
  40. package/core/hooks/adapters/mcp_proxy.py +133 -0
  41. package/core/hooks/adapters/shell_adapter.py +65 -0
  42. package/core/hooks/dispatcher.py +118 -0
  43. package/core/hooks/policy_engine.py +375 -0
  44. package/core/hooks/session_end.py +141 -0
  45. package/core/hooks/types.py +147 -0
  46. package/core/integrations/__init__.py +28 -0
  47. package/core/integrations/installer.py +225 -0
  48. package/core/integrations/lifecycle_director.py +175 -0
  49. package/core/integrations/registry.py +105 -0
  50. package/core/multi_agent_system.py +164 -0
  51. package/core/skills_indexer.py +742 -0
  52. package/core/system/__init__.py +25 -0
  53. package/core/system/announcements.py +72 -0
  54. package/core/system/dependencies.py +69 -0
  55. package/core/system/doctor.py +171 -0
  56. package/core/system/health.py +144 -0
  57. package/core/system/installer.py +137 -0
  58. package/core/system/notifications.py +97 -0
  59. package/core/system/refresher.py +110 -0
  60. package/core/system/updater.py +167 -0
  61. package/core/system/version_tracker.py +65 -0
  62. package/docs/architecture_plan.md +7 -0
  63. package/docs/guides/failure-recovery.md +714 -0
  64. package/docs/implementation_summary.md +10 -0
  65. package/docs/protocols/autopilot-execution.md +148 -0
  66. package/docs/protocols/code-change-protocol.md +49 -0
  67. package/docs/protocols/context-preservation-detail.md +114 -0
  68. package/docs/protocols/quality-gates.md +110 -0
  69. package/docs/protocols/ulw-mode.md +60 -0
  70. package/docs/research_findings.md +10 -0
  71. package/docs/solutions/autonomous-autopilot-engine-architecture.md +38 -0
  72. package/install.sh +111 -0
  73. package/marketplace.json +37 -0
  74. package/package.json +81 -0
  75. package/skills/agentic-workflow/SKILL.md +132 -0
  76. package/skills/agentic-workflow/skill-spec.json +100 -0
  77. package/soul.md +445 -0
  78. package/src/engine_ts/decider.ts +186 -0
  79. package/src/engine_ts/event-bus.ts +57 -0
  80. package/src/engine_ts/executor.ts +262 -0
  81. package/src/engine_ts/index.ts +12 -0
  82. package/src/engine_ts/queue.ts +93 -0
  83. package/src/engine_ts/runner.ts +108 -0
  84. package/src/engine_ts/skills-indexer.ts +264 -0
  85. package/src/engine_ts/toon-adapter.ts +91 -0
  86. package/src/engine_ts/types.ts +134 -0
  87. package/src/engine_ts/verification-controller.ts +204 -0
  88. package/src/engine_ts/worker.ts +280 -0
  89. package/src/hooks/adapters/claude-adapter.ts +54 -0
  90. package/src/hooks/adapters/cli-agent-adapter.ts +46 -0
  91. package/src/hooks/adapters/codex-adapter.ts +69 -0
  92. package/src/hooks/adapters/cursor-adapter.ts +60 -0
  93. package/src/hooks/adapters/gemini-adapter.ts +71 -0
  94. package/src/hooks/adapters/homebrew-adapter.ts +36 -0
  95. package/src/hooks/adapters/mcp-proxy.ts +66 -0
  96. package/src/hooks/adapters/shell-adapter.ts +42 -0
  97. package/src/hooks/dispatcher.ts +113 -0
  98. package/src/hooks/index.ts +16 -0
  99. package/src/hooks/policy-engine.ts +376 -0
  100. package/src/hooks/session-end.ts +125 -0
  101. package/src/hooks/types.ts +61 -0
  102. package/src/index.d.ts +34 -0
  103. package/src/index.ts +23 -0
  104. package/src/integrations/index.ts +7 -0
  105. package/src/integrations/installer.ts +208 -0
  106. package/src/integrations/lifecycle-director.ts +139 -0
  107. package/src/integrations/registry.ts +82 -0
  108. package/src/system/announcements.ts +143 -0
  109. package/src/system/dependencies.ts +176 -0
  110. package/src/system/doctor.ts +374 -0
  111. package/src/system/health.ts +270 -0
  112. package/src/system/index.ts +14 -0
  113. package/src/system/installer.ts +262 -0
  114. package/src/system/notifications.ts +180 -0
  115. package/src/system/refresher.ts +207 -0
  116. package/src/system/types.ts +268 -0
  117. package/src/system/updater.ts +219 -0
  118. package/src/system/version-tracker.ts +137 -0
@@ -0,0 +1,25 @@
1
+ """
2
+ Universal Agentic Hooks Framework (UAHF) — Adapters
3
+ ===================================================
4
+ Adapters for normalizing hooks across Claude, Cursor, Antigravity, Gemini CLI, Codex, Shell, Homebrew, MCP, and CLI.
5
+ """
6
+
7
+ from .claude_adapter import ClaudeHookAdapter
8
+ from .gemini_adapter import GeminiHookAdapter
9
+ from .cursor_adapter import CursorHookAdapter
10
+ from .codex_adapter import CodexHookAdapter
11
+ from .shell_adapter import ShellHookAdapter
12
+ from .homebrew_adapter import HomebrewHookAdapter
13
+ from .mcp_proxy import McpHookProxy
14
+ from .cli_agent_adapter import CliAgentAdapter
15
+
16
+ __all__ = [
17
+ "ClaudeHookAdapter",
18
+ "GeminiHookAdapter",
19
+ "CursorHookAdapter",
20
+ "CodexHookAdapter",
21
+ "ShellHookAdapter",
22
+ "HomebrewHookAdapter",
23
+ "McpHookProxy",
24
+ "CliAgentAdapter",
25
+ ]
@@ -0,0 +1,83 @@
1
+ """
2
+ Claude Code Hook Adapter
3
+ ========================
4
+ Consumes native Claude Code PreToolUse, PostToolUse, and Session lifecycle hooks.
5
+ Interprets stdin JSON, normalizes to HookEvent, and controls Claude via exit codes (0 vs 2).
6
+ """
7
+
8
+ import json
9
+ import sys
10
+ from typing import Any, Dict, Optional
11
+
12
+ from ..types import (
13
+ HookEvent,
14
+ HookResult,
15
+ HookSource,
16
+ HookType,
17
+ HookVerdict,
18
+ )
19
+ from ..policy_engine import UniversalPolicyEngine
20
+
21
+
22
+ class ClaudeHookAdapter:
23
+ """Consumes and mediates Claude Code lifecycle hooks."""
24
+
25
+ def __init__(self, policy_engine: Optional[UniversalPolicyEngine] = None):
26
+ self.engine = policy_engine or UniversalPolicyEngine()
27
+
28
+ def parse_payload(self, raw_input: str, hook_type: HookType = HookType.PRE_TOOL) -> Optional[HookEvent]:
29
+ try:
30
+ data = json.loads(raw_input) if isinstance(raw_input, str) else raw_input
31
+ except Exception:
32
+ return None
33
+
34
+ if not isinstance(data, dict):
35
+ return None
36
+
37
+ tool_name = data.get("tool_name") or data.get("name")
38
+ tool_input = data.get("tool_input") or data.get("input") or {}
39
+ tool_response = data.get("tool_response") or data.get("output")
40
+
41
+ command = None
42
+ file_path = None
43
+
44
+ if isinstance(tool_input, dict):
45
+ command = tool_input.get("command")
46
+ file_path = tool_input.get("file_path") or tool_input.get("path") or tool_input.get("target")
47
+
48
+ return HookEvent(
49
+ source=HookSource.CLAUDE,
50
+ hook_type=hook_type,
51
+ tool_name=tool_name,
52
+ command=command,
53
+ file_path=file_path,
54
+ args=tool_input if isinstance(tool_input, dict) else {"raw": tool_input},
55
+ output=tool_response,
56
+ metadata={"raw_claude_payload": data},
57
+ )
58
+
59
+ def handle(self, raw_input: str, hook_type: HookType = HookType.PRE_TOOL) -> HookResult:
60
+ event = self.parse_payload(raw_input, hook_type)
61
+ if not event:
62
+ return HookResult(event_id="claude_empty", verdict=HookVerdict.ALLOW, exit_code=0)
63
+
64
+ result = self.engine.evaluate(event)
65
+ return result
66
+
67
+ def run_cli(self, hook_type_str: str = "pre_tool"):
68
+ """CLI runner for direct pipe execution in .claude/settings.json."""
69
+ raw = sys.stdin.read()
70
+ if not raw.strip():
71
+ sys.exit(0)
72
+
73
+ htype = HookType.POST_TOOL if "post" in hook_type_str.lower() else HookType.PRE_TOOL
74
+ result = self.handle(raw, htype)
75
+
76
+ if result.verdict == HookVerdict.BLOCK:
77
+ print(f"🛑 [UAHF Claude Hook] {result.message}", file=sys.stderr)
78
+ sys.exit(result.exit_code or 2)
79
+ elif result.verdict == HookVerdict.WARN:
80
+ print(f"⚠️ [UAHF Claude Hook] {result.message}", file=sys.stderr)
81
+ sys.exit(0)
82
+
83
+ sys.exit(0)
@@ -0,0 +1,82 @@
1
+ """
2
+ CLI Agent Wrapper & Interceptor Adapter
3
+ =======================================
4
+ Supervises external CLI agents (OpenAI Codex, Moonshot Kimi, Aider, etc.).
5
+ Intercepts agent execution, enforces pre-flight governance, and injects protective hooks.
6
+ """
7
+
8
+ import os
9
+ import shutil
10
+ import subprocess
11
+ import sys
12
+ from typing import List, Optional
13
+
14
+ from ..types import (
15
+ HookEvent,
16
+ HookResult,
17
+ HookSource,
18
+ HookType,
19
+ HookVerdict,
20
+ )
21
+ from ..policy_engine import UniversalPolicyEngine
22
+
23
+
24
+ class CliAgentAdapter:
25
+ """Wraps and mediates execution of autonomous CLI agents."""
26
+
27
+ def __init__(self, policy_engine: Optional[UniversalPolicyEngine] = None):
28
+ self.engine = policy_engine or UniversalPolicyEngine()
29
+
30
+ def identify_agent_source(self, binary_name: str) -> HookSource:
31
+ name = os.path.basename(binary_name).lower()
32
+ if "codex" in name:
33
+ return HookSource.CODEX
34
+ elif "kimi" in name:
35
+ return HookSource.KIMI
36
+ elif "cursor" in name:
37
+ return HookSource.CURSOR
38
+ elif "antigravity" in name:
39
+ return HookSource.ANTIGRAVITY
40
+ elif "claude" in name:
41
+ return HookSource.CLAUDE
42
+ return HookSource.CLI
43
+
44
+ def run_supervised(self, command_args: List[str]) -> int:
45
+ if not command_args:
46
+ print("❌ No command provided to CLI Agent supervisor.", file=sys.stderr)
47
+ return 1
48
+
49
+ binary = command_args[0]
50
+ source = self.identify_agent_source(binary)
51
+
52
+ # Pre-execution policy check on the agent invocation itself
53
+ full_command = " ".join(command_args)
54
+ event = HookEvent(
55
+ source=source,
56
+ hook_type=HookType.SESSION_START,
57
+ command=full_command,
58
+ tool_name=binary,
59
+ args={"raw_args": command_args[1:]},
60
+ agent_id=f"agent_{source.value}",
61
+ )
62
+
63
+ result = self.engine.evaluate(event)
64
+ if result.verdict == HookVerdict.BLOCK:
65
+ print(f"\033[1;31m🛑 [CLI Agent Guard] INVOCATION BLOCKED:\033[0m\n {result.message}", file=sys.stderr)
66
+ return 2
67
+
68
+ # Inject environment hook overrides so subshells spawned by the agent hit our hooks
69
+ env = os.environ.copy()
70
+ repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../.."))
71
+ hook_bin = os.path.join(repo_root, "bin")
72
+
73
+ current_path = env.get("PATH", "")
74
+ if hook_bin not in current_path:
75
+ env["PATH"] = f"{hook_bin}:{current_path}"
76
+
77
+ env["AGENTIC_HOOKS_ACTIVE"] = "1"
78
+ env["AGENTIC_AGENT_NAME"] = source.value
79
+
80
+ # Execute target agent
81
+ proc = subprocess.run(command_args, env=env)
82
+ return proc.returncode
@@ -0,0 +1,78 @@
1
+ """
2
+ OpenAI Codex & ChatGPT Plugin Hook Driver Adapter
3
+ =================================================
4
+ Consumes execution events from OpenAI Codex and ChatGPT plugin environments.
5
+ Normalizes events to HookEvent(source=HookSource.CODEX) and applies sandbox security
6
+ and governance policies.
7
+ """
8
+
9
+ import json
10
+ from typing import Any, Dict, Optional
11
+
12
+ from ..types import (
13
+ HookEvent,
14
+ HookResult,
15
+ HookSource,
16
+ HookType,
17
+ HookVerdict,
18
+ )
19
+ from ..policy_engine import UniversalPolicyEngine
20
+
21
+
22
+ class CodexHookAdapter:
23
+ """Consumes and mediates OpenAI Codex & ChatGPT plugin lifecycle events."""
24
+
25
+ def __init__(self, policy_engine: Optional[UniversalPolicyEngine] = None):
26
+ self.engine = policy_engine or UniversalPolicyEngine()
27
+
28
+ def parse_payload(self, raw_input: Any, hook_type: HookType = HookType.PRE_TOOL) -> Optional[HookEvent]:
29
+ """Parses a Codex / ChatGPT tool call payload into a normalized HookEvent."""
30
+ try:
31
+ data = json.loads(raw_input) if isinstance(raw_input, str) else raw_input
32
+ except Exception:
33
+ return None
34
+
35
+ if not isinstance(data, dict):
36
+ return None
37
+
38
+ tool_name = data.get("name") or data.get("tool_name") or data.get("function")
39
+ tool_args = data.get("arguments") or data.get("args") or data.get("input") or {}
40
+ output = data.get("output") or data.get("response")
41
+
42
+ if isinstance(tool_args, str):
43
+ try:
44
+ tool_args = json.loads(tool_args)
45
+ except Exception:
46
+ tool_args = {"raw": tool_args}
47
+
48
+ command = None
49
+ file_path = None
50
+
51
+ if isinstance(tool_args, dict):
52
+ command = tool_args.get("command") or tool_args.get("cmd") or tool_args.get("code")
53
+ file_path = tool_args.get("path") or tool_args.get("file_path") or tool_args.get("filename")
54
+
55
+ return HookEvent(
56
+ source=HookSource.CODEX,
57
+ hook_type=hook_type,
58
+ tool_name=tool_name or ("exec" if command else "plugin_call"),
59
+ command=command,
60
+ file_path=file_path,
61
+ args=tool_args if isinstance(tool_args, dict) else {"raw": tool_args},
62
+ output=output,
63
+ metadata={"raw_codex_payload": data},
64
+ )
65
+
66
+ def handle(self, raw_input: Any, hook_type: HookType = HookType.PRE_TOOL) -> HookResult:
67
+ """Evaluates Codex event against universal policies."""
68
+ event = self.parse_payload(raw_input, hook_type)
69
+ if not event:
70
+ return HookResult(event_id="codex_empty", verdict=HookVerdict.ALLOW, exit_code=0)
71
+
72
+ return self.engine.evaluate(event)
73
+
74
+ def evaluate_tool(self, name: str, arguments: Dict[str, Any], is_pre: bool = True, output: Optional[Any] = None) -> HookResult:
75
+ """Convenience method for programmatic Codex tool call evaluation."""
76
+ hook_type = HookType.PRE_TOOL if is_pre else HookType.POST_TOOL
77
+ payload = {"name": name, "arguments": arguments, "output": output}
78
+ return self.handle(payload, hook_type)
@@ -0,0 +1,73 @@
1
+ """
2
+ Cursor IDE & Windsurf Hook Driver Adapter
3
+ =========================================
4
+ Consumes tool execution and terminal command events from Cursor IDE rules (.cursor/rules)
5
+ and Windsurf cascades. Normalizes events to HookEvent(source=HookSource.CURSOR) and applies
6
+ governance policies.
7
+ """
8
+
9
+ import json
10
+ from typing import Any, Dict, Optional
11
+
12
+ from ..types import (
13
+ HookEvent,
14
+ HookResult,
15
+ HookSource,
16
+ HookType,
17
+ HookVerdict,
18
+ )
19
+ from ..policy_engine import UniversalPolicyEngine
20
+
21
+
22
+ class CursorHookAdapter:
23
+ """Consumes and mediates Cursor IDE & Windsurf lifecycle events."""
24
+
25
+ def __init__(self, policy_engine: Optional[UniversalPolicyEngine] = None):
26
+ self.engine = policy_engine or UniversalPolicyEngine()
27
+
28
+ def parse_payload(self, raw_input: Any, hook_type: HookType = HookType.PRE_TOOL) -> Optional[HookEvent]:
29
+ """Parses Cursor / Windsurf command or tool invocation payload."""
30
+ try:
31
+ data = json.loads(raw_input) if isinstance(raw_input, str) else raw_input
32
+ except Exception:
33
+ return None
34
+
35
+ if not isinstance(data, dict):
36
+ return None
37
+
38
+ tool_name = data.get("tool") or data.get("tool_name") or data.get("action")
39
+ command = data.get("command") or data.get("cmd")
40
+ file_path = data.get("file_path") or data.get("path") or data.get("target_file")
41
+ tool_args = data.get("args") or data.get("parameters") or {}
42
+ output = data.get("output") or data.get("result")
43
+
44
+ if isinstance(tool_args, dict):
45
+ if not command:
46
+ command = tool_args.get("command") or tool_args.get("cmd")
47
+ if not file_path:
48
+ file_path = tool_args.get("file_path") or tool_args.get("path") or tool_args.get("target_file")
49
+
50
+ return HookEvent(
51
+ source=HookSource.CURSOR,
52
+ hook_type=hook_type,
53
+ tool_name=tool_name or ("terminal" if command else "file_op"),
54
+ command=command,
55
+ file_path=file_path,
56
+ args=tool_args if isinstance(tool_args, dict) else {"raw": tool_args},
57
+ output=output,
58
+ metadata={"raw_cursor_payload": data},
59
+ )
60
+
61
+ def handle(self, raw_input: Any, hook_type: HookType = HookType.PRE_TOOL) -> HookResult:
62
+ """Evaluates Cursor event against universal policies."""
63
+ event = self.parse_payload(raw_input, hook_type)
64
+ if not event:
65
+ return HookResult(event_id="cursor_empty", verdict=HookVerdict.ALLOW, exit_code=0)
66
+
67
+ return self.engine.evaluate(event)
68
+
69
+ def evaluate_command(self, command_line: str, is_pre: bool = True, output: Optional[str] = None) -> HookResult:
70
+ """Convenience method for evaluating Cursor terminal commands."""
71
+ hook_type = HookType.PRE_COMMAND if is_pre else HookType.POST_COMMAND
72
+ payload = {"command": command_line, "output": output}
73
+ return self.handle(payload, hook_type)
@@ -0,0 +1,93 @@
1
+ """
2
+ Google Antigravity & Gemini CLI Hook Driver Adapter
3
+ ===================================================
4
+ Consumes tool execution events from Google Antigravity and Gemini CLI environments.
5
+ Normalizes tool calls (run_command, write_to_file, replace_file_content, view_file, invoke_subagent)
6
+ into canonical HookEvent instances and applies UAHF policy engine gates.
7
+ """
8
+
9
+ import json
10
+ import os
11
+ import re
12
+ import sys
13
+ from typing import Any, Dict, Optional
14
+
15
+ from ..types import (
16
+ HookEvent,
17
+ HookResult,
18
+ HookSource,
19
+ HookType,
20
+ HookVerdict,
21
+ )
22
+ from ..policy_engine import UniversalPolicyEngine
23
+
24
+
25
+ class GeminiHookAdapter:
26
+ """Consumes and mediates Gemini CLI & Google Antigravity lifecycle tool events."""
27
+
28
+ def __init__(self, policy_engine: Optional[UniversalPolicyEngine] = None):
29
+ self.engine = policy_engine or UniversalPolicyEngine()
30
+
31
+ def parse_payload(self, raw_input: Any, hook_type: HookType = HookType.PRE_TOOL) -> Optional[HookEvent]:
32
+ """Parses a Gemini CLI / Antigravity tool call payload into a normalized HookEvent."""
33
+ try:
34
+ data = json.loads(raw_input) if isinstance(raw_input, str) else raw_input
35
+ except Exception:
36
+ return None
37
+
38
+ if not isinstance(data, dict):
39
+ return None
40
+
41
+ tool_name = data.get("tool_name") or data.get("name") or data.get("tool")
42
+ tool_args = data.get("args") or data.get("arguments") or data.get("parameters") or {}
43
+ tool_response = data.get("response") or data.get("output") or data.get("result")
44
+
45
+ command = None
46
+ file_path = None
47
+
48
+ if isinstance(tool_args, dict):
49
+ # Antigravity run_command convention
50
+ command = tool_args.get("CommandLine") or tool_args.get("command") or tool_args.get("cmd")
51
+ # Antigravity file tools (view_file, write_to_file, replace_file_content)
52
+ file_path = (
53
+ tool_args.get("AbsolutePath")
54
+ or tool_args.get("TargetFile")
55
+ or tool_args.get("file_path")
56
+ or tool_args.get("path")
57
+ )
58
+
59
+ return HookEvent(
60
+ source=HookSource.ANTIGRAVITY,
61
+ hook_type=hook_type,
62
+ tool_name=tool_name,
63
+ command=command,
64
+ file_path=file_path,
65
+ args=tool_args if isinstance(tool_args, dict) else {"raw": tool_args},
66
+ output=tool_response,
67
+ metadata={"raw_gemini_payload": data},
68
+ )
69
+
70
+ def handle(self, raw_input: Any, hook_type: HookType = HookType.PRE_TOOL) -> HookResult:
71
+ """Evaluates payload against universal policies."""
72
+ event = self.parse_payload(raw_input, hook_type)
73
+ if not event:
74
+ return HookResult(event_id="gemini_empty", verdict=HookVerdict.ALLOW, exit_code=0)
75
+
76
+ result = self.engine.evaluate(event)
77
+ return result
78
+
79
+ def evaluate_tool_call(
80
+ self,
81
+ tool_name: str,
82
+ arguments: Dict[str, Any],
83
+ is_pre: bool = True,
84
+ output: Optional[Any] = None
85
+ ) -> HookResult:
86
+ """Programmatic evaluation entry point for Python-driven Gemini / Antigravity agents."""
87
+ hook_type = HookType.PRE_TOOL if is_pre else HookType.POST_TOOL
88
+ payload = {
89
+ "tool_name": tool_name,
90
+ "args": arguments,
91
+ "output": output
92
+ }
93
+ return self.handle(payload, hook_type)
@@ -0,0 +1,69 @@
1
+ """
2
+ Homebrew Package Manager Hook Adapter
3
+ =====================================
4
+ Dedicated gatekeeper for Homebrew command interception and package hygiene.
5
+ Enforces prohibitions against heavy or banned packages (e.g. Colima) and unsafe flags.
6
+ """
7
+
8
+ import shutil
9
+ import subprocess
10
+ import sys
11
+ from typing import List, Optional
12
+
13
+ from ..types import (
14
+ HookEvent,
15
+ HookResult,
16
+ HookSource,
17
+ HookType,
18
+ HookVerdict,
19
+ )
20
+ from ..policy_engine import UniversalPolicyEngine
21
+
22
+
23
+ class HomebrewHookAdapter:
24
+ """Intercepts and verifies brew operations before execution."""
25
+
26
+ def __init__(self, policy_engine: Optional[UniversalPolicyEngine] = None):
27
+ self.engine = policy_engine or UniversalPolicyEngine()
28
+
29
+ def evaluate_brew_args(self, args: List[str]) -> HookResult:
30
+ full_command = f"brew {' '.join(args)}".strip()
31
+ package_target = None
32
+ for i, arg in enumerate(args):
33
+ if arg in ["install", "reinstall", "cask"] and i + 1 < len(args):
34
+ package_target = args[i + 1]
35
+ break
36
+
37
+ event = HookEvent(
38
+ source=HookSource.HOMEBREW,
39
+ hook_type=HookType.PRE_COMMAND,
40
+ command=full_command,
41
+ tool_name="homebrew",
42
+ args={"raw_args": args, "package": package_target},
43
+ )
44
+ return self.engine.evaluate(event)
45
+
46
+ def run_shim(self, brew_args: List[str]) -> int:
47
+ result = self.evaluate_brew_args(brew_args)
48
+ if result.verdict == HookVerdict.BLOCK:
49
+ print(f"\033[1;31m🛑 [Homebrew Hook Guard] OPERATION BLOCKED:\033[0m\n {result.message}", file=sys.stderr)
50
+ return 2
51
+ elif result.verdict == HookVerdict.WARN:
52
+ print(f"\033[1;33m⚠️ [Homebrew Hook Guard] ADVISORY:\033[0m {result.message}", file=sys.stderr)
53
+
54
+ # Locate real brew binary
55
+ real_brew = None
56
+ for candidate in ["/opt/homebrew/bin/brew", "/usr/local/bin/brew"]:
57
+ if shutil.which(candidate):
58
+ real_brew = candidate
59
+ break
60
+ if not real_brew:
61
+ real_brew = shutil.which("brew")
62
+
63
+ if not real_brew:
64
+ print("❌ [Homebrew Hook Guard] Homebrew executable not found on system PATH.", file=sys.stderr)
65
+ return 127
66
+
67
+ # Pass through to real brew
68
+ res = subprocess.run([real_brew] + brew_args)
69
+ return res.returncode
@@ -0,0 +1,133 @@
1
+ """
2
+ Universal MCP (Model Context Protocol) Hook Proxy
3
+ =================================================
4
+ Transparent JSON-RPC proxy sitting between any MCP client (Cursor, Antigravity, Claude,
5
+ Codex, Kimi) and downstream MCP servers.
6
+ Intercepts 'tools/call' requests for policy gating and inspects tool responses for leaks.
7
+ """
8
+
9
+ import json
10
+ import logging
11
+ import subprocess
12
+ import sys
13
+ from typing import Any, Dict, List, Optional
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+ from ..types import (
18
+ HookEvent,
19
+ HookResult,
20
+ HookSource,
21
+ HookType,
22
+ HookVerdict,
23
+ )
24
+ from ..policy_engine import UniversalPolicyEngine
25
+
26
+
27
+ class McpHookProxy:
28
+ """Proxies and governs Model Context Protocol tool invocations."""
29
+
30
+ def __init__(self, policy_engine: Optional[UniversalPolicyEngine] = None):
31
+ self.engine = policy_engine or UniversalPolicyEngine()
32
+
33
+ def inspect_request(self, message: Dict[str, Any]) -> Optional[Dict[str, Any]]:
34
+ """
35
+ Inspect an MCP client JSON-RPC request.
36
+ If it's a tools/call and policy triggers a BLOCK, return a synthetic JSON-RPC error.
37
+ Otherwise return None (allow pass-through).
38
+ """
39
+ if message.get("method") != "tools/call":
40
+ return None
41
+
42
+ params = message.get("params") or {}
43
+ tool_name = params.get("name")
44
+ tool_args = params.get("arguments") or {}
45
+
46
+ event = HookEvent(
47
+ source=HookSource.MCP,
48
+ hook_type=HookType.PRE_TOOL,
49
+ tool_name=tool_name,
50
+ command=tool_args.get("command") or tool_args.get("cmd"),
51
+ file_path=tool_args.get("path") or tool_args.get("file_path"),
52
+ args=tool_args,
53
+ )
54
+
55
+ result = self.engine.evaluate(event)
56
+ if result.verdict == HookVerdict.BLOCK:
57
+ msg_id = message.get("id")
58
+ return {
59
+ "jsonrpc": "2.0",
60
+ "id": msg_id,
61
+ "error": {
62
+ "code": -32000,
63
+ "message": f"MCP Tool Execution Blocked by Policy: {result.message}",
64
+ "data": {"rule_id": result.rule_id, "verdict": "blocked"},
65
+ },
66
+ }
67
+ return None
68
+
69
+ def inspect_response(self, response: Dict[str, Any]) -> Dict[str, Any]:
70
+ """
71
+ Inspect MCP server JSON-RPC response before delivering back to the client.
72
+ Checks for secret leakage in tool output.
73
+ """
74
+ result_payload = response.get("result")
75
+ if not result_payload:
76
+ return response
77
+
78
+ event = HookEvent(
79
+ source=HookSource.MCP,
80
+ hook_type=HookType.POST_TOOL,
81
+ output=json.dumps(result_payload),
82
+ )
83
+
84
+ res = self.engine.evaluate(event)
85
+ if res.verdict == HookVerdict.WARN and "SECRET LEAK DETECTED" in res.message:
86
+ # Mask or annotate leak advisory
87
+ if isinstance(result_payload, dict) and "content" in result_payload:
88
+ result_payload["_security_advisory"] = res.message
89
+ return response
90
+
91
+ def run_proxy(self, server_command: List[str]):
92
+ """Run downstream MCP server as subprocess, proxying stdin/stdout."""
93
+ proc = subprocess.Popen(
94
+ server_command,
95
+ stdin=subprocess.PIPE,
96
+ stdout=subprocess.PIPE,
97
+ stderr=sys.stderr,
98
+ text=True,
99
+ bufsize=1,
100
+ )
101
+
102
+ try:
103
+ for line in sys.stdin:
104
+ line = line.strip()
105
+ if not line:
106
+ continue
107
+
108
+ try:
109
+ msg = json.loads(line)
110
+ blocked_resp = self.inspect_request(msg)
111
+ if blocked_resp:
112
+ print(json.dumps(blocked_resp), flush=True)
113
+ continue
114
+ except Exception as parse_err:
115
+ logger.debug("MCP JSON parse error: %s", parse_err)
116
+
117
+ # Forward to server
118
+ if proc.stdin:
119
+ proc.stdin.write(line + "\n")
120
+ proc.stdin.flush()
121
+
122
+ # Read response from server
123
+ if proc.stdout:
124
+ resp_line = proc.stdout.readline()
125
+ if resp_line:
126
+ try:
127
+ resp_json = json.loads(resp_line.strip())
128
+ sanitized = self.inspect_response(resp_json)
129
+ print(json.dumps(sanitized), flush=True)
130
+ except Exception:
131
+ print(resp_line.strip(), flush=True)
132
+ finally:
133
+ proc.terminate()