gobby 0.2.5__py3-none-any.whl → 0.2.6__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 (148) hide show
  1. gobby/adapters/claude_code.py +13 -4
  2. gobby/adapters/codex.py +43 -3
  3. gobby/agents/runner.py +8 -0
  4. gobby/cli/__init__.py +6 -0
  5. gobby/cli/clones.py +419 -0
  6. gobby/cli/conductor.py +266 -0
  7. gobby/cli/installers/antigravity.py +3 -9
  8. gobby/cli/installers/claude.py +9 -9
  9. gobby/cli/installers/codex.py +2 -8
  10. gobby/cli/installers/gemini.py +2 -8
  11. gobby/cli/installers/shared.py +71 -8
  12. gobby/cli/skills.py +858 -0
  13. gobby/cli/tasks/ai.py +0 -440
  14. gobby/cli/tasks/crud.py +44 -6
  15. gobby/cli/tasks/main.py +0 -4
  16. gobby/cli/tui.py +2 -2
  17. gobby/cli/utils.py +3 -3
  18. gobby/clones/__init__.py +13 -0
  19. gobby/clones/git.py +547 -0
  20. gobby/conductor/__init__.py +16 -0
  21. gobby/conductor/alerts.py +135 -0
  22. gobby/conductor/loop.py +164 -0
  23. gobby/conductor/monitors/__init__.py +11 -0
  24. gobby/conductor/monitors/agents.py +116 -0
  25. gobby/conductor/monitors/tasks.py +155 -0
  26. gobby/conductor/pricing.py +234 -0
  27. gobby/conductor/token_tracker.py +160 -0
  28. gobby/config/app.py +63 -1
  29. gobby/config/search.py +110 -0
  30. gobby/config/servers.py +1 -1
  31. gobby/config/skills.py +43 -0
  32. gobby/config/tasks.py +6 -14
  33. gobby/hooks/event_handlers.py +145 -2
  34. gobby/hooks/hook_manager.py +48 -2
  35. gobby/hooks/skill_manager.py +130 -0
  36. gobby/install/claude/hooks/hook_dispatcher.py +4 -4
  37. gobby/install/codex/hooks/hook_dispatcher.py +1 -1
  38. gobby/install/gemini/hooks/hook_dispatcher.py +87 -12
  39. gobby/llm/claude.py +22 -34
  40. gobby/llm/claude_executor.py +46 -256
  41. gobby/llm/codex_executor.py +59 -291
  42. gobby/llm/executor.py +21 -0
  43. gobby/llm/gemini.py +134 -110
  44. gobby/llm/litellm_executor.py +143 -6
  45. gobby/llm/resolver.py +95 -33
  46. gobby/mcp_proxy/instructions.py +54 -0
  47. gobby/mcp_proxy/models.py +15 -0
  48. gobby/mcp_proxy/registries.py +68 -5
  49. gobby/mcp_proxy/server.py +33 -3
  50. gobby/mcp_proxy/services/tool_proxy.py +81 -1
  51. gobby/mcp_proxy/stdio.py +2 -1
  52. gobby/mcp_proxy/tools/__init__.py +0 -2
  53. gobby/mcp_proxy/tools/agent_messaging.py +317 -0
  54. gobby/mcp_proxy/tools/clones.py +903 -0
  55. gobby/mcp_proxy/tools/memory.py +1 -24
  56. gobby/mcp_proxy/tools/metrics.py +65 -1
  57. gobby/mcp_proxy/tools/orchestration/__init__.py +3 -0
  58. gobby/mcp_proxy/tools/orchestration/cleanup.py +151 -0
  59. gobby/mcp_proxy/tools/orchestration/wait.py +467 -0
  60. gobby/mcp_proxy/tools/session_messages.py +1 -2
  61. gobby/mcp_proxy/tools/skills/__init__.py +631 -0
  62. gobby/mcp_proxy/tools/task_orchestration.py +7 -0
  63. gobby/mcp_proxy/tools/task_readiness.py +14 -0
  64. gobby/mcp_proxy/tools/task_sync.py +1 -1
  65. gobby/mcp_proxy/tools/tasks/_context.py +0 -20
  66. gobby/mcp_proxy/tools/tasks/_crud.py +91 -4
  67. gobby/mcp_proxy/tools/tasks/_expansion.py +348 -0
  68. gobby/mcp_proxy/tools/tasks/_factory.py +6 -16
  69. gobby/mcp_proxy/tools/tasks/_lifecycle.py +60 -29
  70. gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +18 -29
  71. gobby/mcp_proxy/tools/workflows.py +1 -1
  72. gobby/mcp_proxy/tools/worktrees.py +5 -0
  73. gobby/memory/backends/__init__.py +6 -1
  74. gobby/memory/backends/mem0.py +6 -1
  75. gobby/memory/extractor.py +477 -0
  76. gobby/memory/manager.py +11 -2
  77. gobby/prompts/defaults/handoff/compact.md +63 -0
  78. gobby/prompts/defaults/handoff/session_end.md +57 -0
  79. gobby/prompts/defaults/memory/extract.md +61 -0
  80. gobby/runner.py +37 -16
  81. gobby/search/__init__.py +48 -6
  82. gobby/search/backends/__init__.py +159 -0
  83. gobby/search/backends/embedding.py +225 -0
  84. gobby/search/embeddings.py +238 -0
  85. gobby/search/models.py +148 -0
  86. gobby/search/unified.py +496 -0
  87. gobby/servers/http.py +23 -8
  88. gobby/servers/routes/admin.py +280 -0
  89. gobby/servers/routes/mcp/tools.py +241 -52
  90. gobby/servers/websocket.py +2 -2
  91. gobby/sessions/analyzer.py +2 -0
  92. gobby/sessions/transcripts/base.py +1 -0
  93. gobby/sessions/transcripts/claude.py +64 -5
  94. gobby/skills/__init__.py +91 -0
  95. gobby/skills/loader.py +685 -0
  96. gobby/skills/manager.py +384 -0
  97. gobby/skills/parser.py +258 -0
  98. gobby/skills/search.py +463 -0
  99. gobby/skills/sync.py +119 -0
  100. gobby/skills/updater.py +385 -0
  101. gobby/skills/validator.py +368 -0
  102. gobby/storage/clones.py +378 -0
  103. gobby/storage/database.py +1 -1
  104. gobby/storage/memories.py +43 -13
  105. gobby/storage/migrations.py +180 -6
  106. gobby/storage/sessions.py +73 -0
  107. gobby/storage/skills.py +749 -0
  108. gobby/storage/tasks/_crud.py +4 -4
  109. gobby/storage/tasks/_lifecycle.py +41 -6
  110. gobby/storage/tasks/_manager.py +14 -5
  111. gobby/storage/tasks/_models.py +8 -3
  112. gobby/sync/memories.py +39 -4
  113. gobby/sync/tasks.py +83 -6
  114. gobby/tasks/__init__.py +1 -2
  115. gobby/tasks/validation.py +24 -15
  116. gobby/tui/api_client.py +4 -7
  117. gobby/tui/app.py +5 -3
  118. gobby/tui/screens/orchestrator.py +1 -2
  119. gobby/tui/screens/tasks.py +2 -4
  120. gobby/tui/ws_client.py +1 -1
  121. gobby/utils/daemon_client.py +2 -2
  122. gobby/workflows/actions.py +84 -2
  123. gobby/workflows/context_actions.py +43 -0
  124. gobby/workflows/detection_helpers.py +115 -31
  125. gobby/workflows/engine.py +13 -2
  126. gobby/workflows/lifecycle_evaluator.py +29 -1
  127. gobby/workflows/loader.py +19 -6
  128. gobby/workflows/memory_actions.py +74 -0
  129. gobby/workflows/summary_actions.py +17 -0
  130. gobby/workflows/task_enforcement_actions.py +448 -6
  131. {gobby-0.2.5.dist-info → gobby-0.2.6.dist-info}/METADATA +82 -21
  132. {gobby-0.2.5.dist-info → gobby-0.2.6.dist-info}/RECORD +136 -107
  133. gobby/install/codex/prompts/forget.md +0 -7
  134. gobby/install/codex/prompts/memories.md +0 -7
  135. gobby/install/codex/prompts/recall.md +0 -7
  136. gobby/install/codex/prompts/remember.md +0 -13
  137. gobby/llm/gemini_executor.py +0 -339
  138. gobby/mcp_proxy/tools/task_expansion.py +0 -591
  139. gobby/tasks/context.py +0 -747
  140. gobby/tasks/criteria.py +0 -342
  141. gobby/tasks/expansion.py +0 -626
  142. gobby/tasks/prompts/expand.py +0 -327
  143. gobby/tasks/research.py +0 -421
  144. gobby/tasks/tdd.py +0 -352
  145. {gobby-0.2.5.dist-info → gobby-0.2.6.dist-info}/WHEEL +0 -0
  146. {gobby-0.2.5.dist-info → gobby-0.2.6.dist-info}/entry_points.txt +0 -0
  147. {gobby-0.2.5.dist-info → gobby-0.2.6.dist-info}/licenses/LICENSE.md +0 -0
  148. {gobby-0.2.5.dist-info → gobby-0.2.6.dist-info}/top_level.txt +0 -0
