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,25 @@
1
+ """Task tools package.
2
+
3
+ This package provides MCP tools for task management. Re-exports maintain
4
+ backwards compatibility with the original tasks.py module.
5
+
6
+ Public API:
7
+ - create_task_registry: Factory function to create the task tool registry
8
+ - resolve_task_id_for_mcp: Resolve task references to UUIDs
9
+
10
+ Internal (exported for test compatibility):
11
+ - SKIP_REASONS: Reasons that skip validation on close
12
+ - _infer_category: Infer task category from title/description
13
+ """
14
+
15
+ from gobby.mcp_proxy.tools.tasks._factory import create_task_registry
16
+ from gobby.mcp_proxy.tools.tasks._helpers import SKIP_REASONS, _infer_category
17
+ from gobby.mcp_proxy.tools.tasks._resolution import resolve_task_id_for_mcp
18
+
19
+ __all__ = [
20
+ "create_task_registry",
21
+ "resolve_task_id_for_mcp",
22
+ # Internal exports for backward compatibility
23
+ "SKIP_REASONS",
24
+ "_infer_category",
25
+ ]
@@ -0,0 +1,112 @@
1
+ """Registry context for task tools.
2
+
3
+ Provides RegistryContext dataclass that bundles shared state and helpers
4
+ used across task tool modules.
5
+ """
6
+
7
+ from dataclasses import dataclass, field
8
+ from typing import TYPE_CHECKING
9
+
10
+ from gobby.storage.projects import LocalProjectManager
11
+ from gobby.storage.session_tasks import SessionTaskManager
12
+ from gobby.storage.task_dependencies import TaskDependencyManager
13
+ from gobby.storage.tasks import LocalTaskManager
14
+ from gobby.utils.project_context import get_project_context
15
+ from gobby.workflows.definitions import WorkflowState
16
+ from gobby.workflows.state_manager import WorkflowStateManager
17
+
18
+ if TYPE_CHECKING:
19
+ from gobby.agents.runner import AgentRunner
20
+ from gobby.config.app import DaemonConfig
21
+ from gobby.config.tasks import TaskValidationConfig
22
+ from gobby.sync.tasks import TaskSyncManager
23
+ from gobby.tasks.expansion import TaskExpander
24
+ from gobby.tasks.validation import TaskValidator
25
+
26
+
27
+ @dataclass
28
+ class RegistryContext:
29
+ """Shared context for task tool registries.
30
+
31
+ Bundles managers, config, and helper methods used across all task tools.
32
+ """
33
+
34
+ # Core managers
35
+ task_manager: LocalTaskManager
36
+ sync_manager: "TaskSyncManager"
37
+
38
+ # Optional managers
39
+ task_expander: "TaskExpander | None" = None
40
+ task_validator: "TaskValidator | None" = None
41
+ agent_runner: "AgentRunner | None" = None
42
+ config: "DaemonConfig | None" = None
43
+
44
+ # Derived managers (initialized in __post_init__)
45
+ dep_manager: TaskDependencyManager = field(init=False)
46
+ session_task_manager: SessionTaskManager = field(init=False)
47
+ workflow_state_manager: WorkflowStateManager = field(init=False)
48
+ project_manager: LocalProjectManager = field(init=False)
49
+
50
+ # Config settings (initialized in __post_init__)
51
+ show_result_on_create: bool = field(init=False)
52
+ auto_generate_on_expand: bool = field(init=False)
53
+ tdd_mode_config: bool = field(init=False)
54
+ validation_config: "TaskValidationConfig | None" = field(init=False)
55
+
56
+ def __post_init__(self) -> None:
57
+ """Initialize derived managers and config settings."""
58
+ # Initialize managers from task_manager's database connection
59
+ db = self.task_manager.db
60
+ self.dep_manager = TaskDependencyManager(db)
61
+ self.session_task_manager = SessionTaskManager(db)
62
+ self.workflow_state_manager = WorkflowStateManager(db)
63
+ self.project_manager = LocalProjectManager(db)
64
+
65
+ # Initialize config settings
66
+ self.show_result_on_create = False
67
+ self.auto_generate_on_expand = True
68
+ self.tdd_mode_config = False
69
+ self.validation_config = None
70
+
71
+ if self.config is not None:
72
+ tasks_config = self.config.get_gobby_tasks_config()
73
+ self.show_result_on_create = tasks_config.show_result_on_create
74
+ self.validation_config = tasks_config.validation
75
+ self.auto_generate_on_expand = self.validation_config.auto_generate_on_expand
76
+ self.tdd_mode_config = tasks_config.expansion.tdd_mode
77
+
78
+ def get_project_repo_path(self, project_id: str | None) -> str | None:
79
+ """Get the repo_path for a project by ID."""
80
+ if not project_id:
81
+ return None
82
+ project = self.project_manager.get(project_id)
83
+ return project.repo_path if project else None
84
+
85
+ def get_current_project_id(self) -> str | None:
86
+ """Get the current project ID from context, or None if not in a project."""
87
+ ctx = get_project_context()
88
+ if ctx and ctx.get("id"):
89
+ project_id: str = ctx["id"]
90
+ return project_id
91
+ return None
92
+
93
+ def get_workflow_state(self, session_id: str | None) -> WorkflowState | None:
94
+ """Get workflow state for a session, if available."""
95
+ if not session_id:
96
+ return None
97
+ return self.workflow_state_manager.get_state(session_id)
98
+
99
+ def resolve_tdd_mode(self, session_id: str | None) -> bool:
100
+ """
101
+ Resolve tdd_mode from workflow state > config hierarchy.
102
+
103
+ Returns:
104
+ True if TDD mode is enabled, False otherwise.
105
+ """
106
+ # Check workflow state first (takes precedence)
107
+ state = self.get_workflow_state(session_id)
108
+ if state and "tdd_mode" in state.variables:
109
+ return bool(state.variables["tdd_mode"])
110
+
111
+ # Fall back to config
112
+ return self.tdd_mode_config
@@ -0,0 +1,516 @@
1
+ """CRUD operations for task management.
2
+
3
+ Provides core task operations: create, get, update, list, and tree building.
4
+ """
5
+
6
+ from typing import Any
7
+
8
+ from gobby.mcp_proxy.tools.internal import InternalToolRegistry
9
+ from gobby.mcp_proxy.tools.tasks._context import RegistryContext
10
+ from gobby.mcp_proxy.tools.tasks._helpers import _infer_category
11
+ from gobby.mcp_proxy.tools.tasks._resolution import resolve_task_id_for_mcp
12
+ from gobby.storage.tasks import TaskNotFoundError
13
+ from gobby.utils.project_context import get_project_context
14
+ from gobby.utils.project_init import initialize_project
15
+
16
+
17
+ def create_crud_registry(ctx: RegistryContext) -> InternalToolRegistry:
18
+ """Create a registry with task CRUD tools.
19
+
20
+ Args:
21
+ ctx: Shared registry context
22
+
23
+ Returns:
24
+ InternalToolRegistry with CRUD tools registered
25
+ """
26
+ registry = InternalToolRegistry(
27
+ name="gobby-tasks-crud",
28
+ description="Task CRUD operations",
29
+ )
30
+
31
+ async def create_task(
32
+ title: str,
33
+ session_id: str,
34
+ description: str | None = None,
35
+ priority: int = 2,
36
+ task_type: str = "task",
37
+ parent_task_id: str | None = None,
38
+ blocks: list[str] | None = None,
39
+ labels: list[str] | None = None,
40
+ category: str | None = None,
41
+ validation_criteria: str | None = None,
42
+ ) -> dict[str, Any]:
43
+ """Create a single task in the current project.
44
+
45
+ This tool creates exactly ONE task. Auto-decomposition of multi-step
46
+ descriptions is disabled. Use expand_task for complex decompositions.
47
+
48
+ Args:
49
+ title: Task title
50
+ session_id: Your session ID for tracking (REQUIRED)
51
+ description: Detailed description
52
+ priority: Priority level (1=High, 2=Medium, 3=Low)
53
+ task_type: Task type (task, bug, feature, epic)
54
+ parent_task_id: Optional parent task ID
55
+ blocks: List of task IDs that this new task blocks
56
+ labels: List of labels
57
+ category: Task domain category (test, code, document, research, config, manual)
58
+ validation_criteria: Acceptance criteria for validating completion.
59
+
60
+ Returns:
61
+ Created task dict with id (minimal) or full task details based on config.
62
+ """
63
+ # Get current project context which is required for task creation
64
+ project_ctx = get_project_context()
65
+ if project_ctx and project_ctx.get("id"):
66
+ project_id = project_ctx["id"]
67
+ else:
68
+ init_result = initialize_project()
69
+ project_id = init_result.project_id
70
+
71
+ # Resolve parent_task_id if it's a reference format
72
+ if parent_task_id:
73
+ try:
74
+ parent_task_id = resolve_task_id_for_mcp(
75
+ ctx.task_manager, parent_task_id, project_id
76
+ )
77
+ except (TaskNotFoundError, ValueError) as e:
78
+ return {"error": f"Invalid parent_task_id: {e}"}
79
+
80
+ # Auto-infer category if not provided
81
+ effective_category = category
82
+ if effective_category is None:
83
+ effective_category = _infer_category(title, description)
84
+
85
+ # Create task
86
+ create_result = ctx.task_manager.create_task_with_decomposition(
87
+ project_id=project_id,
88
+ title=title,
89
+ description=description,
90
+ priority=priority,
91
+ task_type=task_type,
92
+ parent_task_id=parent_task_id,
93
+ labels=labels,
94
+ category=effective_category,
95
+ validation_criteria=validation_criteria,
96
+ created_in_session_id=session_id,
97
+ )
98
+
99
+ task = ctx.task_manager.get_task(create_result["task"]["id"])
100
+
101
+ # Handle 'blocks' argument if provided (syntactic sugar)
102
+ if blocks:
103
+ for blocked_id in blocks:
104
+ ctx.dep_manager.add_dependency(task.id, blocked_id, "blocks")
105
+
106
+ # Return minimal or full result based on config
107
+ if ctx.show_result_on_create:
108
+ result = task.to_dict()
109
+ else:
110
+ result = {
111
+ "id": task.id,
112
+ "seq_num": task.seq_num,
113
+ "ref": f"#{task.seq_num}",
114
+ }
115
+
116
+ return result
117
+
118
+ registry.register(
119
+ name="create_task",
120
+ description="Create a new task in the current project.",
121
+ input_schema={
122
+ "type": "object",
123
+ "properties": {
124
+ "title": {"type": "string", "description": "Task title"},
125
+ "description": {
126
+ "type": "string",
127
+ "description": "Detailed description",
128
+ "default": None,
129
+ },
130
+ "priority": {
131
+ "type": "integer",
132
+ "description": "Priority level (1=High, 2=Medium, 3=Low)",
133
+ "default": 2,
134
+ },
135
+ "task_type": {
136
+ "type": "string",
137
+ "description": "Task type (task, bug, feature, epic)",
138
+ "default": "task",
139
+ },
140
+ "parent_task_id": {
141
+ "type": "string",
142
+ "description": "Parent task reference: #N, N (seq_num), path (1.2.3), or UUID",
143
+ "default": None,
144
+ },
145
+ "blocks": {
146
+ "type": "array",
147
+ "items": {"type": "string"},
148
+ "description": "List of task IDs that this new task blocks (optional)",
149
+ "default": None,
150
+ },
151
+ "labels": {
152
+ "type": "array",
153
+ "items": {"type": "string"},
154
+ "description": "List of labels (optional)",
155
+ "default": None,
156
+ },
157
+ "category": {
158
+ "type": "string",
159
+ "description": "Task domain: 'code' (implementation), 'config' (configuration files), 'docs' (documentation), 'test' (test-writing), 'research' (investigation), 'planning' (design/architecture), or 'manual' (manual verification).",
160
+ "enum": ["code", "config", "docs", "test", "research", "planning", "manual"],
161
+ "default": None,
162
+ },
163
+ "validation_criteria": {
164
+ "type": "string",
165
+ "description": "Acceptance criteria for validating task completion (optional). If not provided and generate_validation is True, criteria will be auto-generated.",
166
+ "default": None,
167
+ },
168
+ "session_id": {
169
+ "type": "string",
170
+ "description": "Your session ID (from system context). Required to track which session created the task.",
171
+ },
172
+ },
173
+ "required": ["title", "session_id"],
174
+ },
175
+ func=create_task,
176
+ )
177
+
178
+ def get_task(task_id: str) -> dict[str, Any]:
179
+ """Get task details including dependencies."""
180
+ # Resolve task reference (supports #N, path, UUID formats)
181
+ try:
182
+ resolved_id = resolve_task_id_for_mcp(ctx.task_manager, task_id)
183
+ except TaskNotFoundError as e:
184
+ return {"error": str(e), "found": False}
185
+ except ValueError as e:
186
+ return {"error": str(e), "found": False}
187
+
188
+ task = ctx.task_manager.get_task(resolved_id)
189
+ if not task:
190
+ return {"error": f"Task {task_id} not found", "found": False}
191
+
192
+ result: dict[str, Any] = task.to_dict()
193
+
194
+ # Enrich with dependency info
195
+ blockers = ctx.dep_manager.get_blockers(resolved_id)
196
+ blocking = ctx.dep_manager.get_blocking(resolved_id)
197
+
198
+ result["dependencies"] = {
199
+ "blocked_by": [b.to_dict() for b in blockers],
200
+ "blocking": [b.to_dict() for b in blocking],
201
+ }
202
+
203
+ return result
204
+
205
+ registry.register(
206
+ name="get_task",
207
+ description="Get task details including dependencies. Task ID can be #N (e.g., #1), path (e.g., 1.2.3), or UUID.",
208
+ input_schema={
209
+ "type": "object",
210
+ "properties": {
211
+ "task_id": {
212
+ "type": "string",
213
+ "description": "Task reference: #N (e.g., #1, #47), path (e.g., 1.2.3), or UUID",
214
+ },
215
+ },
216
+ "required": ["task_id"],
217
+ },
218
+ func=get_task,
219
+ )
220
+
221
+ def update_task(
222
+ task_id: str,
223
+ title: str | None = None,
224
+ description: str | None = None,
225
+ status: str | None = None,
226
+ priority: int | None = None,
227
+ assignee: str | None = None,
228
+ labels: list[str] | None = None,
229
+ validation_criteria: str | None = None,
230
+ parent_task_id: str | None = None,
231
+ category: str | None = None,
232
+ workflow_name: str | None = None,
233
+ verification: str | None = None,
234
+ sequence_order: int | None = None,
235
+ ) -> dict[str, Any]:
236
+ """Update task fields."""
237
+ # Resolve task reference (supports #N, path, UUID formats)
238
+ try:
239
+ resolved_id = resolve_task_id_for_mcp(ctx.task_manager, task_id)
240
+ except TaskNotFoundError as e:
241
+ return {"error": str(e)}
242
+ except ValueError as e:
243
+ return {"error": str(e)}
244
+
245
+ # Build kwargs only for non-None values to avoid overwriting with NULL
246
+ kwargs: dict[str, Any] = {}
247
+ if title is not None:
248
+ kwargs["title"] = title
249
+ if description is not None:
250
+ kwargs["description"] = description
251
+ if status is not None:
252
+ kwargs["status"] = status
253
+ if priority is not None:
254
+ kwargs["priority"] = priority
255
+ if assignee is not None:
256
+ kwargs["assignee"] = assignee
257
+ if labels is not None:
258
+ kwargs["labels"] = labels
259
+ if validation_criteria is not None:
260
+ kwargs["validation_criteria"] = validation_criteria
261
+ if parent_task_id is not None:
262
+ # Empty string means "clear parent" - convert to None for storage layer
263
+ # Also resolve parent_task_id if it's a reference format
264
+ if parent_task_id:
265
+ try:
266
+ resolved_parent = resolve_task_id_for_mcp(ctx.task_manager, parent_task_id)
267
+ kwargs["parent_task_id"] = resolved_parent
268
+ except (TaskNotFoundError, ValueError):
269
+ kwargs["parent_task_id"] = parent_task_id # Fall back to original
270
+ else:
271
+ kwargs["parent_task_id"] = None
272
+ if category is not None:
273
+ kwargs["category"] = category
274
+ if workflow_name is not None:
275
+ kwargs["workflow_name"] = workflow_name
276
+ if verification is not None:
277
+ kwargs["verification"] = verification
278
+ if sequence_order is not None:
279
+ kwargs["sequence_order"] = sequence_order
280
+
281
+ task = ctx.task_manager.update_task(resolved_id, **kwargs)
282
+ if not task:
283
+ return {"error": f"Task {task_id} not found"}
284
+ return {}
285
+
286
+ registry.register(
287
+ name="update_task",
288
+ description="Update task fields.",
289
+ input_schema={
290
+ "type": "object",
291
+ "properties": {
292
+ "task_id": {
293
+ "type": "string",
294
+ "description": "Task reference: #N (e.g., #1, #47), path (e.g., 1.2.3), or UUID",
295
+ },
296
+ "title": {"type": "string", "description": "New title", "default": None},
297
+ "description": {
298
+ "type": "string",
299
+ "description": "New description",
300
+ "default": None,
301
+ },
302
+ "status": {
303
+ "type": "string",
304
+ "description": "New status (open, in_progress, review, closed)",
305
+ "default": None,
306
+ },
307
+ "priority": {"type": "integer", "description": "New priority", "default": None},
308
+ "assignee": {"type": "string", "description": "New assignee", "default": None},
309
+ "labels": {
310
+ "type": "array",
311
+ "items": {"type": "string"},
312
+ "description": "New labels list",
313
+ "default": None,
314
+ },
315
+ "validation_criteria": {
316
+ "type": "string",
317
+ "description": "Acceptance criteria for validating task completion",
318
+ "default": None,
319
+ },
320
+ "parent_task_id": {
321
+ "type": "string",
322
+ "description": "Parent task reference: #N, N (seq_num), path (1.2.3), or UUID. Empty string clears parent.",
323
+ "default": None,
324
+ },
325
+ "category": {
326
+ "type": "string",
327
+ "description": "Task domain: 'code' (implementation), 'config' (configuration files), 'docs' (documentation), 'test' (test-writing), 'research' (investigation), 'planning' (design/architecture), or 'manual' (manual verification).",
328
+ "enum": ["code", "config", "docs", "test", "research", "planning", "manual"],
329
+ "default": None,
330
+ },
331
+ "workflow_name": {
332
+ "type": "string",
333
+ "description": "Workflow name for execution context",
334
+ "default": None,
335
+ },
336
+ "verification": {
337
+ "type": "string",
338
+ "description": "Verification steps or notes",
339
+ "default": None,
340
+ },
341
+ "sequence_order": {
342
+ "type": "integer",
343
+ "description": "Order in a sequence of tasks",
344
+ "default": None,
345
+ },
346
+ },
347
+ "required": ["task_id"],
348
+ },
349
+ func=update_task,
350
+ )
351
+
352
+ def list_tasks(
353
+ status: str | list[str] | None = None,
354
+ priority: int | None = None,
355
+ task_type: str | None = None,
356
+ assignee: str | None = None,
357
+ label: str | None = None,
358
+ parent_task_id: str | None = None,
359
+ title_like: str | None = None,
360
+ limit: int = 50,
361
+ all_projects: bool = False,
362
+ ) -> dict[str, Any]:
363
+ """List tasks with optional filters."""
364
+ # Filter by current project unless all_projects is True
365
+ project_id = None if all_projects else ctx.get_current_project_id()
366
+
367
+ # Resolve parent_task_id if it's a reference format
368
+ if parent_task_id:
369
+ try:
370
+ parent_task_id = resolve_task_id_for_mcp(
371
+ ctx.task_manager, parent_task_id, project_id
372
+ )
373
+ except (TaskNotFoundError, ValueError) as e:
374
+ return {"error": f"Invalid parent_task_id: {e}", "tasks": [], "count": 0}
375
+
376
+ # Handle comma-separated status string
377
+ status_filter: str | list[str] | None = status
378
+ if isinstance(status, str) and "," in status:
379
+ status_filter = [s.strip() for s in status.split(",")]
380
+
381
+ tasks = ctx.task_manager.list_tasks(
382
+ status=status_filter,
383
+ priority=priority,
384
+ task_type=task_type,
385
+ assignee=assignee,
386
+ label=label,
387
+ parent_task_id=parent_task_id,
388
+ title_like=title_like,
389
+ limit=limit,
390
+ project_id=project_id,
391
+ )
392
+ return {"tasks": [t.to_brief() for t in tasks], "count": len(tasks)}
393
+
394
+ registry.register(
395
+ name="list_tasks",
396
+ description="List tasks with optional filters.",
397
+ input_schema={
398
+ "type": "object",
399
+ "properties": {
400
+ "status": {
401
+ "oneOf": [{"type": "string"}, {"type": "array", "items": {"type": "string"}}],
402
+ "description": "Filter by status. Can be a single status, array of statuses, or comma-separated string (e.g., 'open,in_progress')",
403
+ "default": None,
404
+ },
405
+ "priority": {
406
+ "type": "integer",
407
+ "description": "Filter by priority",
408
+ "default": None,
409
+ },
410
+ "task_type": {
411
+ "type": "string",
412
+ "description": "Filter by task type",
413
+ "default": None,
414
+ },
415
+ "assignee": {
416
+ "type": "string",
417
+ "description": "Filter by assignee",
418
+ "default": None,
419
+ },
420
+ "label": {
421
+ "type": "string",
422
+ "description": "Filter by label presence",
423
+ "default": None,
424
+ },
425
+ "parent_task_id": {
426
+ "type": "string",
427
+ "description": "Filter by parent task: #N, N (seq_num), path (1.2.3), or UUID",
428
+ "default": None,
429
+ },
430
+ "title_like": {
431
+ "type": "string",
432
+ "description": "Filter by title (fuzzy match)",
433
+ "default": None,
434
+ },
435
+ "limit": {
436
+ "type": "integer",
437
+ "description": "Max number of tasks to return",
438
+ "default": 50,
439
+ },
440
+ "all_projects": {
441
+ "type": "boolean",
442
+ "description": "If true, list tasks from all projects instead of just the current project",
443
+ "default": False,
444
+ },
445
+ },
446
+ },
447
+ func=list_tasks,
448
+ )
449
+
450
+ return registry
451
+
452
+
453
+ def build_task_tree(
454
+ ctx: RegistryContext,
455
+ tree: dict[str, Any],
456
+ session_id: str,
457
+ ) -> dict[str, Any]:
458
+ """Create an entire task tree in one call.
459
+
460
+ Creates tasks with parent-child relationships and wires dependencies
461
+ based on `depends_on` title references within siblings.
462
+
463
+ This is an internal helper function, NOT registered as an MCP tool.
464
+ Agents should use expand_task with iterative mode for tree expansion.
465
+
466
+ Args:
467
+ ctx: Registry context
468
+ tree: JSON tree structure with title, task_type, children, depends_on
469
+ session_id: Your session ID for tracking (REQUIRED)
470
+
471
+ Returns:
472
+ Dict with success status, tasks_created count, epic_ref, task_refs
473
+
474
+ Example tree:
475
+ {
476
+ "title": "Epic Title",
477
+ "task_type": "epic",
478
+ "children": [
479
+ {
480
+ "title": "Phase 1",
481
+ "children": [
482
+ {"title": "Task A", "category": "code"},
483
+ {"title": "Task B", "category": "code", "depends_on": ["Task A"]}
484
+ ]
485
+ }
486
+ ]
487
+ }
488
+ """
489
+ from gobby.tasks.tree_builder import TaskTreeBuilder
490
+
491
+ # Get current project context
492
+ project_ctx = get_project_context()
493
+ if project_ctx and project_ctx.get("id"):
494
+ project_id = project_ctx["id"]
495
+ else:
496
+ init_result = initialize_project()
497
+ project_id = init_result.project_id
498
+
499
+ # Build the tree
500
+ builder = TaskTreeBuilder(
501
+ task_manager=ctx.task_manager,
502
+ project_id=project_id,
503
+ session_id=session_id,
504
+ )
505
+ result = builder.build(tree)
506
+
507
+ response: dict[str, Any] = {
508
+ "success": len(result.errors) == 0,
509
+ "tasks_created": result.tasks_created,
510
+ "epic_ref": result.epic_ref,
511
+ "task_refs": result.task_refs,
512
+ }
513
+ if result.errors:
514
+ response["errors"] = result.errors
515
+
516
+ return response