@trac3r/oh-my-god 2.2.11

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