gobby 0.2.5__py3-none-any.whl

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 (383) hide show
  1. gobby/__init__.py +3 -0
  2. gobby/adapters/__init__.py +30 -0
  3. gobby/adapters/base.py +93 -0
  4. gobby/adapters/claude_code.py +276 -0
  5. gobby/adapters/codex.py +1292 -0
  6. gobby/adapters/gemini.py +343 -0
  7. gobby/agents/__init__.py +37 -0
  8. gobby/agents/codex_session.py +120 -0
  9. gobby/agents/constants.py +112 -0
  10. gobby/agents/context.py +362 -0
  11. gobby/agents/definitions.py +133 -0
  12. gobby/agents/gemini_session.py +111 -0
  13. gobby/agents/registry.py +618 -0
  14. gobby/agents/runner.py +968 -0
  15. gobby/agents/session.py +259 -0
  16. gobby/agents/spawn.py +916 -0
  17. gobby/agents/spawners/__init__.py +77 -0
  18. gobby/agents/spawners/base.py +142 -0
  19. gobby/agents/spawners/cross_platform.py +266 -0
  20. gobby/agents/spawners/embedded.py +225 -0
  21. gobby/agents/spawners/headless.py +226 -0
  22. gobby/agents/spawners/linux.py +125 -0
  23. gobby/agents/spawners/macos.py +277 -0
  24. gobby/agents/spawners/windows.py +308 -0
  25. gobby/agents/tty_config.py +319 -0
  26. gobby/autonomous/__init__.py +32 -0
  27. gobby/autonomous/progress_tracker.py +447 -0
  28. gobby/autonomous/stop_registry.py +269 -0
  29. gobby/autonomous/stuck_detector.py +383 -0
  30. gobby/cli/__init__.py +67 -0
  31. gobby/cli/__main__.py +8 -0
  32. gobby/cli/agents.py +529 -0
  33. gobby/cli/artifacts.py +266 -0
  34. gobby/cli/daemon.py +329 -0
  35. gobby/cli/extensions.py +526 -0
  36. gobby/cli/github.py +263 -0
  37. gobby/cli/init.py +53 -0
  38. gobby/cli/install.py +614 -0
  39. gobby/cli/installers/__init__.py +37 -0
  40. gobby/cli/installers/antigravity.py +65 -0
  41. gobby/cli/installers/claude.py +363 -0
  42. gobby/cli/installers/codex.py +192 -0
  43. gobby/cli/installers/gemini.py +294 -0
  44. gobby/cli/installers/git_hooks.py +377 -0
  45. gobby/cli/installers/shared.py +737 -0
  46. gobby/cli/linear.py +250 -0
  47. gobby/cli/mcp.py +30 -0
  48. gobby/cli/mcp_proxy.py +698 -0
  49. gobby/cli/memory.py +304 -0
  50. gobby/cli/merge.py +384 -0
  51. gobby/cli/projects.py +79 -0
  52. gobby/cli/sessions.py +622 -0
  53. gobby/cli/tasks/__init__.py +30 -0
  54. gobby/cli/tasks/_utils.py +658 -0
  55. gobby/cli/tasks/ai.py +1025 -0
  56. gobby/cli/tasks/commits.py +169 -0
  57. gobby/cli/tasks/crud.py +685 -0
  58. gobby/cli/tasks/deps.py +135 -0
  59. gobby/cli/tasks/labels.py +63 -0
  60. gobby/cli/tasks/main.py +273 -0
  61. gobby/cli/tasks/search.py +178 -0
  62. gobby/cli/tui.py +34 -0
  63. gobby/cli/utils.py +513 -0
  64. gobby/cli/workflows.py +927 -0
  65. gobby/cli/worktrees.py +481 -0
  66. gobby/config/__init__.py +129 -0
  67. gobby/config/app.py +551 -0
  68. gobby/config/extensions.py +167 -0
  69. gobby/config/features.py +472 -0
  70. gobby/config/llm_providers.py +98 -0
  71. gobby/config/logging.py +66 -0
  72. gobby/config/mcp.py +346 -0
  73. gobby/config/persistence.py +247 -0
  74. gobby/config/servers.py +141 -0
  75. gobby/config/sessions.py +250 -0
  76. gobby/config/tasks.py +784 -0
  77. gobby/hooks/__init__.py +104 -0
  78. gobby/hooks/artifact_capture.py +213 -0
  79. gobby/hooks/broadcaster.py +243 -0
  80. gobby/hooks/event_handlers.py +723 -0
  81. gobby/hooks/events.py +218 -0
  82. gobby/hooks/git.py +169 -0
  83. gobby/hooks/health_monitor.py +171 -0
  84. gobby/hooks/hook_manager.py +856 -0
  85. gobby/hooks/hook_types.py +575 -0
  86. gobby/hooks/plugins.py +813 -0
  87. gobby/hooks/session_coordinator.py +396 -0
  88. gobby/hooks/verification_runner.py +268 -0
  89. gobby/hooks/webhooks.py +339 -0
  90. gobby/install/claude/commands/gobby/bug.md +51 -0
  91. gobby/install/claude/commands/gobby/chore.md +51 -0
  92. gobby/install/claude/commands/gobby/epic.md +52 -0
  93. gobby/install/claude/commands/gobby/eval.md +235 -0
  94. gobby/install/claude/commands/gobby/feat.md +49 -0
  95. gobby/install/claude/commands/gobby/nit.md +52 -0
  96. gobby/install/claude/commands/gobby/ref.md +52 -0
  97. gobby/install/claude/hooks/HOOK_SCHEMAS.md +632 -0
  98. gobby/install/claude/hooks/hook_dispatcher.py +364 -0
  99. gobby/install/claude/hooks/validate_settings.py +102 -0
  100. gobby/install/claude/hooks-template.json +118 -0
  101. gobby/install/codex/hooks/hook_dispatcher.py +153 -0
  102. gobby/install/codex/prompts/forget.md +7 -0
  103. gobby/install/codex/prompts/memories.md +7 -0
  104. gobby/install/codex/prompts/recall.md +7 -0
  105. gobby/install/codex/prompts/remember.md +13 -0
  106. gobby/install/gemini/hooks/hook_dispatcher.py +268 -0
  107. gobby/install/gemini/hooks-template.json +138 -0
  108. gobby/install/shared/plugins/code_guardian.py +456 -0
  109. gobby/install/shared/plugins/example_notify.py +331 -0
  110. gobby/integrations/__init__.py +10 -0
  111. gobby/integrations/github.py +145 -0
  112. gobby/integrations/linear.py +145 -0
  113. gobby/llm/__init__.py +40 -0
  114. gobby/llm/base.py +120 -0
  115. gobby/llm/claude.py +578 -0
  116. gobby/llm/claude_executor.py +503 -0
  117. gobby/llm/codex.py +322 -0
  118. gobby/llm/codex_executor.py +513 -0
  119. gobby/llm/executor.py +316 -0
  120. gobby/llm/factory.py +34 -0
  121. gobby/llm/gemini.py +258 -0
  122. gobby/llm/gemini_executor.py +339 -0
  123. gobby/llm/litellm.py +287 -0
  124. gobby/llm/litellm_executor.py +303 -0
  125. gobby/llm/resolver.py +499 -0
  126. gobby/llm/service.py +236 -0
  127. gobby/mcp_proxy/__init__.py +29 -0
  128. gobby/mcp_proxy/actions.py +175 -0
  129. gobby/mcp_proxy/daemon_control.py +198 -0
  130. gobby/mcp_proxy/importer.py +436 -0
  131. gobby/mcp_proxy/lazy.py +325 -0
  132. gobby/mcp_proxy/manager.py +798 -0
  133. gobby/mcp_proxy/metrics.py +609 -0
  134. gobby/mcp_proxy/models.py +139 -0
  135. gobby/mcp_proxy/registries.py +215 -0
  136. gobby/mcp_proxy/schema_hash.py +381 -0
  137. gobby/mcp_proxy/semantic_search.py +706 -0
  138. gobby/mcp_proxy/server.py +549 -0
  139. gobby/mcp_proxy/services/__init__.py +0 -0
  140. gobby/mcp_proxy/services/fallback.py +306 -0
  141. gobby/mcp_proxy/services/recommendation.py +224 -0
  142. gobby/mcp_proxy/services/server_mgmt.py +214 -0
  143. gobby/mcp_proxy/services/system.py +72 -0
  144. gobby/mcp_proxy/services/tool_filter.py +231 -0
  145. gobby/mcp_proxy/services/tool_proxy.py +309 -0
  146. gobby/mcp_proxy/stdio.py +565 -0
  147. gobby/mcp_proxy/tools/__init__.py +27 -0
  148. gobby/mcp_proxy/tools/agents.py +1103 -0
  149. gobby/mcp_proxy/tools/artifacts.py +207 -0
  150. gobby/mcp_proxy/tools/hub.py +335 -0
  151. gobby/mcp_proxy/tools/internal.py +337 -0
  152. gobby/mcp_proxy/tools/memory.py +543 -0
  153. gobby/mcp_proxy/tools/merge.py +422 -0
  154. gobby/mcp_proxy/tools/metrics.py +283 -0
  155. gobby/mcp_proxy/tools/orchestration/__init__.py +23 -0
  156. gobby/mcp_proxy/tools/orchestration/cleanup.py +619 -0
  157. gobby/mcp_proxy/tools/orchestration/monitor.py +380 -0
  158. gobby/mcp_proxy/tools/orchestration/orchestrate.py +746 -0
  159. gobby/mcp_proxy/tools/orchestration/review.py +736 -0
  160. gobby/mcp_proxy/tools/orchestration/utils.py +16 -0
  161. gobby/mcp_proxy/tools/session_messages.py +1056 -0
  162. gobby/mcp_proxy/tools/task_dependencies.py +219 -0
  163. gobby/mcp_proxy/tools/task_expansion.py +591 -0
  164. gobby/mcp_proxy/tools/task_github.py +393 -0
  165. gobby/mcp_proxy/tools/task_linear.py +379 -0
  166. gobby/mcp_proxy/tools/task_orchestration.py +77 -0
  167. gobby/mcp_proxy/tools/task_readiness.py +522 -0
  168. gobby/mcp_proxy/tools/task_sync.py +351 -0
  169. gobby/mcp_proxy/tools/task_validation.py +843 -0
  170. gobby/mcp_proxy/tools/tasks/__init__.py +25 -0
  171. gobby/mcp_proxy/tools/tasks/_context.py +112 -0
  172. gobby/mcp_proxy/tools/tasks/_crud.py +516 -0
  173. gobby/mcp_proxy/tools/tasks/_factory.py +176 -0
  174. gobby/mcp_proxy/tools/tasks/_helpers.py +129 -0
  175. gobby/mcp_proxy/tools/tasks/_lifecycle.py +517 -0
  176. gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +301 -0
  177. gobby/mcp_proxy/tools/tasks/_resolution.py +55 -0
  178. gobby/mcp_proxy/tools/tasks/_search.py +215 -0
  179. gobby/mcp_proxy/tools/tasks/_session.py +125 -0
  180. gobby/mcp_proxy/tools/workflows.py +973 -0
  181. gobby/mcp_proxy/tools/worktrees.py +1264 -0
  182. gobby/mcp_proxy/transports/__init__.py +0 -0
  183. gobby/mcp_proxy/transports/base.py +95 -0
  184. gobby/mcp_proxy/transports/factory.py +44 -0
  185. gobby/mcp_proxy/transports/http.py +139 -0
  186. gobby/mcp_proxy/transports/stdio.py +213 -0
  187. gobby/mcp_proxy/transports/websocket.py +136 -0
  188. gobby/memory/backends/__init__.py +116 -0
  189. gobby/memory/backends/mem0.py +408 -0
  190. gobby/memory/backends/memu.py +485 -0
  191. gobby/memory/backends/null.py +111 -0
  192. gobby/memory/backends/openmemory.py +537 -0
  193. gobby/memory/backends/sqlite.py +304 -0
  194. gobby/memory/context.py +87 -0
  195. gobby/memory/manager.py +1001 -0
  196. gobby/memory/protocol.py +451 -0
  197. gobby/memory/search/__init__.py +66 -0
  198. gobby/memory/search/text.py +127 -0
  199. gobby/memory/viz.py +258 -0
  200. gobby/prompts/__init__.py +13 -0
  201. gobby/prompts/defaults/expansion/system.md +119 -0
  202. gobby/prompts/defaults/expansion/user.md +48 -0
  203. gobby/prompts/defaults/external_validation/agent.md +72 -0
  204. gobby/prompts/defaults/external_validation/external.md +63 -0
  205. gobby/prompts/defaults/external_validation/spawn.md +83 -0
  206. gobby/prompts/defaults/external_validation/system.md +6 -0
  207. gobby/prompts/defaults/features/import_mcp.md +22 -0
  208. gobby/prompts/defaults/features/import_mcp_github.md +17 -0
  209. gobby/prompts/defaults/features/import_mcp_search.md +16 -0
  210. gobby/prompts/defaults/features/recommend_tools.md +32 -0
  211. gobby/prompts/defaults/features/recommend_tools_hybrid.md +35 -0
  212. gobby/prompts/defaults/features/recommend_tools_llm.md +30 -0
  213. gobby/prompts/defaults/features/server_description.md +20 -0
  214. gobby/prompts/defaults/features/server_description_system.md +6 -0
  215. gobby/prompts/defaults/features/task_description.md +31 -0
  216. gobby/prompts/defaults/features/task_description_system.md +6 -0
  217. gobby/prompts/defaults/features/tool_summary.md +17 -0
  218. gobby/prompts/defaults/features/tool_summary_system.md +6 -0
  219. gobby/prompts/defaults/research/step.md +58 -0
  220. gobby/prompts/defaults/validation/criteria.md +47 -0
  221. gobby/prompts/defaults/validation/validate.md +38 -0
  222. gobby/prompts/loader.py +346 -0
  223. gobby/prompts/models.py +113 -0
  224. gobby/py.typed +0 -0
  225. gobby/runner.py +488 -0
  226. gobby/search/__init__.py +23 -0
  227. gobby/search/protocol.py +104 -0
  228. gobby/search/tfidf.py +232 -0
  229. gobby/servers/__init__.py +7 -0
  230. gobby/servers/http.py +636 -0
  231. gobby/servers/models.py +31 -0
  232. gobby/servers/routes/__init__.py +23 -0
  233. gobby/servers/routes/admin.py +416 -0
  234. gobby/servers/routes/dependencies.py +118 -0
  235. gobby/servers/routes/mcp/__init__.py +24 -0
  236. gobby/servers/routes/mcp/hooks.py +135 -0
  237. gobby/servers/routes/mcp/plugins.py +121 -0
  238. gobby/servers/routes/mcp/tools.py +1337 -0
  239. gobby/servers/routes/mcp/webhooks.py +159 -0
  240. gobby/servers/routes/sessions.py +582 -0
  241. gobby/servers/websocket.py +766 -0
  242. gobby/sessions/__init__.py +13 -0
  243. gobby/sessions/analyzer.py +322 -0
  244. gobby/sessions/lifecycle.py +240 -0
  245. gobby/sessions/manager.py +563 -0
  246. gobby/sessions/processor.py +225 -0
  247. gobby/sessions/summary.py +532 -0
  248. gobby/sessions/transcripts/__init__.py +41 -0
  249. gobby/sessions/transcripts/base.py +125 -0
  250. gobby/sessions/transcripts/claude.py +386 -0
  251. gobby/sessions/transcripts/codex.py +143 -0
  252. gobby/sessions/transcripts/gemini.py +195 -0
  253. gobby/storage/__init__.py +21 -0
  254. gobby/storage/agents.py +409 -0
  255. gobby/storage/artifact_classifier.py +341 -0
  256. gobby/storage/artifacts.py +285 -0
  257. gobby/storage/compaction.py +67 -0
  258. gobby/storage/database.py +357 -0
  259. gobby/storage/inter_session_messages.py +194 -0
  260. gobby/storage/mcp.py +680 -0
  261. gobby/storage/memories.py +562 -0
  262. gobby/storage/merge_resolutions.py +550 -0
  263. gobby/storage/migrations.py +860 -0
  264. gobby/storage/migrations_legacy.py +1359 -0
  265. gobby/storage/projects.py +166 -0
  266. gobby/storage/session_messages.py +251 -0
  267. gobby/storage/session_tasks.py +97 -0
  268. gobby/storage/sessions.py +817 -0
  269. gobby/storage/task_dependencies.py +223 -0
  270. gobby/storage/tasks/__init__.py +42 -0
  271. gobby/storage/tasks/_aggregates.py +180 -0
  272. gobby/storage/tasks/_crud.py +449 -0
  273. gobby/storage/tasks/_id.py +104 -0
  274. gobby/storage/tasks/_lifecycle.py +311 -0
  275. gobby/storage/tasks/_manager.py +889 -0
  276. gobby/storage/tasks/_models.py +300 -0
  277. gobby/storage/tasks/_ordering.py +119 -0
  278. gobby/storage/tasks/_path_cache.py +110 -0
  279. gobby/storage/tasks/_queries.py +343 -0
  280. gobby/storage/tasks/_search.py +143 -0
  281. gobby/storage/workflow_audit.py +393 -0
  282. gobby/storage/worktrees.py +547 -0
  283. gobby/sync/__init__.py +29 -0
  284. gobby/sync/github.py +333 -0
  285. gobby/sync/linear.py +304 -0
  286. gobby/sync/memories.py +284 -0
  287. gobby/sync/tasks.py +641 -0
  288. gobby/tasks/__init__.py +8 -0
  289. gobby/tasks/build_verification.py +193 -0
  290. gobby/tasks/commits.py +633 -0
  291. gobby/tasks/context.py +747 -0
  292. gobby/tasks/criteria.py +342 -0
  293. gobby/tasks/enhanced_validator.py +226 -0
  294. gobby/tasks/escalation.py +263 -0
  295. gobby/tasks/expansion.py +626 -0
  296. gobby/tasks/external_validator.py +764 -0
  297. gobby/tasks/issue_extraction.py +171 -0
  298. gobby/tasks/prompts/expand.py +327 -0
  299. gobby/tasks/research.py +421 -0
  300. gobby/tasks/tdd.py +352 -0
  301. gobby/tasks/tree_builder.py +263 -0
  302. gobby/tasks/validation.py +712 -0
  303. gobby/tasks/validation_history.py +357 -0
  304. gobby/tasks/validation_models.py +89 -0
  305. gobby/tools/__init__.py +0 -0
  306. gobby/tools/summarizer.py +170 -0
  307. gobby/tui/__init__.py +5 -0
  308. gobby/tui/api_client.py +281 -0
  309. gobby/tui/app.py +327 -0
  310. gobby/tui/screens/__init__.py +25 -0
  311. gobby/tui/screens/agents.py +333 -0
  312. gobby/tui/screens/chat.py +450 -0
  313. gobby/tui/screens/dashboard.py +377 -0
  314. gobby/tui/screens/memory.py +305 -0
  315. gobby/tui/screens/metrics.py +231 -0
  316. gobby/tui/screens/orchestrator.py +904 -0
  317. gobby/tui/screens/sessions.py +412 -0
  318. gobby/tui/screens/tasks.py +442 -0
  319. gobby/tui/screens/workflows.py +289 -0
  320. gobby/tui/screens/worktrees.py +174 -0
  321. gobby/tui/widgets/__init__.py +21 -0
  322. gobby/tui/widgets/chat.py +210 -0
  323. gobby/tui/widgets/conductor.py +104 -0
  324. gobby/tui/widgets/menu.py +132 -0
  325. gobby/tui/widgets/message_panel.py +160 -0
  326. gobby/tui/widgets/review_gate.py +224 -0
  327. gobby/tui/widgets/task_tree.py +99 -0
  328. gobby/tui/widgets/token_budget.py +166 -0
  329. gobby/tui/ws_client.py +258 -0
  330. gobby/utils/__init__.py +3 -0
  331. gobby/utils/daemon_client.py +235 -0
  332. gobby/utils/git.py +222 -0
  333. gobby/utils/id.py +38 -0
  334. gobby/utils/json_helpers.py +161 -0
  335. gobby/utils/logging.py +376 -0
  336. gobby/utils/machine_id.py +135 -0
  337. gobby/utils/metrics.py +589 -0
  338. gobby/utils/project_context.py +182 -0
  339. gobby/utils/project_init.py +263 -0
  340. gobby/utils/status.py +256 -0
  341. gobby/utils/validation.py +80 -0
  342. gobby/utils/version.py +23 -0
  343. gobby/workflows/__init__.py +4 -0
  344. gobby/workflows/actions.py +1310 -0
  345. gobby/workflows/approval_flow.py +138 -0
  346. gobby/workflows/artifact_actions.py +103 -0
  347. gobby/workflows/audit_helpers.py +110 -0
  348. gobby/workflows/autonomous_actions.py +286 -0
  349. gobby/workflows/context_actions.py +394 -0
  350. gobby/workflows/definitions.py +130 -0
  351. gobby/workflows/detection_helpers.py +208 -0
  352. gobby/workflows/engine.py +485 -0
  353. gobby/workflows/evaluator.py +669 -0
  354. gobby/workflows/git_utils.py +96 -0
  355. gobby/workflows/hooks.py +169 -0
  356. gobby/workflows/lifecycle_evaluator.py +613 -0
  357. gobby/workflows/llm_actions.py +70 -0
  358. gobby/workflows/loader.py +333 -0
  359. gobby/workflows/mcp_actions.py +60 -0
  360. gobby/workflows/memory_actions.py +272 -0
  361. gobby/workflows/premature_stop.py +164 -0
  362. gobby/workflows/session_actions.py +139 -0
  363. gobby/workflows/state_actions.py +123 -0
  364. gobby/workflows/state_manager.py +104 -0
  365. gobby/workflows/stop_signal_actions.py +163 -0
  366. gobby/workflows/summary_actions.py +344 -0
  367. gobby/workflows/task_actions.py +249 -0
  368. gobby/workflows/task_enforcement_actions.py +901 -0
  369. gobby/workflows/templates.py +52 -0
  370. gobby/workflows/todo_actions.py +84 -0
  371. gobby/workflows/webhook.py +223 -0
  372. gobby/workflows/webhook_executor.py +399 -0
  373. gobby/worktrees/__init__.py +5 -0
  374. gobby/worktrees/git.py +690 -0
  375. gobby/worktrees/merge/__init__.py +20 -0
  376. gobby/worktrees/merge/conflict_parser.py +177 -0
  377. gobby/worktrees/merge/resolver.py +485 -0
  378. gobby-0.2.5.dist-info/METADATA +351 -0
  379. gobby-0.2.5.dist-info/RECORD +383 -0
  380. gobby-0.2.5.dist-info/WHEEL +5 -0
  381. gobby-0.2.5.dist-info/entry_points.txt +2 -0
  382. gobby-0.2.5.dist-info/licenses/LICENSE.md +193 -0
  383. gobby-0.2.5.dist-info/top_level.txt +1 -0
