@trac3er/oh-my-god 2.1.0 → 2.1.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 (371) hide show
  1. package/.agents/skills/omg/AGENTS.fragment.md +2 -2
  2. package/.agents/skills/omg/algorithms/SKILL.md +1 -1
  3. package/.agents/skills/omg/algorithms/openai.yaml +1 -1
  4. package/.agents/skills/omg/api-twin/SKILL.md +1 -1
  5. package/.agents/skills/omg/api-twin/openai.yaml +1 -1
  6. package/.agents/skills/omg/claim-judge/SKILL.md +1 -1
  7. package/.agents/skills/omg/claim-judge/openai.yaml +1 -1
  8. package/.agents/skills/omg/codex-rules.md +1 -1
  9. package/.agents/skills/omg/control-plane/SKILL.md +1 -1
  10. package/.agents/skills/omg/control-plane/openai.yaml +1 -1
  11. package/.agents/skills/omg/data-lineage/SKILL.md +1 -1
  12. package/.agents/skills/omg/data-lineage/openai.yaml +1 -1
  13. package/.agents/skills/omg/delta-classifier/SKILL.md +1 -1
  14. package/.agents/skills/omg/delta-classifier/openai.yaml +1 -1
  15. package/.agents/skills/omg/eval-gate/SKILL.md +1 -1
  16. package/.agents/skills/omg/eval-gate/openai.yaml +1 -1
  17. package/.agents/skills/omg/health/SKILL.md +1 -1
  18. package/.agents/skills/omg/health/openai.yaml +1 -1
  19. package/.agents/skills/omg/hook-governor/SKILL.md +1 -1
  20. package/.agents/skills/omg/hook-governor/openai.yaml +1 -1
  21. package/.agents/skills/omg/incident-replay/SKILL.md +1 -1
  22. package/.agents/skills/omg/incident-replay/openai.yaml +1 -1
  23. package/.agents/skills/omg/lsp-pack/SKILL.md +1 -1
  24. package/.agents/skills/omg/lsp-pack/openai.yaml +1 -1
  25. package/.agents/skills/omg/mcp-fabric/SKILL.md +1 -1
  26. package/.agents/skills/omg/mcp-fabric/openai.yaml +1 -1
  27. package/.agents/skills/omg/plan-council/SKILL.md +1 -1
  28. package/.agents/skills/omg/plan-council/openai.yaml +1 -1
  29. package/.agents/skills/omg/preflight/SKILL.md +1 -1
  30. package/.agents/skills/omg/preflight/openai.yaml +1 -1
  31. package/.agents/skills/omg/proof-gate/SKILL.md +1 -1
  32. package/.agents/skills/omg/proof-gate/openai.yaml +1 -1
  33. package/.agents/skills/omg/remote-supervisor/SKILL.md +1 -1
  34. package/.agents/skills/omg/remote-supervisor/openai.yaml +1 -1
  35. package/.agents/skills/omg/robotics/SKILL.md +1 -1
  36. package/.agents/skills/omg/robotics/openai.yaml +1 -1
  37. package/.agents/skills/omg/secure-worktree-pipeline/SKILL.md +1 -1
  38. package/.agents/skills/omg/secure-worktree-pipeline/openai.yaml +1 -1
  39. package/.agents/skills/omg/security-check/SKILL.md +1 -1
  40. package/.agents/skills/omg/security-check/openai.yaml +1 -1
  41. package/.agents/skills/omg/test-intent-lock/SKILL.md +1 -1
  42. package/.agents/skills/omg/test-intent-lock/openai.yaml +1 -1
  43. package/.agents/skills/omg/tracebank/SKILL.md +1 -1
  44. package/.agents/skills/omg/tracebank/openai.yaml +1 -1
  45. package/.agents/skills/omg/vision/SKILL.md +1 -1
  46. package/.agents/skills/omg/vision/openai.yaml +1 -1
  47. package/.claude-plugin/marketplace.json +3 -3
  48. package/.claude-plugin/plugin.json +1 -1
  49. package/CHANGELOG.md +16 -0
  50. package/CLI-ADAPTER-MAP.md +3 -3
  51. package/OMG-setup.sh +114 -1
  52. package/OMG_COMPAT_CONTRACT.md +1 -1
  53. package/README.md +2 -1
  54. package/artifacts/release/.claude-plugin/marketplace.json +3 -3
  55. package/artifacts/release/.claude-plugin/plugin.json +1 -1
  56. package/artifacts/release/OMG_COMPAT_CONTRACT.md +1 -1
  57. package/artifacts/release/dist/enterprise/bundle/.claude-plugin/marketplace.json +3 -3
  58. package/artifacts/release/dist/enterprise/bundle/.claude-plugin/plugin.json +1 -1
  59. package/artifacts/release/dist/enterprise/bundle/OMG_COMPAT_CONTRACT.md +1 -1
  60. package/artifacts/release/dist/enterprise/bundle/plugins/advanced/plugin.json +26 -9
  61. package/artifacts/release/dist/enterprise/bundle/registry/bundles/algorithms.yaml +1 -1
  62. package/artifacts/release/dist/enterprise/bundle/registry/bundles/api-twin.yaml +1 -1
  63. package/artifacts/release/dist/enterprise/bundle/registry/bundles/claim-judge.yaml +1 -1
  64. package/artifacts/release/dist/enterprise/bundle/registry/bundles/control-plane.yaml +1 -1
  65. package/artifacts/release/dist/enterprise/bundle/registry/bundles/data-lineage.yaml +1 -1
  66. package/artifacts/release/dist/enterprise/bundle/registry/bundles/delta-classifier.yaml +1 -1
  67. package/artifacts/release/dist/enterprise/bundle/registry/bundles/eval-gate.yaml +1 -1
  68. package/artifacts/release/dist/enterprise/bundle/registry/bundles/health.yaml +1 -1
  69. package/artifacts/release/dist/enterprise/bundle/registry/bundles/hook-governor.yaml +1 -1
  70. package/artifacts/release/dist/enterprise/bundle/registry/bundles/incident-replay.yaml +1 -1
  71. package/artifacts/release/dist/enterprise/bundle/registry/bundles/lsp-pack.yaml +1 -1
  72. package/artifacts/release/dist/enterprise/bundle/registry/bundles/mcp-fabric.yaml +1 -1
  73. package/artifacts/release/dist/enterprise/bundle/registry/bundles/plan-council.yaml +1 -1
  74. package/artifacts/release/dist/enterprise/bundle/registry/bundles/preflight.yaml +1 -1
  75. package/artifacts/release/dist/enterprise/bundle/registry/bundles/proof-gate.yaml +1 -1
  76. package/artifacts/release/dist/enterprise/bundle/registry/bundles/remote-supervisor.yaml +1 -1
  77. package/artifacts/release/dist/enterprise/bundle/registry/bundles/robotics.yaml +1 -1
  78. package/artifacts/release/dist/enterprise/bundle/registry/bundles/secure-worktree-pipeline.yaml +1 -1
  79. package/artifacts/release/dist/enterprise/bundle/registry/bundles/security-check.yaml +1 -1
  80. package/artifacts/release/dist/enterprise/bundle/registry/bundles/test-intent-lock.yaml +1 -1
  81. package/artifacts/release/dist/enterprise/bundle/registry/bundles/tracebank.yaml +1 -1
  82. package/artifacts/release/dist/enterprise/bundle/registry/bundles/vision.yaml +1 -1
  83. package/artifacts/release/dist/enterprise/bundle/registry/omg-capability.schema.json +1 -1
  84. package/artifacts/release/dist/enterprise/bundle/settings.json +3 -3
  85. package/artifacts/release/dist/enterprise/manifest.json +30 -40
  86. package/artifacts/release/dist/public/bundle/.claude-plugin/marketplace.json +3 -3
  87. package/artifacts/release/dist/public/bundle/.claude-plugin/plugin.json +1 -1
  88. package/artifacts/release/dist/public/bundle/OMG_COMPAT_CONTRACT.md +1 -1
  89. package/artifacts/release/dist/public/bundle/plugins/advanced/plugin.json +26 -9
  90. package/artifacts/release/dist/public/bundle/registry/bundles/algorithms.yaml +1 -1
  91. package/artifacts/release/dist/public/bundle/registry/bundles/api-twin.yaml +1 -1
  92. package/artifacts/release/dist/public/bundle/registry/bundles/claim-judge.yaml +1 -1
  93. package/artifacts/release/dist/public/bundle/registry/bundles/control-plane.yaml +1 -1
  94. package/artifacts/release/dist/public/bundle/registry/bundles/data-lineage.yaml +1 -1
  95. package/artifacts/release/dist/public/bundle/registry/bundles/delta-classifier.yaml +1 -1
  96. package/artifacts/release/dist/public/bundle/registry/bundles/eval-gate.yaml +1 -1
  97. package/artifacts/release/dist/public/bundle/registry/bundles/health.yaml +1 -1
  98. package/artifacts/release/dist/public/bundle/registry/bundles/hook-governor.yaml +1 -1
  99. package/artifacts/release/dist/public/bundle/registry/bundles/incident-replay.yaml +1 -1
  100. package/artifacts/release/dist/public/bundle/registry/bundles/lsp-pack.yaml +1 -1
  101. package/artifacts/release/dist/public/bundle/registry/bundles/mcp-fabric.yaml +1 -1
  102. package/artifacts/release/dist/public/bundle/registry/bundles/plan-council.yaml +1 -1
  103. package/artifacts/release/dist/public/bundle/registry/bundles/preflight.yaml +1 -1
  104. package/artifacts/release/dist/public/bundle/registry/bundles/proof-gate.yaml +1 -1
  105. package/artifacts/release/dist/public/bundle/registry/bundles/remote-supervisor.yaml +1 -1
  106. package/artifacts/release/dist/public/bundle/registry/bundles/robotics.yaml +1 -1
  107. package/artifacts/release/dist/public/bundle/registry/bundles/secure-worktree-pipeline.yaml +1 -1
  108. package/artifacts/release/dist/public/bundle/registry/bundles/security-check.yaml +1 -1
  109. package/artifacts/release/dist/public/bundle/registry/bundles/test-intent-lock.yaml +1 -1
  110. package/artifacts/release/dist/public/bundle/registry/bundles/tracebank.yaml +1 -1
  111. package/artifacts/release/dist/public/bundle/registry/bundles/vision.yaml +1 -1
  112. package/artifacts/release/dist/public/bundle/registry/omg-capability.schema.json +1 -1
  113. package/artifacts/release/dist/public/bundle/settings.json +3 -3
  114. package/artifacts/release/dist/public/manifest.json +30 -40
  115. package/artifacts/release/plugins/advanced/plugin.json +26 -9
  116. package/artifacts/release/registry/bundles/algorithms.yaml +1 -1
  117. package/artifacts/release/registry/bundles/api-twin.yaml +1 -1
  118. package/artifacts/release/registry/bundles/claim-judge.yaml +1 -1
  119. package/artifacts/release/registry/bundles/control-plane.yaml +1 -1
  120. package/artifacts/release/registry/bundles/data-lineage.yaml +1 -1
  121. package/artifacts/release/registry/bundles/delta-classifier.yaml +1 -1
  122. package/artifacts/release/registry/bundles/eval-gate.yaml +1 -1
  123. package/artifacts/release/registry/bundles/health.yaml +1 -1
  124. package/artifacts/release/registry/bundles/hook-governor.yaml +1 -1
  125. package/artifacts/release/registry/bundles/incident-replay.yaml +1 -1
  126. package/artifacts/release/registry/bundles/lsp-pack.yaml +1 -1
  127. package/artifacts/release/registry/bundles/mcp-fabric.yaml +1 -1
  128. package/artifacts/release/registry/bundles/plan-council.yaml +1 -1
  129. package/artifacts/release/registry/bundles/preflight.yaml +1 -1
  130. package/artifacts/release/registry/bundles/proof-gate.yaml +1 -1
  131. package/artifacts/release/registry/bundles/remote-supervisor.yaml +1 -1
  132. package/artifacts/release/registry/bundles/robotics.yaml +1 -1
  133. package/artifacts/release/registry/bundles/secure-worktree-pipeline.yaml +1 -1
  134. package/artifacts/release/registry/bundles/security-check.yaml +1 -1
  135. package/artifacts/release/registry/bundles/test-intent-lock.yaml +1 -1
  136. package/artifacts/release/registry/bundles/tracebank.yaml +1 -1
  137. package/artifacts/release/registry/bundles/vision.yaml +1 -1
  138. package/artifacts/release/registry/omg-capability.schema.json +1 -1
  139. package/artifacts/release/settings.json +3 -3
  140. package/build/lib/commands/OMG:profile-review.md +43 -0
  141. package/build/lib/commands/OMG:validate.md +59 -0
  142. package/build/lib/control_plane/service.py +40 -1
  143. package/build/lib/hooks/_agent_registry.py +2 -0
  144. package/build/lib/hooks/firewall.py +263 -4
  145. package/build/lib/hooks/intentgate-keyword-detector.py +15 -3
  146. package/build/lib/hooks/policy_engine.py +77 -0
  147. package/build/lib/hooks/prompt-enhancer.py +146 -0
  148. package/build/lib/hooks/session-end-capture.py +374 -6
  149. package/build/lib/hooks/session-start.py +13 -35
  150. package/build/lib/hooks/setup_wizard.py +333 -1
  151. package/build/lib/hooks/shadow_manager.py +111 -2
  152. package/build/lib/hooks/stop_dispatcher.py +108 -4
  153. package/build/lib/plugins/advanced/plugin.json +26 -9
  154. package/build/lib/plugins/core/plugin.json +44 -11
  155. package/build/lib/registry/bundles/algorithms.yaml +1 -1
  156. package/build/lib/registry/bundles/api-twin.yaml +1 -1
  157. package/build/lib/registry/bundles/claim-judge.yaml +1 -1
  158. package/build/lib/registry/bundles/control-plane.yaml +1 -1
  159. package/build/lib/registry/bundles/data-lineage.yaml +1 -1
  160. package/build/lib/registry/bundles/delta-classifier.yaml +1 -1
  161. package/build/lib/registry/bundles/eval-gate.yaml +1 -1
  162. package/build/lib/registry/bundles/health.yaml +1 -1
  163. package/build/lib/registry/bundles/hook-governor.yaml +1 -1
  164. package/build/lib/registry/bundles/incident-replay.yaml +1 -1
  165. package/build/lib/registry/bundles/lsp-pack.yaml +1 -1
  166. package/build/lib/registry/bundles/mcp-fabric.yaml +1 -1
  167. package/build/lib/registry/bundles/plan-council.yaml +1 -1
  168. package/build/lib/registry/bundles/preflight.yaml +1 -1
  169. package/build/lib/registry/bundles/proof-gate.yaml +1 -1
  170. package/build/lib/registry/bundles/remote-supervisor.yaml +1 -1
  171. package/build/lib/registry/bundles/robotics.yaml +1 -1
  172. package/build/lib/registry/bundles/secure-worktree-pipeline.yaml +1 -1
  173. package/build/lib/registry/bundles/security-check.yaml +1 -1
  174. package/build/lib/registry/bundles/test-intent-lock.yaml +1 -1
  175. package/build/lib/registry/bundles/tracebank.yaml +1 -1
  176. package/build/lib/registry/bundles/vision.yaml +1 -1
  177. package/build/lib/registry/omg-capability.schema.json +1 -1
  178. package/build/lib/runtime/adoption.py +1 -1
  179. package/build/lib/runtime/background_verification.py +62 -0
  180. package/build/lib/runtime/claim_judge.py +165 -5
  181. package/build/lib/runtime/compat.py +15 -1
  182. package/build/lib/runtime/context_engine.py +261 -4
  183. package/build/lib/runtime/contract_compiler.py +370 -93
  184. package/build/lib/runtime/defense_state.py +79 -8
  185. package/build/lib/runtime/delta_classifier.py +80 -2
  186. package/build/lib/runtime/evidence_query.py +56 -4
  187. package/build/lib/runtime/evidence_registry.py +16 -0
  188. package/build/lib/runtime/evidence_requirements.py +47 -0
  189. package/build/lib/runtime/forge_agents.py +179 -9
  190. package/build/lib/runtime/forge_contracts.py +208 -2
  191. package/build/lib/runtime/interaction_journal.py +318 -42
  192. package/build/lib/runtime/memory_store.py +134 -1
  193. package/build/lib/runtime/mutation_gate.py +57 -11
  194. package/build/lib/runtime/omg_compat_contract_snapshot.json +2 -2
  195. package/build/lib/runtime/omg_mcp_server.py +14 -4
  196. package/build/lib/runtime/preflight.py +14 -0
  197. package/build/lib/runtime/profile_io.py +294 -0
  198. package/build/lib/runtime/proof_gate.py +122 -7
  199. package/build/lib/runtime/release_run_coordinator.py +339 -0
  200. package/build/lib/runtime/release_surfaces.py +243 -0
  201. package/build/lib/runtime/rollback_manifest.py +136 -0
  202. package/build/lib/runtime/router_critics.py +50 -0
  203. package/build/lib/runtime/runtime_contracts.py +137 -0
  204. package/build/lib/runtime/security_check.py +16 -0
  205. package/build/lib/runtime/session_health.py +60 -16
  206. package/build/lib/runtime/team_router.py +156 -13
  207. package/build/lib/runtime/test_intent_lock.py +102 -0
  208. package/build/lib/runtime/tool_plan_gate.py +249 -24
  209. package/build/lib/runtime/validate.py +194 -0
  210. package/build/lib/runtime/verification_controller.py +52 -2
  211. package/commands/OMG:diagnose-plugins.md +33 -0
  212. package/commands/OMG:profile-review.md +43 -0
  213. package/commands/OMG:setup.md +4 -1
  214. package/commands/OMG:validate.md +59 -0
  215. package/control_plane/service.py +40 -1
  216. package/dist/enterprise/bundle/.claude-plugin/marketplace.json +3 -3
  217. package/dist/enterprise/bundle/.claude-plugin/plugin.json +1 -1
  218. package/dist/enterprise/bundle/OMG_COMPAT_CONTRACT.md +1 -1
  219. package/dist/enterprise/bundle/plugins/advanced/plugin.json +26 -9
  220. package/dist/enterprise/bundle/registry/bundles/algorithms.yaml +1 -1
  221. package/dist/enterprise/bundle/registry/bundles/api-twin.yaml +1 -1
  222. package/dist/enterprise/bundle/registry/bundles/claim-judge.yaml +1 -1
  223. package/dist/enterprise/bundle/registry/bundles/control-plane.yaml +1 -1
  224. package/dist/enterprise/bundle/registry/bundles/data-lineage.yaml +1 -1
  225. package/dist/enterprise/bundle/registry/bundles/delta-classifier.yaml +1 -1
  226. package/dist/enterprise/bundle/registry/bundles/eval-gate.yaml +1 -1
  227. package/dist/enterprise/bundle/registry/bundles/health.yaml +1 -1
  228. package/dist/enterprise/bundle/registry/bundles/hook-governor.yaml +1 -1
  229. package/dist/enterprise/bundle/registry/bundles/incident-replay.yaml +1 -1
  230. package/dist/enterprise/bundle/registry/bundles/lsp-pack.yaml +1 -1
  231. package/dist/enterprise/bundle/registry/bundles/mcp-fabric.yaml +1 -1
  232. package/dist/enterprise/bundle/registry/bundles/plan-council.yaml +1 -1
  233. package/dist/enterprise/bundle/registry/bundles/preflight.yaml +1 -1
  234. package/dist/enterprise/bundle/registry/bundles/proof-gate.yaml +1 -1
  235. package/dist/enterprise/bundle/registry/bundles/remote-supervisor.yaml +1 -1
  236. package/dist/enterprise/bundle/registry/bundles/robotics.yaml +1 -1
  237. package/dist/enterprise/bundle/registry/bundles/secure-worktree-pipeline.yaml +1 -1
  238. package/dist/enterprise/bundle/registry/bundles/security-check.yaml +1 -1
  239. package/dist/enterprise/bundle/registry/bundles/test-intent-lock.yaml +1 -1
  240. package/dist/enterprise/bundle/registry/bundles/tracebank.yaml +1 -1
  241. package/dist/enterprise/bundle/registry/bundles/vision.yaml +1 -1
  242. package/dist/enterprise/bundle/registry/omg-capability.schema.json +1 -1
  243. package/dist/enterprise/bundle/settings.json +3 -3
  244. package/dist/enterprise/manifest.json +30 -40
  245. package/dist/oh_my_god-2.1.4-py3-none-any.whl +0 -0
  246. package/dist/oh_my_god-2.1.4.tar.gz +0 -0
  247. package/dist/public/bundle/.claude-plugin/marketplace.json +3 -3
  248. package/dist/public/bundle/.claude-plugin/plugin.json +1 -1
  249. package/dist/public/bundle/OMG_COMPAT_CONTRACT.md +1 -1
  250. package/dist/public/bundle/plugins/advanced/plugin.json +26 -9
  251. package/dist/public/bundle/registry/bundles/algorithms.yaml +1 -1
  252. package/dist/public/bundle/registry/bundles/api-twin.yaml +1 -1
  253. package/dist/public/bundle/registry/bundles/claim-judge.yaml +1 -1
  254. package/dist/public/bundle/registry/bundles/control-plane.yaml +1 -1
  255. package/dist/public/bundle/registry/bundles/data-lineage.yaml +1 -1
  256. package/dist/public/bundle/registry/bundles/delta-classifier.yaml +1 -1
  257. package/dist/public/bundle/registry/bundles/eval-gate.yaml +1 -1
  258. package/dist/public/bundle/registry/bundles/health.yaml +1 -1
  259. package/dist/public/bundle/registry/bundles/hook-governor.yaml +1 -1
  260. package/dist/public/bundle/registry/bundles/incident-replay.yaml +1 -1
  261. package/dist/public/bundle/registry/bundles/lsp-pack.yaml +1 -1
  262. package/dist/public/bundle/registry/bundles/mcp-fabric.yaml +1 -1
  263. package/dist/public/bundle/registry/bundles/plan-council.yaml +1 -1
  264. package/dist/public/bundle/registry/bundles/preflight.yaml +1 -1
  265. package/dist/public/bundle/registry/bundles/proof-gate.yaml +1 -1
  266. package/dist/public/bundle/registry/bundles/remote-supervisor.yaml +1 -1
  267. package/dist/public/bundle/registry/bundles/robotics.yaml +1 -1
  268. package/dist/public/bundle/registry/bundles/secure-worktree-pipeline.yaml +1 -1
  269. package/dist/public/bundle/registry/bundles/security-check.yaml +1 -1
  270. package/dist/public/bundle/registry/bundles/test-intent-lock.yaml +1 -1
  271. package/dist/public/bundle/registry/bundles/tracebank.yaml +1 -1
  272. package/dist/public/bundle/registry/bundles/vision.yaml +1 -1
  273. package/dist/public/bundle/registry/omg-capability.schema.json +1 -1
  274. package/dist/public/bundle/settings.json +3 -3
  275. package/dist/public/manifest.json +30 -40
  276. package/docs/install/opencode.md +30 -0
  277. package/docs/proof.md +11 -0
  278. package/docs/release-checklist.md +14 -1
  279. package/hooks/_agent_registry.py +2 -0
  280. package/hooks/firewall.py +263 -4
  281. package/hooks/intentgate-keyword-detector.py +15 -3
  282. package/hooks/policy_engine.py +77 -0
  283. package/hooks/prompt-enhancer.py +146 -0
  284. package/hooks/session-end-capture.py +374 -6
  285. package/hooks/session-start.py +13 -35
  286. package/hooks/setup_wizard.py +342 -2
  287. package/hooks/shadow_manager.py +111 -2
  288. package/hooks/stop_dispatcher.py +108 -4
  289. package/hud/omg-hud.mjs +30 -6
  290. package/lab/pipeline.py +197 -26
  291. package/package.json +1 -1
  292. package/plugins/advanced/plugin.json +26 -9
  293. package/plugins/core/plugin.json +51 -12
  294. package/pyproject.toml +10 -2
  295. package/registry/bundles/algorithms.yaml +1 -1
  296. package/registry/bundles/api-twin.yaml +1 -1
  297. package/registry/bundles/claim-judge.yaml +1 -1
  298. package/registry/bundles/control-plane.yaml +1 -1
  299. package/registry/bundles/data-lineage.yaml +1 -1
  300. package/registry/bundles/delta-classifier.yaml +1 -1
  301. package/registry/bundles/eval-gate.yaml +1 -1
  302. package/registry/bundles/health.yaml +1 -1
  303. package/registry/bundles/hook-governor.yaml +1 -1
  304. package/registry/bundles/incident-replay.yaml +1 -1
  305. package/registry/bundles/lsp-pack.yaml +1 -1
  306. package/registry/bundles/mcp-fabric.yaml +1 -1
  307. package/registry/bundles/plan-council.yaml +1 -1
  308. package/registry/bundles/preflight.yaml +1 -1
  309. package/registry/bundles/proof-gate.yaml +1 -1
  310. package/registry/bundles/remote-supervisor.yaml +1 -1
  311. package/registry/bundles/robotics.yaml +1 -1
  312. package/registry/bundles/secure-worktree-pipeline.yaml +1 -1
  313. package/registry/bundles/security-check.yaml +1 -1
  314. package/registry/bundles/test-intent-lock.yaml +1 -1
  315. package/registry/bundles/tracebank.yaml +1 -1
  316. package/registry/bundles/vision.yaml +1 -1
  317. package/registry/omg-capability.schema.json +1 -1
  318. package/runtime/adoption.py +7 -5
  319. package/runtime/background_verification.py +62 -0
  320. package/runtime/claim_judge.py +165 -5
  321. package/runtime/compat.py +48 -1
  322. package/runtime/context_engine.py +261 -4
  323. package/runtime/contract_compiler.py +370 -93
  324. package/runtime/defense_state.py +79 -8
  325. package/runtime/delta_classifier.py +80 -2
  326. package/runtime/evidence_query.py +56 -4
  327. package/runtime/evidence_registry.py +16 -0
  328. package/runtime/evidence_requirements.py +47 -0
  329. package/runtime/forge_agents.py +179 -9
  330. package/runtime/forge_contracts.py +208 -2
  331. package/runtime/hook_governor.py +19 -1
  332. package/runtime/interaction_journal.py +318 -42
  333. package/runtime/memory_store.py +134 -1
  334. package/runtime/mutation_gate.py +57 -11
  335. package/runtime/omg_compat_contract_snapshot.json +2 -2
  336. package/runtime/omg_mcp_server.py +14 -4
  337. package/runtime/plugin_diagnostics.py +248 -0
  338. package/runtime/plugin_interop.py +1007 -0
  339. package/runtime/preflight.py +14 -0
  340. package/runtime/profile_io.py +294 -0
  341. package/runtime/proof_gate.py +122 -7
  342. package/runtime/providers/opencode_provider.py +94 -0
  343. package/runtime/release_run_coordinator.py +339 -0
  344. package/runtime/release_surfaces.py +243 -0
  345. package/runtime/rollback_manifest.py +136 -0
  346. package/runtime/router_critics.py +50 -0
  347. package/runtime/runtime_contracts.py +137 -0
  348. package/runtime/security_check.py +16 -0
  349. package/runtime/session_health.py +60 -16
  350. package/runtime/team_router.py +156 -13
  351. package/runtime/test_intent_lock.py +102 -0
  352. package/runtime/tool_plan_gate.py +218 -19
  353. package/runtime/validate.py +289 -0
  354. package/runtime/verification_controller.py +52 -2
  355. package/scripts/omg.py +306 -3
  356. package/scripts/prepare-release-proof-fixtures.py +191 -7
  357. package/scripts/print-canonical-version.py +80 -0
  358. package/scripts/sync-release-identity.py +476 -0
  359. package/scripts/validate-release-identity.py +246 -0
  360. package/scripts/verify-standalone.sh +3 -0
  361. package/settings.json +3 -3
  362. package/templates/plugins-allowlist.yaml +19 -0
  363. package/artifacts/release/dist/enterprise/bundle/.gemini/settings.json +0 -11
  364. package/artifacts/release/dist/enterprise/bundle/.kimi/mcp.json +0 -11
  365. package/artifacts/release/dist/public/bundle/.gemini/settings.json +0 -11
  366. package/artifacts/release/dist/public/bundle/.kimi/mcp.json +0 -11
  367. package/build/lib/yaml.py +0 -321
  368. package/dist/enterprise/bundle/.gemini/settings.json +0 -11
  369. package/dist/enterprise/bundle/.kimi/mcp.json +0 -11
  370. package/dist/public/bundle/.gemini/settings.json +0 -11
  371. package/dist/public/bundle/.kimi/mcp.json +0 -11
