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
gobby/cli/github.py ADDED
@@ -0,0 +1,263 @@
1
+ """
2
+ CLI commands for GitHub integration.
3
+
4
+ Provides commands for syncing gobby tasks with GitHub issues and PRs.
5
+ """
6
+
7
+ import asyncio
8
+ import json
9
+ import logging
10
+
11
+ import click
12
+
13
+ from gobby.integrations.github import GitHubIntegration
14
+ from gobby.mcp_proxy.manager import MCPClientManager
15
+ from gobby.storage.database import LocalDatabase
16
+ from gobby.storage.projects import LocalProjectManager
17
+ from gobby.storage.tasks import LocalTaskManager
18
+ from gobby.sync.github import GitHubSyncService
19
+ from gobby.utils.project_context import get_project_context
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ def get_github_deps() -> tuple[LocalTaskManager, MCPClientManager, LocalProjectManager, str]:
25
+ """Get dependencies for GitHub commands."""
26
+ db = LocalDatabase()
27
+ task_manager = LocalTaskManager(db)
28
+ project_manager = LocalProjectManager(db)
29
+ mcp_manager = MCPClientManager()
30
+
31
+ ctx = get_project_context()
32
+ if not ctx or not ctx.get("id"):
33
+ raise click.ClickException("Not in a gobby project directory. Run 'gobby init' first.")
34
+
35
+ project_id: str = ctx["id"]
36
+ return task_manager, mcp_manager, project_manager, project_id
37
+
38
+
39
+ def get_sync_service(repo: str | None = None) -> GitHubSyncService:
40
+ """Create GitHubSyncService for CLI commands."""
41
+ task_manager, mcp_manager, _, project_id = get_github_deps()
42
+ return GitHubSyncService(
43
+ mcp_manager=mcp_manager,
44
+ task_manager=task_manager,
45
+ project_id=project_id,
46
+ github_repo=repo,
47
+ )
48
+
49
+
50
+ @click.group()
51
+ def github() -> None:
52
+ """GitHub integration commands."""
53
+ pass
54
+
55
+
56
+ @github.command("status")
57
+ @click.option("--json", "json_format", is_flag=True, help="Output as JSON")
58
+ def github_status(json_format: bool) -> None:
59
+ """Show GitHub integration status."""
60
+ try:
61
+ task_manager, mcp_manager, project_manager, project_id = get_github_deps()
62
+
63
+ # Get project info
64
+ project = project_manager.get(project_id)
65
+ github_repo = project.github_repo if project else None
66
+
67
+ # Check GitHub MCP availability
68
+ github = GitHubIntegration(mcp_manager)
69
+ available = github.is_available()
70
+ unavailable_reason = github.get_unavailable_reason() if not available else None
71
+
72
+ # Count linked tasks
73
+ row = task_manager.db.fetchone(
74
+ "SELECT COUNT(*) as count FROM tasks WHERE project_id = ? AND github_issue_number IS NOT NULL",
75
+ (project_id,),
76
+ )
77
+ linked_count = row["count"] if row else 0
78
+
79
+ if json_format:
80
+ click.echo(
81
+ json.dumps(
82
+ {
83
+ "project_id": project_id,
84
+ "github_repo": github_repo,
85
+ "github_available": available,
86
+ "unavailable_reason": unavailable_reason,
87
+ "linked_tasks_count": linked_count,
88
+ },
89
+ indent=2,
90
+ )
91
+ )
92
+ else:
93
+ click.echo("GitHub Integration Status")
94
+ click.echo("=" * 40)
95
+ click.echo(f"Project ID: {project_id}")
96
+ click.echo(f"Linked repo: {github_repo or '(not linked)'}")
97
+ click.echo(f"GitHub MCP available: {'✓' if available else '✗'}")
98
+ if not available:
99
+ click.echo(f" Reason: {unavailable_reason}")
100
+ click.echo(f"Linked tasks: {linked_count}")
101
+
102
+ except click.ClickException:
103
+ raise
104
+ except Exception as e:
105
+ raise click.ClickException(str(e)) from None
106
+
107
+
108
+ @github.command("link")
109
+ @click.argument("repo")
110
+ def github_link(repo: str) -> None:
111
+ """Link a GitHub repo to this project.
112
+
113
+ REPO should be in 'owner/repo' format (e.g., 'anthropics/claude-code').
114
+ """
115
+ try:
116
+ _, _, project_manager, project_id = get_github_deps()
117
+
118
+ # Validate repo format
119
+ if "/" not in repo or repo.count("/") != 1:
120
+ raise click.ClickException(f"Invalid repo format: '{repo}'. Expected 'owner/repo'")
121
+
122
+ project_manager.update(project_id, github_repo=repo)
123
+ click.echo(f"✓ Linked project to GitHub repo: {repo}")
124
+
125
+ except click.ClickException:
126
+ raise
127
+ except Exception as e:
128
+ raise click.ClickException(str(e)) from None
129
+
130
+
131
+ @github.command("unlink")
132
+ def github_unlink() -> None:
133
+ """Remove GitHub repo link from this project."""
134
+ try:
135
+ _, _, project_manager, project_id = get_github_deps()
136
+
137
+ project_manager.update(project_id, github_repo=None)
138
+ click.echo("✓ Unlinked GitHub repo from project")
139
+
140
+ except click.ClickException:
141
+ raise
142
+ except Exception as e:
143
+ raise click.ClickException(str(e)) from None
144
+
145
+
146
+ @github.command("import")
147
+ @click.argument("repo", required=False)
148
+ @click.option("--labels", "-l", help="Comma-separated labels to filter issues")
149
+ @click.option(
150
+ "--state",
151
+ "-s",
152
+ type=click.Choice(["open", "closed", "all"]),
153
+ default="open",
154
+ help="Issue state filter",
155
+ )
156
+ @click.option("--json", "json_format", is_flag=True, help="Output as JSON")
157
+ def github_import(repo: str | None, labels: str | None, state: str, json_format: bool) -> None:
158
+ """Import GitHub issues as gobby tasks.
159
+
160
+ If REPO is not specified, uses the linked repo.
161
+ """
162
+ try:
163
+ task_manager, mcp_manager, project_manager, project_id = get_github_deps()
164
+
165
+ # Get repo from argument or project config
166
+ if not repo:
167
+ project = project_manager.get(project_id)
168
+ repo = project.github_repo if project else None
169
+ if not repo:
170
+ raise click.ClickException(
171
+ "No repo specified and project not linked to a GitHub repo. "
172
+ "Use 'gobby github link <owner/repo>' first or specify the repo."
173
+ )
174
+
175
+ service = GitHubSyncService(
176
+ mcp_manager=mcp_manager,
177
+ task_manager=task_manager,
178
+ project_id=project_id,
179
+ github_repo=repo,
180
+ )
181
+
182
+ # Run async import
183
+ label_list = labels.split(",") if labels else None
184
+ tasks = asyncio.run(service.import_github_issues(repo=repo, labels=label_list, state=state))
185
+
186
+ if json_format:
187
+ click.echo(json.dumps({"tasks": tasks, "count": len(tasks)}, indent=2))
188
+ else:
189
+ click.echo(f"✓ Imported {len(tasks)} issues from {repo}")
190
+ for task in tasks:
191
+ click.echo(f" - {task.get('id', 'unknown')}: {task.get('title', 'Untitled')}")
192
+
193
+ except click.ClickException:
194
+ raise
195
+ except Exception as e:
196
+ raise click.ClickException(str(e)) from None
197
+
198
+
199
+ @github.command("sync")
200
+ @click.argument("task_id")
201
+ @click.option("--json", "json_format", is_flag=True, help="Output as JSON")
202
+ def github_sync(task_id: str, json_format: bool) -> None:
203
+ """Sync a task to its linked GitHub issue.
204
+
205
+ Updates the GitHub issue title and body to match the task.
206
+ """
207
+ try:
208
+ service = get_sync_service()
209
+ result = asyncio.run(service.sync_task_to_github(task_id))
210
+
211
+ if json_format:
212
+ click.echo(json.dumps(result, indent=2))
213
+ else:
214
+ click.echo(f"✓ Synced task {task_id} to GitHub")
215
+
216
+ except click.ClickException:
217
+ raise
218
+ except ValueError as e:
219
+ raise click.ClickException(str(e)) from None
220
+ except Exception as e:
221
+ raise click.ClickException(str(e)) from None
222
+
223
+
224
+ @github.command("pr")
225
+ @click.argument("task_id")
226
+ @click.option("--head", "-H", "head_branch", required=True, help="Branch with changes")
227
+ @click.option("--base", "-b", "base_branch", default="main", help="Branch to merge into")
228
+ @click.option("--draft", "-d", is_flag=True, help="Create as draft PR")
229
+ @click.option("--json", "json_format", is_flag=True, help="Output as JSON")
230
+ def github_pr(
231
+ task_id: str,
232
+ head_branch: str,
233
+ base_branch: str,
234
+ draft: bool,
235
+ json_format: bool,
236
+ ) -> None:
237
+ """Create a GitHub PR for a task."""
238
+ try:
239
+ service = get_sync_service()
240
+ result = asyncio.run(
241
+ service.create_pr_for_task(
242
+ task_id=task_id,
243
+ head_branch=head_branch,
244
+ base_branch=base_branch,
245
+ draft=draft,
246
+ )
247
+ )
248
+
249
+ if json_format:
250
+ click.echo(json.dumps(result, indent=2))
251
+ else:
252
+ pr_number = result.get("number", "unknown")
253
+ pr_url = result.get("html_url") or result.get("url", "")
254
+ click.echo(f"✓ Created PR #{pr_number} for task {task_id}")
255
+ if pr_url:
256
+ click.echo(f" {pr_url}")
257
+
258
+ except click.ClickException:
259
+ raise
260
+ except ValueError as e:
261
+ raise click.ClickException(str(e)) from None
262
+ except Exception as e:
263
+ raise click.ClickException(str(e)) from None
gobby/cli/init.py ADDED
@@ -0,0 +1,53 @@
1
+ """
2
+ Project initialization commands.
3
+ """
4
+
5
+ import logging
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ import click
10
+
11
+ from gobby.utils.project_init import initialize_project
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ @click.command()
17
+ @click.option("--name", "-n", help="Project name")
18
+ @click.option("--github-url", "-g", help="GitHub repository URL")
19
+ @click.pass_context
20
+ def init(ctx: click.Context, name: str | None, github_url: str | None) -> None:
21
+ """Initialize a new Gobby project in the current directory."""
22
+ cwd = Path.cwd()
23
+
24
+ try:
25
+ result = initialize_project(cwd=cwd, name=name, github_url=github_url)
26
+ except Exception as e:
27
+ click.echo(f"Failed to initialize project: {e}", err=True)
28
+ sys.exit(1)
29
+
30
+ if result.already_existed:
31
+ click.echo(f"Project already initialized: {result.project_name}")
32
+ click.echo(f" Project ID: {result.project_id}")
33
+ else:
34
+ click.echo(f"Initialized project '{result.project_name}' in {cwd}")
35
+ click.echo(f" Project ID: {result.project_id}")
36
+ click.echo(f" Config: {cwd / '.gobby' / 'project.json'}")
37
+
38
+ # Show detected verification commands
39
+ if result.verification:
40
+ verification_dict = result.verification.to_dict()
41
+ if verification_dict:
42
+ click.echo(" Detected verification commands:")
43
+ for key, value in verification_dict.items():
44
+ if key != "custom":
45
+ if value is None:
46
+ continue
47
+ click.echo(f" {key}: {value}")
48
+ elif value: # custom dict
49
+ if isinstance(value, dict):
50
+ for custom_name, custom_cmd in value.items():
51
+ click.echo(f" {custom_name}: {custom_cmd}")
52
+ else:
53
+ click.echo(f" custom: {value}")