@@ -3,7 +3,7 @@ Internal MCP tools for Gobby Memory System.
3
3
 
4
4
  Exposes functionality for:
5
5
  - Creating memories (create_memory)
6
- - Searching memories (search_memories, formerly recall_memory)
6
+ - Searching memories (search_memories)
7
7
  - Deleting memories (delete_memory)
8
8
  - Listing memories (list_memories)
9
9
  - Getting memory details (get_memory)
@@ -144,29 +144,6 @@ def create_memory_registry(
144
144
  except Exception as e:
145
145
  return {"success": False, "error": str(e)}
146
146
 
147
- # Backward compatibility alias for recall_memory -> search_memories
148
- @registry.tool(
149
- name="recall_memory",
150
- description="[DEPRECATED: Use search_memories] Search memories based on query.",
151
- )
152
- def recall_memory(
153
- query: str | None = None,
154
- limit: int = 10,
155
- min_importance: float | None = None,
156
- tags_all: list[str] | None = None,
157
- tags_any: list[str] | None = None,
158
- tags_none: list[str] | None = None,
159
- ) -> dict[str, Any]:
160
- """Deprecated alias for search_memories. Use search_memories instead."""
161
- return search_memories( # type: ignore[no-any-return]
162
- query=query,
163
- limit=limit,
164
- min_importance=min_importance,
165
- tags_all=tags_all,
166
- tags_any=tags_any,
167
- tags_none=tags_none,
168
- )
169
-
170
147
  @registry.tool(
171
148
  name="delete_memory",
172
149
  description="Delete a memory by ID.",
@@ -11,20 +11,34 @@ via the downstream proxy pattern (call_tool).
11
11
 
12
12
  from typing import Any
13
13
 
14
+ from gobby.conductor.token_tracker import SessionTokenTracker
14
15
  from gobby.mcp_proxy.metrics import ToolMetricsManager
15
16
  from gobby.mcp_proxy.tools.internal import InternalToolRegistry
16
17
 
17
18
 
18
- def create_metrics_registry(metrics_manager: ToolMetricsManager) -> InternalToolRegistry:
19
+ def create_metrics_registry(
20
+ metrics_manager: ToolMetricsManager,
21
+ session_storage: Any | None = None,
22
+ daily_budget_usd: float = 50.0,
23
+ ) -> InternalToolRegistry:
19
24
  """
20
25
  Create a metrics tool registry with all metrics-related tools.
21
26
 
22
27
  Args:
23
28
  metrics_manager: ToolMetricsManager instance
29
+ session_storage: Optional LocalSessionManager for token/cost tracking
30
+ daily_budget_usd: Daily budget limit for token tracking (default: $50)
24
31
 
25
32
  Returns:
26
33
  InternalToolRegistry with metrics tools registered
27
34
  """
35
+ # Create token tracker if session storage is provided
36
+ token_tracker: SessionTokenTracker | None = None
37
+ if session_storage is not None:
38
+ token_tracker = SessionTokenTracker(
39
+ session_storage=session_storage,
40
+ daily_budget_usd=daily_budget_usd,
41
+ )
28
42
  registry = InternalToolRegistry(
29
43
  name="gobby-metrics",
30
44
  description="Tool metrics - query call counts, success rates, latency",
@@ -280,4 +294,54 @@ def create_metrics_registry(metrics_manager: ToolMetricsManager) -> InternalTool
280
294
  except Exception as e:
281
295
  return {"success": False, "error": str(e)}
282
296
 
297
+ # Token/cost tracking tools (only available if session_storage provided)
298
+ @registry.tool(
299
+ name="get_usage_report",
300
+ description="Get token and cost usage report for a specified time period.",
301
+ )
302
+ def get_usage_report(days: int = 1) -> dict[str, Any]:
303
+ """
304
+ Get usage report including token counts and costs.
305
+
306
+ Args:
307
+ days: Number of days to look back (default: 1 = today)
308
+
309
+ Returns:
310
+ Dictionary with usage summary
311
+ """
312
+ if token_tracker is None:
313
+ return {"success": False, "error": "Token tracking not configured"}
314
+
315
+ try:
316
+ summary = token_tracker.get_usage_summary(days=days)
317
+ return {
318
+ "success": True,
319
+ "usage": summary,
320
+ }
321
+ except Exception as e:
322
+ return {"success": False, "error": str(e)}
323
+
324
+ @registry.tool(
325
+ name="get_budget_status",
326
+ description="Get current daily budget status including used amount and remaining budget.",
327
+ )
328
+ def get_budget_status() -> dict[str, Any]:
329
+ """
330
+ Get current budget status for today.
331
+
332
+ Returns:
333
+ Dictionary with budget info
334
+ """
335
+ if token_tracker is None:
336
+ return {"success": False, "error": "Token tracking not configured"}
337
+
338
+ try:
339
+ status = token_tracker.get_budget_status()
340
+ return {
341
+ "success": True,
342
+ "budget": status,
343
+ }
344
+ except Exception as e:
345
+ return {"success": False, "error": str(e)}
346
+
283
347
  return registry
@@ -5,6 +5,7 @@ Contains decomposed orchestration functionality:
5
5
  - monitor: Status monitoring tools (get_orchestration_status, poll_agent_status)
6
6
  - review: Review workflow tools (spawn_review_agent, process_completed_agents)
7
7
  - cleanup: Cleanup tools (cleanup_reviewed_worktrees, cleanup_stale_worktrees)
8
+ - wait: Blocking wait tools (wait_for_task, wait_for_any_task, wait_for_all_tasks)
8
9
  - utils: Shared utilities
9
10
  """
10
11
 
@@ -13,11 +14,13 @@ from gobby.mcp_proxy.tools.orchestration.monitor import register_monitor
13
14
  from gobby.mcp_proxy.tools.orchestration.orchestrate import register_orchestrator
14
15
  from gobby.mcp_proxy.tools.orchestration.review import register_reviewer
15
16
  from gobby.mcp_proxy.tools.orchestration.utils import get_current_project_id
17
+ from gobby.mcp_proxy.tools.orchestration.wait import register_wait
16
18
 
17
19
  __all__ = [
18
20
  "register_cleanup",
19
21
  "register_monitor",
20
22
  "register_orchestrator",
21
23
  "register_reviewer",
24
+ "register_wait",
22
25
  "get_current_project_id",
23
26
  ]
@@ -26,6 +26,157 @@ def register_cleanup(
26
26
  default_project_id: str | None = None,
27
27
  ) -> None:
28
28
  """Register cleanup tools."""
29
+ from gobby.mcp_proxy.tools.tasks import resolve_task_id_for_mcp
30
+ from gobby.storage.tasks import TaskNotFoundError
31
+
32
+ async def approve_and_cleanup(
33
+ task_id: str,
34
+ push_branch: bool = False,
35
+ delete_worktree: bool = True,
36
+ force: bool = False,
37
+ ) -> dict[str, Any]:
38
+ """
39
+ Approve a reviewed task and clean up its worktree.
40
+
41
+ This tool transitions a task from "review" to "closed" status
42
+ and optionally deletes the associated worktree.
43
+
44
+ Args:
45
+ task_id: Task reference (#N, N, path, or UUID)
46
+ push_branch: Whether to push the branch to remote before cleanup
47
+ delete_worktree: Whether to delete the git worktree (default: True)
48
+ force: Force deletion even if worktree is dirty
49
+
50
+ Returns:
51
+ Dict with:
52
+ - success: Whether the operation succeeded
53
+ - task_status: New task status
54
+ - worktree_deleted: Whether worktree was deleted
55
+ - branch_pushed: Whether branch was pushed
56
+ """
57
+ # Resolve task ID
58
+ try:
59
+ resolved_task_id = resolve_task_id_for_mcp(task_manager, task_id)
60
+ except (TaskNotFoundError, ValueError) as e:
61
+ return {
62
+ "success": False,
63
+ "error": f"Task not found: {task_id} ({e})",
64
+ }
65
+
66
+ # Get the task
67
+ task = task_manager.get_task(resolved_task_id)
68
+ if task is None:
69
+ return {
70
+ "success": False,
71
+ "error": f"Task not found: {task_id}",
72
+ }
73
+
74
+ # Verify task is in review status
75
+ if task.status != "review":
76
+ return {
77
+ "success": False,
78
+ "error": f"Task must be in 'review' status to approve. Current status: {task.status}",
79
+ }
80
+
81
+ # Get associated worktree (if any)
82
+ worktree = worktree_storage.get_by_task(resolved_task_id)
83
+ branch_pushed = False
84
+ worktree_deleted = False
85
+
86
+ # Push branch to remote if requested
87
+ if push_branch and worktree and git_manager:
88
+ try:
89
+ push_result = git_manager._run_git(
90
+ ["push", "origin", worktree.branch_name],
91
+ timeout=60,
92
+ )
93
+ branch_pushed = push_result.returncode == 0
94
+ if not branch_pushed:
95
+ logger.warning(f"Failed to push branch: {push_result.stderr}")
96
+ except Exception as e:
97
+ logger.warning(f"Error pushing branch: {e}")
98
+
99
+ # Update task status FIRST - before worktree deletion
100
+ try:
101
+ task_manager.update_task(
102
+ resolved_task_id,
103
+ status="closed",
104
+ )
105
+ except Exception as e:
106
+ return {
107
+ "success": False,
108
+ "error": f"Failed to update task status: {e}",
109
+ "task_id": resolved_task_id,
110
+ "worktree_deleted": False,
111
+ }
112
+
113
+ # Delete worktree if requested and available (after task is closed)
114
+ if delete_worktree and worktree:
115
+ if git_manager is None:
116
+ # No git manager - can't delete worktree, but continue
117
+ logger.warning("Git manager not available, skipping worktree deletion")
118
+ else:
119
+ try:
120
+ delete_result = git_manager.delete_worktree(
121
+ worktree_path=worktree.worktree_path,
122
+ force=force,
123
+ delete_branch=False, # Keep branch for history
124
+ )
125
+
126
+ if delete_result.success:
127
+ worktree_deleted = True
128
+ # Mark worktree as merged and delete record
129
+ worktree_storage.mark_merged(worktree.id)
130
+ worktree_storage.delete(worktree.id)
131
+ else:
132
+ # Task is closed but worktree deletion failed
133
+ logger.warning(f"Failed to delete worktree: {delete_result.message}")
134
+ except Exception as e:
135
+ # Task is closed but worktree deletion failed
136
+ logger.warning(f"Error deleting worktree: {e}")
137
+
138
+ return {
139
+ "success": True,
140
+ "task_id": resolved_task_id,
141
+ "task_status": "closed",
142
+ "worktree_deleted": worktree_deleted,
143
+ "branch_pushed": branch_pushed,
144
+ "message": f"Task {task_id} approved and marked as closed",
145
+ }
146
+
147
+ registry.register(
148
+ name="approve_and_cleanup",
149
+ description=(
150
+ "Approve a reviewed task and clean up its worktree. "
151
+ "Transitions task from 'review' to 'closed' status and deletes worktree."
152
+ ),
153
+ input_schema={
154
+ "type": "object",
155
+ "properties": {
156
+ "task_id": {
157
+ "type": "string",
158
+ "description": "Task reference: #N, N (seq_num), path (1.2.3), or UUID",
159
+ },
160
+ "push_branch": {
161
+ "type": "boolean",
162
+ "description": "Whether to push branch to remote before cleanup",
163
+ "default": False,
164
+ },
165
+ "delete_worktree": {
166
+ "type": "boolean",
167
+ "description": "Whether to delete the git worktree",
168
+ "default": True,
169
+ },
170
+ "force": {
171
+ "type": "boolean",
172
+ "description": "Force deletion even if worktree is dirty",
173
+ "default": False,
174
+ },
175
+ },
176
+ "required": ["task_id"],
177
+ },
178
+ func=approve_and_cleanup,
179
+ )
29
180
 
30
181
  async def cleanup_reviewed_worktrees(
31
182
  parent_session_id: str,