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,357 @@
1
+ """Validation history management for Task System V2.
2
+
3
+ Provides storage and retrieval of validation iteration history.
4
+ """
5
+
6
+ import json
7
+ import logging
8
+ from dataclasses import dataclass
9
+ from difflib import SequenceMatcher
10
+ from typing import TYPE_CHECKING, Any
11
+
12
+ from gobby.tasks.validation_models import Issue
13
+
14
+ if TYPE_CHECKING:
15
+ from gobby.storage.database import DatabaseProtocol
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ @dataclass
21
+ class ValidationIteration:
22
+ """Represents a single validation iteration for a task.
23
+
24
+ Attributes:
25
+ id: Database ID of the iteration record
26
+ task_id: ID of the task being validated
27
+ iteration: Iteration number (1-based)
28
+ status: Validation status ("valid", "invalid", "error")
29
+ feedback: Human-readable feedback from validator
30
+ issues: List of Issue objects found during validation
31
+ context_type: Type of context provided (e.g., "git_diff", "code_review")
32
+ context_summary: Summary of the context provided
33
+ validator_type: Type of validator used (e.g., "llm", "external_webhook")
34
+ created_at: Timestamp when iteration was recorded
35
+ """
36
+
37
+ id: int
38
+ task_id: str
39
+ iteration: int
40
+ status: str
41
+ feedback: str | None = None
42
+ issues: list[Issue] | None = None
43
+ context_type: str | None = None
44
+ context_summary: str | None = None
45
+ validator_type: str | None = None
46
+ created_at: str | None = None
47
+
48
+
49
+ class ValidationHistoryManager:
50
+ """Manages validation iteration history for tasks.
51
+
52
+ Stores and retrieves validation history from the task_validation_history table.
53
+ """
54
+
55
+ def __init__(self, db: "DatabaseProtocol"):
56
+ """Initialize ValidationHistoryManager.
57
+
58
+ Args:
59
+ db: LocalDatabase instance for database operations.
60
+ """
61
+ self.db = db
62
+
63
+ def record_iteration(
64
+ self,
65
+ task_id: str,
66
+ iteration: int,
67
+ status: str,
68
+ feedback: str | None = None,
69
+ issues: list[Issue] | None = None,
70
+ context_type: str | None = None,
71
+ context_summary: str | None = None,
72
+ validator_type: str | None = None,
73
+ ) -> None:
74
+ """Record a validation iteration for a task.
75
+
76
+ Args:
77
+ task_id: ID of the task being validated.
78
+ iteration: Iteration number (1-based).
79
+ status: Validation status ("valid", "invalid", "error").
80
+ feedback: Human-readable feedback from validator.
81
+ issues: List of Issue objects found during validation.
82
+ context_type: Type of context provided.
83
+ context_summary: Summary of the context provided.
84
+ validator_type: Type of validator used.
85
+ """
86
+ # Serialize issues to JSON
87
+ issues_json = None
88
+ if issues:
89
+ issues_json = json.dumps([issue.to_dict() for issue in issues])
90
+
91
+ with self.db.transaction() as conn:
92
+ conn.execute(
93
+ """INSERT INTO task_validation_history
94
+ (task_id, iteration, status, feedback, issues, context_type,
95
+ context_summary, validator_type)
96
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)""",
97
+ (
98
+ task_id,
99
+ iteration,
100
+ status,
101
+ feedback,
102
+ issues_json,
103
+ context_type,
104
+ context_summary,
105
+ validator_type,
106
+ ),
107
+ )
108
+
109
+ logger.debug(f"Recorded validation iteration {iteration} for task {task_id}: {status}")
110
+
111
+ def get_iteration_history(self, task_id: str) -> list[ValidationIteration]:
112
+ """Get all validation iterations for a task.
113
+
114
+ Args:
115
+ task_id: ID of the task to get history for.
116
+
117
+ Returns:
118
+ List of ValidationIteration objects ordered by iteration number.
119
+ """
120
+ rows = self.db.fetchall(
121
+ """SELECT * FROM task_validation_history
122
+ WHERE task_id = ?
123
+ ORDER BY iteration ASC""",
124
+ (task_id,),
125
+ )
126
+
127
+ return [self._row_to_iteration(row) for row in rows]
128
+
129
+ def get_latest_iteration(self, task_id: str) -> ValidationIteration | None:
130
+ """Get the most recent validation iteration for a task.
131
+
132
+ Args:
133
+ task_id: ID of the task to get latest iteration for.
134
+
135
+ Returns:
136
+ Latest ValidationIteration or None if no history exists.
137
+ """
138
+ row = self.db.fetchone(
139
+ """SELECT * FROM task_validation_history
140
+ WHERE task_id = ?
141
+ ORDER BY iteration DESC
142
+ LIMIT 1""",
143
+ (task_id,),
144
+ )
145
+
146
+ if row:
147
+ return self._row_to_iteration(row)
148
+ return None
149
+
150
+ def clear_history(self, task_id: str) -> None:
151
+ """Remove all validation history for a task.
152
+
153
+ Args:
154
+ task_id: ID of the task to clear history for.
155
+ """
156
+ with self.db.transaction() as conn:
157
+ conn.execute(
158
+ "DELETE FROM task_validation_history WHERE task_id = ?",
159
+ (task_id,),
160
+ )
161
+
162
+ logger.debug(f"Cleared validation history for task {task_id}")
163
+
164
+ def _row_to_iteration(self, row: Any) -> ValidationIteration:
165
+ """Convert a database row to a ValidationIteration object.
166
+
167
+ Args:
168
+ row: Database row from task_validation_history.
169
+
170
+ Returns:
171
+ ValidationIteration object.
172
+ """
173
+ # Parse issues from JSON
174
+ issues = None
175
+ issues_json = row["issues"]
176
+ if issues_json:
177
+ issues_data = json.loads(issues_json)
178
+ issues = [Issue.from_dict(d) for d in issues_data]
179
+
180
+ return ValidationIteration(
181
+ id=row["id"],
182
+ task_id=row["task_id"],
183
+ iteration=row["iteration"],
184
+ status=row["status"],
185
+ feedback=row["feedback"],
186
+ issues=issues,
187
+ context_type=row["context_type"],
188
+ context_summary=row["context_summary"],
189
+ validator_type=row["validator_type"],
190
+ created_at=row["created_at"],
191
+ )
192
+
193
+ # =========================================================================
194
+ # Recurring Issue Detection
195
+ # =========================================================================
196
+
197
+ def group_similar_issues(
198
+ self,
199
+ issues: list[Issue],
200
+ similarity_threshold: float = 0.8,
201
+ ) -> list[list[Issue]]:
202
+ """Group issues by similarity.
203
+
204
+ Uses fuzzy string matching on titles and exact matching on locations.
205
+ Issues at the same location are always grouped together.
206
+
207
+ Args:
208
+ issues: List of Issue objects to group.
209
+ similarity_threshold: Minimum similarity ratio (0-1) for title matching.
210
+
211
+ Returns:
212
+ List of groups, where each group is a list of similar Issues.
213
+ """
214
+ if not issues:
215
+ return []
216
+
217
+ groups: list[list[Issue]] = []
218
+ used: set[int] = set()
219
+
220
+ for i, issue in enumerate(issues):
221
+ if i in used:
222
+ continue
223
+
224
+ # Start a new group with this issue
225
+ group = [issue]
226
+ used.add(i)
227
+
228
+ # Find similar issues
229
+ for j, other in enumerate(issues):
230
+ if j in used:
231
+ continue
232
+
233
+ if self._issues_are_similar(issue, other, similarity_threshold):
234
+ group.append(other)
235
+ used.add(j)
236
+
237
+ groups.append(group)
238
+
239
+ return groups
240
+
241
+ def _issues_are_similar(
242
+ self,
243
+ issue1: Issue,
244
+ issue2: Issue,
245
+ threshold: float,
246
+ ) -> bool:
247
+ """Check if two issues are similar.
248
+
249
+ Args:
250
+ issue1: First issue to compare.
251
+ issue2: Second issue to compare.
252
+ threshold: Minimum similarity ratio for title matching.
253
+
254
+ Returns:
255
+ True if issues are considered similar.
256
+ """
257
+ # Same location is a strong match signal
258
+ if issue1.location and issue2.location and issue1.location == issue2.location:
259
+ return True
260
+
261
+ # Check title similarity using SequenceMatcher
262
+ ratio = SequenceMatcher(None, issue1.title.lower(), issue2.title.lower()).ratio()
263
+ return ratio >= threshold
264
+
265
+ def has_recurring_issues(
266
+ self,
267
+ task_id: str,
268
+ threshold: int = 2,
269
+ similarity_threshold: float = 0.8,
270
+ ) -> bool:
271
+ """Check if a task has recurring issues across iterations.
272
+
273
+ Args:
274
+ task_id: ID of the task to check.
275
+ threshold: Minimum number of occurrences to consider recurring.
276
+ similarity_threshold: Minimum similarity ratio for grouping issues.
277
+
278
+ Returns:
279
+ True if any issue recurs at least `threshold` times.
280
+ """
281
+ history = self.get_iteration_history(task_id)
282
+ if not history:
283
+ return False
284
+
285
+ # Collect all issues from all iterations
286
+ all_issues: list[Issue] = []
287
+ for iteration in history:
288
+ if iteration.issues:
289
+ all_issues.extend(iteration.issues)
290
+
291
+ if not all_issues:
292
+ return False
293
+
294
+ # Group similar issues
295
+ groups = self.group_similar_issues(all_issues, similarity_threshold)
296
+
297
+ # Check if any group exceeds the threshold
298
+ return any(len(group) >= threshold for group in groups)
299
+
300
+ def get_recurring_issue_summary(
301
+ self,
302
+ task_id: str,
303
+ threshold: int = 2,
304
+ similarity_threshold: float = 0.8,
305
+ ) -> dict[str, Any]:
306
+ """Get a summary of recurring issues for a task.
307
+
308
+ Args:
309
+ task_id: ID of the task to analyze.
310
+ threshold: Minimum occurrences to consider an issue recurring.
311
+ similarity_threshold: Minimum similarity ratio for grouping.
312
+
313
+ Returns:
314
+ Dictionary with:
315
+ - recurring_issues: List of recurring issue summaries
316
+ - total_iterations: Total number of validation iterations
317
+ """
318
+ history = self.get_iteration_history(task_id)
319
+
320
+ if not history:
321
+ return {
322
+ "recurring_issues": [],
323
+ "total_iterations": 0,
324
+ }
325
+
326
+ # Collect all issues
327
+ all_issues: list[Issue] = []
328
+ for iteration in history:
329
+ if iteration.issues:
330
+ all_issues.extend(iteration.issues)
331
+
332
+ # Group similar issues
333
+ groups = self.group_similar_issues(all_issues, similarity_threshold)
334
+
335
+ # Filter to only recurring issues (meeting threshold)
336
+ recurring_issues = []
337
+ for group in groups:
338
+ if len(group) >= threshold:
339
+ # Use the first issue as the representative
340
+ representative = group[0]
341
+ recurring_issues.append(
342
+ {
343
+ "title": representative.title,
344
+ "type": representative.issue_type.value,
345
+ "severity": representative.severity.value,
346
+ "location": representative.location,
347
+ "count": len(group),
348
+ }
349
+ )
350
+
351
+ # Sort by count descending
352
+ recurring_issues.sort(key=lambda x: int(x["count"] or 0), reverse=True)
353
+
354
+ return {
355
+ "recurring_issues": recurring_issues,
356
+ "total_iterations": len(history),
357
+ }
@@ -0,0 +1,89 @@
1
+ """Validation models for Task System V2.
2
+
3
+ Provides Issue dataclass and related enums for representing validation issues.
4
+ """
5
+
6
+ from dataclasses import dataclass
7
+ from enum import Enum
8
+ from typing import Any
9
+
10
+
11
+ class IssueType(Enum):
12
+ """Types of issues that can occur during task validation."""
13
+
14
+ TEST_FAILURE = "test_failure"
15
+ LINT_ERROR = "lint_error"
16
+ ACCEPTANCE_GAP = "acceptance_gap"
17
+ TYPE_ERROR = "type_error"
18
+ SECURITY = "security"
19
+
20
+
21
+ class IssueSeverity(Enum):
22
+ """Severity levels for validation issues."""
23
+
24
+ BLOCKER = "blocker"
25
+ MAJOR = "major"
26
+ MINOR = "minor"
27
+
28
+
29
+ @dataclass
30
+ class Issue:
31
+ """Represents a validation issue found during task verification.
32
+
33
+ Attributes:
34
+ issue_type: The category of issue (test failure, lint error, etc.)
35
+ severity: How critical the issue is (blocker, major, minor)
36
+ title: A short summary of the issue
37
+ location: File path and line number where the issue occurred (optional)
38
+ details: Extended description of the issue (optional)
39
+ suggested_fix: Recommended action to resolve the issue (optional)
40
+ recurring_count: Number of times this issue has recurred across iterations
41
+ """
42
+
43
+ issue_type: IssueType
44
+ severity: IssueSeverity
45
+ title: str
46
+ location: str | None = None
47
+ details: str | None = None
48
+ suggested_fix: str | None = None
49
+ recurring_count: int = 0
50
+
51
+ def to_dict(self) -> dict[str, Any]:
52
+ """Serialize Issue to dictionary for JSON storage."""
53
+ return {
54
+ "type": self.issue_type.value,
55
+ "severity": self.severity.value,
56
+ "title": self.title,
57
+ "location": self.location,
58
+ "details": self.details,
59
+ "suggested_fix": self.suggested_fix,
60
+ "recurring_count": self.recurring_count,
61
+ }
62
+
63
+ @classmethod
64
+ def from_dict(cls, data: dict[str, Any]) -> "Issue":
65
+ """Deserialize Issue from dictionary.
66
+
67
+ Args:
68
+ data: Dictionary with issue fields. Required: type, severity, title.
69
+
70
+ Returns:
71
+ Issue instance
72
+
73
+ Raises:
74
+ KeyError: If required fields ("type", "severity", "title") are missing
75
+ ValueError: If type or severity values are invalid
76
+ """
77
+ # Parse enums - these will raise ValueError if invalid
78
+ issue_type = IssueType(data["type"])
79
+ severity = IssueSeverity(data["severity"])
80
+
81
+ return cls(
82
+ issue_type=issue_type,
83
+ severity=severity,
84
+ title=data["title"],
85
+ location=data.get("location"),
86
+ details=data.get("details"),
87
+ suggested_fix=data.get("suggested_fix"),
88
+ recurring_count=data.get("recurring_count", 0),
89
+ )
File without changes
@@ -0,0 +1,170 @@
1
+ """
2
+ Tool description summarization using Claude Agent SDK.
3
+
4
+ Intelligently summarizes long MCP tool descriptions to fit within
5
+ the 200-character limit for config file storage.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import logging
11
+ from typing import TYPE_CHECKING, Any
12
+
13
+ if TYPE_CHECKING:
14
+ from gobby.config.app import ToolSummarizerConfig
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ # Maximum description length for tool summaries
19
+ MAX_DESCRIPTION_LENGTH = 200
20
+
21
+ # Module-level config reference (set by init_summarizer_config)
22
+ _config: ToolSummarizerConfig | None = None
23
+
24
+
25
+ def init_summarizer_config(config: ToolSummarizerConfig) -> None:
26
+ """Initialize the summarizer with configuration."""
27
+ global _config
28
+ _config = config
29
+
30
+
31
+ def _get_config() -> ToolSummarizerConfig:
32
+ """Get the current config, with fallback to defaults."""
33
+ if _config is not None:
34
+ return _config
35
+ # Import here to avoid circular imports
36
+ from gobby.config.app import ToolSummarizerConfig
37
+
38
+ return ToolSummarizerConfig()
39
+
40
+
41
+ async def _summarize_description_with_claude(description: str) -> str:
42
+ """
43
+ Summarize a tool description using Claude Agent SDK.
44
+
45
+ Args:
46
+ description: Long tool description to summarize
47
+
48
+ Returns:
49
+ Summarized description (max 180 chars)
50
+ """
51
+ config = _get_config()
52
+
53
+ try:
54
+ from claude_agent_sdk import AssistantMessage, ClaudeAgentOptions, TextBlock, query
55
+
56
+ prompt = config.prompt.format(description=description)
57
+
58
+ # Configure for single-turn completion
59
+ options = ClaudeAgentOptions(
60
+ system_prompt=config.system_prompt,
61
+ max_turns=1,
62
+ model=config.model,
63
+ allowed_tools=[],
64
+ permission_mode="default",
65
+ )
66
+
67
+ # Run async query
68
+ summary_text = ""
69
+ async for message in query(prompt=prompt, options=options):
70
+ if isinstance(message, AssistantMessage):
71
+ for block in message.content:
72
+ if isinstance(block, TextBlock):
73
+ summary_text = block.text
74
+ return summary_text.strip()
75
+
76
+ except Exception as e:
77
+ logger.warning(f"Failed to summarize description with Claude: {e}")
78
+ # Fallback: truncate to 200 chars with ellipsis
79
+ return description[:197] + "..." if len(description) > 200 else description
80
+
81
+
82
+ async def summarize_tools(tools: list[Any]) -> list[dict[str, Any]]:
83
+ """
84
+ Create lightweight tool summaries with intelligent description shortening.
85
+
86
+ Args:
87
+ tools: List of MCP Tool objects with name, description, and inputSchema
88
+
89
+ Returns:
90
+ List of dicts with name, summarized description, and args:
91
+ [{"name": "tool_name", "description": "Short summary...", "args": {...}}]
92
+ """
93
+ summaries = []
94
+
95
+ for tool in tools:
96
+ description = tool.description or ""
97
+
98
+ # Summarize if needed
99
+ if len(description) > MAX_DESCRIPTION_LENGTH:
100
+ logger.debug(
101
+ f"Summarizing description for tool '{tool.name}' ({len(description)} chars)"
102
+ )
103
+ description = await _summarize_description_with_claude(description)
104
+
105
+ summaries.append(
106
+ {
107
+ "name": tool.name,
108
+ "description": description,
109
+ "args": tool.inputSchema if hasattr(tool, "inputSchema") else {},
110
+ }
111
+ )
112
+
113
+ return summaries
114
+
115
+
116
+ async def generate_server_description(
117
+ server_name: str, tool_summaries: list[dict[str, Any]]
118
+ ) -> str:
119
+ """
120
+ Generate a concise server description from tool summaries.
121
+
122
+ Uses Claude Haiku to synthesize a single-sentence description of what
123
+ the MCP server does based on all its available tools.
124
+
125
+ Args:
126
+ server_name: Name of the MCP server
127
+ tool_summaries: List of tool summaries from summarize_tools()
128
+
129
+ Returns:
130
+ Single-sentence description (aiming for <100 chars)
131
+ """
132
+ config = _get_config()
133
+
134
+ try:
135
+ from claude_agent_sdk import AssistantMessage, ClaudeAgentOptions, TextBlock, query
136
+
137
+ # Build tools list for prompt
138
+ tools_list = "\n".join([f"- {t['name']}: {t['description']}" for t in tool_summaries])
139
+
140
+ prompt = config.server_description_prompt.format(
141
+ server_name=server_name,
142
+ tools_list=tools_list,
143
+ )
144
+
145
+ # Configure for single-turn completion
146
+ options = ClaudeAgentOptions(
147
+ system_prompt=config.server_description_system_prompt,
148
+ max_turns=1,
149
+ model=config.model,
150
+ allowed_tools=[],
151
+ permission_mode="default",
152
+ )
153
+
154
+ # Run async query
155
+ description = ""
156
+ async for message in query(prompt=prompt, options=options):
157
+ if isinstance(message, AssistantMessage):
158
+ for block in message.content:
159
+ if isinstance(block, TextBlock):
160
+ description = block.text
161
+
162
+ return description.strip()
163
+
164
+ except Exception as e:
165
+ logger.warning(f"Failed to generate server description for '{server_name}': {e}")
166
+ # Fallback: Generate simple description from first few tools
167
+ if tool_summaries:
168
+ first_tools = ", ".join([t["name"] for t in tool_summaries[:3]])
169
+ return f"Provides {first_tools} and more"
170
+ return f"MCP server: {server_name}"
gobby/tui/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ """Gobby TUI - Textual-based dashboard for Gobby daemon."""
2
+
3
+ from gobby.tui.app import GobbyApp, run_tui
4
+
5
+ __all__ = ["GobbyApp", "run_tui"]