@@ -0,0 +1,1007 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ import re
6
+ import subprocess
7
+ import time
8
+ from collections.abc import Mapping
9
+ from dataclasses import dataclass, field
10
+ from enum import Enum
11
+ from pathlib import Path
12
+ from typing import cast
13
+ import yaml
14
+
15
+ from runtime.hook_governor import validate_order
16
+
17
+
18
+ class Layer(str, Enum):
19
+ AUTHORED = "authored"
20
+ COMPILED = "compiled"
21
+ LIVE = "live"
22
+ DISCOVERED = "discovered"
23
+
24
+
25
+ class Source(str, Enum):
26
+ PLUGIN_MANIFEST = "plugin_manifest"
27
+ MCP_JSON = "mcp_json"
28
+ COMPILED_BUNDLE = "compiled_bundle"
29
+ SKILL_REGISTRY = "skill_registry"
30
+ HOST_CONFIG = "host_config"
31
+ PLUGIN_DIR = "plugin_dir"
32
+
33
+
34
+ class ConflictCode(str, Enum):
35
+ MCP_NAME_COLLISION = "mcp_name_collision"
36
+ COMMAND_COLLISION = "command_collision"
37
+ HOOK_ORDER_VIOLATION = "hook_order_violation"
38
+ PRESET_ESCALATION = "preset_escalation"
39
+ IDENTITY_DRIFT = "identity_drift"
40
+ UNSUPPORTED_HOST = "unsupported_host"
41
+ STALE_COMPILED_ARTIFACT = "stale_compiled_artifact"
42
+ PARTIAL_INSTALL = "partial_install"
43
+ HOST_PRECEDENCE_OVERLAP = "host_precedence_overlap"
44
+
45
+
46
+ class ConflictSeverity(str, Enum):
47
+ BLOCKER = "blocker"
48
+ WARNING = "warning"
49
+ INFO = "info"
50
+
51
+
52
+ CONFLICT_SEVERITY_MAP: dict[ConflictCode, ConflictSeverity] = {
53
+ ConflictCode.MCP_NAME_COLLISION: ConflictSeverity.BLOCKER,
54
+ ConflictCode.HOOK_ORDER_VIOLATION: ConflictSeverity.BLOCKER,
55
+ ConflictCode.COMMAND_COLLISION: ConflictSeverity.BLOCKER,
56
+ ConflictCode.PRESET_ESCALATION: ConflictSeverity.WARNING,
57
+ ConflictCode.IDENTITY_DRIFT: ConflictSeverity.WARNING,
58
+ ConflictCode.UNSUPPORTED_HOST: ConflictSeverity.WARNING,
59
+ ConflictCode.STALE_COMPILED_ARTIFACT: ConflictSeverity.INFO,
60
+ ConflictCode.PARTIAL_INSTALL: ConflictSeverity.INFO,
61
+ ConflictCode.HOST_PRECEDENCE_OVERLAP: ConflictSeverity.INFO,
62
+ }
63
+
64
+ SUPPORTED_HOSTS: set[str] = {"claude", "codex", "gemini", "kimi", "opencode"}
65
+ SECURITY_PRETOOL_PLUGINS: tuple[str, ...] = ("firewall", "secret-guard")
66
+ PRESET_ORDER: tuple[str, ...] = ("safe", "balanced", "interop", "labs")
67
+ _PRESET_ORDER_INDEX: dict[str, int] = {name: idx for idx, name in enumerate(PRESET_ORDER)}
68
+ _SEVERITY_RANK: dict[str, int] = {
69
+ ConflictSeverity.BLOCKER.value: 0,
70
+ ConflictSeverity.WARNING.value: 1,
71
+ ConflictSeverity.INFO.value: 2,
72
+ }
73
+
74
+
75
+ @dataclass(slots=True)
76
+ class PluginInteropRecord:
77
+ plugin_id: str
78
+ layer: str
79
+ host: str
80
+ source: str
81
+ commands: list[str] = field(default_factory=list)
82
+ hook_events: list[str] = field(default_factory=list)
83
+ mcp_servers: list[str] = field(default_factory=list)
84
+ preset_floor: str | None = None
85
+ enabled: bool = True
86
+ source_path: str | None = None
87
+ metadata: dict[str, object] = field(default_factory=dict)
88
+
89
+ def __post_init__(self) -> None:
90
+ valid_layers = {member.value for member in Layer}
91
+ valid_sources = {member.value for member in Source}
92
+ if self.layer not in valid_layers:
93
+ raise ValueError(f"Invalid layer: {self.layer}. Expected one of {sorted(valid_layers)}")
94
+ if self.source not in valid_sources:
95
+ raise ValueError(f"Invalid source: {self.source}. Expected one of {sorted(valid_sources)}")
96
+
97
+ def to_dict(self) -> dict[str, object]:
98
+ return {
99
+ "plugin_id": self.plugin_id,
100
+ "layer": self.layer,
101
+ "host": self.host,
102
+ "source": self.source,
103
+ "commands": list(self.commands),
104
+ "hook_events": list(self.hook_events),
105
+ "mcp_servers": list(self.mcp_servers),
106
+ "preset_floor": self.preset_floor,
107
+ "enabled": self.enabled,
108
+ "source_path": self.source_path,
109
+ "metadata": dict(self.metadata),
110
+ }
111
+
112
+ @classmethod
113
+ def from_dict(cls, payload: Mapping[str, object]) -> PluginInteropRecord:
114
+ commands = _to_str_list(payload.get("commands"), "commands")
115
+ hook_events = _to_str_list(payload.get("hook_events"), "hook_events")
116
+ mcp_servers = _to_str_list(payload.get("mcp_servers"), "mcp_servers")
117
+ metadata = _to_str_object_dict(payload.get("metadata"), "metadata")
118
+ return cls(
119
+ plugin_id=_to_str_required(payload, "plugin_id"),
120
+ layer=_to_str_required(payload, "layer"),
121
+ host=_to_str_required(payload, "host"),
122
+ source=_to_str_required(payload, "source"),
123
+ commands=commands,
124
+ hook_events=hook_events,
125
+ mcp_servers=mcp_servers,
126
+ preset_floor=_to_optional_str(payload.get("preset_floor"), "preset_floor"),
127
+ enabled=_to_bool(payload.get("enabled", True), "enabled"),
128
+ source_path=_to_optional_str(payload.get("source_path"), "source_path"),
129
+ metadata=metadata,
130
+ )
131
+
132
+
133
+ @dataclass(slots=True)
134
+ class ConflictResult:
135
+ code: str
136
+ severity: str
137
+ affected_plugin_ids: list[str]
138
+ affected_hosts: list[str]
139
+ detail: str
140
+ next_action: str
141
+
142
+
143
+ @dataclass(slots=True)
144
+ class PluginAllowlistEntry:
145
+ source: str
146
+ host: str
147
+ resource_type: str
148
+ reason: str
149
+ scope: str = "project"
150
+ timestamp: str | None = None
151
+ approver: str | None = None
152
+
153
+ def to_dict(self) -> dict[str, object]:
154
+ return {
155
+ "source": self.source,
156
+ "host": self.host,
157
+ "resource_type": self.resource_type,
158
+ "reason": self.reason,
159
+ "scope": self.scope,
160
+ "timestamp": self.timestamp,
161
+ "approver": self.approver,
162
+ }
163
+
164
+
165
+ def validate_plugin_allowlist_entry(payload: Mapping[str, object]) -> None:
166
+ source = payload.get("source")
167
+ host = payload.get("host")
168
+ resource_type = payload.get("resource_type")
169
+ reason = payload.get("reason")
170
+ scope = payload.get("scope", "project")
171
+ timestamp = payload.get("timestamp")
172
+ approver = payload.get("approver")
173
+
174
+ if not isinstance(source, str) or not source:
175
+ raise ValueError("source must be a non-empty string")
176
+ if not isinstance(host, str) or host not in SUPPORTED_HOSTS:
177
+ raise ValueError(f"host must be one of {sorted(SUPPORTED_HOSTS)}")
178
+ if not isinstance(resource_type, str) or resource_type not in {"mcp_server", "skill", "plugin"}:
179
+ raise ValueError("resource_type must be one of: mcp_server, skill, plugin")
180
+ if not isinstance(reason, str) or not reason.strip():
181
+ raise ValueError("reason must be a non-empty string")
182
+ if not isinstance(scope, str) or scope not in {"project", "user"}:
183
+ raise ValueError("scope must be 'project' or 'user'")
184
+ if timestamp is not None and not isinstance(timestamp, str):
185
+ raise ValueError("timestamp must be a string or None")
186
+ if approver is not None and not isinstance(approver, str):
187
+ raise ValueError("approver must be a string or None")
188
+
189
+ source_prefix, _, _ = source.partition(":")
190
+ expected_resource_by_prefix = {
191
+ "mcp": "mcp_server",
192
+ "skill": "skill",
193
+ "plugin": "plugin",
194
+ }
195
+ expected_resource = expected_resource_by_prefix.get(source_prefix)
196
+ if expected_resource is None:
197
+ raise ValueError("source must start with one of: mcp:, skill:, plugin:")
198
+ if resource_type != expected_resource:
199
+ raise ValueError(f"resource_type '{resource_type}' does not match source prefix '{source_prefix}:'")
200
+
201
+
202
+ def load_plugin_allowlist(root: str | None = None) -> list[PluginAllowlistEntry]:
203
+ root_path = Path(root or ".").resolve()
204
+ allowlist_path = root_path / ".omg" / "state" / "plugins-allowlist.yaml"
205
+ try:
206
+ raw = allowlist_path.read_text(encoding="utf-8")
207
+ except OSError:
208
+ return []
209
+
210
+ parsed_obj = cast(object, yaml.safe_load(raw))
211
+ if not isinstance(parsed_obj, list):
212
+ return []
213
+
214
+ entries: list[PluginAllowlistEntry] = []
215
+ for item in cast(list[object], parsed_obj):
216
+ if not isinstance(item, dict):
217
+ continue
218
+ payload = cast(dict[str, object], item)
219
+ try:
220
+ validate_plugin_allowlist_entry(payload)
221
+ except ValueError:
222
+ continue
223
+ entries.append(
224
+ PluginAllowlistEntry(
225
+ source=cast(str, payload["source"]),
226
+ host=cast(str, payload["host"]),
227
+ resource_type=cast(str, payload["resource_type"]),
228
+ reason=cast(str, payload["reason"]),
229
+ scope=cast(str, payload.get("scope", "project")),
230
+ timestamp=cast(str | None, payload.get("timestamp")),
231
+ approver=cast(str | None, payload.get("approver")),
232
+ )
233
+ )
234
+ return entries
235
+
236
+
237
+ def save_plugin_allowlist(entries: list[PluginAllowlistEntry], root: str | None = None) -> str:
238
+ root_path = Path(root or ".").resolve()
239
+ state_dir = root_path / ".omg" / "state"
240
+ state_dir.mkdir(parents=True, exist_ok=True)
241
+ allowlist_path = state_dir / "plugins-allowlist.yaml"
242
+
243
+ serializable: list[dict[str, object]] = []
244
+ for entry in entries:
245
+ payload = entry.to_dict()
246
+ validate_plugin_allowlist_entry(payload)
247
+ serializable.append(payload)
248
+
249
+ dumped = yaml.safe_dump(serializable, sort_keys=False)
250
+ if not isinstance(dumped, str):
251
+ raise ValueError("plugins allowlist serialization failed")
252
+ _ = allowlist_path.write_text(cast(str, dumped), encoding="utf-8")
253
+ return str(allowlist_path)
254
+
255
+
256
+ @dataclass(slots=True)
257
+ class HookChainPlan:
258
+ event: str
259
+ chain: list[str]
260
+ status: str
261
+ blockers: list[str]
262
+
263
+
264
+ def plan_hook_chain(event: str, foreign_hook_names: list[str]) -> HookChainPlan:
265
+ proposed_chain: list[str]
266
+ blockers: list[str] = []
267
+
268
+ if event == "PreToolUse":
269
+ for hook_name in foreign_hook_names:
270
+ if hook_name in SECURITY_PRETOOL_PLUGINS:
271
+ blockers.append(
272
+ f"foreign hook '{hook_name}' must not appear before immovable security hooks"
273
+ )
274
+
275
+ non_security_hooks = [name for name in foreign_hook_names if name not in SECURITY_PRETOOL_PLUGINS]
276
+ cleanup_hooks = [name for name in non_security_hooks if "cleanup" in name.lower()]
277
+ standard_hooks = [name for name in non_security_hooks if "cleanup" not in name.lower()]
278
+ proposed_chain = [*SECURITY_PRETOOL_PLUGINS, *standard_hooks, *cleanup_hooks]
279
+ else:
280
+ proposed_chain = list(foreign_hook_names)
281
+
282
+ validation = validate_order(event, proposed_chain)
283
+ validation_blockers = list(validation.get("blockers", []))
284
+ no_canonical_order = validation_blockers == [f"no canonical hook order for event {event}"]
285
+ if not (event != "PreToolUse" and no_canonical_order):
286
+ blockers.extend(validation_blockers)
287
+
288
+ status = "blocked" if blockers else "ok"
289
+ return HookChainPlan(event=event, chain=proposed_chain, status=status, blockers=blockers)
290
+
291
+
292
+ def classify_conflicts(records: list[PluginInteropRecord]) -> list[ConflictResult]:
293
+ if not records:
294
+ return []
295
+
296
+ conflicts: list[ConflictResult] = []
297
+ seen_signatures: set[tuple[str, tuple[str, ...], tuple[str, ...], str]] = set()
298
+
299
+ def add_conflict(
300
+ code: ConflictCode,
301
+ plugin_ids: list[str],
302
+ hosts: list[str],
303
+ detail: str,
304
+ next_action: str,
305
+ ) -> None:
306
+ normalized_plugins = sorted(set(plugin_ids))
307
+ normalized_hosts = sorted(set(hosts))
308
+ signature = (code.value, tuple(normalized_plugins), tuple(normalized_hosts), detail)
309
+ if signature in seen_signatures:
310
+ return
311
+ seen_signatures.add(signature)
312
+ conflicts.append(
313
+ ConflictResult(
314
+ code=code.value,
315
+ severity=CONFLICT_SEVERITY_MAP[code].value,
316
+ affected_plugin_ids=normalized_plugins,
317
+ affected_hosts=normalized_hosts,
318
+ detail=detail,
319
+ next_action=next_action,
320
+ )
321
+ )
322
+
323
+ records_by_host_mcp: dict[tuple[str, str], list[PluginInteropRecord]] = {}
324
+ records_by_host_command: dict[tuple[str, str], list[PluginInteropRecord]] = {}
325
+ records_by_mcp_name: dict[str, list[PluginInteropRecord]] = {}
326
+ records_by_plugin_id: dict[str, list[PluginInteropRecord]] = {}
327
+
328
+ security_indices: dict[str, int] = {}
329
+ for idx, record in enumerate(records):
330
+ records_by_plugin_id.setdefault(record.plugin_id, []).append(record)
331
+ if "PreToolUse" in record.hook_events and record.plugin_id in SECURITY_PRETOOL_PLUGINS:
332
+ if record.plugin_id not in security_indices:
333
+ security_indices[record.plugin_id] = idx
334
+
335
+ for mcp_server in record.mcp_servers:
336
+ records_by_host_mcp.setdefault((record.host, mcp_server), []).append(record)
337
+ records_by_mcp_name.setdefault(mcp_server, []).append(record)
338
+
339
+ for command in record.commands:
340
+ records_by_host_command.setdefault((record.host, command), []).append(record)
341
+
342
+ for (host, mcp_server), owner_records in sorted(records_by_host_mcp.items()):
343
+ if len(owner_records) < 2:
344
+ continue
345
+ add_conflict(
346
+ ConflictCode.MCP_NAME_COLLISION,
347
+ [record.plugin_id for record in owner_records],
348
+ [host],
349
+ f"Multiple plugins on host '{host}' declare MCP server '{mcp_server}'.",
350
+ "Keep one owner per host/MCP name and rename or remove conflicting declarations.",
351
+ )
352
+
353
+ for (host, command), owner_records in sorted(records_by_host_command.items()):
354
+ if len(owner_records) < 2:
355
+ continue
356
+ add_conflict(
357
+ ConflictCode.COMMAND_COLLISION,
358
+ [record.plugin_id for record in owner_records],
359
+ [host],
360
+ f"Multiple plugins on host '{host}' declare command '{command}'.",
361
+ "Assign command ownership to one plugin per host or rename conflicting commands.",
362
+ )
363
+
364
+ firewall_index = security_indices.get("firewall")
365
+ secret_guard_index = security_indices.get("secret-guard")
366
+ for idx, record in enumerate(records):
367
+ if "PreToolUse" not in record.hook_events or record.plugin_id in SECURITY_PRETOOL_PLUGINS:
368
+ continue
369
+ before_firewall = firewall_index is not None and idx < firewall_index
370
+ before_secret_guard = secret_guard_index is not None and idx < secret_guard_index
371
+ if before_firewall or before_secret_guard:
372
+ add_conflict(
373
+ ConflictCode.HOOK_ORDER_VIOLATION,
374
+ [record.plugin_id] + list(SECURITY_PRETOOL_PLUGINS),
375
+ [record.host],
376
+ f"Plugin '{record.plugin_id}' registers PreToolUse before required security hooks.",
377
+ "Move firewall and secret-guard ahead of all non-security PreToolUse hooks.",
378
+ )
379
+
380
+ for record in records:
381
+ preset_floor = record.preset_floor
382
+ if preset_floor is None:
383
+ continue
384
+ preset_index = _PRESET_ORDER_INDEX.get(preset_floor)
385
+ safe_index = _PRESET_ORDER_INDEX["safe"]
386
+ if preset_index is not None and preset_index > safe_index:
387
+ add_conflict(
388
+ ConflictCode.PRESET_ESCALATION,
389
+ [record.plugin_id],
390
+ [record.host],
391
+ f"Plugin '{record.plugin_id}' raises preset floor to '{preset_floor}'.",
392
+ "Keep preset_floor at safe unless the higher floor is explicitly approved.",
393
+ )
394
+
395
+ for plugin_id, plugin_records in sorted(records_by_plugin_id.items()):
396
+ layers = sorted({record.layer for record in plugin_records})
397
+ if len(layers) > 1:
398
+ add_conflict(
399
+ ConflictCode.IDENTITY_DRIFT,
400
+ [plugin_id],
401
+ [record.host for record in plugin_records],
402
+ f"Plugin '{plugin_id}' appears with inconsistent layers: {', '.join(layers)}.",
403
+ "Align authored/compiled/live ownership metadata to a single layer identity.",
404
+ )
405
+
406
+ for record in records:
407
+ if record.host not in SUPPORTED_HOSTS:
408
+ add_conflict(
409
+ ConflictCode.UNSUPPORTED_HOST,
410
+ [record.plugin_id],
411
+ [record.host],
412
+ f"Plugin '{record.plugin_id}' targets unsupported host '{record.host}'.",
413
+ "Use one of the supported hosts: claude, codex, gemini, kimi, opencode.",
414
+ )
415
+
416
+ for record in records:
417
+ if record.layer == Layer.COMPILED.value and not record.enabled:
418
+ add_conflict(
419
+ ConflictCode.STALE_COMPILED_ARTIFACT,
420
+ [record.plugin_id],
421
+ [record.host],
422
+ f"Compiled artifact for plugin '{record.plugin_id}' is disabled.",
423
+ "Rebuild artifacts or remove stale compiled entries that are no longer active.",
424
+ )
425
+
426
+ for record in records:
427
+ if record.layer != Layer.AUTHORED.value:
428
+ continue
429
+ if record.commands or record.mcp_servers or record.hook_events:
430
+ continue
431
+ add_conflict(
432
+ ConflictCode.PARTIAL_INSTALL,
433
+ [record.plugin_id],
434
+ [record.host],
435
+ f"Authored plugin '{record.plugin_id}' has no commands, MCP servers, or hook events.",
436
+ "Finish installation wiring or remove placeholder authored plugin entries.",
437
+ )
438
+
439
+ for mcp_server, owner_records in sorted(records_by_mcp_name.items()):
440
+ hosts = sorted({record.host for record in owner_records})
441
+ if len(hosts) < 2:
442
+ continue
443
+ add_conflict(
444
+ ConflictCode.HOST_PRECEDENCE_OVERLAP,
445
+ [record.plugin_id for record in owner_records],
446
+ hosts,
447
+ f"MCP server '{mcp_server}' is declared across multiple hosts.",
448
+ "Validate intended host precedence and keep cross-host ownership explicit.",
449
+ )
450
+
451
+ return sorted(
452
+ conflicts,
453
+ key=lambda conflict: (
454
+ _SEVERITY_RANK.get(conflict.severity, 99),
455
+ conflict.code,
456
+ conflict.detail,
457
+ tuple(conflict.affected_plugin_ids),
458
+ tuple(conflict.affected_hosts),
459
+ ),
460
+ )
461
+
462
+
463
+ def approval_status_for_record(record: PluginInteropRecord, approvals: list[PluginAllowlistEntry]) -> str:
464
+ if record.host not in SUPPORTED_HOSTS:
465
+ return "blocked"
466
+
467
+ record_conflicts = classify_conflicts([record])
468
+ if any(conflict.severity == ConflictSeverity.BLOCKER.value for conflict in record_conflicts):
469
+ return "blocked"
470
+
471
+ approved_sources = _approval_sources_for_record(record)
472
+ for entry in approvals:
473
+ if entry.host == record.host and entry.source in approved_sources:
474
+ return "approved"
475
+
476
+ return "discoverable"
477
+
478
+
479
+ def get_approval_status_for_all(
480
+ records: list[PluginInteropRecord], approvals: list[PluginAllowlistEntry]
481
+ ) -> dict[str, str]:
482
+ return {record.plugin_id: approval_status_for_record(record, approvals) for record in records}
483
+
484
+
485
+ def _approval_sources_for_record(record: PluginInteropRecord) -> set[str]:
486
+ source_ids: set[str] = set()
487
+ for server_name in record.mcp_servers:
488
+ source_ids.add(f"mcp:{server_name}")
489
+
490
+ if record.source == Source.SKILL_REGISTRY.value:
491
+ source_ids.add(f"skill:{record.plugin_id}")
492
+ else:
493
+ source_ids.add(f"plugin:{record.plugin_id}")
494
+
495
+ return source_ids
496
+
497
+
498
+ @dataclass(slots=True)
499
+ class PluginInteropPayload:
500
+ records: list[PluginInteropRecord]
501
+ elapsed_ms: float
502
+ root: str
503
+
504
+
505
+ INTEROP_RECORD_SCHEMA: dict[str, object] = {
506
+ "type": "object",
507
+ "additionalProperties": False,
508
+ "required": ["plugin_id", "layer", "host", "source"],
509
+ "properties": {
510
+ "plugin_id": {"type": "string"},
511
+ "layer": {"type": "string", "enum": [member.value for member in Layer]},
512
+ "host": {"type": "string"},
513
+ "source": {"type": "string", "enum": [member.value for member in Source]},
514
+ "commands": {"type": "array", "items": {"type": "string"}, "default": []},
515
+ "hook_events": {"type": "array", "items": {"type": "string"}, "default": []},
516
+ "mcp_servers": {"type": "array", "items": {"type": "string"}, "default": []},
517
+ "preset_floor": {"type": ["string", "null"], "default": None},
518
+ "enabled": {"type": "boolean", "default": True},
519
+ "source_path": {"type": ["string", "null"], "default": None},
520
+ "metadata": {"type": "object", "default": {}},
521
+ },
522
+ }
523
+
524
+
525
+ def _to_str_required(payload: Mapping[str, object], key: str) -> str:
526
+ value = payload.get(key)
527
+ if not isinstance(value, str):
528
+ raise ValueError(f"{key} must be a string")
529
+ return value
530
+
531
+
532
+ def _to_optional_str(value: object, key: str) -> str | None:
533
+ if value is None:
534
+ return None
535
+ if isinstance(value, str):
536
+ return value
537
+ raise ValueError(f"{key} must be a string or None")
538
+
539
+
540
+ def _to_bool(value: object, key: str) -> bool:
541
+ if isinstance(value, bool):
542
+ return value
543
+ raise ValueError(f"{key} must be a boolean")
544
+
545
+
546
+ def _to_str_list(value: object, key: str) -> list[str]:
547
+ if value is None:
548
+ return []
549
+ if not isinstance(value, list):
550
+ raise ValueError(f"{key} must be a list of strings")
551
+ result: list[str] = []
552
+ for item in cast(list[object], value):
553
+ if not isinstance(item, str):
554
+ raise ValueError(f"{key} must be a list of strings")
555
+ result.append(item)
556
+ return result
557
+
558
+
559
+ def _to_str_object_dict(value: object, key: str) -> dict[str, object]:
560
+ if value is None:
561
+ return {}
562
+ if not isinstance(value, dict):
563
+ raise ValueError(f"{key} must be an object")
564
+ result: dict[str, object] = {}
565
+ for candidate_key, candidate_value in cast(dict[object, object], value).items():
566
+ if not isinstance(candidate_key, str):
567
+ raise ValueError(f"{key} keys must be strings")
568
+ result[candidate_key] = candidate_value
569
+ return result
570
+
571
+
572
+ def discover_omg_plugin_state(root: str | None = None) -> PluginInteropPayload:
573
+ started = time.monotonic()
574
+ root_path = Path(root or ".").resolve()
575
+ records: list[PluginInteropRecord] = []
576
+
577
+ for manifest_path in root_path.glob("plugins/*/plugin.json"):
578
+ _append_plugin_manifest_record(records, manifest_path, layer=Layer.AUTHORED)
579
+
580
+ _append_plugin_manifest_record(records, root_path / ".claude-plugin" / "plugin.json", layer=Layer.AUTHORED)
581
+
582
+ for mcp_path in (root_path / ".claude-plugin" / "mcp.json", root_path / ".mcp.json"):
583
+ records.extend(_records_from_mcp_config(mcp_path))
584
+
585
+ records.extend(_records_from_skill_registry(root_path / ".omg" / "state" / "skill_registry" / "compact.json"))
586
+
587
+ build_lib_dir = root_path / "build" / "lib"
588
+ if build_lib_dir.is_dir():
589
+ for compiled_manifest_path in build_lib_dir.rglob("plugin.json"):
590
+ _append_plugin_manifest_record(records, compiled_manifest_path, layer=Layer.COMPILED)
591
+
592
+ elapsed_ms = (time.monotonic() - started) * 1000.0
593
+ return PluginInteropPayload(records=records, elapsed_ms=elapsed_ms, root=str(root_path))
594
+
595
+
596
+ def discover_host_plugin_state(root: str | None = None) -> PluginInteropPayload:
597
+ started = time.monotonic()
598
+ root_path = Path(root or ".").resolve()
599
+ home_path = Path.home()
600
+ records: list[PluginInteropRecord] = []
601
+
602
+ records.extend(_records_from_codex_config(home_path / ".codex" / "config.toml"))
603
+ records.extend(_records_from_host_json_config(home_path / ".gemini" / "settings.json", host="gemini", mcp_key="mcpServers"))
604
+ records.extend(_records_from_host_json_config(home_path / ".kimi" / "mcp.json", host="kimi", mcp_key="mcpServers"))
605
+ records.extend(
606
+ _records_from_host_json_config(
607
+ home_path / ".config" / "opencode" / "opencode.json",
608
+ host="opencode",
609
+ mcp_key="mcp",
610
+ )
611
+ )
612
+ records.extend(_records_from_host_json_config(root_path / "opencode.json", host="opencode", mcp_key="mcp"))
613
+ records.extend(_records_from_opencode_plugin_dir(root_path / ".opencode" / "plugins"))
614
+
615
+ elapsed_ms = (time.monotonic() - started) * 1000.0
616
+ return PluginInteropPayload(records=records, elapsed_ms=elapsed_ms, root=str(root_path))
617
+
618
+
619
+ def _append_plugin_manifest_record(
620
+ records: list[PluginInteropRecord],
621
+ manifest_path: Path,
622
+ *,
623
+ layer: Layer,
624
+ ) -> None:
625
+ manifest = _read_json_dict(manifest_path)
626
+ if manifest is None:
627
+ return
628
+
629
+ commands = _extract_omg_commands(manifest)
630
+ plugin_name = manifest.get("name")
631
+ plugin_id = plugin_name if isinstance(plugin_name, str) and plugin_name else manifest_path.parent.name
632
+ records.append(
633
+ PluginInteropRecord(
634
+ plugin_id=plugin_id,
635
+ layer=layer.value,
636
+ host="claude",
637
+ source=Source.PLUGIN_MANIFEST.value if layer is Layer.AUTHORED else Source.COMPILED_BUNDLE.value,
638
+ commands=commands,
639
+ source_path=str(manifest_path),
640
+ )
641
+ )
642
+
643
+
644
+ def _records_from_mcp_config(mcp_path: Path) -> list[PluginInteropRecord]:
645
+ mcp_config = _read_json_dict(mcp_path)
646
+ if mcp_config is None:
647
+ return []
648
+
649
+ mcp_servers = mcp_config.get("mcpServers")
650
+ if not isinstance(mcp_servers, dict):
651
+ return []
652
+
653
+ server_names = [name for name in cast(dict[object, object], mcp_servers).keys() if isinstance(name, str)]
654
+ records: list[PluginInteropRecord] = []
655
+ for server_name in server_names:
656
+ records.append(
657
+ PluginInteropRecord(
658
+ plugin_id=server_name,
659
+ layer=Layer.LIVE.value,
660
+ host="claude",
661
+ source=Source.MCP_JSON.value,
662
+ mcp_servers=[server_name],
663
+ source_path=str(mcp_path),
664
+ )
665
+ )
666
+ return records
667
+
668
+
669
+ def _records_from_skill_registry(registry_path: Path) -> list[PluginInteropRecord]:
670
+ payload = _read_json_dict(registry_path)
671
+ if payload is None:
672
+ return []
673
+
674
+ skill_ids = _extract_skill_ids(payload)
675
+ return [
676
+ PluginInteropRecord(
677
+ plugin_id=skill_id,
678
+ layer=Layer.COMPILED.value,
679
+ host="claude",
680
+ source=Source.SKILL_REGISTRY.value,
681
+ source_path=str(registry_path),
682
+ )
683
+ for skill_id in skill_ids
684
+ ]
685
+
686
+
687
+ def _records_from_codex_config(config_path: Path) -> list[PluginInteropRecord]:
688
+ try:
689
+ lines = config_path.read_text(encoding="utf-8").splitlines()
690
+ except OSError:
691
+ return []
692
+
693
+ discovered: dict[str, bool] = {}
694
+ active_section: str | None = None
695
+ section_server: str | None = None
696
+
697
+ for raw_line in lines:
698
+ line = raw_line.strip()
699
+ if not line or line.startswith("#"):
700
+ continue
701
+ if "#" in line:
702
+ line = line.split("#", 1)[0].strip()
703
+ if not line:
704
+ continue
705
+
706
+ if line.startswith("[") and line.endswith("]"):
707
+ active_section = line[1:-1].strip()
708
+ section_server = _codex_server_from_section(active_section)
709
+ if section_server is not None:
710
+ _ = discovered.setdefault(section_server, True)
711
+ continue
712
+
713
+ if active_section == "mcp_servers" and "=" in line:
714
+ key, value = line.split("=", 1)
715
+ candidate = _unquote_toml_key(key.strip())
716
+ if candidate:
717
+ _ = discovered.setdefault(candidate, True)
718
+ inline_enabled = _extract_enabled_from_toml_value(value)
719
+ if inline_enabled is not None:
720
+ discovered[candidate] = inline_enabled
721
+ continue
722
+
723
+ if section_server is not None and "=" in line:
724
+ key, value = line.split("=", 1)
725
+ if key.strip() == "enabled":
726
+ enabled = _parse_toml_bool(value)
727
+ if enabled is not None:
728
+ discovered[section_server] = enabled
729
+
730
+ return [
731
+ PluginInteropRecord(
732
+ plugin_id=server_name,
733
+ layer=Layer.LIVE.value,
734
+ host="codex",
735
+ source=Source.HOST_CONFIG.value,
736
+ mcp_servers=[server_name],
737
+ enabled=enabled,
738
+ source_path=str(config_path),
739
+ )
740
+ for server_name, enabled in discovered.items()
741
+ ]
742
+
743
+
744
+ def _records_from_host_json_config(config_path: Path, *, host: str, mcp_key: str) -> list[PluginInteropRecord]:
745
+ payload = _read_json_dict(config_path)
746
+ if payload is None:
747
+ return []
748
+
749
+ mcp_obj = payload.get(mcp_key)
750
+ if not isinstance(mcp_obj, dict):
751
+ return []
752
+
753
+ records: list[PluginInteropRecord] = []
754
+ for server_name, server_payload in cast(dict[object, object], mcp_obj).items():
755
+ if not isinstance(server_name, str):
756
+ continue
757
+ records.append(
758
+ PluginInteropRecord(
759
+ plugin_id=server_name,
760
+ layer=Layer.LIVE.value,
761
+ host=host,
762
+ source=Source.HOST_CONFIG.value,
763
+ mcp_servers=[server_name],
764
+ enabled=_extract_enabled_from_json_payload(server_payload),
765
+ source_path=str(config_path),
766
+ )
767
+ )
768
+ return records
769
+
770
+
771
+ def _records_from_opencode_plugin_dir(plugin_dir: Path) -> list[PluginInteropRecord]:
772
+ try:
773
+ entries = sorted(plugin_dir.iterdir(), key=lambda item: item.name)
774
+ except OSError:
775
+ return []
776
+
777
+ return [
778
+ PluginInteropRecord(
779
+ plugin_id=entry.name,
780
+ layer=Layer.DISCOVERED.value,
781
+ host="opencode",
782
+ source=Source.PLUGIN_DIR.value,
783
+ source_path=str(entry),
784
+ )
785
+ for entry in entries
786
+ ]
787
+
788
+
789
+ def _codex_server_from_section(section_name: str) -> str | None:
790
+ if not section_name.startswith("mcp_servers."):
791
+ return None
792
+ return _unquote_toml_key(section_name[len("mcp_servers.") :].strip())
793
+
794
+
795
+ def _unquote_toml_key(value: str) -> str:
796
+ if len(value) >= 2 and value[0] == '"' and value[-1] == '"':
797
+ return value[1:-1]
798
+ return value
799
+
800
+
801
+ def _parse_toml_bool(value: str) -> bool | None:
802
+ normalized = value.strip().lower()
803
+ if normalized == "true":
804
+ return True
805
+ if normalized == "false":
806
+ return False
807
+ return None
808
+
809
+
810
+ def _extract_enabled_from_toml_value(value: str) -> bool | None:
811
+ match = re.search(r"\benabled\s*=\s*(true|false)\b", value, flags=re.IGNORECASE)
812
+ if match is None:
813
+ return None
814
+ return match.group(1).lower() == "true"
815
+
816
+
817
+ def _extract_enabled_from_json_payload(payload: object) -> bool:
818
+ if isinstance(payload, bool):
819
+ return payload
820
+ if isinstance(payload, dict):
821
+ enabled = cast(dict[object, object], payload).get("enabled")
822
+ if isinstance(enabled, bool):
823
+ return enabled
824
+ return True
825
+
826
+
827
+ def _extract_omg_commands(payload: Mapping[str, object]) -> list[str]:
828
+ commands = payload.get("commands")
829
+ if not isinstance(commands, dict):
830
+ return []
831
+ return [
832
+ key
833
+ for key in cast(dict[object, object], commands).keys()
834
+ if isinstance(key, str) and key.startswith("/OMG:")
835
+ ]
836
+
837
+
838
+ def _extract_skill_ids(payload: Mapping[str, object]) -> list[str]:
839
+ discovered: list[str] = []
840
+
841
+ skills = payload.get("skills")
842
+ if isinstance(skills, list):
843
+ for entry in cast(list[object], skills):
844
+ if isinstance(entry, str):
845
+ discovered.append(entry)
846
+ elif isinstance(entry, dict):
847
+ candidate = cast(dict[object, object], entry).get("id")
848
+ if isinstance(candidate, str):
849
+ discovered.append(candidate)
850
+ elif isinstance(skills, dict):
851
+ discovered.extend(key for key in cast(dict[object, object], skills).keys() if isinstance(key, str))
852
+
853
+ discovered.extend(key for key in payload if key not in {"skills", "schema", "version"})
854
+
855
+ deduped: list[str] = []
856
+ seen: set[str] = set()
857
+ for skill_id in discovered:
858
+ if skill_id not in seen:
859
+ seen.add(skill_id)
860
+ deduped.append(skill_id)
861
+ return deduped
862
+
863
+
864
+ def _read_json_dict(path: Path) -> dict[str, object] | None:
865
+ try:
866
+ raw_text = path.read_text(encoding="utf-8")
867
+ except OSError:
868
+ return None
869
+
870
+ try:
871
+ payload_obj = cast(object, json.loads(raw_text))
872
+ except json.JSONDecodeError:
873
+ return None
874
+
875
+ if not isinstance(payload_obj, dict):
876
+ return None
877
+ return cast(dict[str, object], payload_obj)
878
+
879
+
880
+ _SAFE_ENV_KEYS: frozenset[str] = frozenset({"PATH", "HOME", "TMPDIR"})
881
+
882
+
883
+ def probe_mcp_server_live(
884
+ server_name: str,
885
+ command: list[str],
886
+ timeout_ms: int = 1500,
887
+ ) -> dict[str, object]:
888
+ """Spawn an MCP server subprocess and send a JSON-RPC initialize request.
889
+
890
+ Returns a dict with ``server``, ``status`` (``ok`` | ``timeout`` | ``error``),
891
+ and ``elapsed_ms``. Only ``PATH``, ``HOME``, and ``TMPDIR`` are forwarded
892
+ from the current environment — no secrets leak to the child process.
893
+ """
894
+ sanitized_env: dict[str, str] = {
895
+ key: os.environ[key] for key in _SAFE_ENV_KEYS if key in os.environ
896
+ }
897
+
898
+ initialize_request = json.dumps(
899
+ {
900
+ "jsonrpc": "2.0",
901
+ "id": 1,
902
+ "method": "initialize",
903
+ "params": {
904
+ "protocolVersion": "2024-11-05",
905
+ "capabilities": {},
906
+ "clientInfo": {"name": "omg-probe", "version": "0.1.0"},
907
+ },
908
+ }
909
+ ) + "\n"
910
+
911
+ started = time.monotonic()
912
+ try:
913
+ process = subprocess.Popen(
914
+ command,
915
+ stdin=subprocess.PIPE,
916
+ stdout=subprocess.PIPE,
917
+ stderr=subprocess.PIPE,
918
+ env=sanitized_env,
919
+ )
920
+ except Exception as exc:
921
+ elapsed = (time.monotonic() - started) * 1000.0
922
+ return {
923
+ "server": server_name,
924
+ "status": "error",
925
+ "error": str(exc),
926
+ "elapsed_ms": elapsed,
927
+ }
928
+
929
+ try:
930
+ stdout_bytes, _ = process.communicate(
931
+ input=initialize_request.encode("utf-8"),
932
+ timeout=timeout_ms / 1000.0,
933
+ )
934
+ except subprocess.TimeoutExpired:
935
+ process.kill()
936
+ try:
937
+ process.communicate(timeout=2)
938
+ except Exception:
939
+ pass
940
+ elapsed = (time.monotonic() - started) * 1000.0
941
+ return {
942
+ "server": server_name,
943
+ "status": "timeout",
944
+ "elapsed_ms": elapsed,
945
+ }
946
+ except Exception as exc:
947
+ elapsed = (time.monotonic() - started) * 1000.0
948
+ try:
949
+ process.kill()
950
+ process.communicate(timeout=2)
951
+ except Exception:
952
+ pass
953
+ return {
954
+ "server": server_name,
955
+ "status": "error",
956
+ "error": str(exc),
957
+ "elapsed_ms": elapsed,
958
+ }
959
+
960
+ elapsed = (time.monotonic() - started) * 1000.0
961
+
962
+ tools: list[str] = []
963
+ try:
964
+ response = json.loads(stdout_bytes.decode("utf-8", errors="replace").strip())
965
+ if isinstance(response, dict):
966
+ caps = response.get("result", {})
967
+ if isinstance(caps, dict):
968
+ tool_list = caps.get("capabilities", {}).get("tools", [])
969
+ if isinstance(tool_list, list):
970
+ tools = [
971
+ t.get("name", "") for t in tool_list
972
+ if isinstance(t, dict) and isinstance(t.get("name"), str)
973
+ ]
974
+ except (json.JSONDecodeError, UnicodeDecodeError):
975
+ pass
976
+
977
+ return {
978
+ "server": server_name,
979
+ "status": "ok",
980
+ "tools": tools,
981
+ "elapsed_ms": elapsed,
982
+ }
983
+
984
+
985
+ __all__ = [
986
+ "CONFLICT_SEVERITY_MAP",
987
+ "ConflictResult",
988
+ "PluginAllowlistEntry",
989
+ "INTEROP_RECORD_SCHEMA",
990
+ "ConflictCode",
991
+ "ConflictSeverity",
992
+ "HookChainPlan",
993
+ "Layer",
994
+ "PluginInteropPayload",
995
+ "PluginInteropRecord",
996
+ "Source",
997
+ "approval_status_for_record",
998
+ "classify_conflicts",
999
+ "discover_host_plugin_state",
1000
+ "discover_omg_plugin_state",
1001
+ "get_approval_status_for_all",
1002
+ "load_plugin_allowlist",
1003
+ "plan_hook_chain",
1004
+ "save_plugin_allowlist",
1005
+ "probe_mcp_server_live",
1006
+ "validate_plugin_allowlist_entry",
1007
+ ]