@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,675 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Web Search Provider Abstraction for OMG
4
+
5
+ Clean provider interface for web search with credential integration.
6
+ Providers register via the abstract base class; WebSearchManager dispatches
7
+ search/fetch calls to the active provider.
8
+
9
+ Feature flag: OMG_WEB_SEARCH_ENABLED (default: False)
10
+ """
11
+
12
+ import abc
13
+ import importlib
14
+ import time
15
+ import json
16
+ import os
17
+ import sys
18
+ import urllib.error
19
+ import urllib.request
20
+ from dataclasses import asdict, dataclass, field
21
+ from typing import Any, Dict, List, Optional
22
+
23
+
24
+ # --- Lazy imports for hooks/_common.py ---
25
+
26
+ _get_feature_flag = None
27
+ _atomic_json_write = None
28
+
29
+
30
+ def _ensure_imports():
31
+ """Lazy import feature flag and atomic write from hooks/_common.py."""
32
+ global _get_feature_flag, _atomic_json_write
33
+ if _get_feature_flag is not None:
34
+ return
35
+ repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
36
+ if repo_root not in sys.path:
37
+ sys.path.insert(0, repo_root)
38
+ try:
39
+ from hooks._common import get_feature_flag as _gff
40
+ from hooks._common import atomic_json_write as _ajw
41
+ _get_feature_flag = _gff
42
+ _atomic_json_write = _ajw
43
+ except ImportError:
44
+ pass
45
+
46
+
47
+ # --- Lazy import for credential_store (OPTIONAL) ---
48
+
49
+ _credential_store = None
50
+ _has_credential_store: Optional[bool] = None
51
+ # Backward-compat test seam (patched in existing tests).
52
+ _HAS_CREDENTIAL_STORE = None
53
+
54
+
55
+ def _resolve_project_dir() -> str:
56
+ return os.environ.get("CLAUDE_PROJECT_DIR") or os.getcwd()
57
+
58
+
59
+ def _mark_external_ingress(*, source_ref: str, content: Any) -> None:
60
+ try:
61
+ from runtime.untrusted_content import TrustTier, mark_untrusted_content
62
+ except Exception:
63
+ return
64
+
65
+ payload = content
66
+ if not isinstance(payload, str):
67
+ payload = json.dumps(payload, sort_keys=True, ensure_ascii=True)
68
+
69
+ try:
70
+ mark_untrusted_content(
71
+ _resolve_project_dir(),
72
+ source_type="web",
73
+ source_ref=source_ref,
74
+ content=payload,
75
+ tier=TrustTier.RESEARCH,
76
+ )
77
+ except Exception:
78
+ return
79
+
80
+
81
+ def _check_credential_store() -> bool:
82
+ """Check if credential_store is available (cached after first check)."""
83
+ global _has_credential_store, _credential_store
84
+ override = globals().get("_HAS_CREDENTIAL_STORE")
85
+ if override is not None:
86
+ return bool(override)
87
+
88
+ if _has_credential_store is None:
89
+ try:
90
+ repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
91
+ hooks_dir = os.path.join(repo_root, "hooks")
92
+ if hooks_dir not in sys.path:
93
+ sys.path.insert(0, hooks_dir)
94
+ _cs = importlib.import_module("credential_store")
95
+ _credential_store = _cs
96
+ _has_credential_store = True
97
+ except ImportError:
98
+ _has_credential_store = False
99
+ return bool(_has_credential_store)
100
+
101
+
102
+ # --- Feature flag ---
103
+
104
+ def _is_enabled() -> bool:
105
+ """Check if Web Search feature is enabled."""
106
+ # Fast path: check env var directly
107
+ env_val = os.environ.get("OMG_WEB_SEARCH_ENABLED", "").lower()
108
+ if env_val in ("0", "false", "no"):
109
+ return False
110
+ if env_val in ("1", "true", "yes"):
111
+ return True
112
+ # Fallback to hooks/_common.get_feature_flag
113
+ _ensure_imports()
114
+ if _get_feature_flag is not None:
115
+ return _get_feature_flag("WEB_SEARCH", default=False)
116
+ return False
117
+
118
+
119
+ # =============================================================================
120
+ # Data Types
121
+ # =============================================================================
122
+
123
+
124
+ @dataclass
125
+ class SearchResult:
126
+ """A single search result from a web search provider.
127
+
128
+ Attributes:
129
+ title: The title of the search result.
130
+ url: The URL of the search result.
131
+ snippet: A text snippet or summary from the result.
132
+ source: The provider name that produced this result.
133
+ """
134
+ title: str
135
+ url: str
136
+ snippet: str
137
+ source: str
138
+
139
+ def to_dict(self) -> Dict[str, str]:
140
+ """Convert to a plain dictionary."""
141
+ return asdict(self)
142
+
143
+ @classmethod
144
+ def from_dict(cls, data: Dict[str, str]) -> "SearchResult":
145
+ """Create a SearchResult from a dictionary.
146
+
147
+ Accepts dicts with at least 'title', 'url', 'snippet', 'source' keys.
148
+ Missing keys default to empty string.
149
+ """
150
+ return cls(
151
+ title=data.get("title", ""),
152
+ url=data.get("url", ""),
153
+ snippet=data.get("snippet", ""),
154
+ source=data.get("source", ""),
155
+ )
156
+
157
+
158
+ # =============================================================================
159
+ # Abstract Provider Base Class
160
+ # =============================================================================
161
+
162
+
163
+ class Provider(abc.ABC):
164
+ """Abstract base class for web search providers.
165
+
166
+ Subclasses must implement `search()` and `fetch()`.
167
+ """
168
+
169
+ @abc.abstractmethod
170
+ def search(self, query: str, **kwargs: Any) -> List[Dict[str, str]]:
171
+ """Search the web for the given query.
172
+
173
+ Args:
174
+ query: The search query string.
175
+ **kwargs: Provider-specific options (e.g., num_results, language).
176
+
177
+ Returns:
178
+ A list of dicts, each with at least 'title', 'url', 'snippet' keys.
179
+ """
180
+ ...
181
+
182
+ @abc.abstractmethod
183
+ def fetch(self, url: str) -> str:
184
+ """Fetch the content of a URL and return it as text.
185
+
186
+ Args:
187
+ url: The URL to fetch.
188
+
189
+ Returns:
190
+ The page content as a string.
191
+ """
192
+ ...
193
+
194
+ @property
195
+ def name(self) -> str:
196
+ """Provider name (defaults to class name in lowercase)."""
197
+ return self.__class__.__name__.lower()
198
+
199
+
200
+ # =============================================================================
201
+ # Credential Lookup
202
+ # =============================================================================
203
+
204
+
205
+ def get_api_key(provider_name: str) -> Optional[str]:
206
+ """Get the API key for a provider.
207
+
208
+ Resolution order:
209
+ 1. credential_store.get_active_key(provider_name) if available
210
+ 2. Environment variable {PROVIDER_NAME}_API_KEY
211
+
212
+ Args:
213
+ provider_name: The name of the provider (e.g., 'exa', 'serper').
214
+
215
+ Returns:
216
+ The API key string, or None if not found.
217
+ """
218
+ # Try credential store first
219
+ if _check_credential_store() and _credential_store is not None:
220
+ try:
221
+ key = _credential_store.get_active_key(provider_name)
222
+ if key:
223
+ return key
224
+ except (RuntimeError, ValueError, OSError):
225
+ pass # Fall through to env var
226
+
227
+ # Fallback to environment variable
228
+ env_key = f"{provider_name.upper()}_API_KEY"
229
+ return os.environ.get(env_key)
230
+
231
+
232
+ # =============================================================================
233
+ # Web Search Manager
234
+ # =============================================================================
235
+
236
+
237
+ class WebSearchManager:
238
+ """Central manager for web search operations.
239
+
240
+ Manages registered providers and dispatches search/fetch calls.
241
+ All public methods return early with error/empty if the feature flag
242
+ is disabled.
243
+ """
244
+
245
+ def __init__(self) -> None:
246
+ self._providers: Dict[str, Provider] = {}
247
+ self._default_provider: Optional[str] = None
248
+
249
+ def register_provider(self, name: str, provider: Provider) -> None:
250
+ """Register a search provider.
251
+
252
+ Args:
253
+ name: A unique name for the provider (e.g., 'exa', 'serper').
254
+ provider: An instance of a Provider subclass.
255
+ """
256
+ self._providers[name] = provider
257
+ # First registered provider becomes default
258
+ if self._default_provider is None:
259
+ self._default_provider = name
260
+
261
+ def unregister_provider(self, name: str) -> bool:
262
+ """Remove a registered provider.
263
+
264
+ Args:
265
+ name: The provider name to remove.
266
+
267
+ Returns:
268
+ True if removed, False if not found.
269
+ """
270
+ if name in self._providers:
271
+ del self._providers[name]
272
+ if self._default_provider == name:
273
+ self._default_provider = (
274
+ next(iter(self._providers)) if self._providers else None
275
+ )
276
+ return True
277
+ return False
278
+
279
+ def get_providers(self) -> List[str]:
280
+ """Return a list of registered provider names.
281
+
282
+ Returns:
283
+ List of provider name strings.
284
+ """
285
+ return list(self._providers.keys())
286
+
287
+ def search(
288
+ self,
289
+ query: str,
290
+ provider: Optional[str] = None,
291
+ use_selector: bool = False,
292
+ dry_run: bool = False,
293
+ **kwargs: Any,
294
+ ) -> List[SearchResult]:
295
+ """Search using a registered provider.
296
+
297
+ Args:
298
+ query: The search query string.
299
+ provider: Provider name to use. If None, uses the default provider
300
+ (or ProviderSelector if use_selector=True).
301
+ use_selector: If True, use ProviderSelector for intelligent
302
+ provider selection with fallback and rate-limit awareness.
303
+ dry_run: If True (with use_selector), prefer synthetic provider.
304
+ **kwargs: Additional arguments passed to the provider's search().
305
+
306
+ Returns:
307
+ A list of SearchResult objects. Empty list if feature disabled,
308
+ no providers registered, or provider not found.
309
+ """
310
+ if not _is_enabled():
311
+ return []
312
+
313
+ if use_selector:
314
+ provider_name = provider_selector.select_provider(
315
+ query=query,
316
+ preferred=provider,
317
+ manager=self,
318
+ dry_run=dry_run,
319
+ )
320
+ else:
321
+ provider_name = provider or self._default_provider
322
+
323
+ if provider_name is None or provider_name not in self._providers:
324
+ return []
325
+
326
+ prov = self._providers[provider_name]
327
+ try:
328
+ raw_results = prov.search(query, **kwargs)
329
+ except Exception:
330
+ return []
331
+
332
+ results = []
333
+ for item in raw_results:
334
+ item.setdefault("source", provider_name)
335
+ results.append(SearchResult.from_dict(item))
336
+
337
+ _mark_external_ingress(
338
+ source_ref=f"search:{provider_name}",
339
+ content={
340
+ "query": query,
341
+ "provider": provider_name,
342
+ "results": [result.to_dict() for result in results],
343
+ },
344
+ )
345
+ return results
346
+
347
+ def fetch(
348
+ self,
349
+ url: str,
350
+ provider: Optional[str] = None,
351
+ ) -> str:
352
+ """Fetch URL content using a registered provider.
353
+
354
+ Args:
355
+ url: The URL to fetch.
356
+ provider: Provider name to use. If None, uses default provider.
357
+ If no provider registered, uses stdlib urllib as fallback.
358
+
359
+ Returns:
360
+ The fetched content as a string, or empty string on failure/disabled.
361
+ """
362
+ if not _is_enabled():
363
+ return ""
364
+
365
+ provider_name = provider or self._default_provider
366
+ if provider_name and provider_name in self._providers:
367
+ try:
368
+ content = self._providers[provider_name].fetch(url)
369
+ except Exception:
370
+ return ""
371
+ _mark_external_ingress(
372
+ source_ref=f"fetch:{provider_name}:{url}",
373
+ content={
374
+ "url": url,
375
+ "provider": provider_name,
376
+ "content": content,
377
+ },
378
+ )
379
+ return content
380
+
381
+ # Fallback: use stdlib urllib if no provider available
382
+ content = _stdlib_fetch(url)
383
+ _mark_external_ingress(
384
+ source_ref=f"fetch:stdlib:{url}",
385
+ content={
386
+ "url": url,
387
+ "provider": "stdlib",
388
+ "content": content,
389
+ },
390
+ )
391
+ return content
392
+
393
+
394
+ # =============================================================================
395
+ # Stdlib URL Fetch (fallback)
396
+ # =============================================================================
397
+
398
+
399
+ def _stdlib_fetch(url: str, timeout: int = 15) -> str:
400
+ """Fetch URL content using stdlib urllib.request.
401
+
402
+ Args:
403
+ url: The URL to fetch.
404
+ timeout: Request timeout in seconds.
405
+
406
+ Returns:
407
+ The page content as text, or empty string on failure.
408
+ """
409
+ try:
410
+ req = urllib.request.Request(
411
+ url,
412
+ headers={"User-Agent": "OMG-WebSearch/1.0"},
413
+ )
414
+ with urllib.request.urlopen(req, timeout=timeout) as resp:
415
+ encoding = resp.headers.get_content_charset() or "utf-8"
416
+ return resp.read().decode(encoding, errors="replace")
417
+ except Exception:
418
+ return ""
419
+
420
+ # =============================================================================
421
+ # Provider Selection Logic
422
+ # =============================================================================
423
+
424
+
425
+ # Module-level rate limit state: provider_name -> reset_timestamp
426
+ _rate_limits: Dict[str, float] = {}
427
+
428
+ # Default fallback chain order
429
+ DEFAULT_FALLBACK_CHAIN = ["exa", "brave", "perplexity", "jina", "synthetic"]
430
+
431
+
432
+ class ProviderSelector:
433
+ """Intelligent provider selection with fallback chains and rate-limit tracking.
434
+
435
+ Selection algorithm:
436
+ 1. If dry-run mode, prefer 'synthetic'
437
+ 2. If `preferred` argument given, use it (if not rate-limited)
438
+ 3. If user preference is set, use it (if not rate-limited)
439
+ 4. Iterate fallback chain, pick first not rate-limited + registered
440
+ 5. Return None if all exhausted
441
+ """
442
+
443
+ def __init__(self) -> None:
444
+ self._preference_path: Optional[str] = None
445
+
446
+ def _get_preference_path(self) -> str:
447
+ """Get the path to the preference file."""
448
+ if self._preference_path is None:
449
+ repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
450
+ self._preference_path = os.path.join(
451
+ repo_root, ".omg", "state", "search_preference.json"
452
+ )
453
+ return self._preference_path
454
+
455
+ def select_provider(
456
+ self,
457
+ query: str,
458
+ preferred: Optional[str] = None,
459
+ manager: Optional["WebSearchManager"] = None,
460
+ dry_run: bool = False,
461
+ ) -> Optional[str]:
462
+ """Pick the best available provider for a query.
463
+
464
+ Args:
465
+ query: The search query (reserved for future query-type routing).
466
+ preferred: Explicit provider preference for this call.
467
+ manager: WebSearchManager to check registered providers against.
468
+ dry_run: If True, prefer 'synthetic' provider.
469
+
470
+ Returns:
471
+ Provider name string, or None if no provider available.
472
+ """
473
+ registered = manager.get_providers() if manager else []
474
+
475
+ # Step 0: dry-run mode prefers synthetic
476
+ if dry_run and "synthetic" in registered and not self.is_rate_limited("synthetic"):
477
+ return "synthetic"
478
+
479
+ # Step 1: explicit preferred argument
480
+ if preferred and preferred in registered and not self.is_rate_limited(preferred):
481
+ return preferred
482
+
483
+ # Step 2: user preference from persisted state
484
+ user_pref = self.get_preference()
485
+ if user_pref and user_pref in registered and not self.is_rate_limited(user_pref):
486
+ return user_pref
487
+
488
+ # Step 3: iterate fallback chain
489
+ for provider_name in DEFAULT_FALLBACK_CHAIN:
490
+ if provider_name in registered and not self.is_rate_limited(provider_name):
491
+ return provider_name
492
+
493
+ # Step 4: try any remaining registered provider not in fallback chain
494
+ for provider_name in registered:
495
+ if not self.is_rate_limited(provider_name):
496
+ return provider_name
497
+
498
+ return None
499
+
500
+ def record_rate_limit(
501
+ self, provider_name: str, reset_at: Optional[float] = None
502
+ ) -> None:
503
+ """Mark a provider as rate-limited.
504
+
505
+ Args:
506
+ provider_name: The provider to mark.
507
+ reset_at: Unix timestamp when the rate limit resets.
508
+ Defaults to time.time() + 60 (1 minute).
509
+ """
510
+ if reset_at is None:
511
+ reset_at = time.time() + 60
512
+ _rate_limits[provider_name] = reset_at
513
+
514
+ def is_rate_limited(self, provider_name: str) -> bool:
515
+ """Check if a provider is currently rate-limited.
516
+
517
+ Automatically clears expired rate limits.
518
+
519
+ Args:
520
+ provider_name: The provider to check.
521
+
522
+ Returns:
523
+ True if currently rate-limited, False otherwise.
524
+ """
525
+ if provider_name not in _rate_limits:
526
+ return False
527
+ if time.time() > _rate_limits[provider_name]:
528
+ # Rate limit expired — clean up
529
+ del _rate_limits[provider_name]
530
+ return False
531
+ return True
532
+
533
+ def get_fallback_chain(self, primary_provider: str) -> List[str]:
534
+ """Return ordered fallback list starting after the primary provider.
535
+
536
+ Args:
537
+ primary_provider: The provider to build fallback chain from.
538
+
539
+ Returns:
540
+ List of provider names in fallback order (excluding primary).
541
+ """
542
+ chain = list(DEFAULT_FALLBACK_CHAIN)
543
+ if primary_provider in chain:
544
+ idx = chain.index(primary_provider)
545
+ # Everything after primary, then wrap around before it
546
+ chain = chain[idx + 1:] + chain[:idx]
547
+ return chain
548
+
549
+ def set_preference(self, provider_name: str) -> None:
550
+ """Persist user preferred provider to .omg/state/search_preference.json.
551
+
552
+ Args:
553
+ provider_name: The provider name to set as default.
554
+ """
555
+ import datetime
556
+
557
+ _ensure_imports()
558
+ data = {
559
+ "provider": provider_name,
560
+ "set_at": datetime.datetime.now().isoformat(),
561
+ }
562
+ path = self._get_preference_path()
563
+ if _atomic_json_write is not None:
564
+ _atomic_json_write(path, data)
565
+ else:
566
+ # Fallback: direct write
567
+ os.makedirs(os.path.dirname(path), exist_ok=True)
568
+ with open(path, "w") as f:
569
+ json.dump(data, f, indent=2)
570
+
571
+ def get_preference(self) -> Optional[str]:
572
+ """Read user preferred provider from persisted state.
573
+
574
+ Returns:
575
+ Provider name string, or None if no preference set.
576
+ """
577
+ path = self._get_preference_path()
578
+ try:
579
+ with open(path) as f:
580
+ data = json.load(f)
581
+ return data.get("provider")
582
+ except (FileNotFoundError, json.JSONDecodeError, KeyError):
583
+ return None
584
+
585
+
586
+ # Module-level singleton
587
+ provider_selector = ProviderSelector()
588
+
589
+
590
+ # =============================================================================
591
+ # Module-Level Singleton Instance
592
+ # =============================================================================
593
+
594
+ manager = WebSearchManager()
595
+
596
+ # Auto-register bundled providers so CLI and hooks work out of the box.
597
+ # - synthetic always registers (no API key required)
598
+ # - api-key providers register only when credentials are available
599
+ try:
600
+ _providers_mod = importlib.import_module("search_providers")
601
+ _providers_mod.register_all(manager=manager)
602
+ except Exception:
603
+ # Keep module import non-fatal if provider modules are unavailable.
604
+ pass
605
+
606
+ # =============================================================================
607
+ # CLI Interface
608
+ # =============================================================================
609
+
610
+
611
+ def _cli_main():
612
+ """CLI entry point for web_search.py."""
613
+ import argparse
614
+
615
+ parser = argparse.ArgumentParser(
616
+ description="OMG Web Search Tool — provider-based web search abstraction",
617
+ formatter_class=argparse.RawDescriptionHelpFormatter,
618
+ )
619
+ parser.add_argument("--query", help="Search query string")
620
+ parser.add_argument("--provider", help="Provider name to use")
621
+ parser.add_argument("--fetch", dest="fetch_url", help="Fetch URL content")
622
+ parser.add_argument(
623
+ "--dry-run", action="store_true",
624
+ help="Print what would be searched without making real API calls",
625
+ )
626
+ parser.add_argument(
627
+ "--list-providers", action="store_true",
628
+ help="List registered providers",
629
+ )
630
+
631
+ args = parser.parse_args()
632
+
633
+ # Check feature flag for most operations
634
+ enabled = _is_enabled()
635
+
636
+ if args.list_providers:
637
+ providers = manager.get_providers()
638
+ print(json.dumps({"providers": providers, "enabled": enabled}))
639
+ return
640
+
641
+ if args.dry_run and args.query:
642
+ print(json.dumps({
643
+ "dry_run": True,
644
+ "query": args.query,
645
+ "provider": args.provider or manager._default_provider or "(none)",
646
+ "enabled": enabled,
647
+ "registered_providers": manager.get_providers(),
648
+ "would_search": enabled and len(manager.get_providers()) > 0,
649
+ }, indent=2))
650
+ return
651
+
652
+ if not enabled:
653
+ print(json.dumps({"error": "Web search is disabled (OMG_WEB_SEARCH_ENABLED=false)"}))
654
+ sys.exit(1)
655
+
656
+ if args.fetch_url:
657
+ content = manager.fetch(args.fetch_url, provider=args.provider)
658
+ print(json.dumps({
659
+ "url": args.fetch_url,
660
+ "content_length": len(content),
661
+ "content_preview": content[:500] if content else "",
662
+ }, indent=2))
663
+ return
664
+
665
+ if args.query:
666
+ results = manager.search(args.query, provider=args.provider)
667
+ output = [r.to_dict() for r in results]
668
+ print(json.dumps({"results": output, "count": len(output)}, indent=2))
669
+ return
670
+
671
+ parser.print_help()
672
+
673
+
674
+ if __name__ == "__main__":
675
+ _cli_main()