@@ -0,0 +1,422 @@
1
+ """
2
+ Internal MCP tools for Gobby Merge Resolution.
3
+
4
+ Exposes functionality for:
5
+ - Starting merge operations with AI-powered resolution
6
+ - Getting merge status and conflict details
7
+ - Resolving individual conflicts
8
+ - Applying resolved merges
9
+ - Aborting merge operations
10
+
11
+ These tools are registered with the InternalToolRegistry and accessed
12
+ via the downstream proxy pattern (call_tool, list_tools, get_tool_schema).
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import logging
18
+ from typing import TYPE_CHECKING, Any
19
+
20
+ from gobby.mcp_proxy.tools.internal import InternalToolRegistry
21
+ from gobby.storage.merge_resolutions import ConflictStatus
22
+
23
+ if TYPE_CHECKING:
24
+ from gobby.storage.merge_resolutions import MergeResolutionManager
25
+ from gobby.worktrees.git import WorktreeGitManager
26
+ from gobby.worktrees.merge import MergeResolver
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+
31
+ def create_merge_registry(
32
+ merge_storage: MergeResolutionManager,
33
+ merge_resolver: MergeResolver,
34
+ git_manager: WorktreeGitManager | None = None,
35
+ worktree_manager: Any | None = None,
36
+ ) -> InternalToolRegistry:
37
+ """
38
+ Create a merge tool registry with all merge-related tools.
39
+
40
+ Args:
41
+ merge_storage: MergeResolutionManager for database operations.
42
+ merge_resolver: MergeResolver for AI-powered conflict resolution.
43
+ git_manager: WorktreeGitManager for git operations.
44
+ worktree_manager: LocalWorktreeManager for resolving worktree paths.
45
+
46
+ Returns:
47
+ InternalToolRegistry with all merge tools registered.
48
+ """
49
+ registry = InternalToolRegistry(
50
+ name="gobby-merge",
51
+ description="AI-powered merge conflict resolution - start merges, resolve conflicts, and apply resolutions",
52
+ )
53
+
54
+ @registry.tool(
55
+ name="merge_start",
56
+ description="Start a merge operation with AI-powered conflict resolution.",
57
+ )
58
+ async def merge_start(
59
+ worktree_id: str,
60
+ source_branch: str,
61
+ target_branch: str = "main",
62
+ strategy: str = "auto",
63
+ ) -> dict[str, Any]:
64
+ """
65
+ Start a merge operation.
66
+
67
+ Args:
68
+ worktree_id: ID of the worktree to merge in.
69
+ source_branch: Branch being merged in.
70
+ target_branch: Target branch (default: main).
71
+ strategy: Resolution strategy ('auto', 'conflict_only', 'full_file', 'manual').
72
+
73
+ Returns:
74
+ Dict with resolution_id, success status, and conflict details.
75
+ """
76
+ # Validate required parameters
77
+ if not worktree_id:
78
+ return {
79
+ "success": False,
80
+ "error": "worktree_id is required",
81
+ }
82
+ if not source_branch:
83
+ return {
84
+ "success": False,
85
+ "error": "source_branch is required",
86
+ }
87
+
88
+ try:
89
+ # Create resolution record
90
+ resolution = merge_storage.create_resolution(
91
+ worktree_id=worktree_id,
92
+ source_branch=source_branch,
93
+ target_branch=target_branch,
94
+ status="pending",
95
+ )
96
+
97
+ # Attempt merge resolution
98
+ from gobby.worktrees.merge import ResolutionTier
99
+
100
+ force_tier = None
101
+ if strategy == "conflict_only":
102
+ force_tier = ResolutionTier.CONFLICT_ONLY_AI
103
+ elif strategy == "full_file":
104
+ force_tier = ResolutionTier.FULL_FILE_AI
105
+
106
+ # Get worktree path from manager
107
+ worktree_path = None
108
+ if worktree_manager:
109
+ worktree = worktree_manager.get_worktree(worktree_id)
110
+ if worktree and worktree.worktree_path:
111
+ worktree_path = worktree.worktree_path
112
+
113
+ if not worktree_path:
114
+ return {
115
+ "success": False,
116
+ "error": f"Worktree '{worktree_id}' not found or has no path",
117
+ }
118
+
119
+ result = await merge_resolver.resolve(
120
+ worktree_path=worktree_path,
121
+ source_branch=source_branch,
122
+ target_branch=target_branch,
123
+ force_tier=force_tier,
124
+ )
125
+
126
+ # Update resolution with result
127
+ merge_storage.update_resolution(
128
+ resolution_id=resolution.id,
129
+ status="resolved" if result.success else "pending",
130
+ tier_used=result.tier.value if result.success else None,
131
+ )
132
+
133
+ # Create conflict records if needed
134
+ for conflict in result.conflicts:
135
+ file_path = conflict.get("file", "")
136
+ merge_storage.create_conflict(
137
+ resolution_id=resolution.id,
138
+ file_path=file_path,
139
+ ours_content=conflict.get("ours_content"),
140
+ theirs_content=conflict.get("theirs_content"),
141
+ status="pending" if not result.success else "resolved",
142
+ )
143
+
144
+ return {
145
+ "success": result.success,
146
+ "resolution_id": resolution.id,
147
+ "tier": result.tier.value,
148
+ "needs_human_review": result.needs_human_review,
149
+ "conflicts": [{"file": c.get("file", "")} for c in result.unresolved_conflicts],
150
+ "resolved_files": result.resolved_files,
151
+ }
152
+
153
+ except Exception as e:
154
+ logger.exception(f"Error starting merge: {e}")
155
+ return {
156
+ "success": False,
157
+ "error": str(e),
158
+ }
159
+
160
+ @registry.tool(
161
+ name="merge_status",
162
+ description="Get the status of a merge resolution including conflict details.",
163
+ )
164
+ async def merge_status(resolution_id: str) -> dict[str, Any]:
165
+ """
166
+ Get merge resolution status.
167
+
168
+ Args:
169
+ resolution_id: The resolution ID.
170
+
171
+ Returns:
172
+ Dict with resolution details and conflicts.
173
+ """
174
+ if not resolution_id:
175
+ return {
176
+ "success": False,
177
+ "error": "resolution_id is required",
178
+ }
179
+
180
+ resolution = merge_storage.get_resolution(resolution_id)
181
+ if not resolution:
182
+ return {
183
+ "success": False,
184
+ "error": f"Resolution '{resolution_id}' not found",
185
+ }
186
+
187
+ conflicts = merge_storage.list_conflicts(resolution_id=resolution_id)
188
+
189
+ return {
190
+ "success": True,
191
+ "resolution": resolution.to_dict(),
192
+ "conflicts": [c.to_dict() for c in conflicts],
193
+ "pending_count": sum(1 for c in conflicts if c.status == "pending"),
194
+ "resolved_count": sum(1 for c in conflicts if c.status == "resolved"),
195
+ }
196
+
197
+ @registry.tool(
198
+ name="merge_resolve",
199
+ description="Resolve a specific conflict, optionally with AI assistance.",
200
+ )
201
+ async def merge_resolve(
202
+ conflict_id: str,
203
+ resolved_content: str | None = None,
204
+ use_ai: bool = True,
205
+ ) -> dict[str, Any]:
206
+ """
207
+ Resolve a specific conflict.
208
+
209
+ Args:
210
+ conflict_id: The conflict ID.
211
+ resolved_content: Manual resolution content (skips AI).
212
+ use_ai: Whether to use AI for resolution (default: True).
213
+
214
+ Returns:
215
+ Dict with resolution result.
216
+ """
217
+ if not conflict_id:
218
+ return {
219
+ "success": False,
220
+ "error": "conflict_id is required",
221
+ }
222
+
223
+ conflict = merge_storage.get_conflict(conflict_id)
224
+ if not conflict:
225
+ return {
226
+ "success": False,
227
+ "error": f"Conflict '{conflict_id}' not found",
228
+ }
229
+
230
+ try:
231
+ if resolved_content is not None:
232
+ # Manual resolution
233
+ updated = merge_storage.update_conflict(
234
+ conflict_id=conflict_id,
235
+ status=ConflictStatus.RESOLVED.value,
236
+ resolved_content=resolved_content,
237
+ )
238
+ return {
239
+ "success": True,
240
+ "conflict": updated.to_dict() if updated else None,
241
+ "resolution_method": "manual",
242
+ }
243
+
244
+ if use_ai:
245
+ # Use AI resolver
246
+ from gobby.worktrees.merge import ConflictHunk
247
+
248
+ # Create hunk from conflict data
249
+ hunks = [
250
+ ConflictHunk(
251
+ ours=conflict.ours_content or "",
252
+ theirs=conflict.theirs_content or "",
253
+ base=None,
254
+ start_line=1,
255
+ end_line=1,
256
+ context_before="",
257
+ context_after="",
258
+ )
259
+ ]
260
+
261
+ result = await merge_resolver.resolve_file(
262
+ path=conflict.file_path,
263
+ conflict_hunks=hunks,
264
+ )
265
+
266
+ if result.success:
267
+ # Get resolved content from result (would be in resolved_files)
268
+ resolved = "AI resolved content" # Placeholder
269
+ updated = merge_storage.update_conflict(
270
+ conflict_id=conflict_id,
271
+ status=ConflictStatus.RESOLVED.value,
272
+ resolved_content=resolved,
273
+ )
274
+ return {
275
+ "success": True,
276
+ "conflict": updated.to_dict() if updated else None,
277
+ "resolution_method": "ai",
278
+ "tier": result.tier.value,
279
+ }
280
+ else:
281
+ return {
282
+ "success": False,
283
+ "error": "AI resolution failed",
284
+ "needs_human_review": result.needs_human_review,
285
+ }
286
+
287
+ return {
288
+ "success": False,
289
+ "error": "No resolution method specified",
290
+ }
291
+
292
+ except Exception as e:
293
+ logger.exception(f"Error resolving conflict: {e}")
294
+ return {
295
+ "success": False,
296
+ "error": str(e),
297
+ }
298
+
299
+ @registry.tool(
300
+ name="merge_apply",
301
+ description="Apply all resolved conflicts and complete the merge.",
302
+ )
303
+ async def merge_apply(resolution_id: str) -> dict[str, Any]:
304
+ """
305
+ Apply all resolutions and complete the merge.
306
+
307
+ Args:
308
+ resolution_id: The resolution ID.
309
+
310
+ Returns:
311
+ Dict with merge completion status.
312
+ """
313
+ if not resolution_id:
314
+ return {
315
+ "success": False,
316
+ "error": "resolution_id is required",
317
+ }
318
+
319
+ resolution = merge_storage.get_resolution(resolution_id)
320
+ if not resolution:
321
+ return {
322
+ "success": False,
323
+ "error": f"Resolution '{resolution_id}' not found",
324
+ }
325
+
326
+ conflicts = merge_storage.list_conflicts(resolution_id=resolution_id)
327
+
328
+ # Check if all conflicts are resolved
329
+ pending = [c for c in conflicts if c.status != "resolved"]
330
+ if pending:
331
+ return {
332
+ "success": False,
333
+ "error": f"Cannot apply: {len(pending)} unresolved conflicts remaining",
334
+ "pending_conflicts": [{"id": c.id, "file_path": c.file_path} for c in pending],
335
+ }
336
+
337
+ try:
338
+ # Apply resolutions to git (would write files and stage)
339
+ if git_manager:
340
+ for conflict in conflicts:
341
+ if conflict.resolved_content:
342
+ # Would write conflict.resolved_content to conflict.file_path
343
+ pass
344
+
345
+ # Update resolution status
346
+ updated = merge_storage.update_resolution(
347
+ resolution_id=resolution_id,
348
+ status="resolved",
349
+ tier_used=resolution.tier_used or "manual",
350
+ )
351
+
352
+ return {
353
+ "success": True,
354
+ "resolution": updated.to_dict() if updated else None,
355
+ "message": "Merge completed successfully",
356
+ "files_merged": [c.file_path for c in conflicts],
357
+ }
358
+
359
+ except Exception as e:
360
+ logger.exception(f"Error applying merge: {e}")
361
+ return {
362
+ "success": False,
363
+ "error": str(e),
364
+ }
365
+
366
+ @registry.tool(
367
+ name="merge_abort",
368
+ description="Abort the merge operation and restore the previous state.",
369
+ )
370
+ async def merge_abort(resolution_id: str) -> dict[str, Any]:
371
+ """
372
+ Abort a merge operation.
373
+
374
+ Args:
375
+ resolution_id: The resolution ID.
376
+
377
+ Returns:
378
+ Dict with abort status.
379
+ """
380
+ if not resolution_id:
381
+ return {
382
+ "success": False,
383
+ "error": "resolution_id is required",
384
+ }
385
+
386
+ resolution = merge_storage.get_resolution(resolution_id)
387
+ if not resolution:
388
+ return {
389
+ "success": False,
390
+ "error": f"Resolution '{resolution_id}' not found",
391
+ }
392
+
393
+ # Can't abort already resolved merges
394
+ if resolution.status == "resolved":
395
+ return {
396
+ "success": False,
397
+ "error": "Cannot abort: merge is already resolved",
398
+ }
399
+
400
+ try:
401
+ # Abort git merge if in progress
402
+ if git_manager:
403
+ # Would run git merge --abort
404
+ pass
405
+
406
+ # Delete resolution and associated conflicts (cascade)
407
+ deleted = merge_storage.delete_resolution(resolution_id)
408
+
409
+ return {
410
+ "success": deleted,
411
+ "message": "Merge aborted successfully" if deleted else "Failed to abort merge",
412
+ "resolution_id": resolution_id,
413
+ }
414
+
415
+ except Exception as e:
416
+ logger.exception(f"Error aborting merge: {e}")
417
+ return {
418
+ "success": False,
419
+ "error": str(e),
420
+ }
421
+
422
+ return registry
@@ -0,0 +1,283 @@
1
+ """
2
+ Internal MCP tools for Tool Metrics.
3
+
4
+ Exposes functionality for:
5
+ - Querying tool call metrics (get_tool_metrics)
6
+ - Getting top performing tools (get_top_tools)
7
+
8
+ These tools are registered with the InternalToolRegistry and accessed
9
+ via the downstream proxy pattern (call_tool).
10
+ """
11
+
12
+ from typing import Any
13
+
14
+ from gobby.mcp_proxy.metrics import ToolMetricsManager
15
+ from gobby.mcp_proxy.tools.internal import InternalToolRegistry
16
+
17
+
18
+ def create_metrics_registry(metrics_manager: ToolMetricsManager) -> InternalToolRegistry:
19
+ """
20
+ Create a metrics tool registry with all metrics-related tools.
21
+
22
+ Args:
23
+ metrics_manager: ToolMetricsManager instance
24
+
25
+ Returns:
26
+ InternalToolRegistry with metrics tools registered
27
+ """
28
+ registry = InternalToolRegistry(
29
+ name="gobby-metrics",
30
+ description="Tool metrics - query call counts, success rates, latency",
31
+ )
32
+
33
+ @registry.tool(
34
+ name="get_tool_metrics",
35
+ description="Get metrics for MCP tools including call count, success rate, and latency.",
36
+ )
37
+ def get_tool_metrics(
38
+ server_name: str | None = None,
39
+ tool_name: str | None = None,
40
+ project_id: str | None = None,
41
+ ) -> dict[str, Any]:
42
+ """
43
+ Get metrics for MCP tools.
44
+
45
+ Args:
46
+ server_name: Optional server name to filter by
47
+ tool_name: Optional tool name to filter by
48
+ project_id: Optional project ID to filter by
49
+
50
+ Returns:
51
+ Dictionary with tool metrics including call counts, success rates, and latency
52
+ """
53
+ try:
54
+ result = metrics_manager.get_metrics(
55
+ project_id=project_id,
56
+ server_name=server_name,
57
+ tool_name=tool_name,
58
+ )
59
+ return {
60
+ "success": True,
61
+ "metrics": result,
62
+ }
63
+ except Exception as e:
64
+ return {"success": False, "error": str(e)}
65
+
66
+ @registry.tool(
67
+ name="get_top_tools",
68
+ description="Get top tools by usage, success rate, or latency.",
69
+ )
70
+ def get_top_tools(
71
+ project_id: str | None = None,
72
+ limit: int = 10,
73
+ order_by: str = "call_count",
74
+ ) -> dict[str, Any]:
75
+ """
76
+ Get top tools by various metrics.
77
+
78
+ Args:
79
+ project_id: Optional project ID to filter by
80
+ limit: Maximum number of tools to return (default: 10)
81
+ order_by: Sort criteria - "call_count", "success_count", or "avg_latency_ms"
82
+
83
+ Returns:
84
+ List of top tools with their metrics
85
+ """
86
+ try:
87
+ tools = metrics_manager.get_top_tools(
88
+ project_id=project_id,
89
+ limit=limit,
90
+ order_by=order_by,
91
+ )
92
+ return {
93
+ "success": True,
94
+ "tools": tools,
95
+ "count": len(tools),
96
+ }
97
+ except Exception as e:
98
+ return {"success": False, "error": str(e)}
99
+
100
+ @registry.tool(
101
+ name="get_failing_tools",
102
+ description="Get tools with high failure rates above a threshold.",
103
+ )
104
+ def get_failing_tools(
105
+ project_id: str | None = None,
106
+ threshold: float = 0.5,
107
+ limit: int = 10,
108
+ ) -> dict[str, Any]:
109
+ """
110
+ Get tools with failure rate above a threshold.
111
+
112
+ Args:
113
+ project_id: Optional project ID to filter by
114
+ threshold: Minimum failure rate (0.0-1.0) to include a tool (default: 0.5)
115
+ limit: Maximum number of tools to return (default: 10)
116
+
117
+ Returns:
118
+ List of failing tools sorted by failure rate descending
119
+ """
120
+ try:
121
+ tools = metrics_manager.get_failing_tools(
122
+ project_id=project_id,
123
+ threshold=threshold,
124
+ limit=limit,
125
+ )
126
+ return {
127
+ "success": True,
128
+ "tools": tools,
129
+ "count": len(tools),
130
+ "threshold": threshold,
131
+ }
132
+ except Exception as e:
133
+ return {"success": False, "error": str(e)}
134
+
135
+ @registry.tool(
136
+ name="get_tool_success_rate",
137
+ description="Get success rate for a specific tool.",
138
+ )
139
+ def get_tool_success_rate(
140
+ server_name: str,
141
+ tool_name: str,
142
+ project_id: str,
143
+ ) -> dict[str, Any]:
144
+ """
145
+ Get success rate for a specific tool.
146
+
147
+ Args:
148
+ server_name: Name of the MCP server
149
+ tool_name: Name of the tool
150
+ project_id: Project ID
151
+
152
+ Returns:
153
+ Success rate as a float between 0 and 1
154
+ """
155
+ try:
156
+ rate = metrics_manager.get_tool_success_rate(
157
+ server_name=server_name,
158
+ tool_name=tool_name,
159
+ project_id=project_id,
160
+ )
161
+ return {
162
+ "success": True,
163
+ "server_name": server_name,
164
+ "tool_name": tool_name,
165
+ "success_rate": rate,
166
+ }
167
+ except Exception as e:
168
+ return {"success": False, "error": str(e)}
169
+
170
+ @registry.tool(
171
+ name="reset_metrics",
172
+ description="Reset/delete metrics for a project, server, or specific tool.",
173
+ )
174
+ def reset_metrics(
175
+ project_id: str | None = None,
176
+ server_name: str | None = None,
177
+ tool_name: str | None = None,
178
+ ) -> dict[str, Any]:
179
+ """
180
+ Reset/delete metrics.
181
+
182
+ Args:
183
+ project_id: Reset only for this project
184
+ server_name: Reset only for this server
185
+ tool_name: Reset only for this specific tool
186
+
187
+ Returns:
188
+ Number of rows deleted
189
+ """
190
+ try:
191
+ deleted = metrics_manager.reset_metrics(
192
+ project_id=project_id,
193
+ server_name=server_name,
194
+ tool_name=tool_name,
195
+ )
196
+ return {
197
+ "success": True,
198
+ "deleted_count": deleted,
199
+ }
200
+ except Exception as e:
201
+ return {"success": False, "error": str(e)}
202
+
203
+ @registry.tool(
204
+ name="reset_tool_metrics",
205
+ description="Admin tool to reset/delete metrics for a specific tool.",
206
+ )
207
+ def reset_tool_metrics(
208
+ server_name: str | None = None,
209
+ tool_name: str | None = None,
210
+ ) -> dict[str, Any]:
211
+ """
212
+ Reset/delete metrics for a specific tool (admin operation).
213
+
214
+ Args:
215
+ server_name: Server containing the tool
216
+ tool_name: Specific tool to reset metrics for
217
+
218
+ Returns:
219
+ Number of rows deleted
220
+ """
221
+ try:
222
+ deleted = metrics_manager.reset_metrics(
223
+ server_name=server_name,
224
+ tool_name=tool_name,
225
+ )
226
+ return {
227
+ "success": True,
228
+ "deleted_count": deleted,
229
+ "server_name": server_name,
230
+ "tool_name": tool_name,
231
+ }
232
+ except Exception as e:
233
+ return {"success": False, "error": str(e)}
234
+
235
+ @registry.tool(
236
+ name="cleanup_old_metrics",
237
+ description="Delete metrics older than retention period (default 7 days).",
238
+ )
239
+ def cleanup_old_metrics(
240
+ retention_days: int = 7,
241
+ ) -> dict[str, Any]:
242
+ """
243
+ Delete metrics older than the retention period.
244
+
245
+ Args:
246
+ retention_days: Number of days to retain metrics (default: 7)
247
+
248
+ Returns:
249
+ Number of rows deleted
250
+ """
251
+ try:
252
+ deleted = metrics_manager.cleanup_old_metrics(
253
+ retention_days=retention_days,
254
+ )
255
+ return {
256
+ "success": True,
257
+ "deleted_count": deleted,
258
+ "retention_days": retention_days,
259
+ }
260
+ except Exception as e:
261
+ return {"success": False, "error": str(e)}
262
+
263
+ @registry.tool(
264
+ name="get_retention_stats",
265
+ description="Get statistics about metrics retention and age.",
266
+ )
267
+ def get_retention_stats() -> dict[str, Any]:
268
+ """
269
+ Get statistics about metrics retention.
270
+
271
+ Returns:
272
+ Dictionary with retention statistics
273
+ """
274
+ try:
275
+ stats = metrics_manager.get_retention_stats()
276
+ return {
277
+ "success": True,
278
+ "stats": stats,
279
+ }
280
+ except Exception as e:
281
+ return {"success": False, "error": str(e)}
282
+
283
+ return registry