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,351 @@
1
+ """
2
+ Task sync and commit linking MCP tools module.
3
+
4
+ Provides tools for task synchronization and commit linking:
5
+ - sync_tasks: Manually trigger task import/export
6
+ - get_sync_status: Get current sync status
7
+ - link_commit: Link a git commit to a task
8
+ - unlink_commit: Unlink a git commit from a task
9
+ - auto_link_commits: Auto-detect and link commits mentioning task IDs
10
+ - get_task_diff: Get combined diff for all commits linked to a task
11
+
12
+ Extracted from tasks.py using Strangler Fig pattern for code decomposition.
13
+ """
14
+
15
+ from collections.abc import Callable
16
+ from typing import TYPE_CHECKING, Any
17
+
18
+ from gobby.mcp_proxy.tools.internal import InternalToolRegistry
19
+ from gobby.storage.tasks import TaskNotFoundError
20
+ from gobby.utils.project_context import get_project_context
21
+
22
+ if TYPE_CHECKING:
23
+ from gobby.storage.projects import LocalProjectManager
24
+ from gobby.storage.tasks import LocalTaskManager
25
+ from gobby.sync.tasks import TaskSyncManager
26
+
27
+ __all__ = ["create_sync_registry"]
28
+
29
+
30
+ def get_current_project_id() -> str | None:
31
+ """Get the current project ID from context."""
32
+ context = get_project_context()
33
+ return context.get("id") if context else None
34
+
35
+
36
+ class SyncToolRegistry(InternalToolRegistry):
37
+ """Registry for sync tools with test-friendly get_tool method."""
38
+
39
+ def get_tool(self, name: str) -> Callable[..., Any] | None:
40
+ """Get a tool function by name (for testing)."""
41
+ tool = self._tools.get(name)
42
+ return tool.func if tool else None
43
+
44
+
45
+ def create_sync_registry(
46
+ sync_manager: "TaskSyncManager | None" = None,
47
+ task_manager: "LocalTaskManager | None" = None,
48
+ project_manager: "LocalProjectManager | None" = None,
49
+ auto_link_commits_fn: Callable[..., Any] | None = None,
50
+ get_task_diff_fn: Callable[..., Any] | None = None,
51
+ ) -> SyncToolRegistry:
52
+ """
53
+ Create a registry with task sync and commit linking tools.
54
+
55
+ Args:
56
+ sync_manager: TaskSyncManager instance
57
+ task_manager: LocalTaskManager instance (required for task ID resolution)
58
+ project_manager: LocalProjectManager instance (for repo_path lookup)
59
+ auto_link_commits_fn: Function for auto-linking commits (injectable for testing)
60
+ get_task_diff_fn: Function for getting task diff (injectable for testing)
61
+
62
+ Returns:
63
+ SyncToolRegistry with sync tools registered
64
+ """
65
+ # Lazy import to avoid circular dependency
66
+ from gobby.mcp_proxy.tools.tasks import resolve_task_id_for_mcp
67
+
68
+ registry = SyncToolRegistry(
69
+ name="gobby-tasks-sync",
70
+ description="Task synchronization and commit linking tools",
71
+ )
72
+
73
+ if sync_manager is None:
74
+ raise ValueError("sync_manager is required")
75
+ if task_manager is None:
76
+ raise ValueError("task_manager is required for task ID resolution")
77
+
78
+ # --- sync_tasks ---
79
+
80
+ def sync_tasks(direction: str = "both") -> dict[str, Any]:
81
+ """Manually trigger task synchronization."""
82
+ valid_directions = ("import", "export", "both")
83
+ if direction not in valid_directions:
84
+ return {
85
+ "error": f"Invalid direction '{direction}'. Must be one of: {', '.join(valid_directions)}"
86
+ }
87
+
88
+ result = {}
89
+ if direction in ["import", "both"]:
90
+ # Get current project ID for context-aware sync
91
+ project_id = get_current_project_id()
92
+ sync_manager.import_from_jsonl(project_id=project_id)
93
+ result["import"] = "completed"
94
+
95
+ if direction in ["export", "both"]:
96
+ # Get current project ID for context-aware sync
97
+ project_id = get_current_project_id()
98
+ sync_manager.export_to_jsonl(project_id=project_id)
99
+ result["export"] = "completed"
100
+
101
+ return result
102
+
103
+ registry.register(
104
+ name="sync_tasks",
105
+ description="Manually trigger task synchronization.",
106
+ input_schema={
107
+ "type": "object",
108
+ "properties": {
109
+ "direction": {
110
+ "type": "string",
111
+ "description": '"import", "export", or "both"',
112
+ "default": "both",
113
+ },
114
+ },
115
+ },
116
+ func=sync_tasks,
117
+ )
118
+
119
+ # --- get_sync_status ---
120
+
121
+ def get_sync_status() -> dict[str, Any]:
122
+ """Get current synchronization status."""
123
+ result: dict[str, Any] = sync_manager.get_sync_status()
124
+ return result
125
+
126
+ registry.register(
127
+ name="get_sync_status",
128
+ description="Get current synchronization status.",
129
+ input_schema={"type": "object", "properties": {}},
130
+ func=get_sync_status,
131
+ )
132
+
133
+ # --- link_commit ---
134
+
135
+ def link_commit(task_id: str, commit_sha: str) -> dict[str, Any]:
136
+ """Link a git commit to a task."""
137
+ # Resolve task reference
138
+ try:
139
+ resolved_task_id = resolve_task_id_for_mcp(task_manager, task_id)
140
+ except (TaskNotFoundError, ValueError) as e:
141
+ return {"error": f"Invalid task_id: {e}"}
142
+
143
+ # Get project repo path for git operations
144
+ ctx = get_project_context()
145
+ repo_path = None
146
+ if ctx and ctx.get("id") and project_manager:
147
+ project = project_manager.get(ctx["id"])
148
+ if project:
149
+ repo_path = project.repo_path
150
+
151
+ try:
152
+ task = task_manager.link_commit(resolved_task_id, commit_sha, cwd=repo_path)
153
+ return {
154
+ "task_id": task.id,
155
+ "commits": task.commits or [],
156
+ }
157
+ except ValueError as e:
158
+ return {"error": str(e)}
159
+
160
+ registry.register(
161
+ name="link_commit",
162
+ description="Link a git commit to a task. Useful for tracking which commits implement a task.",
163
+ input_schema={
164
+ "type": "object",
165
+ "properties": {
166
+ "task_id": {
167
+ "type": "string",
168
+ "description": "Task reference: #N, N (seq_num), path (1.2.3), or UUID",
169
+ },
170
+ "commit_sha": {
171
+ "type": "string",
172
+ "description": "Git commit SHA (short or full)",
173
+ },
174
+ },
175
+ "required": ["task_id", "commit_sha"],
176
+ },
177
+ func=link_commit,
178
+ )
179
+
180
+ # --- unlink_commit ---
181
+
182
+ def unlink_commit(task_id: str, commit_sha: str) -> dict[str, Any]:
183
+ """Unlink a git commit from a task."""
184
+ # Resolve task reference
185
+ try:
186
+ resolved_task_id = resolve_task_id_for_mcp(task_manager, task_id)
187
+ except (TaskNotFoundError, ValueError) as e:
188
+ return {"error": f"Invalid task_id: {e}"}
189
+
190
+ # Get project repo path for git operations
191
+ ctx = get_project_context()
192
+ repo_path = None
193
+ if ctx and ctx.get("id") and project_manager:
194
+ project = project_manager.get(ctx["id"])
195
+ if project:
196
+ repo_path = project.repo_path
197
+
198
+ try:
199
+ task = task_manager.unlink_commit(resolved_task_id, commit_sha, cwd=repo_path)
200
+ return {
201
+ "task_id": task.id,
202
+ "commits": task.commits or [],
203
+ }
204
+ except ValueError as e:
205
+ return {"error": str(e)}
206
+
207
+ registry.register(
208
+ name="unlink_commit",
209
+ description="Unlink a git commit from a task.",
210
+ input_schema={
211
+ "type": "object",
212
+ "properties": {
213
+ "task_id": {
214
+ "type": "string",
215
+ "description": "Task reference: #N, N (seq_num), path (1.2.3), or UUID",
216
+ },
217
+ "commit_sha": {
218
+ "type": "string",
219
+ "description": "Git commit SHA to unlink",
220
+ },
221
+ },
222
+ "required": ["task_id", "commit_sha"],
223
+ },
224
+ func=unlink_commit,
225
+ )
226
+
227
+ # --- auto_link_commits ---
228
+
229
+ def auto_link_commits(
230
+ task_id: str | None = None,
231
+ since: str | None = None,
232
+ ) -> dict[str, Any]:
233
+ """Auto-detect and link commits that mention task IDs."""
234
+ if auto_link_commits_fn is None:
235
+ return {"error": "auto_link_commits_fn not configured"}
236
+
237
+ # Resolve task reference if provided
238
+ resolved_task_id = None
239
+ if task_id:
240
+ try:
241
+ resolved_task_id = resolve_task_id_for_mcp(task_manager, task_id)
242
+ except (TaskNotFoundError, ValueError) as e:
243
+ return {"error": f"Invalid task_id: {e}"}
244
+
245
+ # Get project repo_path
246
+ ctx = get_project_context()
247
+ repo_path = None
248
+ if ctx and ctx.get("id") and project_manager:
249
+ project = project_manager.get(ctx["id"])
250
+ if project:
251
+ repo_path = project.repo_path
252
+
253
+ result = auto_link_commits_fn(
254
+ task_manager=task_manager,
255
+ task_id=resolved_task_id,
256
+ since=since,
257
+ cwd=repo_path,
258
+ )
259
+
260
+ return {
261
+ "linked_tasks": result.linked_tasks,
262
+ "total_linked": result.total_linked,
263
+ "skipped": result.skipped,
264
+ }
265
+
266
+ registry.register(
267
+ name="auto_link_commits",
268
+ description="Auto-detect and link commits that mention task IDs in their messages. "
269
+ "Supports patterns: [gt-xxxxx], gt-xxxxx:, Implements/Fixes/Closes gt-xxxxx.",
270
+ input_schema={
271
+ "type": "object",
272
+ "properties": {
273
+ "task_id": {
274
+ "type": "string",
275
+ "description": "Filter to specific task (#N, N, path, or UUID). Optional.",
276
+ "default": None,
277
+ },
278
+ "since": {
279
+ "type": "string",
280
+ "description": "Git --since parameter (e.g., '1 week ago', '2024-01-01')",
281
+ "default": None,
282
+ },
283
+ },
284
+ },
285
+ func=auto_link_commits,
286
+ )
287
+
288
+ # --- get_task_diff ---
289
+
290
+ def get_task_diff_tool(
291
+ task_id: str,
292
+ include_uncommitted: bool = False,
293
+ ) -> dict[str, Any]:
294
+ """Get the combined diff for all commits linked to a task."""
295
+ # Resolve task reference
296
+ try:
297
+ resolved_task_id = resolve_task_id_for_mcp(task_manager, task_id)
298
+ except (TaskNotFoundError, ValueError) as e:
299
+ return {"error": f"Invalid task_id: {e}"}
300
+
301
+ task = task_manager.get_task(resolved_task_id)
302
+ if not task:
303
+ return {"error": f"Task {task_id} not found"}
304
+
305
+ if get_task_diff_fn is None:
306
+ return {"error": "get_task_diff_fn not configured"}
307
+
308
+ # Get project repo_path
309
+ repo_path = None
310
+ if project_manager and task.project_id:
311
+ project = project_manager.get(task.project_id)
312
+ if project:
313
+ repo_path = project.repo_path
314
+
315
+ result = get_task_diff_fn(
316
+ task_id=resolved_task_id,
317
+ task_manager=task_manager,
318
+ include_uncommitted=include_uncommitted,
319
+ cwd=repo_path,
320
+ )
321
+
322
+ return {
323
+ "diff": result.diff,
324
+ "commits": result.commits,
325
+ "has_uncommitted_changes": result.has_uncommitted_changes,
326
+ "file_count": result.file_count,
327
+ }
328
+
329
+ registry.register(
330
+ name="get_task_diff",
331
+ description="Get the combined diff for all commits linked to a task. "
332
+ "Optionally include uncommitted changes.",
333
+ input_schema={
334
+ "type": "object",
335
+ "properties": {
336
+ "task_id": {
337
+ "type": "string",
338
+ "description": "Task reference: #N, N (seq_num), path (1.2.3), or UUID",
339
+ },
340
+ "include_uncommitted": {
341
+ "type": "boolean",
342
+ "description": "Include uncommitted changes in the diff",
343
+ "default": False,
344
+ },
345
+ },
346
+ "required": ["task_id"],
347
+ },
348
+ func=get_task_diff_tool,
349
+ )
350
+
351
+ return registry