@trac3er/oh-my-god 2.0.2 → 2.0.4
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/.agents/skills/omg/AGENTS.fragment.md +5 -0
- package/.agents/skills/omg/codex-mcp.toml +4 -0
- package/.agents/skills/omg/control-plane/SKILL.md +11 -0
- package/.agents/skills/omg/control-plane/openai.yaml +14 -0
- package/.agents/skills/omg/hook-governor/SKILL.md +11 -0
- package/.agents/skills/omg/hook-governor/openai.yaml +11 -0
- package/.agents/skills/omg/lsp-pack/SKILL.md +11 -0
- package/.agents/skills/omg/lsp-pack/openai.yaml +11 -0
- package/.agents/skills/omg/mcp-fabric/SKILL.md +11 -0
- package/.agents/skills/omg/mcp-fabric/openai.yaml +13 -0
- package/.agents/skills/omg/secure-worktree-pipeline/SKILL.md +11 -0
- package/.agents/skills/omg/secure-worktree-pipeline/openai.yaml +12 -0
- package/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +1 -1
- package/.mcp.json +20 -4
- package/CHANGELOG.md +16 -0
- package/OMG-setup.sh +9 -3
- package/OMG_COMPAT_CONTRACT.md +92 -0
- package/README.md +26 -8
- package/SECURITY.md +6 -0
- package/commands/OMG:api-twin.md +22 -0
- package/commands/OMG:preflight.md +26 -0
- package/commands/OMG:security-check.md +28 -0
- package/commands/OMG:setup.md +1 -2
- package/dist/enterprise/bundle/.agents/skills/omg/AGENTS.fragment.md +5 -0
- package/dist/enterprise/bundle/.agents/skills/omg/codex-mcp.toml +4 -0
- package/dist/enterprise/bundle/.agents/skills/omg/control-plane/SKILL.md +11 -0
- package/dist/enterprise/bundle/.agents/skills/omg/control-plane/openai.yaml +14 -0
- package/dist/enterprise/bundle/.agents/skills/omg/hook-governor/SKILL.md +11 -0
- package/dist/enterprise/bundle/.agents/skills/omg/hook-governor/openai.yaml +11 -0
- package/dist/enterprise/bundle/.agents/skills/omg/lsp-pack/SKILL.md +11 -0
- package/dist/enterprise/bundle/.agents/skills/omg/lsp-pack/openai.yaml +11 -0
- package/dist/enterprise/bundle/.agents/skills/omg/mcp-fabric/SKILL.md +11 -0
- package/dist/enterprise/bundle/.agents/skills/omg/mcp-fabric/openai.yaml +13 -0
- package/dist/enterprise/bundle/.agents/skills/omg/secure-worktree-pipeline/SKILL.md +11 -0
- package/dist/enterprise/bundle/.agents/skills/omg/secure-worktree-pipeline/openai.yaml +12 -0
- package/dist/enterprise/bundle/.claude-plugin/marketplace.json +36 -0
- package/dist/enterprise/bundle/.claude-plugin/plugin.json +23 -0
- package/dist/enterprise/bundle/.mcp.json +40 -0
- package/dist/enterprise/bundle/OMG_COMPAT_CONTRACT.md +92 -0
- package/dist/enterprise/bundle/settings.json +366 -0
- package/dist/enterprise/manifest.json +99 -0
- package/dist/public/bundle/.agents/skills/omg/AGENTS.fragment.md +5 -0
- package/dist/public/bundle/.agents/skills/omg/codex-mcp.toml +4 -0
- package/dist/public/bundle/.agents/skills/omg/control-plane/SKILL.md +11 -0
- package/dist/public/bundle/.agents/skills/omg/control-plane/openai.yaml +14 -0
- package/dist/public/bundle/.agents/skills/omg/hook-governor/SKILL.md +11 -0
- package/dist/public/bundle/.agents/skills/omg/hook-governor/openai.yaml +11 -0
- package/dist/public/bundle/.agents/skills/omg/lsp-pack/SKILL.md +11 -0
- package/dist/public/bundle/.agents/skills/omg/lsp-pack/openai.yaml +11 -0
- package/dist/public/bundle/.agents/skills/omg/mcp-fabric/SKILL.md +11 -0
- package/dist/public/bundle/.agents/skills/omg/mcp-fabric/openai.yaml +13 -0
- package/dist/public/bundle/.agents/skills/omg/secure-worktree-pipeline/SKILL.md +11 -0
- package/dist/public/bundle/.agents/skills/omg/secure-worktree-pipeline/openai.yaml +12 -0
- package/dist/public/bundle/.claude-plugin/marketplace.json +36 -0
- package/dist/public/bundle/.claude-plugin/plugin.json +23 -0
- package/dist/public/bundle/.mcp.json +40 -0
- package/dist/public/bundle/OMG_COMPAT_CONTRACT.md +92 -0
- package/dist/public/bundle/settings.json +366 -0
- package/dist/public/manifest.json +99 -0
- package/hooks/policy_engine.py +38 -7
- package/hooks/post-write.py +1 -1
- package/hooks/prompt-enhancer.py +2 -2
- package/hooks/security_validators.py +75 -0
- package/hooks/setup_wizard.py +44 -20
- package/hooks/shadow_manager.py +22 -2
- package/package.json +1 -1
- package/plugins/README.md +4 -2
- package/plugins/advanced/commands/OMG:deep-plan.md +1 -1
- package/plugins/advanced/commands/OMG:security-review.md +10 -113
- package/plugins/advanced/commands/OMG:ship.md +1 -1
- package/plugins/advanced/plugin.json +1 -10
- package/plugins/core/plugin.json +25 -2
- package/pyproject.toml +1 -1
- package/runtime/adoption.py +1 -1
- package/runtime/api_twin.py +130 -0
- package/runtime/compat.py +21 -1
- package/runtime/contract_compiler.py +698 -0
- package/runtime/domain_packs.py +34 -0
- package/runtime/guide_assert.py +45 -0
- package/runtime/mcp_config_writers.py +145 -39
- package/runtime/omg_compat_contract_snapshot.json +8 -7
- package/runtime/omg_contract_snapshot.json +8 -7
- package/runtime/omg_mcp_server.py +205 -0
- package/runtime/preflight.py +52 -0
- package/runtime/providers/codex_provider.py +2 -12
- package/runtime/providers/gemini_provider.py +2 -21
- package/runtime/providers/kimi_provider.py +2 -21
- package/runtime/runtime_profile.py +61 -0
- package/runtime/security_check.py +347 -0
- package/runtime/subagent_dispatcher.py +117 -10
- package/runtime/team_router.py +3 -3
- package/runtime/untrusted_content.py +102 -0
- package/scripts/omg.py +174 -1
- package/settings.json +66 -18
- package/tools/python_repl.py +33 -3
- package/runtime/providers/opencode_provider.py +0 -144
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Optional domain pack contracts for high-risk verticals."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
DOMAIN_PACKS: dict[str, dict[str, Any]] = {
|
|
8
|
+
"robotics": {
|
|
9
|
+
"name": "robotics",
|
|
10
|
+
"required_approvals": ["actuation-approval"],
|
|
11
|
+
"required_evidence": ["simulator-replay", "kill-switch-check"],
|
|
12
|
+
},
|
|
13
|
+
"vision": {
|
|
14
|
+
"name": "vision",
|
|
15
|
+
"required_approvals": [],
|
|
16
|
+
"required_evidence": ["dataset-provenance", "drift-check"],
|
|
17
|
+
},
|
|
18
|
+
"algorithms": {
|
|
19
|
+
"name": "algorithms",
|
|
20
|
+
"required_approvals": [],
|
|
21
|
+
"required_evidence": ["benchmark-harness", "determinism-check"],
|
|
22
|
+
},
|
|
23
|
+
"health": {
|
|
24
|
+
"name": "health",
|
|
25
|
+
"required_approvals": ["human-review"],
|
|
26
|
+
"required_evidence": ["audit-trail", "restricted-tools", "provenance"],
|
|
27
|
+
},
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def get_domain_pack_contract(name: str) -> dict[str, Any]:
|
|
32
|
+
if name not in DOMAIN_PACKS:
|
|
33
|
+
raise KeyError(name)
|
|
34
|
+
return dict(DOMAIN_PACKS[name])
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Rule-based output assertions for OMG guide checks."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def guide_assert(candidate: str, rules: dict[str, Any]) -> dict[str, Any]:
|
|
8
|
+
text = candidate or ""
|
|
9
|
+
lowered = text.lower()
|
|
10
|
+
violations: list[dict[str, str]] = []
|
|
11
|
+
|
|
12
|
+
for goal in _as_list(rules.get("goals")):
|
|
13
|
+
if "todo" in goal.lower() and "todo" in lowered:
|
|
14
|
+
violations.append({"rule_type": "goal", "rule": goal, "reason": "candidate still includes TODO markers"})
|
|
15
|
+
|
|
16
|
+
for non_goal in _as_list(rules.get("non_goals")):
|
|
17
|
+
if _mentions(lowered, non_goal):
|
|
18
|
+
violations.append({"rule_type": "non_goal", "rule": non_goal, "reason": "candidate mentions an explicit non-goal"})
|
|
19
|
+
|
|
20
|
+
for criterion in _as_list(rules.get("acceptance_criteria")):
|
|
21
|
+
if "production-ready" in criterion.lower() and any(token in lowered for token in ("todo", "insecure", "placeholder")):
|
|
22
|
+
violations.append({"rule_type": "acceptance_criteria", "rule": criterion, "reason": "candidate contains non-production wording"})
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
"schema": "GuideAssertionResult",
|
|
26
|
+
"verdict": "fail" if violations else "pass",
|
|
27
|
+
"violations": violations,
|
|
28
|
+
"summary": {
|
|
29
|
+
"rule_count": sum(len(_as_list(rules.get(key))) for key in ("goals", "non_goals", "acceptance_criteria", "architecture_constraints", "style_rules", "risk_appetite")),
|
|
30
|
+
"violation_count": len(violations),
|
|
31
|
+
},
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _as_list(value: Any) -> list[str]:
|
|
36
|
+
if not isinstance(value, list):
|
|
37
|
+
return []
|
|
38
|
+
return [str(item) for item in value if str(item).strip()]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _mentions(lowered_candidate: str, rule: str) -> bool:
|
|
42
|
+
tokens = [token.lower() for token in rule.split() if len(token) >= 4]
|
|
43
|
+
if not tokens:
|
|
44
|
+
return False
|
|
45
|
+
return all(token in lowered_candidate for token in tokens)
|
|
@@ -5,6 +5,12 @@ import os
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from typing import cast
|
|
7
7
|
|
|
8
|
+
from hooks.security_validators import (
|
|
9
|
+
toml_quote_string,
|
|
10
|
+
validate_server_name,
|
|
11
|
+
validate_server_url,
|
|
12
|
+
)
|
|
13
|
+
|
|
8
14
|
|
|
9
15
|
def _atomic_write_text(path: Path, content: str) -> None:
|
|
10
16
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -29,22 +35,61 @@ def _write_json(path: Path, data: dict[str, object]) -> None:
|
|
|
29
35
|
_atomic_write_text(path, json.dumps(data, indent=2) + "\n")
|
|
30
36
|
|
|
31
37
|
|
|
32
|
-
def
|
|
33
|
-
|
|
34
|
-
|
|
38
|
+
def _validated_server_input(server_url: str, server_name: str) -> tuple[str, str]:
|
|
39
|
+
return validate_server_url(server_url), validate_server_name(server_name)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _validated_stdio_input(command: str, args: list[str], server_name: str) -> tuple[str, list[str], str]:
|
|
43
|
+
normalized_name = validate_server_name(server_name)
|
|
44
|
+
normalized_command = str(command).strip()
|
|
45
|
+
if not normalized_command or "\n" in normalized_command or "\r" in normalized_command:
|
|
46
|
+
raise ValueError("Invalid command: newline characters are not allowed")
|
|
47
|
+
normalized_args = [str(arg) for arg in args]
|
|
48
|
+
for arg in normalized_args:
|
|
49
|
+
if "\n" in arg or "\r" in arg:
|
|
50
|
+
raise ValueError("Invalid args: newline characters are not allowed")
|
|
51
|
+
return normalized_command, normalized_args, normalized_name
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _write_json_mcp_server(path: Path, server_name: str, payload: dict[str, object]) -> None:
|
|
55
|
+
config = _load_json(path)
|
|
35
56
|
mcp_servers = config.get("mcpServers")
|
|
36
57
|
if not isinstance(mcp_servers, dict):
|
|
37
58
|
mcp_servers = {}
|
|
38
59
|
config["mcpServers"] = mcp_servers
|
|
39
|
-
mcp_servers[server_name] =
|
|
40
|
-
_write_json(
|
|
60
|
+
mcp_servers[server_name] = payload
|
|
61
|
+
_write_json(path, config)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def write_claude_mcp_config(project_dir: str, server_url: str, server_name: str = "memory-server") -> None:
|
|
65
|
+
server_url, server_name = _validated_server_input(server_url, server_name)
|
|
66
|
+
config_path = Path(project_dir) / ".mcp.json"
|
|
67
|
+
_write_json_mcp_server(config_path, server_name, {"type": "http", "url": server_url})
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def write_claude_mcp_stdio_config(
|
|
71
|
+
project_dir: str,
|
|
72
|
+
*,
|
|
73
|
+
command: str,
|
|
74
|
+
args: list[str],
|
|
75
|
+
server_name: str = "omg-control",
|
|
76
|
+
) -> None:
|
|
77
|
+
command, args, server_name = _validated_stdio_input(command, args, server_name)
|
|
78
|
+
config_path = Path(project_dir) / ".mcp.json"
|
|
79
|
+
_write_json_mcp_server(config_path, server_name, {"command": command, "args": args})
|
|
41
80
|
|
|
42
81
|
|
|
43
|
-
def write_codex_mcp_config(
|
|
44
|
-
|
|
45
|
-
|
|
82
|
+
def write_codex_mcp_config(
|
|
83
|
+
server_url: str,
|
|
84
|
+
server_name: str = "memory-server",
|
|
85
|
+
*,
|
|
86
|
+
config_path: str | Path | None = None,
|
|
87
|
+
) -> None:
|
|
88
|
+
server_url, server_name = _validated_server_input(server_url, server_name)
|
|
89
|
+
target_path = Path(config_path) if config_path is not None else Path.home() / ".codex" / "config.toml"
|
|
90
|
+
target_path.parent.mkdir(parents=True, exist_ok=True)
|
|
46
91
|
|
|
47
|
-
existing =
|
|
92
|
+
existing = target_path.read_text() if target_path.exists() else ""
|
|
48
93
|
lines = existing.splitlines(keepends=True)
|
|
49
94
|
|
|
50
95
|
header_unquoted = f"[mcp_servers.{server_name}]"
|
|
@@ -60,7 +105,7 @@ def write_codex_mcp_config(server_url: str, server_name: str = "memory-server")
|
|
|
60
105
|
block = [
|
|
61
106
|
f"{header_unquoted}\n",
|
|
62
107
|
'type = "http"\n',
|
|
63
|
-
f'url = "{server_url}"\n',
|
|
108
|
+
f'url = "{toml_quote_string(server_url)}"\n',
|
|
64
109
|
"\n",
|
|
65
110
|
]
|
|
66
111
|
|
|
@@ -68,7 +113,7 @@ def write_codex_mcp_config(server_url: str, server_name: str = "memory-server")
|
|
|
68
113
|
if existing and not existing.endswith("\n"):
|
|
69
114
|
existing += "\n"
|
|
70
115
|
content = existing + "".join(block)
|
|
71
|
-
_atomic_write_text(
|
|
116
|
+
_atomic_write_text(target_path, content)
|
|
72
117
|
return
|
|
73
118
|
|
|
74
119
|
end_idx = len(lines)
|
|
@@ -79,37 +124,98 @@ def write_codex_mcp_config(server_url: str, server_name: str = "memory-server")
|
|
|
79
124
|
break
|
|
80
125
|
|
|
81
126
|
updated_lines = lines[:start_idx] + block + lines[end_idx:]
|
|
82
|
-
_atomic_write_text(
|
|
127
|
+
_atomic_write_text(target_path, "".join(updated_lines))
|
|
83
128
|
|
|
84
129
|
|
|
85
|
-
def
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
130
|
+
def write_codex_mcp_stdio_config(
|
|
131
|
+
*,
|
|
132
|
+
command: str,
|
|
133
|
+
args: list[str],
|
|
134
|
+
server_name: str = "omg-control",
|
|
135
|
+
config_path: str | Path | None = None,
|
|
136
|
+
) -> None:
|
|
137
|
+
command, args, server_name = _validated_stdio_input(command, args, server_name)
|
|
138
|
+
target_path = Path(config_path) if config_path is not None else Path.home() / ".codex" / "config.toml"
|
|
139
|
+
target_path.parent.mkdir(parents=True, exist_ok=True)
|
|
140
|
+
|
|
141
|
+
existing = target_path.read_text() if target_path.exists() else ""
|
|
142
|
+
lines = existing.splitlines(keepends=True)
|
|
143
|
+
header_unquoted = f"[mcp_servers.{server_name}]"
|
|
144
|
+
header_quoted = f"[mcp_servers.\"{server_name}\"]"
|
|
145
|
+
headers = {header_unquoted, header_quoted}
|
|
146
|
+
|
|
147
|
+
start_idx: int | None = None
|
|
148
|
+
for idx, line in enumerate(lines):
|
|
149
|
+
if line.strip() in headers:
|
|
150
|
+
start_idx = idx
|
|
151
|
+
break
|
|
94
152
|
|
|
153
|
+
args_text = ", ".join(f'"{toml_quote_string(arg)}"' for arg in args)
|
|
154
|
+
block = [
|
|
155
|
+
f"{header_unquoted}\n",
|
|
156
|
+
f'command = "{toml_quote_string(command)}"\n',
|
|
157
|
+
f"args = [{args_text}]\n",
|
|
158
|
+
"\n",
|
|
159
|
+
]
|
|
95
160
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
mcp = {}
|
|
102
|
-
config["mcp"] = mcp
|
|
103
|
-
mcp[server_name] = {"type": "remote", "url": server_url}
|
|
104
|
-
_write_json(config_path, config)
|
|
161
|
+
if start_idx is None:
|
|
162
|
+
if existing and not existing.endswith("\n"):
|
|
163
|
+
existing += "\n"
|
|
164
|
+
_atomic_write_text(target_path, existing + "".join(block))
|
|
165
|
+
return
|
|
105
166
|
|
|
167
|
+
end_idx = len(lines)
|
|
168
|
+
for idx in range(start_idx + 1, len(lines)):
|
|
169
|
+
stripped = lines[idx].strip()
|
|
170
|
+
if stripped.startswith("[") and stripped.endswith("]"):
|
|
171
|
+
end_idx = idx
|
|
172
|
+
break
|
|
106
173
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
174
|
+
updated_lines = lines[:start_idx] + block + lines[end_idx:]
|
|
175
|
+
_atomic_write_text(target_path, "".join(updated_lines))
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def write_gemini_mcp_config(
|
|
179
|
+
server_url: str,
|
|
180
|
+
server_name: str = "memory-server",
|
|
181
|
+
*,
|
|
182
|
+
config_path: str | Path | None = None,
|
|
183
|
+
) -> None:
|
|
184
|
+
server_url, server_name = _validated_server_input(server_url, server_name)
|
|
185
|
+
target_path = Path(config_path) if config_path is not None else Path.home() / ".gemini" / "settings.json"
|
|
186
|
+
_write_json_mcp_server(target_path, server_name, {"httpUrl": server_url})
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def write_gemini_mcp_stdio_config(
|
|
190
|
+
*,
|
|
191
|
+
command: str,
|
|
192
|
+
args: list[str],
|
|
193
|
+
server_name: str = "omg-control",
|
|
194
|
+
config_path: str | Path | None = None,
|
|
195
|
+
) -> None:
|
|
196
|
+
command, args, server_name = _validated_stdio_input(command, args, server_name)
|
|
197
|
+
target_path = Path(config_path) if config_path is not None else Path.home() / ".gemini" / "settings.json"
|
|
198
|
+
_write_json_mcp_server(target_path, server_name, {"command": command, "args": args})
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def write_kimi_mcp_config(
|
|
202
|
+
server_url: str,
|
|
203
|
+
server_name: str = "memory-server",
|
|
204
|
+
*,
|
|
205
|
+
config_path: str | Path | None = None,
|
|
206
|
+
) -> None:
|
|
207
|
+
server_url, server_name = _validated_server_input(server_url, server_name)
|
|
208
|
+
target_path = Path(config_path) if config_path is not None else Path.home() / ".kimi" / "mcp.json"
|
|
209
|
+
_write_json_mcp_server(target_path, server_name, {"type": "http", "url": server_url})
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def write_kimi_mcp_stdio_config(
|
|
213
|
+
*,
|
|
214
|
+
command: str,
|
|
215
|
+
args: list[str],
|
|
216
|
+
server_name: str = "omg-control",
|
|
217
|
+
config_path: str | Path | None = None,
|
|
218
|
+
) -> None:
|
|
219
|
+
command, args, server_name = _validated_stdio_input(command, args, server_name)
|
|
220
|
+
target_path = Path(config_path) if config_path is not None else Path.home() / ".kimi" / "mcp.json"
|
|
221
|
+
_write_json_mcp_server(target_path, server_name, {"command": command, "args": args})
|
|
@@ -736,19 +736,19 @@
|
|
|
736
736
|
},
|
|
737
737
|
{
|
|
738
738
|
"skill": "security-review",
|
|
739
|
-
"route": "
|
|
739
|
+
"route": "security_check",
|
|
740
740
|
"maturity": "native",
|
|
741
741
|
"inputs": {
|
|
742
|
-
"required": [
|
|
742
|
+
"required": [],
|
|
743
|
+
"optional": [
|
|
743
744
|
"problem"
|
|
744
|
-
]
|
|
745
|
-
"optional": []
|
|
745
|
+
]
|
|
746
746
|
},
|
|
747
747
|
"outputs": {
|
|
748
|
-
"schema": "
|
|
748
|
+
"schema": "SecurityCheckResult"
|
|
749
749
|
},
|
|
750
750
|
"side_effects": [],
|
|
751
|
-
"notes": ""
|
|
751
|
+
"notes": "Deprecated alias to the canonical OMG security-check engine."
|
|
752
752
|
},
|
|
753
753
|
{
|
|
754
754
|
"skill": "skill",
|
|
@@ -911,5 +911,6 @@
|
|
|
911
911
|
],
|
|
912
912
|
"notes": "Writes long-form memory artifact for writing workflows."
|
|
913
913
|
}
|
|
914
|
-
]
|
|
914
|
+
],
|
|
915
|
+
"generated_at": "2026-03-07T04:51:08.940394+00:00"
|
|
915
916
|
}
|
|
@@ -736,19 +736,19 @@
|
|
|
736
736
|
},
|
|
737
737
|
{
|
|
738
738
|
"skill": "security-review",
|
|
739
|
-
"route": "
|
|
739
|
+
"route": "security_check",
|
|
740
740
|
"maturity": "native",
|
|
741
741
|
"inputs": {
|
|
742
|
-
"required": [
|
|
742
|
+
"required": [],
|
|
743
|
+
"optional": [
|
|
743
744
|
"problem"
|
|
744
|
-
]
|
|
745
|
-
"optional": []
|
|
745
|
+
]
|
|
746
746
|
},
|
|
747
747
|
"outputs": {
|
|
748
|
-
"schema": "
|
|
748
|
+
"schema": "SecurityCheckResult"
|
|
749
749
|
},
|
|
750
750
|
"side_effects": [],
|
|
751
|
-
"notes": ""
|
|
751
|
+
"notes": "Deprecated alias to the canonical OMG security-check engine."
|
|
752
752
|
},
|
|
753
753
|
{
|
|
754
754
|
"skill": "skill",
|
|
@@ -911,5 +911,6 @@
|
|
|
911
911
|
],
|
|
912
912
|
"notes": "Writes long-form memory artifact for writing workflows."
|
|
913
913
|
}
|
|
914
|
-
]
|
|
914
|
+
],
|
|
915
|
+
"generated_at": "2026-03-07T04:51:08.940453+00:00"
|
|
915
916
|
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from collections.abc import AsyncIterator
|
|
5
|
+
from contextlib import asynccontextmanager
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
_MCP_IMPORT_ERROR: ModuleNotFoundError | None = None
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
from fastmcp import FastMCP
|
|
14
|
+
except ModuleNotFoundError as exc:
|
|
15
|
+
_MCP_IMPORT_ERROR = exc
|
|
16
|
+
|
|
17
|
+
def _passthrough_decorator(*_args: Any, **_kwargs: Any):
|
|
18
|
+
def decorator(func: Any) -> Any:
|
|
19
|
+
return func
|
|
20
|
+
|
|
21
|
+
return decorator
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class _StubPrompt:
|
|
25
|
+
name: str
|
|
26
|
+
description: str
|
|
27
|
+
handler: Any
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class _StubResource:
|
|
31
|
+
uri: str
|
|
32
|
+
name: str
|
|
33
|
+
description: str
|
|
34
|
+
mime_type: str
|
|
35
|
+
handler: Any
|
|
36
|
+
|
|
37
|
+
class FastMCP: # type: ignore[override]
|
|
38
|
+
def __init__(self, *_args: Any, **_kwargs: Any) -> None:
|
|
39
|
+
self._import_error = _MCP_IMPORT_ERROR
|
|
40
|
+
self.instructions = str(_kwargs.get("instructions", ""))
|
|
41
|
+
self._prompts: list[_StubPrompt] = []
|
|
42
|
+
self._resources: dict[str, _StubResource] = {}
|
|
43
|
+
|
|
44
|
+
def tool(self, *_args: Any, **_kwargs: Any):
|
|
45
|
+
return _passthrough_decorator(*_args, **_kwargs)
|
|
46
|
+
|
|
47
|
+
def prompt(self, *, name: str, description: str = ""):
|
|
48
|
+
def decorator(func: Any) -> Any:
|
|
49
|
+
self._prompts.append(_StubPrompt(name=name, description=description, handler=func))
|
|
50
|
+
return func
|
|
51
|
+
|
|
52
|
+
return decorator
|
|
53
|
+
|
|
54
|
+
def resource(
|
|
55
|
+
self,
|
|
56
|
+
uri: str,
|
|
57
|
+
*,
|
|
58
|
+
name: str = "",
|
|
59
|
+
description: str = "",
|
|
60
|
+
mime_type: str = "text/plain",
|
|
61
|
+
):
|
|
62
|
+
def decorator(func: Any) -> Any:
|
|
63
|
+
self._resources[uri] = _StubResource(
|
|
64
|
+
uri=uri,
|
|
65
|
+
name=name,
|
|
66
|
+
description=description,
|
|
67
|
+
mime_type=mime_type,
|
|
68
|
+
handler=func,
|
|
69
|
+
)
|
|
70
|
+
return func
|
|
71
|
+
|
|
72
|
+
return decorator
|
|
73
|
+
|
|
74
|
+
async def list_prompts(self) -> list[_StubPrompt]:
|
|
75
|
+
return list(self._prompts)
|
|
76
|
+
|
|
77
|
+
async def list_resources(self) -> list[_StubResource]:
|
|
78
|
+
return list(self._resources.values())
|
|
79
|
+
|
|
80
|
+
async def read_resource(self, uri: str) -> Any:
|
|
81
|
+
resource = self._resources[str(uri)]
|
|
82
|
+
return resource.handler()
|
|
83
|
+
|
|
84
|
+
def run(self, *_args: Any, **_kwargs: Any) -> None:
|
|
85
|
+
raise RuntimeError("fastmcp is required to run the OMG MCP server") from self._import_error
|
|
86
|
+
|
|
87
|
+
FastMCP.__module__ = "fastmcp"
|
|
88
|
+
|
|
89
|
+
from control_plane.service import ControlPlaneService
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
MCP_INSTRUCTIONS = (
|
|
93
|
+
"OMG production control plane MCP. Prefer omg-control prompts and resources for "
|
|
94
|
+
"contract, release-readiness, and governance context before using direct tools."
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _root_dir() -> Path:
|
|
99
|
+
return Path(__file__).resolve().parents[1]
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@asynccontextmanager
|
|
103
|
+
async def lifespan(_: object) -> AsyncIterator[None]:
|
|
104
|
+
yield
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
mcp = FastMCP("OMG Control MCP", lifespan=lifespan, instructions=MCP_INSTRUCTIONS)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _service() -> ControlPlaneService:
|
|
111
|
+
return ControlPlaneService(project_dir=os.environ.get("CLAUDE_PROJECT_DIR", os.getcwd()))
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _read_repo_text(rel_path: str) -> str:
|
|
115
|
+
return (_root_dir() / rel_path).read_text(encoding="utf-8")
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@mcp.tool()
|
|
119
|
+
def omg_policy_evaluate(tool: str, input: dict[str, Any]) -> dict[str, Any]:
|
|
120
|
+
_status, payload = _service().policy_evaluate({"tool": tool, "input": input})
|
|
121
|
+
return payload
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@mcp.tool()
|
|
125
|
+
def omg_trust_review(file_path: str, old_config: dict[str, Any], new_config: dict[str, Any]) -> dict[str, Any]:
|
|
126
|
+
_status, payload = _service().trust_review({"file_path": file_path, "old_config": old_config, "new_config": new_config})
|
|
127
|
+
return payload
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@mcp.tool()
|
|
131
|
+
def omg_evidence_ingest(
|
|
132
|
+
run_id: str,
|
|
133
|
+
tests: list[dict[str, Any]],
|
|
134
|
+
security_scans: list[dict[str, Any]],
|
|
135
|
+
diff_summary: dict[str, Any],
|
|
136
|
+
reproducibility: dict[str, Any],
|
|
137
|
+
unresolved_risks: list[str],
|
|
138
|
+
provenance: list[dict[str, Any]] | None = None,
|
|
139
|
+
trust_scores: dict[str, Any] | None = None,
|
|
140
|
+
api_twin: dict[str, Any] | None = None,
|
|
141
|
+
) -> dict[str, Any]:
|
|
142
|
+
_status, payload = _service().evidence_ingest(
|
|
143
|
+
{
|
|
144
|
+
"run_id": run_id,
|
|
145
|
+
"tests": tests,
|
|
146
|
+
"security_scans": security_scans,
|
|
147
|
+
"diff_summary": diff_summary,
|
|
148
|
+
"reproducibility": reproducibility,
|
|
149
|
+
"unresolved_risks": unresolved_risks,
|
|
150
|
+
"provenance": provenance or [],
|
|
151
|
+
"trust_scores": trust_scores or {},
|
|
152
|
+
"api_twin": api_twin or {},
|
|
153
|
+
}
|
|
154
|
+
)
|
|
155
|
+
return payload
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@mcp.tool()
|
|
159
|
+
def omg_runtime_dispatch(runtime: str, idea: dict[str, Any]) -> dict[str, Any]:
|
|
160
|
+
_status, payload = _service().runtime_dispatch({"runtime": runtime, "idea": idea})
|
|
161
|
+
return payload
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@mcp.tool()
|
|
165
|
+
def omg_security_check(scope: str = ".", include_live_enrichment: bool = False) -> dict[str, Any]:
|
|
166
|
+
_status, payload = _service().security_check({"scope": scope, "include_live_enrichment": include_live_enrichment})
|
|
167
|
+
return payload
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@mcp.tool()
|
|
171
|
+
def omg_guide_assert(candidate: str, rules: dict[str, Any]) -> dict[str, Any]:
|
|
172
|
+
_status, payload = _service().guide_assert({"candidate": candidate, "rules": rules})
|
|
173
|
+
return payload
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@mcp.prompt(name="omg_contract_summary", description="Summarize the OMG production contract and generated host outputs")
|
|
177
|
+
def omg_contract_summary(channel: str = "public") -> str:
|
|
178
|
+
return (
|
|
179
|
+
"Summarize the OMG production control plane contract for channel "
|
|
180
|
+
f"`{channel}`. Include execution_contract, host_compilation_rules, "
|
|
181
|
+
"MCP resources, prompts, and release-readiness expectations."
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@mcp.resource("resource://omg/contract", name="omg_contract", description="Canonical OMG production contract document", mime_type="text/markdown")
|
|
186
|
+
def omg_contract_resource() -> str:
|
|
187
|
+
return _read_repo_text("OMG_COMPAT_CONTRACT.md")
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@mcp.resource(
|
|
191
|
+
"resource://omg/release-checklist",
|
|
192
|
+
name="omg_release_checklist",
|
|
193
|
+
description="Public release checklist for OMG",
|
|
194
|
+
mime_type="text/markdown",
|
|
195
|
+
)
|
|
196
|
+
def omg_release_checklist_resource() -> str:
|
|
197
|
+
return _read_repo_text("docs/release-checklist.md")
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def run_server() -> None:
|
|
201
|
+
mcp.run(transport="stdio")
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
if __name__ == "__main__":
|
|
205
|
+
run_server()
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Structured preflight routing for OMG."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def run_preflight(project_dir: str, *, goal: str) -> dict[str, Any]:
|
|
8
|
+
lowered = goal.lower()
|
|
9
|
+
task_class = "implementation"
|
|
10
|
+
risk_class = "medium"
|
|
11
|
+
route = "teams"
|
|
12
|
+
|
|
13
|
+
if any(token in lowered for token in ("openapi", "swagger", "postman", "contract", "fixture", "replay")):
|
|
14
|
+
task_class = "contract"
|
|
15
|
+
route = "api-twin"
|
|
16
|
+
elif any(token in lowered for token in ("auth", "secret", "security", "token", "injection")):
|
|
17
|
+
task_class = "security"
|
|
18
|
+
risk_class = "high"
|
|
19
|
+
route = "security-check"
|
|
20
|
+
elif any(token in lowered for token in ("full stack", "frontend and backend", "dashboard", "orchestrate")):
|
|
21
|
+
task_class = "orchestration"
|
|
22
|
+
risk_class = "high"
|
|
23
|
+
route = "crazy"
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
"schema": "PreflightResult",
|
|
27
|
+
"project_dir": project_dir,
|
|
28
|
+
"goal": goal,
|
|
29
|
+
"task_class": task_class,
|
|
30
|
+
"risk_class": risk_class,
|
|
31
|
+
"route": route,
|
|
32
|
+
"required_tools": _required_tools(route),
|
|
33
|
+
"required_mcps": ["omg-control"] if route in {"security-check", "api-twin", "crazy"} else [],
|
|
34
|
+
"missing_constraints": [],
|
|
35
|
+
"evidence_plan": _evidence_plan(route),
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _required_tools(route: str) -> list[str]:
|
|
40
|
+
return {
|
|
41
|
+
"security-check": ["security"],
|
|
42
|
+
"api-twin": ["api-twin"],
|
|
43
|
+
"crazy": ["teams", "ccg"],
|
|
44
|
+
}.get(route, ["teams"])
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _evidence_plan(route: str) -> list[str]:
|
|
48
|
+
return {
|
|
49
|
+
"security-check": ["security findings", "provenance"],
|
|
50
|
+
"api-twin": ["fixture fidelity", "live verification"],
|
|
51
|
+
"crazy": ["verification output", "evidence pack"],
|
|
52
|
+
}.get(route, ["verification output"])
|
|
@@ -11,6 +11,7 @@ import uuid
|
|
|
11
11
|
from typing import Any
|
|
12
12
|
|
|
13
13
|
from runtime.cli_provider import CLIProvider, register_provider
|
|
14
|
+
from runtime.mcp_config_writers import write_codex_mcp_config
|
|
14
15
|
from runtime.tmux_session_manager import TmuxSessionManager
|
|
15
16
|
|
|
16
17
|
_logger = logging.getLogger(__name__)
|
|
@@ -94,18 +95,7 @@ class CodexProvider(CLIProvider):
|
|
|
94
95
|
|
|
95
96
|
def write_mcp_config(self, server_url: str, server_name: str = "memory-server") -> None:
|
|
96
97
|
"""Write an MCP server entry to ``~/.codex/config.toml``."""
|
|
97
|
-
config_path
|
|
98
|
-
os.makedirs(os.path.dirname(config_path), exist_ok=True)
|
|
99
|
-
|
|
100
|
-
entry = (
|
|
101
|
-
f'[mcp_servers."{server_name}"]\n'
|
|
102
|
-
f'url = "{server_url}"\n'
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
# Append or create
|
|
106
|
-
mode = "a" if os.path.exists(config_path) else "w"
|
|
107
|
-
with open(config_path, mode) as fh:
|
|
108
|
-
fh.write(entry)
|
|
98
|
+
write_codex_mcp_config(server_url, server_name, config_path=self.get_config_path())
|
|
109
99
|
|
|
110
100
|
|
|
111
101
|
# -- auto-register on import -----------------------------------------------
|