@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.
Files changed (97) hide show
  1. package/.agents/skills/omg/AGENTS.fragment.md +5 -0
  2. package/.agents/skills/omg/codex-mcp.toml +4 -0
  3. package/.agents/skills/omg/control-plane/SKILL.md +11 -0
  4. package/.agents/skills/omg/control-plane/openai.yaml +14 -0
  5. package/.agents/skills/omg/hook-governor/SKILL.md +11 -0
  6. package/.agents/skills/omg/hook-governor/openai.yaml +11 -0
  7. package/.agents/skills/omg/lsp-pack/SKILL.md +11 -0
  8. package/.agents/skills/omg/lsp-pack/openai.yaml +11 -0
  9. package/.agents/skills/omg/mcp-fabric/SKILL.md +11 -0
  10. package/.agents/skills/omg/mcp-fabric/openai.yaml +13 -0
  11. package/.agents/skills/omg/secure-worktree-pipeline/SKILL.md +11 -0
  12. package/.agents/skills/omg/secure-worktree-pipeline/openai.yaml +12 -0
  13. package/.claude-plugin/marketplace.json +3 -3
  14. package/.claude-plugin/plugin.json +1 -1
  15. package/.mcp.json +20 -4
  16. package/CHANGELOG.md +16 -0
  17. package/OMG-setup.sh +9 -3
  18. package/OMG_COMPAT_CONTRACT.md +92 -0
  19. package/README.md +26 -8
  20. package/SECURITY.md +6 -0
  21. package/commands/OMG:api-twin.md +22 -0
  22. package/commands/OMG:preflight.md +26 -0
  23. package/commands/OMG:security-check.md +28 -0
  24. package/commands/OMG:setup.md +1 -2
  25. package/dist/enterprise/bundle/.agents/skills/omg/AGENTS.fragment.md +5 -0
  26. package/dist/enterprise/bundle/.agents/skills/omg/codex-mcp.toml +4 -0
  27. package/dist/enterprise/bundle/.agents/skills/omg/control-plane/SKILL.md +11 -0
  28. package/dist/enterprise/bundle/.agents/skills/omg/control-plane/openai.yaml +14 -0
  29. package/dist/enterprise/bundle/.agents/skills/omg/hook-governor/SKILL.md +11 -0
  30. package/dist/enterprise/bundle/.agents/skills/omg/hook-governor/openai.yaml +11 -0
  31. package/dist/enterprise/bundle/.agents/skills/omg/lsp-pack/SKILL.md +11 -0
  32. package/dist/enterprise/bundle/.agents/skills/omg/lsp-pack/openai.yaml +11 -0
  33. package/dist/enterprise/bundle/.agents/skills/omg/mcp-fabric/SKILL.md +11 -0
  34. package/dist/enterprise/bundle/.agents/skills/omg/mcp-fabric/openai.yaml +13 -0
  35. package/dist/enterprise/bundle/.agents/skills/omg/secure-worktree-pipeline/SKILL.md +11 -0
  36. package/dist/enterprise/bundle/.agents/skills/omg/secure-worktree-pipeline/openai.yaml +12 -0
  37. package/dist/enterprise/bundle/.claude-plugin/marketplace.json +36 -0
  38. package/dist/enterprise/bundle/.claude-plugin/plugin.json +23 -0
  39. package/dist/enterprise/bundle/.mcp.json +40 -0
  40. package/dist/enterprise/bundle/OMG_COMPAT_CONTRACT.md +92 -0
  41. package/dist/enterprise/bundle/settings.json +366 -0
  42. package/dist/enterprise/manifest.json +99 -0
  43. package/dist/public/bundle/.agents/skills/omg/AGENTS.fragment.md +5 -0
  44. package/dist/public/bundle/.agents/skills/omg/codex-mcp.toml +4 -0
  45. package/dist/public/bundle/.agents/skills/omg/control-plane/SKILL.md +11 -0
  46. package/dist/public/bundle/.agents/skills/omg/control-plane/openai.yaml +14 -0
  47. package/dist/public/bundle/.agents/skills/omg/hook-governor/SKILL.md +11 -0
  48. package/dist/public/bundle/.agents/skills/omg/hook-governor/openai.yaml +11 -0
  49. package/dist/public/bundle/.agents/skills/omg/lsp-pack/SKILL.md +11 -0
  50. package/dist/public/bundle/.agents/skills/omg/lsp-pack/openai.yaml +11 -0
  51. package/dist/public/bundle/.agents/skills/omg/mcp-fabric/SKILL.md +11 -0
  52. package/dist/public/bundle/.agents/skills/omg/mcp-fabric/openai.yaml +13 -0
  53. package/dist/public/bundle/.agents/skills/omg/secure-worktree-pipeline/SKILL.md +11 -0
  54. package/dist/public/bundle/.agents/skills/omg/secure-worktree-pipeline/openai.yaml +12 -0
  55. package/dist/public/bundle/.claude-plugin/marketplace.json +36 -0
  56. package/dist/public/bundle/.claude-plugin/plugin.json +23 -0
  57. package/dist/public/bundle/.mcp.json +40 -0
  58. package/dist/public/bundle/OMG_COMPAT_CONTRACT.md +92 -0
  59. package/dist/public/bundle/settings.json +366 -0
  60. package/dist/public/manifest.json +99 -0
  61. package/hooks/policy_engine.py +38 -7
  62. package/hooks/post-write.py +1 -1
  63. package/hooks/prompt-enhancer.py +2 -2
  64. package/hooks/security_validators.py +75 -0
  65. package/hooks/setup_wizard.py +44 -20
  66. package/hooks/shadow_manager.py +22 -2
  67. package/package.json +1 -1
  68. package/plugins/README.md +4 -2
  69. package/plugins/advanced/commands/OMG:deep-plan.md +1 -1
  70. package/plugins/advanced/commands/OMG:security-review.md +10 -113
  71. package/plugins/advanced/commands/OMG:ship.md +1 -1
  72. package/plugins/advanced/plugin.json +1 -10
  73. package/plugins/core/plugin.json +25 -2
  74. package/pyproject.toml +1 -1
  75. package/runtime/adoption.py +1 -1
  76. package/runtime/api_twin.py +130 -0
  77. package/runtime/compat.py +21 -1
  78. package/runtime/contract_compiler.py +698 -0
  79. package/runtime/domain_packs.py +34 -0
  80. package/runtime/guide_assert.py +45 -0
  81. package/runtime/mcp_config_writers.py +145 -39
  82. package/runtime/omg_compat_contract_snapshot.json +8 -7
  83. package/runtime/omg_contract_snapshot.json +8 -7
  84. package/runtime/omg_mcp_server.py +205 -0
  85. package/runtime/preflight.py +52 -0
  86. package/runtime/providers/codex_provider.py +2 -12
  87. package/runtime/providers/gemini_provider.py +2 -21
  88. package/runtime/providers/kimi_provider.py +2 -21
  89. package/runtime/runtime_profile.py +61 -0
  90. package/runtime/security_check.py +347 -0
  91. package/runtime/subagent_dispatcher.py +117 -10
  92. package/runtime/team_router.py +3 -3
  93. package/runtime/untrusted_content.py +102 -0
  94. package/scripts/omg.py +174 -1
  95. package/settings.json +66 -18
  96. package/tools/python_repl.py +33 -3
  97. 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 write_claude_mcp_config(project_dir: str, server_url: str, server_name: str = "memory-server") -> None:
33
- config_path = Path(project_dir) / ".mcp.json"
34
- config = _load_json(config_path)
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] = {"type": "http", "url": server_url}
40
- _write_json(config_path, config)
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(server_url: str, server_name: str = "memory-server") -> None:
44
- config_path = Path.home() / ".codex" / "config.toml"
45
- config_path.parent.mkdir(parents=True, exist_ok=True)
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 = config_path.read_text() if config_path.exists() else ""
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(config_path, content)
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(config_path, "".join(updated_lines))
127
+ _atomic_write_text(target_path, "".join(updated_lines))
83
128
 
84
129
 
85
- def write_gemini_mcp_config(server_url: str, server_name: str = "memory-server") -> None:
86
- config_path = Path.home() / ".gemini" / "settings.json"
87
- config = _load_json(config_path)
88
- mcp_servers = config.get("mcpServers")
89
- if not isinstance(mcp_servers, dict):
90
- mcp_servers = {}
91
- config["mcpServers"] = mcp_servers
92
- mcp_servers[server_name] = {"httpUrl": server_url}
93
- _write_json(config_path, config)
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
- def write_opencode_mcp_config(server_url: str, server_name: str = "memory-server") -> None:
97
- config_path = Path.home() / ".config" / "opencode" / "opencode.json"
98
- config = _load_json(config_path)
99
- mcp = config.get("mcp")
100
- if not isinstance(mcp, dict):
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
- def write_kimi_mcp_config(server_url: str, server_name: str = "memory-server") -> None:
108
- config_path = Path.home() / ".kimi" / "mcp.json"
109
- config = _load_json(config_path)
110
- mcp_servers = config.get("mcpServers")
111
- if not isinstance(mcp_servers, dict):
112
- mcp_servers = {}
113
- config["mcpServers"] = mcp_servers
114
- mcp_servers[server_name] = {"type": "http", "url": server_url}
115
- _write_json(config_path, config)
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": "secure",
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": "PolicyDecision"
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": "secure",
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": "PolicyDecision"
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 = self.get_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 -----------------------------------------------