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.
- gobby/adapters/claude_code.py +13 -4
- gobby/adapters/codex.py +43 -3
- gobby/agents/runner.py +8 -0
- gobby/cli/__init__.py +6 -0
- gobby/cli/clones.py +419 -0
- gobby/cli/conductor.py +266 -0
- gobby/cli/installers/antigravity.py +3 -9
- gobby/cli/installers/claude.py +9 -9
- gobby/cli/installers/codex.py +2 -8
- gobby/cli/installers/gemini.py +2 -8
- gobby/cli/installers/shared.py +71 -8
- gobby/cli/skills.py +858 -0
- gobby/cli/tasks/ai.py +0 -440
- gobby/cli/tasks/crud.py +44 -6
- gobby/cli/tasks/main.py +0 -4
- gobby/cli/tui.py +2 -2
- gobby/cli/utils.py +3 -3
- gobby/clones/__init__.py +13 -0
- gobby/clones/git.py +547 -0
- gobby/conductor/__init__.py +16 -0
- gobby/conductor/alerts.py +135 -0
- gobby/conductor/loop.py +164 -0
- gobby/conductor/monitors/__init__.py +11 -0
- gobby/conductor/monitors/agents.py +116 -0
- gobby/conductor/monitors/tasks.py +155 -0
- gobby/conductor/pricing.py +234 -0
- gobby/conductor/token_tracker.py +160 -0
- gobby/config/app.py +63 -1
- gobby/config/search.py +110 -0
- gobby/config/servers.py +1 -1
- gobby/config/skills.py +43 -0
- gobby/config/tasks.py +6 -14
- gobby/hooks/event_handlers.py +145 -2
- gobby/hooks/hook_manager.py +48 -2
- gobby/hooks/skill_manager.py +130 -0
- gobby/install/claude/hooks/hook_dispatcher.py +4 -4
- gobby/install/codex/hooks/hook_dispatcher.py +1 -1
- gobby/install/gemini/hooks/hook_dispatcher.py +87 -12
- gobby/llm/claude.py +22 -34
- gobby/llm/claude_executor.py +46 -256
- gobby/llm/codex_executor.py +59 -291
- gobby/llm/executor.py +21 -0
- gobby/llm/gemini.py +134 -110
- gobby/llm/litellm_executor.py +143 -6
- gobby/llm/resolver.py +95 -33
- gobby/mcp_proxy/instructions.py +54 -0
- gobby/mcp_proxy/models.py +15 -0
- gobby/mcp_proxy/registries.py +68 -5
- gobby/mcp_proxy/server.py +33 -3
- gobby/mcp_proxy/services/tool_proxy.py +81 -1
- gobby/mcp_proxy/stdio.py +2 -1
- gobby/mcp_proxy/tools/__init__.py +0 -2
- gobby/mcp_proxy/tools/agent_messaging.py +317 -0
- gobby/mcp_proxy/tools/clones.py +903 -0
- gobby/mcp_proxy/tools/memory.py +1 -24
- gobby/mcp_proxy/tools/metrics.py +65 -1
- gobby/mcp_proxy/tools/orchestration/__init__.py +3 -0
- gobby/mcp_proxy/tools/orchestration/cleanup.py +151 -0
- gobby/mcp_proxy/tools/orchestration/wait.py +467 -0
- gobby/mcp_proxy/tools/session_messages.py +1 -2
- gobby/mcp_proxy/tools/skills/__init__.py +631 -0
- gobby/mcp_proxy/tools/task_orchestration.py +7 -0
- gobby/mcp_proxy/tools/task_readiness.py +14 -0
- gobby/mcp_proxy/tools/task_sync.py +1 -1
- gobby/mcp_proxy/tools/tasks/_context.py +0 -20
- gobby/mcp_proxy/tools/tasks/_crud.py +91 -4
- gobby/mcp_proxy/tools/tasks/_expansion.py +348 -0
- gobby/mcp_proxy/tools/tasks/_factory.py +6 -16
- gobby/mcp_proxy/tools/tasks/_lifecycle.py +60 -29
- gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +18 -29
- gobby/mcp_proxy/tools/workflows.py +1 -1
- gobby/mcp_proxy/tools/worktrees.py +5 -0
- gobby/memory/backends/__init__.py +6 -1
- gobby/memory/backends/mem0.py +6 -1
- gobby/memory/extractor.py +477 -0
- gobby/memory/manager.py +11 -2
- gobby/prompts/defaults/handoff/compact.md +63 -0
- gobby/prompts/defaults/handoff/session_end.md +57 -0
- gobby/prompts/defaults/memory/extract.md +61 -0
- gobby/runner.py +37 -16
- gobby/search/__init__.py +48 -6
- gobby/search/backends/__init__.py +159 -0
- gobby/search/backends/embedding.py +225 -0
- gobby/search/embeddings.py +238 -0
- gobby/search/models.py +148 -0
- gobby/search/unified.py +496 -0
- gobby/servers/http.py +23 -8
- gobby/servers/routes/admin.py +280 -0
- gobby/servers/routes/mcp/tools.py +241 -52
- gobby/servers/websocket.py +2 -2
- gobby/sessions/analyzer.py +2 -0
- gobby/sessions/transcripts/base.py +1 -0
- gobby/sessions/transcripts/claude.py +64 -5
- gobby/skills/__init__.py +91 -0
- gobby/skills/loader.py +685 -0
- gobby/skills/manager.py +384 -0
- gobby/skills/parser.py +258 -0
- gobby/skills/search.py +463 -0
- gobby/skills/sync.py +119 -0
- gobby/skills/updater.py +385 -0
- gobby/skills/validator.py +368 -0
- gobby/storage/clones.py +378 -0
- gobby/storage/database.py +1 -1
- gobby/storage/memories.py +43 -13
- gobby/storage/migrations.py +180 -6
- gobby/storage/sessions.py +73 -0
- gobby/storage/skills.py +749 -0
- gobby/storage/tasks/_crud.py +4 -4
- gobby/storage/tasks/_lifecycle.py +41 -6
- gobby/storage/tasks/_manager.py +14 -5
- gobby/storage/tasks/_models.py +8 -3
- gobby/sync/memories.py +39 -4
- gobby/sync/tasks.py +83 -6
- gobby/tasks/__init__.py +1 -2
- gobby/tasks/validation.py +24 -15
- gobby/tui/api_client.py +4 -7
- gobby/tui/app.py +5 -3
- gobby/tui/screens/orchestrator.py +1 -2
- gobby/tui/screens/tasks.py +2 -4
- gobby/tui/ws_client.py +1 -1
- gobby/utils/daemon_client.py +2 -2
- gobby/workflows/actions.py +84 -2
- gobby/workflows/context_actions.py +43 -0
- gobby/workflows/detection_helpers.py +115 -31
- gobby/workflows/engine.py +13 -2
- gobby/workflows/lifecycle_evaluator.py +29 -1
- gobby/workflows/loader.py +19 -6
- gobby/workflows/memory_actions.py +74 -0
- gobby/workflows/summary_actions.py +17 -0
- gobby/workflows/task_enforcement_actions.py +448 -6
- {gobby-0.2.5.dist-info → gobby-0.2.6.dist-info}/METADATA +82 -21
- {gobby-0.2.5.dist-info → gobby-0.2.6.dist-info}/RECORD +136 -107
- gobby/install/codex/prompts/forget.md +0 -7
- gobby/install/codex/prompts/memories.md +0 -7
- gobby/install/codex/prompts/recall.md +0 -7
- gobby/install/codex/prompts/remember.md +0 -13
- gobby/llm/gemini_executor.py +0 -339
- gobby/mcp_proxy/tools/task_expansion.py +0 -591
- gobby/tasks/context.py +0 -747
- gobby/tasks/criteria.py +0 -342
- gobby/tasks/expansion.py +0 -626
- gobby/tasks/prompts/expand.py +0 -327
- gobby/tasks/research.py +0 -421
- gobby/tasks/tdd.py +0 -352
- {gobby-0.2.5.dist-info → gobby-0.2.6.dist-info}/WHEEL +0 -0
- {gobby-0.2.5.dist-info → gobby-0.2.6.dist-info}/entry_points.txt +0 -0
- {gobby-0.2.5.dist-info → gobby-0.2.6.dist-info}/licenses/LICENSE.md +0 -0
- {gobby-0.2.5.dist-info → gobby-0.2.6.dist-info}/top_level.txt +0 -0
gobby/mcp_proxy/tools/memory.py
CHANGED
|
@@ -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
|
|
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.",
|
gobby/mcp_proxy/tools/metrics.py
CHANGED
|
@@ -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(
|
|
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,
|