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,135 @@
1
+ """
2
+ Dependency management commands for tasks.
3
+ """
4
+
5
+ from typing import Literal
6
+
7
+ import click
8
+
9
+ from gobby.cli.tasks._utils import get_task_manager, resolve_task_id
10
+
11
+
12
+ @click.group("dep")
13
+ def dep_cmd() -> None:
14
+ """Manage task dependencies."""
15
+ pass
16
+
17
+
18
+ DependencyType = Literal["blocks", "related", "discovered-from"]
19
+
20
+
21
+ @dep_cmd.command("add")
22
+ @click.argument("task_id", metavar="TASK")
23
+ @click.argument("blocker_id", metavar="BLOCKER")
24
+ @click.option(
25
+ "--type",
26
+ "-t",
27
+ "dep_type",
28
+ default="blocks",
29
+ help="Dependency type (blocks, related, discovered-from)",
30
+ )
31
+ def dep_add(task_id: str, blocker_id: str, dep_type: DependencyType) -> None:
32
+ """Add a dependency: BLOCKER blocks TASK.
33
+
34
+ TASK/BLOCKER can be: #N (e.g., #1, #47), path (e.g., 1.2.3), or UUID.
35
+
36
+ Example: gobby tasks dep add #3 #1
37
+ means #1 blocks #3 (task #3 depends on task #1)
38
+ """
39
+ from gobby.storage.task_dependencies import TaskDependencyManager
40
+
41
+ manager = get_task_manager()
42
+ resolved = resolve_task_id(manager, task_id)
43
+ if not resolved:
44
+ return
45
+
46
+ blocker = resolve_task_id(manager, blocker_id)
47
+ if not blocker:
48
+ return
49
+
50
+ dep_manager = TaskDependencyManager(manager.db)
51
+ try:
52
+ dep_manager.add_dependency(resolved.id, blocker.id, dep_type)
53
+ click.echo(f"Added dependency: {blocker.id[:8]} {dep_type} {resolved.id[:8]}")
54
+ except ValueError as e:
55
+ click.echo(f"Error: {e}", err=True)
56
+
57
+
58
+ @dep_cmd.command("remove")
59
+ @click.argument("task_id", metavar="TASK")
60
+ @click.argument("blocker_id", metavar="BLOCKER")
61
+ def dep_remove(task_id: str, blocker_id: str) -> None:
62
+ """Remove a dependency between tasks.
63
+
64
+ TASK/BLOCKER can be: #N (e.g., #1, #47), path (e.g., 1.2.3), or UUID.
65
+ """
66
+ from gobby.storage.task_dependencies import TaskDependencyManager
67
+
68
+ manager = get_task_manager()
69
+ resolved = resolve_task_id(manager, task_id)
70
+ if not resolved:
71
+ return
72
+
73
+ blocker = resolve_task_id(manager, blocker_id)
74
+ if not blocker:
75
+ return
76
+
77
+ dep_manager = TaskDependencyManager(manager.db)
78
+ dep_manager.remove_dependency(resolved.id, blocker.id)
79
+ click.echo(f"Removed dependency between {resolved.id[:8]} and {blocker.id[:8]}")
80
+
81
+
82
+ @dep_cmd.command("tree")
83
+ @click.argument("task_id", metavar="TASK")
84
+ def dep_tree(task_id: str) -> None:
85
+ """Show dependency tree for a task.
86
+
87
+ TASK can be: #N (e.g., #1, #47), path (e.g., 1.2.3), or UUID.
88
+ """
89
+ from gobby.storage.task_dependencies import TaskDependencyManager
90
+
91
+ manager = get_task_manager()
92
+ resolved = resolve_task_id(manager, task_id)
93
+ if not resolved:
94
+ return
95
+
96
+ dep_manager = TaskDependencyManager(manager.db)
97
+ tree = dep_manager.get_dependency_tree(resolved.id)
98
+
99
+ click.echo(f"Dependency tree for {resolved.id[:8]} ({resolved.title}):")
100
+ click.echo("")
101
+
102
+ # Show blockers (what this task depends on)
103
+ if tree.get("blockers"):
104
+ click.echo("Blocked by:")
105
+ for b in tree["blockers"]:
106
+ status_icon = "✓" if b.get("status") == "closed" else "○"
107
+ click.echo(f" {status_icon} {b['id'][:8]}: {b.get('title', 'Unknown')}")
108
+ else:
109
+ click.echo("Blocked by: (none)")
110
+
111
+ # Show blocking (what depends on this task)
112
+ if tree.get("blocking"):
113
+ click.echo("\nBlocking:")
114
+ for b in tree["blocking"]:
115
+ status_icon = "✓" if b.get("status") == "closed" else "○"
116
+ click.echo(f" {status_icon} {b['id'][:8]}: {b.get('title', 'Unknown')}")
117
+ else:
118
+ click.echo("\nBlocking: (none)")
119
+
120
+
121
+ @dep_cmd.command("cycles")
122
+ def dep_cycles() -> None:
123
+ """Check for dependency cycles."""
124
+ from gobby.storage.task_dependencies import TaskDependencyManager
125
+
126
+ manager = get_task_manager()
127
+ dep_manager = TaskDependencyManager(manager.db)
128
+ cycles = dep_manager.check_cycles()
129
+
130
+ if cycles:
131
+ click.echo(f"Found {len(cycles)} dependency cycles:", err=True)
132
+ for cycle in cycles:
133
+ click.echo(f" {' -> '.join(c[:8] for c in cycle)}", err=True)
134
+ else:
135
+ click.echo("✓ No dependency cycles found")
@@ -0,0 +1,63 @@
1
+ """
2
+ Label management commands for tasks.
3
+ """
4
+
5
+ import click
6
+
7
+ from gobby.cli.tasks._utils import get_task_manager, resolve_task_id
8
+
9
+
10
+ @click.group("label")
11
+ def label_cmd() -> None:
12
+ """Manage task labels."""
13
+ pass
14
+
15
+
16
+ @label_cmd.command("add")
17
+ @click.argument("task_id", metavar="TASK")
18
+ @click.argument("label")
19
+ def add_label(task_id: str, label: str) -> None:
20
+ """Add a label to a task.
21
+
22
+ TASK can be: #N (e.g., #1, #47), path (e.g., 1.2.3), or UUID.
23
+ """
24
+ manager = get_task_manager()
25
+ resolved = resolve_task_id(manager, task_id)
26
+ if not resolved:
27
+ click.secho(f"Error: Could not resolve task '{task_id}'", fg="red", err=True)
28
+ raise SystemExit(1)
29
+
30
+ try:
31
+ manager.add_label(resolved.id, label)
32
+ except ValueError as e:
33
+ click.secho(f"Error: {e}", fg="red", err=True)
34
+ raise SystemExit(1) from None
35
+ except Exception as e:
36
+ click.secho(f"Unexpected error adding label: {e}", fg="red", err=True)
37
+ raise SystemExit(1) from None
38
+ click.echo(f"Added label '{label}' to task {resolved.id}")
39
+
40
+
41
+ @label_cmd.command("remove")
42
+ @click.argument("task_id", metavar="TASK")
43
+ @click.argument("label")
44
+ def remove_label(task_id: str, label: str) -> None:
45
+ """Remove a label from a task.
46
+
47
+ TASK can be: #N (e.g., #1, #47), path (e.g., 1.2.3), or UUID.
48
+ """
49
+ manager = get_task_manager()
50
+ resolved = resolve_task_id(manager, task_id)
51
+ if not resolved:
52
+ click.secho(f"Error: Could not resolve task '{task_id}'", fg="red", err=True)
53
+ raise SystemExit(1)
54
+
55
+ try:
56
+ manager.remove_label(resolved.id, label)
57
+ except ValueError as e:
58
+ click.secho(f"Error: {e}", fg="red", err=True)
59
+ raise SystemExit(1) from None
60
+ except Exception as e:
61
+ click.secho(f"Unexpected error removing label: {e}", fg="red", err=True)
62
+ raise SystemExit(1) from None
63
+ click.echo(f"Removed label '{label}' from task {resolved.id}")
@@ -0,0 +1,273 @@
1
+ """
2
+ Task management commands - entry point and misc utilities.
3
+ """
4
+
5
+ import logging
6
+ from typing import Any
7
+
8
+ import click
9
+
10
+ from gobby.cli.tasks._utils import (
11
+ check_tasks_enabled,
12
+ get_sync_manager,
13
+ get_task_manager,
14
+ )
15
+ from gobby.cli.tasks.ai import (
16
+ complexity_cmd,
17
+ expand_all_cmd,
18
+ expand_task_cmd,
19
+ generate_criteria_cmd,
20
+ suggest_cmd,
21
+ validate_task_cmd,
22
+ )
23
+ from gobby.cli.tasks.commits import commit_cmd, diff_cmd
24
+ from gobby.cli.tasks.crud import (
25
+ blocked_tasks,
26
+ close_task_cmd,
27
+ create_task,
28
+ de_escalate_cmd,
29
+ delete_task,
30
+ list_tasks,
31
+ ready_tasks,
32
+ reopen_task_cmd,
33
+ show_task,
34
+ task_stats,
35
+ update_task,
36
+ validation_history_cmd,
37
+ )
38
+ from gobby.cli.tasks.deps import dep_cmd
39
+ from gobby.cli.tasks.labels import label_cmd
40
+ from gobby.cli.tasks.search import reindex_tasks, search_tasks
41
+
42
+ logger = logging.getLogger(__name__)
43
+
44
+
45
+ @click.group()
46
+ def tasks() -> None:
47
+ """Manage development tasks."""
48
+ check_tasks_enabled()
49
+
50
+
51
+ # Register CRUD commands from extracted module
52
+ tasks.add_command(list_tasks)
53
+ tasks.add_command(ready_tasks)
54
+ tasks.add_command(blocked_tasks)
55
+ tasks.add_command(task_stats)
56
+ tasks.add_command(create_task)
57
+ tasks.add_command(show_task)
58
+ tasks.add_command(update_task)
59
+ tasks.add_command(close_task_cmd)
60
+ tasks.add_command(reopen_task_cmd)
61
+ tasks.add_command(delete_task)
62
+ tasks.add_command(de_escalate_cmd)
63
+ tasks.add_command(validation_history_cmd)
64
+
65
+ # Register AI-powered commands from extracted module
66
+ tasks.add_command(validate_task_cmd)
67
+ tasks.add_command(generate_criteria_cmd)
68
+ tasks.add_command(expand_task_cmd)
69
+ tasks.add_command(complexity_cmd)
70
+ tasks.add_command(expand_all_cmd)
71
+ tasks.add_command(suggest_cmd)
72
+
73
+ # Register search commands
74
+ tasks.add_command(search_tasks)
75
+ tasks.add_command(reindex_tasks)
76
+
77
+
78
+ @tasks.command("sync")
79
+ @click.option("--import", "do_import", is_flag=True, help="Import tasks from JSONL")
80
+ @click.option("--export", "do_export", is_flag=True, help="Export tasks to JSONL")
81
+ @click.option("--quiet", "-q", is_flag=True, help="Suppress output")
82
+ def sync_tasks(do_import: bool, do_export: bool, quiet: bool) -> None:
83
+ """Sync tasks with .gobby/tasks.jsonl.
84
+
85
+ If neither --import nor --export specified, does both.
86
+ """
87
+ manager = get_sync_manager()
88
+
89
+ # Default to both if neither specified
90
+ if not do_import and not do_export:
91
+ do_import = True
92
+ do_export = True
93
+
94
+ if do_import:
95
+ if not quiet:
96
+ click.echo("Importing tasks...")
97
+ manager.import_from_jsonl()
98
+
99
+ if do_export:
100
+ if not quiet:
101
+ click.echo("Exporting tasks...")
102
+ manager.export_to_jsonl()
103
+
104
+ if not quiet:
105
+ click.echo("Sync completed")
106
+
107
+
108
+ @tasks.group("compact")
109
+ def compact_cmd() -> None:
110
+ """Task compaction commands."""
111
+ pass
112
+
113
+
114
+ @compact_cmd.command("analyze")
115
+ @click.option("--days", type=int, default=30, help="Days blocked threshold")
116
+ def compact_analyze(days: int) -> None:
117
+ """Find tasks eligible for compaction."""
118
+ manager = get_task_manager()
119
+ from gobby.storage.compaction import TaskCompactor
120
+
121
+ compactor = TaskCompactor(manager)
122
+ candidates = compactor.find_candidates(days_closed=days)
123
+
124
+ if not candidates:
125
+ click.echo("No compaction candidates found.")
126
+ return
127
+
128
+ click.echo(f"Found {len(candidates)} candidates closed > {days} days:")
129
+ for task in candidates:
130
+ click.echo(f" {task['id']}: {task['title']} (Updated: {task['updated_at']})")
131
+
132
+
133
+ @compact_cmd.command("apply")
134
+ @click.option("--id", "task_id", required=True, help="Task ID to compact")
135
+ @click.option("--summary", required=True, help="Summary text or file path (@path)")
136
+ def compact_apply(task_id: str, summary: str) -> None:
137
+ """Compact a task with a summary."""
138
+ manager = get_task_manager()
139
+ from gobby.storage.compaction import TaskCompactor
140
+
141
+ # Handle file input for summary
142
+ if summary.startswith("@"):
143
+ path = summary[1:]
144
+ try:
145
+ with open(path) as f:
146
+ summary_content = f.read()
147
+ except Exception as e:
148
+ click.echo(f"Error reading summary file: {e}", err=True)
149
+ return
150
+ else:
151
+ summary_content = summary
152
+
153
+ compactor = TaskCompactor(manager)
154
+ try:
155
+ compactor.compact_task(task_id, summary_content)
156
+ click.echo(f"Compacted task {task_id}.")
157
+ except Exception as e:
158
+ click.echo(f"Error compacting task: {e}", err=True)
159
+
160
+
161
+ @compact_cmd.command("stats")
162
+ def compact_stats() -> None:
163
+ """Show compaction statistics."""
164
+ manager = get_task_manager()
165
+ from gobby.storage.compaction import TaskCompactor
166
+
167
+ compactor = TaskCompactor(manager)
168
+ stats = compactor.get_stats()
169
+
170
+ click.echo("Compaction Statistics:")
171
+ click.echo(f" Total Closed: {stats['total_closed']}")
172
+ click.echo(f" Compacted: {stats['compacted']}")
173
+ click.echo(f" Rate: {stats['rate']}%")
174
+
175
+
176
+ # Register subgroups from extracted modules
177
+ tasks.add_command(dep_cmd)
178
+ tasks.add_command(label_cmd)
179
+ tasks.add_command(commit_cmd)
180
+ tasks.add_command(diff_cmd)
181
+
182
+
183
+ @tasks.group("import")
184
+ def import_cmd() -> None:
185
+ """Import tasks from external sources."""
186
+ pass
187
+
188
+
189
+ @import_cmd.command("github")
190
+ @click.argument("url")
191
+ @click.option("--limit", default=50, help="Max issues to import")
192
+ def import_github(url: str, limit: int) -> None:
193
+ """Import open issues from GitHub."""
194
+ import asyncio
195
+
196
+ manager = get_sync_manager()
197
+
198
+ # We need to run async method
199
+ async def run() -> dict[str, Any]:
200
+ result: dict[str, Any] = await manager.import_from_github_issues(url, limit=limit)
201
+ return result
202
+
203
+ try:
204
+ result = asyncio.run(run())
205
+
206
+ if result["success"]:
207
+ click.echo(result["message"])
208
+ for issue_id in result["imported"]:
209
+ click.echo(f" Imported {issue_id}")
210
+ else:
211
+ click.echo(f"Error: {result['error']}", err=True)
212
+ except Exception as e:
213
+ click.echo(f"Failed to run import: {e}", err=True)
214
+
215
+
216
+ @tasks.command("doctor")
217
+ def doctor_cmd() -> None:
218
+ """Validate task data integrity."""
219
+ manager = get_task_manager()
220
+ from gobby.utils.validation import TaskValidator
221
+
222
+ validator = TaskValidator(manager)
223
+ results = validator.validate_all()
224
+
225
+ issues_found = False
226
+
227
+ orphans = results["orphan_dependencies"]
228
+ if orphans:
229
+ issues_found = True
230
+ click.echo(f"Found {len(orphans)} orphan dependencies:", err=True)
231
+ for d in orphans:
232
+ click.echo(f" Dependency {d['id']}: {d['task_id']} -> {d['depends_on']}", err=True)
233
+ else:
234
+ click.echo("✓ No orphan dependencies")
235
+
236
+ invalid_projects = results["invalid_projects"]
237
+ if invalid_projects:
238
+ issues_found = True
239
+ click.echo(f"Found {len(invalid_projects)} tasks with invalid projects:", err=True)
240
+ for t in invalid_projects:
241
+ click.echo(f" Task {t['id']}: {t['title']} (Project ID: {t['project_id']})", err=True)
242
+ else:
243
+ click.echo("✓ No invalid projects")
244
+
245
+ cycles = results["cycles"]
246
+ if cycles:
247
+ issues_found = True
248
+ click.echo(f"Found {len(cycles)} dependency cycles:", err=True)
249
+ for cycle in cycles:
250
+ click.echo(f" Cycle: {' -> '.join(cycle)}", err=True)
251
+ else:
252
+ click.echo("✓ No dependency cycles")
253
+
254
+ if issues_found:
255
+ click.echo("\nIssues found. Run 'gobby tasks clean' to fix fixable issues.")
256
+ # Exit with error code if issues found
257
+ # (Click handles exit code but we can explicitly exit if needed, usually just return is fine unless we want non-zero)
258
+
259
+
260
+ @tasks.command("clean")
261
+ @click.confirmation_option(prompt="This will remove orphaned dependencies. Are you sure?")
262
+ def clean_cmd() -> None:
263
+ """Fix data integrity issues (remove orphans)."""
264
+ manager = get_task_manager()
265
+ from gobby.utils.validation import TaskValidator
266
+
267
+ validator = TaskValidator(manager)
268
+ count = validator.clean_orphans()
269
+
270
+ if count > 0:
271
+ click.echo(f"Removed {count} orphan dependencies.")
272
+ else:
273
+ click.echo("No orphan dependencies found.")
@@ -0,0 +1,178 @@
1
+ """
2
+ Search commands for task management.
3
+ """
4
+
5
+ import json
6
+
7
+ import click
8
+
9
+ from gobby.cli.tasks._utils import (
10
+ get_task_manager,
11
+ normalize_status,
12
+ )
13
+ from gobby.cli.utils import resolve_project_ref
14
+
15
+
16
+ @click.command("search")
17
+ @click.argument("query")
18
+ @click.option(
19
+ "--status",
20
+ "-s",
21
+ help="Filter by status (open, in_progress, review, closed). Comma-separated for multiple.",
22
+ )
23
+ @click.option(
24
+ "--type",
25
+ "-t",
26
+ "task_type",
27
+ help="Filter by task type (task, bug, feature, epic)",
28
+ )
29
+ @click.option(
30
+ "--priority",
31
+ "-p",
32
+ type=int,
33
+ help="Filter by priority (1=High, 2=Medium, 3=Low)",
34
+ )
35
+ @click.option(
36
+ "--project",
37
+ "project_ref",
38
+ help="Filter by project (name or UUID). Default: current project.",
39
+ )
40
+ @click.option(
41
+ "--all-projects",
42
+ "-a",
43
+ is_flag=True,
44
+ help="Search all projects instead of just the current project",
45
+ )
46
+ @click.option(
47
+ "--limit",
48
+ "-n",
49
+ default=20,
50
+ help="Maximum number of results (default: 20)",
51
+ )
52
+ @click.option(
53
+ "--min-score",
54
+ type=float,
55
+ default=0.0,
56
+ help="Minimum similarity score threshold (0.0-1.0)",
57
+ )
58
+ @click.option(
59
+ "--json",
60
+ "json_format",
61
+ is_flag=True,
62
+ help="Output as JSON",
63
+ )
64
+ def search_tasks(
65
+ query: str,
66
+ status: str | None,
67
+ task_type: str | None,
68
+ priority: int | None,
69
+ project_ref: str | None,
70
+ all_projects: bool,
71
+ limit: int,
72
+ min_score: float,
73
+ json_format: bool,
74
+ ) -> None:
75
+ """Search tasks using semantic TF-IDF search.
76
+
77
+ QUERY is the natural language search query.
78
+
79
+ Examples:
80
+
81
+ gobby tasks search "authentication"
82
+
83
+ gobby tasks search "database migration" --status open
84
+
85
+ gobby tasks search "refactor" --type bug --limit 10
86
+ """
87
+ if not query.strip():
88
+ click.echo("Error: Query cannot be empty.", err=True)
89
+ return
90
+
91
+ # Parse comma-separated statuses
92
+ status_filter: str | list[str] | None = None
93
+ if status:
94
+ if "," in status:
95
+ status_filter = [normalize_status(s.strip()) for s in status.split(",")]
96
+ else:
97
+ status_filter = normalize_status(status)
98
+
99
+ # Resolve project
100
+ project_id = None
101
+ if not all_projects:
102
+ project_id = resolve_project_ref(project_ref)
103
+
104
+ manager = get_task_manager()
105
+
106
+ # Perform search
107
+ results = manager.search_tasks(
108
+ query=query.strip(),
109
+ project_id=project_id,
110
+ status=status_filter,
111
+ task_type=task_type,
112
+ priority=priority,
113
+ limit=limit,
114
+ min_score=min_score,
115
+ )
116
+
117
+ if json_format:
118
+ output = {
119
+ "query": query.strip(),
120
+ "count": len(results),
121
+ "tasks": [
122
+ {
123
+ **task.to_dict(),
124
+ "score": round(score, 4),
125
+ }
126
+ for task, score in results
127
+ ],
128
+ }
129
+ click.echo(json.dumps(output, indent=2, default=str))
130
+ return
131
+
132
+ if not results:
133
+ click.echo(f"No tasks found matching '{query}'.")
134
+ return
135
+
136
+ click.echo(f"Found {len(results)} task(s) matching '{query}':\n")
137
+
138
+ # Print header
139
+ click.echo(f"{'#':<6} {'Score':<7} {'Status':<12} {'Pri':<4} {'Title'}")
140
+ click.echo("-" * 70)
141
+
142
+ for task, score in results:
143
+ # Format similar to list_tasks but with score
144
+ seq_ref = f"#{task.seq_num}" if task.seq_num else task.id[:8]
145
+ status_display = task.status[:11] if task.status else ""
146
+ pri_display = str(task.priority) if task.priority else ""
147
+ title_display = task.title[:45] if task.title else ""
148
+
149
+ click.echo(
150
+ f"{seq_ref:<6} {score:<7.3f} {status_display:<12} {pri_display:<4} {title_display}"
151
+ )
152
+
153
+
154
+ @click.command("reindex")
155
+ @click.option(
156
+ "--all-projects",
157
+ "-a",
158
+ is_flag=True,
159
+ help="Reindex all projects instead of just the current project",
160
+ )
161
+ def reindex_tasks(all_projects: bool) -> None:
162
+ """Rebuild the task search index.
163
+
164
+ Use this after bulk operations or if search results seem stale.
165
+ """
166
+ # Resolve project
167
+ project_id = None
168
+ if not all_projects:
169
+ project_id = resolve_project_ref(None)
170
+
171
+ manager = get_task_manager()
172
+
173
+ click.echo("Rebuilding task search index...")
174
+ stats = manager.reindex_search(project_id)
175
+
176
+ click.echo(f"Search index rebuilt with {stats.get('item_count', 0)} tasks.")
177
+ if stats.get("vocabulary_size"):
178
+ click.echo(f"Vocabulary size: {stats['vocabulary_size']}")
gobby/cli/tui.py ADDED
@@ -0,0 +1,34 @@
1
+ """TUI command for launching the Gobby dashboard."""
2
+
3
+ import click
4
+
5
+
6
+ @click.command()
7
+ @click.option(
8
+ "--port",
9
+ "-p",
10
+ default=8765,
11
+ help="Daemon HTTP port",
12
+ show_default=True,
13
+ )
14
+ @click.option(
15
+ "--ws-port",
16
+ "-w",
17
+ default=8766,
18
+ help="Daemon WebSocket port",
19
+ show_default=True,
20
+ )
21
+ def ui(port: int, ws_port: int) -> None:
22
+ """Launch the Gobby TUI dashboard.
23
+
24
+ The TUI provides a terminal-based interface for monitoring and managing
25
+ Gobby sessions, tasks, agents, and more.
26
+
27
+ Requires the Gobby daemon to be running (gobby start).
28
+ """
29
+ from gobby.tui.app import run_tui
30
+
31
+ daemon_url = f"http://localhost:{port}"
32
+ ws_url = f"ws://localhost:{ws_port}"
33
+
34
+ run_tui(daemon_url=daemon_url, ws_url=ws_url)