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/config/app.py ADDED
@@ -0,0 +1,551 @@
1
+ """
2
+ Configuration management for Gobby daemon.
3
+
4
+ Provides YAML-based configuration with CLI overrides,
5
+ configuration hierarchy (CLI > YAML > Defaults), and validation.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import os
11
+ import re
12
+ from pathlib import Path
13
+ from typing import Any
14
+
15
+ import yaml
16
+ from pydantic import BaseModel, Field, field_validator
17
+
18
+ from gobby.config.extensions import (
19
+ HookExtensionsConfig,
20
+ PluginItemConfig,
21
+ PluginsConfig,
22
+ WebhookEndpointConfig,
23
+ WebhooksConfig,
24
+ WebSocketBroadcastConfig,
25
+ )
26
+ from gobby.config.features import (
27
+ ImportMCPServerConfig,
28
+ MetricsConfig,
29
+ ProjectVerificationConfig,
30
+ RecommendToolsConfig,
31
+ TaskDescriptionConfig,
32
+ ToolSummarizerConfig,
33
+ )
34
+
35
+ # Re-export from extracted modules (Strangler Fig pattern for backwards compatibility)
36
+ from gobby.config.llm_providers import LLMProviderConfig, LLMProvidersConfig
37
+ from gobby.config.logging import LoggingSettings
38
+ from gobby.config.persistence import (
39
+ MemoryConfig,
40
+ MemorySyncConfig,
41
+ )
42
+ from gobby.config.servers import MCPClientProxyConfig, WebSocketSettings
43
+ from gobby.config.sessions import (
44
+ ArtifactHandoffConfig,
45
+ ContextInjectionConfig,
46
+ MessageTrackingConfig,
47
+ SessionLifecycleConfig,
48
+ SessionSummaryConfig,
49
+ TitleSynthesisConfig,
50
+ )
51
+ from gobby.config.tasks import (
52
+ CompactHandoffConfig,
53
+ GobbyTasksConfig,
54
+ PatternCriteriaConfig,
55
+ TaskExpansionConfig,
56
+ TaskValidationConfig,
57
+ WorkflowConfig,
58
+ )
59
+
60
+ # Explicit exports for mypy (re-exported symbols from submodules)
61
+ __all__ = [
62
+ # From gobby.config.extensions
63
+ "HookExtensionsConfig",
64
+ "PluginItemConfig",
65
+ "PluginsConfig",
66
+ "WebhookEndpointConfig",
67
+ "WebhooksConfig",
68
+ "WebSocketBroadcastConfig",
69
+ # From gobby.config.features
70
+ "ImportMCPServerConfig",
71
+ "MetricsConfig",
72
+ "ProjectVerificationConfig",
73
+ "RecommendToolsConfig",
74
+ "TaskDescriptionConfig",
75
+ "ToolSummarizerConfig",
76
+ # From gobby.config.llm_providers
77
+ "LLMProviderConfig",
78
+ "LLMProvidersConfig",
79
+ # From gobby.config.logging
80
+ "LoggingSettings",
81
+ # From gobby.config.persistence
82
+ "MemoryConfig",
83
+ "MemorySyncConfig",
84
+ # From gobby.config.servers
85
+ "MCPClientProxyConfig",
86
+ "WebSocketSettings",
87
+ # From gobby.config.sessions
88
+ "ArtifactHandoffConfig",
89
+ "ContextInjectionConfig",
90
+ "MessageTrackingConfig",
91
+ "SessionLifecycleConfig",
92
+ "SessionSummaryConfig",
93
+ "TitleSynthesisConfig",
94
+ # From gobby.config.tasks
95
+ "CompactHandoffConfig",
96
+ "GobbyTasksConfig",
97
+ "PatternCriteriaConfig",
98
+ "TaskExpansionConfig",
99
+ "TaskValidationConfig",
100
+ "WorkflowConfig",
101
+ # Local definitions
102
+ "DaemonConfig",
103
+ "expand_env_vars",
104
+ "load_yaml",
105
+ "apply_cli_overrides",
106
+ "generate_default_config",
107
+ "load_config",
108
+ "save_config",
109
+ ]
110
+
111
+ # Pattern for environment variable substitution:
112
+ # ${VAR} - simple substitution
113
+ # ${VAR:-default} - with default value if VAR is unset or empty
114
+ ENV_VAR_PATTERN = re.compile(r"\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\}")
115
+
116
+
117
+ def expand_env_vars(content: str) -> str:
118
+ """
119
+ Expand environment variables in configuration content.
120
+
121
+ Supports two syntaxes:
122
+ - ${VAR} - replaced with the value of VAR, or left unchanged if unset
123
+ - ${VAR:-default} - replaced with VAR's value, or 'default' if unset/empty
124
+
125
+ Args:
126
+ content: Configuration file content as string
127
+
128
+ Returns:
129
+ Content with environment variables expanded
130
+ """
131
+
132
+ def replace_match(match: re.Match[str]) -> str:
133
+ var_name = match.group(1)
134
+ default_value = match.group(2) # None if no default specified
135
+
136
+ env_value = os.environ.get(var_name)
137
+
138
+ if env_value is not None and env_value != "":
139
+ return env_value
140
+ elif default_value is not None:
141
+ return default_value
142
+ else:
143
+ # Leave unchanged if no value and no default
144
+ return match.group(0)
145
+
146
+ return ENV_VAR_PATTERN.sub(replace_match, content)
147
+
148
+
149
+ # WebSocketSettings moved to gobby.config.servers (re-exported above)
150
+ # LoggingSettings moved to gobby.config.logging (re-exported above)
151
+ # CompactHandoffConfig moved to gobby.config.tasks (re-exported above)
152
+
153
+ # ContextInjectionConfig, SessionSummaryConfig, TitleSynthesisConfig,
154
+ # MessageTrackingConfig, SessionLifecycleConfig
155
+ # moved to gobby.config.sessions (re-exported above)
156
+
157
+ # ToolSummarizerConfig, RecommendToolsConfig, ImportMCPServerConfig,
158
+ # MetricsConfig, ProjectVerificationConfig
159
+ # moved to gobby.config.features (re-exported above)
160
+
161
+ # WebSocketBroadcastConfig, WebhookEndpointConfig, WebhooksConfig,
162
+ # PluginItemConfig, PluginsConfig, HookExtensionsConfig
163
+ # moved to gobby.config.extensions (re-exported above)
164
+
165
+ # PatternCriteriaConfig, TaskExpansionConfig, TaskValidationConfig, WorkflowConfig,
166
+ # GobbyTasksConfig, CompactHandoffConfig
167
+ # moved to gobby.config.tasks (re-exported above)
168
+
169
+ # MCPClientProxyConfig moved to gobby.config.servers (re-exported above)
170
+ # LLMProviderConfig and LLMProvidersConfig moved to gobby.config.llm_providers (re-exported above)
171
+ # MemoryConfig, MemorySyncConfig
172
+ # moved to gobby.config.persistence (re-exported above)
173
+
174
+
175
+ class DaemonConfig(BaseModel):
176
+ """
177
+ Main configuration for Gobby daemon.
178
+
179
+ Configuration is loaded with the following priority:
180
+ 1. CLI arguments (highest)
181
+ 2. YAML file (~/.gobby/config.yaml)
182
+ 3. Defaults (lowest)
183
+
184
+ Note: machine_id is stored separately in ~/.gobby/machine_id
185
+ """
186
+
187
+ model_config = {"populate_by_name": True}
188
+
189
+ # Daemon settings
190
+ daemon_port: int = Field(
191
+ default=8765,
192
+ description="Port for daemon to listen on",
193
+ )
194
+ daemon_health_check_interval: float = Field(
195
+ default=10.0,
196
+ description="Daemon health check interval in seconds",
197
+ )
198
+
199
+ # Local storage
200
+ database_path: str = Field(
201
+ default="~/.gobby/gobby-hub.db",
202
+ description="Path to hub database for cross-project queries.",
203
+ )
204
+
205
+ # Sub-configs
206
+ websocket: WebSocketSettings = Field(
207
+ default_factory=WebSocketSettings,
208
+ description="WebSocket server configuration",
209
+ )
210
+ logging: LoggingSettings = Field(
211
+ default_factory=LoggingSettings,
212
+ description="Logging configuration",
213
+ )
214
+ session_summary: SessionSummaryConfig = Field(
215
+ default_factory=SessionSummaryConfig,
216
+ description="Session summary generation configuration",
217
+ )
218
+ compact_handoff: CompactHandoffConfig = Field(
219
+ default_factory=CompactHandoffConfig,
220
+ description="Compact handoff context configuration",
221
+ )
222
+ context_injection: ContextInjectionConfig = Field(
223
+ default_factory=ContextInjectionConfig,
224
+ description="Context injection configuration for subagent spawning",
225
+ )
226
+ artifact_handoff: ArtifactHandoffConfig = Field(
227
+ default_factory=ArtifactHandoffConfig,
228
+ description="Artifact inclusion in handoff context configuration",
229
+ )
230
+ mcp_client_proxy: MCPClientProxyConfig = Field(
231
+ default_factory=MCPClientProxyConfig,
232
+ description="MCP client proxy configuration",
233
+ )
234
+ gobby_tasks: GobbyTasksConfig = Field(
235
+ default_factory=GobbyTasksConfig,
236
+ alias="gobby-tasks",
237
+ serialization_alias="gobby-tasks",
238
+ description="gobby-tasks internal MCP server configuration",
239
+ )
240
+
241
+ # Multi-provider LLM configuration
242
+ llm_providers: LLMProvidersConfig = Field(
243
+ default_factory=LLMProvidersConfig,
244
+ description="Multi-provider LLM configuration",
245
+ )
246
+ title_synthesis: TitleSynthesisConfig = Field(
247
+ default_factory=TitleSynthesisConfig,
248
+ description="Title synthesis configuration",
249
+ )
250
+ recommend_tools: RecommendToolsConfig = Field(
251
+ default_factory=RecommendToolsConfig,
252
+ description="Tool recommendation configuration",
253
+ )
254
+ tool_summarizer: ToolSummarizerConfig = Field(
255
+ default_factory=ToolSummarizerConfig,
256
+ description="Tool description summarization configuration",
257
+ )
258
+ task_description: TaskDescriptionConfig = Field(
259
+ default_factory=TaskDescriptionConfig,
260
+ description="LLM-based task description generation configuration",
261
+ )
262
+ import_mcp_server: ImportMCPServerConfig = Field(
263
+ default_factory=ImportMCPServerConfig,
264
+ description="MCP server import configuration",
265
+ )
266
+ hook_extensions: HookExtensionsConfig = Field(
267
+ default_factory=HookExtensionsConfig,
268
+ description="Hook extensions configuration",
269
+ )
270
+ workflow: WorkflowConfig = Field(
271
+ default_factory=WorkflowConfig,
272
+ description="Workflow engine configuration",
273
+ )
274
+ memory: MemoryConfig = Field(
275
+ default_factory=MemoryConfig,
276
+ description="Memory system configuration",
277
+ )
278
+ memory_sync: MemorySyncConfig = Field(
279
+ default_factory=MemorySyncConfig,
280
+ description="Memory synchronization configuration",
281
+ )
282
+ message_tracking: MessageTrackingConfig = Field(
283
+ default_factory=MessageTrackingConfig,
284
+ description="Session message tracking configuration",
285
+ )
286
+ session_lifecycle: SessionLifecycleConfig = Field(
287
+ default_factory=SessionLifecycleConfig,
288
+ description="Session lifecycle management configuration",
289
+ )
290
+ metrics: MetricsConfig = Field(
291
+ default_factory=MetricsConfig,
292
+ description="Metrics and status endpoint configuration",
293
+ )
294
+ verification_defaults: ProjectVerificationConfig = Field(
295
+ default_factory=ProjectVerificationConfig,
296
+ description="Default verification commands for projects without auto-detected config",
297
+ )
298
+
299
+ def get_recommend_tools_config(self) -> RecommendToolsConfig:
300
+ """Get recommend_tools configuration."""
301
+ return self.recommend_tools
302
+
303
+ def get_tool_summarizer_config(self) -> ToolSummarizerConfig:
304
+ """Get tool_summarizer configuration."""
305
+ return self.tool_summarizer
306
+
307
+ def get_task_description_config(self) -> TaskDescriptionConfig:
308
+ """Get task_description configuration."""
309
+ return self.task_description
310
+
311
+ def get_import_mcp_server_config(self) -> ImportMCPServerConfig:
312
+ """Get import_mcp_server configuration."""
313
+ return self.import_mcp_server
314
+
315
+ def get_mcp_client_proxy_config(self) -> MCPClientProxyConfig:
316
+ """Get MCP client proxy configuration."""
317
+ return self.mcp_client_proxy
318
+
319
+ def get_memory_config(self) -> MemoryConfig:
320
+ """Get memory configuration."""
321
+ return self.memory
322
+
323
+ def get_memory_sync_config(self) -> MemorySyncConfig:
324
+ """Get memory sync configuration."""
325
+ return self.memory_sync
326
+
327
+ def get_gobby_tasks_config(self) -> GobbyTasksConfig:
328
+ """Get gobby-tasks configuration."""
329
+ return self.gobby_tasks
330
+
331
+ def get_metrics_config(self) -> MetricsConfig:
332
+ """Get metrics configuration."""
333
+ return self.metrics
334
+
335
+ def get_verification_defaults(self) -> ProjectVerificationConfig:
336
+ """Get default verification commands configuration."""
337
+ return self.verification_defaults
338
+
339
+ @field_validator("daemon_port")
340
+ @classmethod
341
+ def validate_port(cls, v: int) -> int:
342
+ """Validate port number is in valid range."""
343
+ if not (1024 <= v <= 65535):
344
+ raise ValueError("Port must be between 1024 and 65535")
345
+ return v
346
+
347
+ @field_validator("daemon_health_check_interval")
348
+ @classmethod
349
+ def validate_health_check_interval(cls, v: float) -> float:
350
+ """Validate health check interval is in valid range."""
351
+ if not (1.0 <= v <= 300.0):
352
+ raise ValueError("daemon_health_check_interval must be between 1.0 and 300.0 seconds")
353
+ return v
354
+
355
+
356
+ def load_yaml(config_file: str) -> dict[str, Any]:
357
+ """
358
+ Load YAML or JSON configuration file.
359
+
360
+ Args:
361
+ config_file: Path to YAML or JSON configuration file
362
+
363
+ Returns:
364
+ Dictionary with parsed YAML/JSON content
365
+
366
+ Raises:
367
+ ValueError: If YAML/JSON is invalid or file format is wrong
368
+ """
369
+ config_path = Path(config_file).expanduser()
370
+
371
+ if not config_path.exists():
372
+ return {}
373
+
374
+ # Validate file extension matches format
375
+ file_ext = config_path.suffix.lower()
376
+ if file_ext not in [".yaml", ".yml", ".json"]:
377
+ raise ValueError(
378
+ f"Config file must have .yaml, .yml, or .json extension, got: {file_ext}\n"
379
+ f"File: {config_path}"
380
+ )
381
+
382
+ import json
383
+
384
+ try:
385
+ with open(config_path) as f:
386
+ content = f.read()
387
+
388
+ # Expand environment variables before parsing
389
+ content = expand_env_vars(content)
390
+
391
+ # Handle JSON files
392
+ if file_ext == ".json":
393
+ return json.loads(content) if content.strip() else {}
394
+
395
+ # Handle YAML files
396
+ data = yaml.safe_load(content)
397
+ return data if data is not None else {}
398
+
399
+ except yaml.YAMLError as e:
400
+ raise ValueError(f"Invalid YAML in config file: {e}") from e
401
+ except json.JSONDecodeError as e:
402
+ raise ValueError(f"Invalid JSON in config file: {e}") from e
403
+
404
+
405
+ def apply_cli_overrides(
406
+ config_dict: dict[str, Any],
407
+ cli_overrides: dict[str, Any] | None = None,
408
+ ) -> dict[str, Any]:
409
+ """
410
+ Apply CLI argument overrides to config dictionary.
411
+
412
+ Args:
413
+ config_dict: Configuration dictionary
414
+ cli_overrides: Dictionary of CLI overrides
415
+
416
+ Returns:
417
+ Configuration dictionary with CLI overrides applied
418
+ """
419
+ if cli_overrides is None:
420
+ return config_dict
421
+
422
+ # Apply overrides at top level
423
+ for key, value in cli_overrides.items():
424
+ if "." in key:
425
+ # Handle nested keys like "logging.level"
426
+ parts = key.split(".")
427
+ current = config_dict
428
+ for part in parts[:-1]:
429
+ if part not in current:
430
+ current[part] = {}
431
+ current = current[part]
432
+ current[parts[-1]] = value
433
+ else:
434
+ config_dict[key] = value
435
+
436
+ return config_dict
437
+
438
+
439
+ def generate_default_config(config_file: str) -> None:
440
+ """
441
+ Generate default configuration file from Pydantic model defaults.
442
+
443
+ Args:
444
+ config_file: Path where to create the config file
445
+ """
446
+ config_path = Path(config_file).expanduser()
447
+ config_path.parent.mkdir(parents=True, exist_ok=True)
448
+
449
+ # Use Pydantic model defaults as source of truth
450
+ # mode="json" ensures Path objects are converted to strings for YAML serialization
451
+ default_config = DaemonConfig().model_dump(mode="json", exclude_none=True)
452
+
453
+ with open(config_path, "w") as f:
454
+ yaml.safe_dump(default_config, f, default_flow_style=False, sort_keys=False)
455
+
456
+ # Set restrictive permissions (owner read/write only)
457
+ config_path.chmod(0o600)
458
+
459
+
460
+ def load_config(
461
+ config_file: str | None = None,
462
+ cli_overrides: dict[str, Any] | None = None,
463
+ create_default: bool = False,
464
+ ) -> DaemonConfig:
465
+ """
466
+ Load configuration with hierarchy: CLI > YAML > Defaults.
467
+
468
+ Args:
469
+ config_file: Path to YAML config file (default: ~/.gobby/config.yaml)
470
+ cli_overrides: Dictionary of CLI argument overrides
471
+ create_default: Create default config file if it doesn't exist
472
+
473
+ Returns:
474
+ Validated DaemonConfig instance
475
+
476
+ Raises:
477
+ ValueError: If configuration is invalid or required fields are missing
478
+ """
479
+ if config_file is None:
480
+ config_file = "~/.gobby/config.yaml"
481
+
482
+ config_path = Path(config_file).expanduser()
483
+
484
+ # Create default config if requested and file doesn't exist
485
+ if create_default and not config_path.exists():
486
+ generate_default_config(config_file)
487
+
488
+ # Load YAML configuration
489
+ config_dict = load_yaml(config_file)
490
+
491
+ # Apply CLI argument overrides
492
+ config_dict = apply_cli_overrides(config_dict, cli_overrides)
493
+
494
+ # SAFETY SWITCH: Protect production resources during tests
495
+ # If GOBBY_TEST_PROTECT is set, force safe paths from environment
496
+ if os.environ.get("GOBBY_TEST_PROTECT") == "1":
497
+ # Override database path
498
+ if safe_db := os.environ.get("GOBBY_DATABASE_PATH"):
499
+ config_dict["database_path"] = safe_db
500
+
501
+ # Override logging paths
502
+ logging_config = config_dict.setdefault("logging", {})
503
+ if safe_client := os.environ.get("GOBBY_LOGGING_CLIENT"):
504
+ logging_config["client"] = safe_client
505
+ if safe_error := os.environ.get("GOBBY_LOGGING_CLIENT_ERROR"):
506
+ logging_config["client_error"] = safe_error
507
+ if safe_mcp_server := os.environ.get("GOBBY_LOGGING_MCP_SERVER"):
508
+ logging_config["mcp_server"] = safe_mcp_server
509
+ if safe_mcp_client := os.environ.get("GOBBY_LOGGING_MCP_CLIENT"):
510
+ logging_config["mcp_client"] = safe_mcp_client
511
+
512
+ # Validate and create config object
513
+ try:
514
+ config = DaemonConfig(**config_dict)
515
+ return config
516
+ except Exception as e:
517
+ raise ValueError(
518
+ f"Configuration validation failed: {e}\n"
519
+ f"Please check your configuration file at {config_file}"
520
+ ) from e
521
+
522
+
523
+ def save_config(config: DaemonConfig, config_file: str | None = None) -> None:
524
+ """
525
+ Save configuration to YAML file.
526
+
527
+ Args:
528
+ config: DaemonConfig instance to save
529
+ config_file: Path to YAML config file (default: ~/.gobby/config.yaml)
530
+
531
+ Raises:
532
+ OSError: If file operations fail
533
+ """
534
+ if config_file is None:
535
+ config_file = "~/.gobby/config.yaml"
536
+
537
+ config_path = Path(config_file).expanduser()
538
+
539
+ # Ensure directory exists
540
+ config_path.parent.mkdir(parents=True, exist_ok=True)
541
+
542
+ # Convert config to dict, excluding None values to keep file clean
543
+ # mode="json" ensures Path objects are converted to strings for YAML serialization
544
+ config_dict = config.model_dump(mode="json", exclude_none=True)
545
+
546
+ # Write to YAML file
547
+ with open(config_path, "w") as f:
548
+ yaml.safe_dump(config_dict, f, default_flow_style=False, sort_keys=False)
549
+
550
+ # Set restrictive permissions (owner read/write only)
551
+ config_path.chmod(0o600)