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.
- gobby/__init__.py +3 -0
- gobby/adapters/__init__.py +30 -0
- gobby/adapters/base.py +93 -0
- gobby/adapters/claude_code.py +276 -0
- gobby/adapters/codex.py +1292 -0
- gobby/adapters/gemini.py +343 -0
- gobby/agents/__init__.py +37 -0
- gobby/agents/codex_session.py +120 -0
- gobby/agents/constants.py +112 -0
- gobby/agents/context.py +362 -0
- gobby/agents/definitions.py +133 -0
- gobby/agents/gemini_session.py +111 -0
- gobby/agents/registry.py +618 -0
- gobby/agents/runner.py +968 -0
- gobby/agents/session.py +259 -0
- gobby/agents/spawn.py +916 -0
- gobby/agents/spawners/__init__.py +77 -0
- gobby/agents/spawners/base.py +142 -0
- gobby/agents/spawners/cross_platform.py +266 -0
- gobby/agents/spawners/embedded.py +225 -0
- gobby/agents/spawners/headless.py +226 -0
- gobby/agents/spawners/linux.py +125 -0
- gobby/agents/spawners/macos.py +277 -0
- gobby/agents/spawners/windows.py +308 -0
- gobby/agents/tty_config.py +319 -0
- gobby/autonomous/__init__.py +32 -0
- gobby/autonomous/progress_tracker.py +447 -0
- gobby/autonomous/stop_registry.py +269 -0
- gobby/autonomous/stuck_detector.py +383 -0
- gobby/cli/__init__.py +67 -0
- gobby/cli/__main__.py +8 -0
- gobby/cli/agents.py +529 -0
- gobby/cli/artifacts.py +266 -0
- gobby/cli/daemon.py +329 -0
- gobby/cli/extensions.py +526 -0
- gobby/cli/github.py +263 -0
- gobby/cli/init.py +53 -0
- gobby/cli/install.py +614 -0
- gobby/cli/installers/__init__.py +37 -0
- gobby/cli/installers/antigravity.py +65 -0
- gobby/cli/installers/claude.py +363 -0
- gobby/cli/installers/codex.py +192 -0
- gobby/cli/installers/gemini.py +294 -0
- gobby/cli/installers/git_hooks.py +377 -0
- gobby/cli/installers/shared.py +737 -0
- gobby/cli/linear.py +250 -0
- gobby/cli/mcp.py +30 -0
- gobby/cli/mcp_proxy.py +698 -0
- gobby/cli/memory.py +304 -0
- gobby/cli/merge.py +384 -0
- gobby/cli/projects.py +79 -0
- gobby/cli/sessions.py +622 -0
- gobby/cli/tasks/__init__.py +30 -0
- gobby/cli/tasks/_utils.py +658 -0
- gobby/cli/tasks/ai.py +1025 -0
- gobby/cli/tasks/commits.py +169 -0
- gobby/cli/tasks/crud.py +685 -0
- gobby/cli/tasks/deps.py +135 -0
- gobby/cli/tasks/labels.py +63 -0
- gobby/cli/tasks/main.py +273 -0
- gobby/cli/tasks/search.py +178 -0
- gobby/cli/tui.py +34 -0
- gobby/cli/utils.py +513 -0
- gobby/cli/workflows.py +927 -0
- gobby/cli/worktrees.py +481 -0
- gobby/config/__init__.py +129 -0
- gobby/config/app.py +551 -0
- gobby/config/extensions.py +167 -0
- gobby/config/features.py +472 -0
- gobby/config/llm_providers.py +98 -0
- gobby/config/logging.py +66 -0
- gobby/config/mcp.py +346 -0
- gobby/config/persistence.py +247 -0
- gobby/config/servers.py +141 -0
- gobby/config/sessions.py +250 -0
- gobby/config/tasks.py +784 -0
- gobby/hooks/__init__.py +104 -0
- gobby/hooks/artifact_capture.py +213 -0
- gobby/hooks/broadcaster.py +243 -0
- gobby/hooks/event_handlers.py +723 -0
- gobby/hooks/events.py +218 -0
- gobby/hooks/git.py +169 -0
- gobby/hooks/health_monitor.py +171 -0
- gobby/hooks/hook_manager.py +856 -0
- gobby/hooks/hook_types.py +575 -0
- gobby/hooks/plugins.py +813 -0
- gobby/hooks/session_coordinator.py +396 -0
- gobby/hooks/verification_runner.py +268 -0
- gobby/hooks/webhooks.py +339 -0
- gobby/install/claude/commands/gobby/bug.md +51 -0
- gobby/install/claude/commands/gobby/chore.md +51 -0
- gobby/install/claude/commands/gobby/epic.md +52 -0
- gobby/install/claude/commands/gobby/eval.md +235 -0
- gobby/install/claude/commands/gobby/feat.md +49 -0
- gobby/install/claude/commands/gobby/nit.md +52 -0
- gobby/install/claude/commands/gobby/ref.md +52 -0
- gobby/install/claude/hooks/HOOK_SCHEMAS.md +632 -0
- gobby/install/claude/hooks/hook_dispatcher.py +364 -0
- gobby/install/claude/hooks/validate_settings.py +102 -0
- gobby/install/claude/hooks-template.json +118 -0
- gobby/install/codex/hooks/hook_dispatcher.py +153 -0
- gobby/install/codex/prompts/forget.md +7 -0
- gobby/install/codex/prompts/memories.md +7 -0
- gobby/install/codex/prompts/recall.md +7 -0
- gobby/install/codex/prompts/remember.md +13 -0
- gobby/install/gemini/hooks/hook_dispatcher.py +268 -0
- gobby/install/gemini/hooks-template.json +138 -0
- gobby/install/shared/plugins/code_guardian.py +456 -0
- gobby/install/shared/plugins/example_notify.py +331 -0
- gobby/integrations/__init__.py +10 -0
- gobby/integrations/github.py +145 -0
- gobby/integrations/linear.py +145 -0
- gobby/llm/__init__.py +40 -0
- gobby/llm/base.py +120 -0
- gobby/llm/claude.py +578 -0
- gobby/llm/claude_executor.py +503 -0
- gobby/llm/codex.py +322 -0
- gobby/llm/codex_executor.py +513 -0
- gobby/llm/executor.py +316 -0
- gobby/llm/factory.py +34 -0
- gobby/llm/gemini.py +258 -0
- gobby/llm/gemini_executor.py +339 -0
- gobby/llm/litellm.py +287 -0
- gobby/llm/litellm_executor.py +303 -0
- gobby/llm/resolver.py +499 -0
- gobby/llm/service.py +236 -0
- gobby/mcp_proxy/__init__.py +29 -0
- gobby/mcp_proxy/actions.py +175 -0
- gobby/mcp_proxy/daemon_control.py +198 -0
- gobby/mcp_proxy/importer.py +436 -0
- gobby/mcp_proxy/lazy.py +325 -0
- gobby/mcp_proxy/manager.py +798 -0
- gobby/mcp_proxy/metrics.py +609 -0
- gobby/mcp_proxy/models.py +139 -0
- gobby/mcp_proxy/registries.py +215 -0
- gobby/mcp_proxy/schema_hash.py +381 -0
- gobby/mcp_proxy/semantic_search.py +706 -0
- gobby/mcp_proxy/server.py +549 -0
- gobby/mcp_proxy/services/__init__.py +0 -0
- gobby/mcp_proxy/services/fallback.py +306 -0
- gobby/mcp_proxy/services/recommendation.py +224 -0
- gobby/mcp_proxy/services/server_mgmt.py +214 -0
- gobby/mcp_proxy/services/system.py +72 -0
- gobby/mcp_proxy/services/tool_filter.py +231 -0
- gobby/mcp_proxy/services/tool_proxy.py +309 -0
- gobby/mcp_proxy/stdio.py +565 -0
- gobby/mcp_proxy/tools/__init__.py +27 -0
- gobby/mcp_proxy/tools/agents.py +1103 -0
- gobby/mcp_proxy/tools/artifacts.py +207 -0
- gobby/mcp_proxy/tools/hub.py +335 -0
- gobby/mcp_proxy/tools/internal.py +337 -0
- gobby/mcp_proxy/tools/memory.py +543 -0
- gobby/mcp_proxy/tools/merge.py +422 -0
- gobby/mcp_proxy/tools/metrics.py +283 -0
- gobby/mcp_proxy/tools/orchestration/__init__.py +23 -0
- gobby/mcp_proxy/tools/orchestration/cleanup.py +619 -0
- gobby/mcp_proxy/tools/orchestration/monitor.py +380 -0
- gobby/mcp_proxy/tools/orchestration/orchestrate.py +746 -0
- gobby/mcp_proxy/tools/orchestration/review.py +736 -0
- gobby/mcp_proxy/tools/orchestration/utils.py +16 -0
- gobby/mcp_proxy/tools/session_messages.py +1056 -0
- gobby/mcp_proxy/tools/task_dependencies.py +219 -0
- gobby/mcp_proxy/tools/task_expansion.py +591 -0
- gobby/mcp_proxy/tools/task_github.py +393 -0
- gobby/mcp_proxy/tools/task_linear.py +379 -0
- gobby/mcp_proxy/tools/task_orchestration.py +77 -0
- gobby/mcp_proxy/tools/task_readiness.py +522 -0
- gobby/mcp_proxy/tools/task_sync.py +351 -0
- gobby/mcp_proxy/tools/task_validation.py +843 -0
- gobby/mcp_proxy/tools/tasks/__init__.py +25 -0
- gobby/mcp_proxy/tools/tasks/_context.py +112 -0
- gobby/mcp_proxy/tools/tasks/_crud.py +516 -0
- gobby/mcp_proxy/tools/tasks/_factory.py +176 -0
- gobby/mcp_proxy/tools/tasks/_helpers.py +129 -0
- gobby/mcp_proxy/tools/tasks/_lifecycle.py +517 -0
- gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +301 -0
- gobby/mcp_proxy/tools/tasks/_resolution.py +55 -0
- gobby/mcp_proxy/tools/tasks/_search.py +215 -0
- gobby/mcp_proxy/tools/tasks/_session.py +125 -0
- gobby/mcp_proxy/tools/workflows.py +973 -0
- gobby/mcp_proxy/tools/worktrees.py +1264 -0
- gobby/mcp_proxy/transports/__init__.py +0 -0
- gobby/mcp_proxy/transports/base.py +95 -0
- gobby/mcp_proxy/transports/factory.py +44 -0
- gobby/mcp_proxy/transports/http.py +139 -0
- gobby/mcp_proxy/transports/stdio.py +213 -0
- gobby/mcp_proxy/transports/websocket.py +136 -0
- gobby/memory/backends/__init__.py +116 -0
- gobby/memory/backends/mem0.py +408 -0
- gobby/memory/backends/memu.py +485 -0
- gobby/memory/backends/null.py +111 -0
- gobby/memory/backends/openmemory.py +537 -0
- gobby/memory/backends/sqlite.py +304 -0
- gobby/memory/context.py +87 -0
- gobby/memory/manager.py +1001 -0
- gobby/memory/protocol.py +451 -0
- gobby/memory/search/__init__.py +66 -0
- gobby/memory/search/text.py +127 -0
- gobby/memory/viz.py +258 -0
- gobby/prompts/__init__.py +13 -0
- gobby/prompts/defaults/expansion/system.md +119 -0
- gobby/prompts/defaults/expansion/user.md +48 -0
- gobby/prompts/defaults/external_validation/agent.md +72 -0
- gobby/prompts/defaults/external_validation/external.md +63 -0
- gobby/prompts/defaults/external_validation/spawn.md +83 -0
- gobby/prompts/defaults/external_validation/system.md +6 -0
- gobby/prompts/defaults/features/import_mcp.md +22 -0
- gobby/prompts/defaults/features/import_mcp_github.md +17 -0
- gobby/prompts/defaults/features/import_mcp_search.md +16 -0
- gobby/prompts/defaults/features/recommend_tools.md +32 -0
- gobby/prompts/defaults/features/recommend_tools_hybrid.md +35 -0
- gobby/prompts/defaults/features/recommend_tools_llm.md +30 -0
- gobby/prompts/defaults/features/server_description.md +20 -0
- gobby/prompts/defaults/features/server_description_system.md +6 -0
- gobby/prompts/defaults/features/task_description.md +31 -0
- gobby/prompts/defaults/features/task_description_system.md +6 -0
- gobby/prompts/defaults/features/tool_summary.md +17 -0
- gobby/prompts/defaults/features/tool_summary_system.md +6 -0
- gobby/prompts/defaults/research/step.md +58 -0
- gobby/prompts/defaults/validation/criteria.md +47 -0
- gobby/prompts/defaults/validation/validate.md +38 -0
- gobby/prompts/loader.py +346 -0
- gobby/prompts/models.py +113 -0
- gobby/py.typed +0 -0
- gobby/runner.py +488 -0
- gobby/search/__init__.py +23 -0
- gobby/search/protocol.py +104 -0
- gobby/search/tfidf.py +232 -0
- gobby/servers/__init__.py +7 -0
- gobby/servers/http.py +636 -0
- gobby/servers/models.py +31 -0
- gobby/servers/routes/__init__.py +23 -0
- gobby/servers/routes/admin.py +416 -0
- gobby/servers/routes/dependencies.py +118 -0
- gobby/servers/routes/mcp/__init__.py +24 -0
- gobby/servers/routes/mcp/hooks.py +135 -0
- gobby/servers/routes/mcp/plugins.py +121 -0
- gobby/servers/routes/mcp/tools.py +1337 -0
- gobby/servers/routes/mcp/webhooks.py +159 -0
- gobby/servers/routes/sessions.py +582 -0
- gobby/servers/websocket.py +766 -0
- gobby/sessions/__init__.py +13 -0
- gobby/sessions/analyzer.py +322 -0
- gobby/sessions/lifecycle.py +240 -0
- gobby/sessions/manager.py +563 -0
- gobby/sessions/processor.py +225 -0
- gobby/sessions/summary.py +532 -0
- gobby/sessions/transcripts/__init__.py +41 -0
- gobby/sessions/transcripts/base.py +125 -0
- gobby/sessions/transcripts/claude.py +386 -0
- gobby/sessions/transcripts/codex.py +143 -0
- gobby/sessions/transcripts/gemini.py +195 -0
- gobby/storage/__init__.py +21 -0
- gobby/storage/agents.py +409 -0
- gobby/storage/artifact_classifier.py +341 -0
- gobby/storage/artifacts.py +285 -0
- gobby/storage/compaction.py +67 -0
- gobby/storage/database.py +357 -0
- gobby/storage/inter_session_messages.py +194 -0
- gobby/storage/mcp.py +680 -0
- gobby/storage/memories.py +562 -0
- gobby/storage/merge_resolutions.py +550 -0
- gobby/storage/migrations.py +860 -0
- gobby/storage/migrations_legacy.py +1359 -0
- gobby/storage/projects.py +166 -0
- gobby/storage/session_messages.py +251 -0
- gobby/storage/session_tasks.py +97 -0
- gobby/storage/sessions.py +817 -0
- gobby/storage/task_dependencies.py +223 -0
- gobby/storage/tasks/__init__.py +42 -0
- gobby/storage/tasks/_aggregates.py +180 -0
- gobby/storage/tasks/_crud.py +449 -0
- gobby/storage/tasks/_id.py +104 -0
- gobby/storage/tasks/_lifecycle.py +311 -0
- gobby/storage/tasks/_manager.py +889 -0
- gobby/storage/tasks/_models.py +300 -0
- gobby/storage/tasks/_ordering.py +119 -0
- gobby/storage/tasks/_path_cache.py +110 -0
- gobby/storage/tasks/_queries.py +343 -0
- gobby/storage/tasks/_search.py +143 -0
- gobby/storage/workflow_audit.py +393 -0
- gobby/storage/worktrees.py +547 -0
- gobby/sync/__init__.py +29 -0
- gobby/sync/github.py +333 -0
- gobby/sync/linear.py +304 -0
- gobby/sync/memories.py +284 -0
- gobby/sync/tasks.py +641 -0
- gobby/tasks/__init__.py +8 -0
- gobby/tasks/build_verification.py +193 -0
- gobby/tasks/commits.py +633 -0
- gobby/tasks/context.py +747 -0
- gobby/tasks/criteria.py +342 -0
- gobby/tasks/enhanced_validator.py +226 -0
- gobby/tasks/escalation.py +263 -0
- gobby/tasks/expansion.py +626 -0
- gobby/tasks/external_validator.py +764 -0
- gobby/tasks/issue_extraction.py +171 -0
- gobby/tasks/prompts/expand.py +327 -0
- gobby/tasks/research.py +421 -0
- gobby/tasks/tdd.py +352 -0
- gobby/tasks/tree_builder.py +263 -0
- gobby/tasks/validation.py +712 -0
- gobby/tasks/validation_history.py +357 -0
- gobby/tasks/validation_models.py +89 -0
- gobby/tools/__init__.py +0 -0
- gobby/tools/summarizer.py +170 -0
- gobby/tui/__init__.py +5 -0
- gobby/tui/api_client.py +281 -0
- gobby/tui/app.py +327 -0
- gobby/tui/screens/__init__.py +25 -0
- gobby/tui/screens/agents.py +333 -0
- gobby/tui/screens/chat.py +450 -0
- gobby/tui/screens/dashboard.py +377 -0
- gobby/tui/screens/memory.py +305 -0
- gobby/tui/screens/metrics.py +231 -0
- gobby/tui/screens/orchestrator.py +904 -0
- gobby/tui/screens/sessions.py +412 -0
- gobby/tui/screens/tasks.py +442 -0
- gobby/tui/screens/workflows.py +289 -0
- gobby/tui/screens/worktrees.py +174 -0
- gobby/tui/widgets/__init__.py +21 -0
- gobby/tui/widgets/chat.py +210 -0
- gobby/tui/widgets/conductor.py +104 -0
- gobby/tui/widgets/menu.py +132 -0
- gobby/tui/widgets/message_panel.py +160 -0
- gobby/tui/widgets/review_gate.py +224 -0
- gobby/tui/widgets/task_tree.py +99 -0
- gobby/tui/widgets/token_budget.py +166 -0
- gobby/tui/ws_client.py +258 -0
- gobby/utils/__init__.py +3 -0
- gobby/utils/daemon_client.py +235 -0
- gobby/utils/git.py +222 -0
- gobby/utils/id.py +38 -0
- gobby/utils/json_helpers.py +161 -0
- gobby/utils/logging.py +376 -0
- gobby/utils/machine_id.py +135 -0
- gobby/utils/metrics.py +589 -0
- gobby/utils/project_context.py +182 -0
- gobby/utils/project_init.py +263 -0
- gobby/utils/status.py +256 -0
- gobby/utils/validation.py +80 -0
- gobby/utils/version.py +23 -0
- gobby/workflows/__init__.py +4 -0
- gobby/workflows/actions.py +1310 -0
- gobby/workflows/approval_flow.py +138 -0
- gobby/workflows/artifact_actions.py +103 -0
- gobby/workflows/audit_helpers.py +110 -0
- gobby/workflows/autonomous_actions.py +286 -0
- gobby/workflows/context_actions.py +394 -0
- gobby/workflows/definitions.py +130 -0
- gobby/workflows/detection_helpers.py +208 -0
- gobby/workflows/engine.py +485 -0
- gobby/workflows/evaluator.py +669 -0
- gobby/workflows/git_utils.py +96 -0
- gobby/workflows/hooks.py +169 -0
- gobby/workflows/lifecycle_evaluator.py +613 -0
- gobby/workflows/llm_actions.py +70 -0
- gobby/workflows/loader.py +333 -0
- gobby/workflows/mcp_actions.py +60 -0
- gobby/workflows/memory_actions.py +272 -0
- gobby/workflows/premature_stop.py +164 -0
- gobby/workflows/session_actions.py +139 -0
- gobby/workflows/state_actions.py +123 -0
- gobby/workflows/state_manager.py +104 -0
- gobby/workflows/stop_signal_actions.py +163 -0
- gobby/workflows/summary_actions.py +344 -0
- gobby/workflows/task_actions.py +249 -0
- gobby/workflows/task_enforcement_actions.py +901 -0
- gobby/workflows/templates.py +52 -0
- gobby/workflows/todo_actions.py +84 -0
- gobby/workflows/webhook.py +223 -0
- gobby/workflows/webhook_executor.py +399 -0
- gobby/worktrees/__init__.py +5 -0
- gobby/worktrees/git.py +690 -0
- gobby/worktrees/merge/__init__.py +20 -0
- gobby/worktrees/merge/conflict_parser.py +177 -0
- gobby/worktrees/merge/resolver.py +485 -0
- gobby-0.2.5.dist-info/METADATA +351 -0
- gobby-0.2.5.dist-info/RECORD +383 -0
- gobby-0.2.5.dist-info/WHEEL +5 -0
- gobby-0.2.5.dist-info/entry_points.txt +2 -0
- gobby-0.2.5.dist-info/licenses/LICENSE.md +193 -0
- gobby-0.2.5.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Internal MCP tools for Gobby Merge Resolution.
|
|
3
|
+
|
|
4
|
+
Exposes functionality for:
|
|
5
|
+
- Starting merge operations with AI-powered resolution
|
|
6
|
+
- Getting merge status and conflict details
|
|
7
|
+
- Resolving individual conflicts
|
|
8
|
+
- Applying resolved merges
|
|
9
|
+
- Aborting merge operations
|
|
10
|
+
|
|
11
|
+
These tools are registered with the InternalToolRegistry and accessed
|
|
12
|
+
via the downstream proxy pattern (call_tool, list_tools, get_tool_schema).
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import logging
|
|
18
|
+
from typing import TYPE_CHECKING, Any
|
|
19
|
+
|
|
20
|
+
from gobby.mcp_proxy.tools.internal import InternalToolRegistry
|
|
21
|
+
from gobby.storage.merge_resolutions import ConflictStatus
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from gobby.storage.merge_resolutions import MergeResolutionManager
|
|
25
|
+
from gobby.worktrees.git import WorktreeGitManager
|
|
26
|
+
from gobby.worktrees.merge import MergeResolver
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def create_merge_registry(
|
|
32
|
+
merge_storage: MergeResolutionManager,
|
|
33
|
+
merge_resolver: MergeResolver,
|
|
34
|
+
git_manager: WorktreeGitManager | None = None,
|
|
35
|
+
worktree_manager: Any | None = None,
|
|
36
|
+
) -> InternalToolRegistry:
|
|
37
|
+
"""
|
|
38
|
+
Create a merge tool registry with all merge-related tools.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
merge_storage: MergeResolutionManager for database operations.
|
|
42
|
+
merge_resolver: MergeResolver for AI-powered conflict resolution.
|
|
43
|
+
git_manager: WorktreeGitManager for git operations.
|
|
44
|
+
worktree_manager: LocalWorktreeManager for resolving worktree paths.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
InternalToolRegistry with all merge tools registered.
|
|
48
|
+
"""
|
|
49
|
+
registry = InternalToolRegistry(
|
|
50
|
+
name="gobby-merge",
|
|
51
|
+
description="AI-powered merge conflict resolution - start merges, resolve conflicts, and apply resolutions",
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
@registry.tool(
|
|
55
|
+
name="merge_start",
|
|
56
|
+
description="Start a merge operation with AI-powered conflict resolution.",
|
|
57
|
+
)
|
|
58
|
+
async def merge_start(
|
|
59
|
+
worktree_id: str,
|
|
60
|
+
source_branch: str,
|
|
61
|
+
target_branch: str = "main",
|
|
62
|
+
strategy: str = "auto",
|
|
63
|
+
) -> dict[str, Any]:
|
|
64
|
+
"""
|
|
65
|
+
Start a merge operation.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
worktree_id: ID of the worktree to merge in.
|
|
69
|
+
source_branch: Branch being merged in.
|
|
70
|
+
target_branch: Target branch (default: main).
|
|
71
|
+
strategy: Resolution strategy ('auto', 'conflict_only', 'full_file', 'manual').
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Dict with resolution_id, success status, and conflict details.
|
|
75
|
+
"""
|
|
76
|
+
# Validate required parameters
|
|
77
|
+
if not worktree_id:
|
|
78
|
+
return {
|
|
79
|
+
"success": False,
|
|
80
|
+
"error": "worktree_id is required",
|
|
81
|
+
}
|
|
82
|
+
if not source_branch:
|
|
83
|
+
return {
|
|
84
|
+
"success": False,
|
|
85
|
+
"error": "source_branch is required",
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
# Create resolution record
|
|
90
|
+
resolution = merge_storage.create_resolution(
|
|
91
|
+
worktree_id=worktree_id,
|
|
92
|
+
source_branch=source_branch,
|
|
93
|
+
target_branch=target_branch,
|
|
94
|
+
status="pending",
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# Attempt merge resolution
|
|
98
|
+
from gobby.worktrees.merge import ResolutionTier
|
|
99
|
+
|
|
100
|
+
force_tier = None
|
|
101
|
+
if strategy == "conflict_only":
|
|
102
|
+
force_tier = ResolutionTier.CONFLICT_ONLY_AI
|
|
103
|
+
elif strategy == "full_file":
|
|
104
|
+
force_tier = ResolutionTier.FULL_FILE_AI
|
|
105
|
+
|
|
106
|
+
# Get worktree path from manager
|
|
107
|
+
worktree_path = None
|
|
108
|
+
if worktree_manager:
|
|
109
|
+
worktree = worktree_manager.get_worktree(worktree_id)
|
|
110
|
+
if worktree and worktree.worktree_path:
|
|
111
|
+
worktree_path = worktree.worktree_path
|
|
112
|
+
|
|
113
|
+
if not worktree_path:
|
|
114
|
+
return {
|
|
115
|
+
"success": False,
|
|
116
|
+
"error": f"Worktree '{worktree_id}' not found or has no path",
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
result = await merge_resolver.resolve(
|
|
120
|
+
worktree_path=worktree_path,
|
|
121
|
+
source_branch=source_branch,
|
|
122
|
+
target_branch=target_branch,
|
|
123
|
+
force_tier=force_tier,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# Update resolution with result
|
|
127
|
+
merge_storage.update_resolution(
|
|
128
|
+
resolution_id=resolution.id,
|
|
129
|
+
status="resolved" if result.success else "pending",
|
|
130
|
+
tier_used=result.tier.value if result.success else None,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# Create conflict records if needed
|
|
134
|
+
for conflict in result.conflicts:
|
|
135
|
+
file_path = conflict.get("file", "")
|
|
136
|
+
merge_storage.create_conflict(
|
|
137
|
+
resolution_id=resolution.id,
|
|
138
|
+
file_path=file_path,
|
|
139
|
+
ours_content=conflict.get("ours_content"),
|
|
140
|
+
theirs_content=conflict.get("theirs_content"),
|
|
141
|
+
status="pending" if not result.success else "resolved",
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
"success": result.success,
|
|
146
|
+
"resolution_id": resolution.id,
|
|
147
|
+
"tier": result.tier.value,
|
|
148
|
+
"needs_human_review": result.needs_human_review,
|
|
149
|
+
"conflicts": [{"file": c.get("file", "")} for c in result.unresolved_conflicts],
|
|
150
|
+
"resolved_files": result.resolved_files,
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
except Exception as e:
|
|
154
|
+
logger.exception(f"Error starting merge: {e}")
|
|
155
|
+
return {
|
|
156
|
+
"success": False,
|
|
157
|
+
"error": str(e),
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
@registry.tool(
|
|
161
|
+
name="merge_status",
|
|
162
|
+
description="Get the status of a merge resolution including conflict details.",
|
|
163
|
+
)
|
|
164
|
+
async def merge_status(resolution_id: str) -> dict[str, Any]:
|
|
165
|
+
"""
|
|
166
|
+
Get merge resolution status.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
resolution_id: The resolution ID.
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
Dict with resolution details and conflicts.
|
|
173
|
+
"""
|
|
174
|
+
if not resolution_id:
|
|
175
|
+
return {
|
|
176
|
+
"success": False,
|
|
177
|
+
"error": "resolution_id is required",
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
resolution = merge_storage.get_resolution(resolution_id)
|
|
181
|
+
if not resolution:
|
|
182
|
+
return {
|
|
183
|
+
"success": False,
|
|
184
|
+
"error": f"Resolution '{resolution_id}' not found",
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
conflicts = merge_storage.list_conflicts(resolution_id=resolution_id)
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
"success": True,
|
|
191
|
+
"resolution": resolution.to_dict(),
|
|
192
|
+
"conflicts": [c.to_dict() for c in conflicts],
|
|
193
|
+
"pending_count": sum(1 for c in conflicts if c.status == "pending"),
|
|
194
|
+
"resolved_count": sum(1 for c in conflicts if c.status == "resolved"),
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
@registry.tool(
|
|
198
|
+
name="merge_resolve",
|
|
199
|
+
description="Resolve a specific conflict, optionally with AI assistance.",
|
|
200
|
+
)
|
|
201
|
+
async def merge_resolve(
|
|
202
|
+
conflict_id: str,
|
|
203
|
+
resolved_content: str | None = None,
|
|
204
|
+
use_ai: bool = True,
|
|
205
|
+
) -> dict[str, Any]:
|
|
206
|
+
"""
|
|
207
|
+
Resolve a specific conflict.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
conflict_id: The conflict ID.
|
|
211
|
+
resolved_content: Manual resolution content (skips AI).
|
|
212
|
+
use_ai: Whether to use AI for resolution (default: True).
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
Dict with resolution result.
|
|
216
|
+
"""
|
|
217
|
+
if not conflict_id:
|
|
218
|
+
return {
|
|
219
|
+
"success": False,
|
|
220
|
+
"error": "conflict_id is required",
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
conflict = merge_storage.get_conflict(conflict_id)
|
|
224
|
+
if not conflict:
|
|
225
|
+
return {
|
|
226
|
+
"success": False,
|
|
227
|
+
"error": f"Conflict '{conflict_id}' not found",
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
try:
|
|
231
|
+
if resolved_content is not None:
|
|
232
|
+
# Manual resolution
|
|
233
|
+
updated = merge_storage.update_conflict(
|
|
234
|
+
conflict_id=conflict_id,
|
|
235
|
+
status=ConflictStatus.RESOLVED.value,
|
|
236
|
+
resolved_content=resolved_content,
|
|
237
|
+
)
|
|
238
|
+
return {
|
|
239
|
+
"success": True,
|
|
240
|
+
"conflict": updated.to_dict() if updated else None,
|
|
241
|
+
"resolution_method": "manual",
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if use_ai:
|
|
245
|
+
# Use AI resolver
|
|
246
|
+
from gobby.worktrees.merge import ConflictHunk
|
|
247
|
+
|
|
248
|
+
# Create hunk from conflict data
|
|
249
|
+
hunks = [
|
|
250
|
+
ConflictHunk(
|
|
251
|
+
ours=conflict.ours_content or "",
|
|
252
|
+
theirs=conflict.theirs_content or "",
|
|
253
|
+
base=None,
|
|
254
|
+
start_line=1,
|
|
255
|
+
end_line=1,
|
|
256
|
+
context_before="",
|
|
257
|
+
context_after="",
|
|
258
|
+
)
|
|
259
|
+
]
|
|
260
|
+
|
|
261
|
+
result = await merge_resolver.resolve_file(
|
|
262
|
+
path=conflict.file_path,
|
|
263
|
+
conflict_hunks=hunks,
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
if result.success:
|
|
267
|
+
# Get resolved content from result (would be in resolved_files)
|
|
268
|
+
resolved = "AI resolved content" # Placeholder
|
|
269
|
+
updated = merge_storage.update_conflict(
|
|
270
|
+
conflict_id=conflict_id,
|
|
271
|
+
status=ConflictStatus.RESOLVED.value,
|
|
272
|
+
resolved_content=resolved,
|
|
273
|
+
)
|
|
274
|
+
return {
|
|
275
|
+
"success": True,
|
|
276
|
+
"conflict": updated.to_dict() if updated else None,
|
|
277
|
+
"resolution_method": "ai",
|
|
278
|
+
"tier": result.tier.value,
|
|
279
|
+
}
|
|
280
|
+
else:
|
|
281
|
+
return {
|
|
282
|
+
"success": False,
|
|
283
|
+
"error": "AI resolution failed",
|
|
284
|
+
"needs_human_review": result.needs_human_review,
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
"success": False,
|
|
289
|
+
"error": "No resolution method specified",
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
except Exception as e:
|
|
293
|
+
logger.exception(f"Error resolving conflict: {e}")
|
|
294
|
+
return {
|
|
295
|
+
"success": False,
|
|
296
|
+
"error": str(e),
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
@registry.tool(
|
|
300
|
+
name="merge_apply",
|
|
301
|
+
description="Apply all resolved conflicts and complete the merge.",
|
|
302
|
+
)
|
|
303
|
+
async def merge_apply(resolution_id: str) -> dict[str, Any]:
|
|
304
|
+
"""
|
|
305
|
+
Apply all resolutions and complete the merge.
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
resolution_id: The resolution ID.
|
|
309
|
+
|
|
310
|
+
Returns:
|
|
311
|
+
Dict with merge completion status.
|
|
312
|
+
"""
|
|
313
|
+
if not resolution_id:
|
|
314
|
+
return {
|
|
315
|
+
"success": False,
|
|
316
|
+
"error": "resolution_id is required",
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
resolution = merge_storage.get_resolution(resolution_id)
|
|
320
|
+
if not resolution:
|
|
321
|
+
return {
|
|
322
|
+
"success": False,
|
|
323
|
+
"error": f"Resolution '{resolution_id}' not found",
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
conflicts = merge_storage.list_conflicts(resolution_id=resolution_id)
|
|
327
|
+
|
|
328
|
+
# Check if all conflicts are resolved
|
|
329
|
+
pending = [c for c in conflicts if c.status != "resolved"]
|
|
330
|
+
if pending:
|
|
331
|
+
return {
|
|
332
|
+
"success": False,
|
|
333
|
+
"error": f"Cannot apply: {len(pending)} unresolved conflicts remaining",
|
|
334
|
+
"pending_conflicts": [{"id": c.id, "file_path": c.file_path} for c in pending],
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
try:
|
|
338
|
+
# Apply resolutions to git (would write files and stage)
|
|
339
|
+
if git_manager:
|
|
340
|
+
for conflict in conflicts:
|
|
341
|
+
if conflict.resolved_content:
|
|
342
|
+
# Would write conflict.resolved_content to conflict.file_path
|
|
343
|
+
pass
|
|
344
|
+
|
|
345
|
+
# Update resolution status
|
|
346
|
+
updated = merge_storage.update_resolution(
|
|
347
|
+
resolution_id=resolution_id,
|
|
348
|
+
status="resolved",
|
|
349
|
+
tier_used=resolution.tier_used or "manual",
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
return {
|
|
353
|
+
"success": True,
|
|
354
|
+
"resolution": updated.to_dict() if updated else None,
|
|
355
|
+
"message": "Merge completed successfully",
|
|
356
|
+
"files_merged": [c.file_path for c in conflicts],
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
except Exception as e:
|
|
360
|
+
logger.exception(f"Error applying merge: {e}")
|
|
361
|
+
return {
|
|
362
|
+
"success": False,
|
|
363
|
+
"error": str(e),
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
@registry.tool(
|
|
367
|
+
name="merge_abort",
|
|
368
|
+
description="Abort the merge operation and restore the previous state.",
|
|
369
|
+
)
|
|
370
|
+
async def merge_abort(resolution_id: str) -> dict[str, Any]:
|
|
371
|
+
"""
|
|
372
|
+
Abort a merge operation.
|
|
373
|
+
|
|
374
|
+
Args:
|
|
375
|
+
resolution_id: The resolution ID.
|
|
376
|
+
|
|
377
|
+
Returns:
|
|
378
|
+
Dict with abort status.
|
|
379
|
+
"""
|
|
380
|
+
if not resolution_id:
|
|
381
|
+
return {
|
|
382
|
+
"success": False,
|
|
383
|
+
"error": "resolution_id is required",
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
resolution = merge_storage.get_resolution(resolution_id)
|
|
387
|
+
if not resolution:
|
|
388
|
+
return {
|
|
389
|
+
"success": False,
|
|
390
|
+
"error": f"Resolution '{resolution_id}' not found",
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
# Can't abort already resolved merges
|
|
394
|
+
if resolution.status == "resolved":
|
|
395
|
+
return {
|
|
396
|
+
"success": False,
|
|
397
|
+
"error": "Cannot abort: merge is already resolved",
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
try:
|
|
401
|
+
# Abort git merge if in progress
|
|
402
|
+
if git_manager:
|
|
403
|
+
# Would run git merge --abort
|
|
404
|
+
pass
|
|
405
|
+
|
|
406
|
+
# Delete resolution and associated conflicts (cascade)
|
|
407
|
+
deleted = merge_storage.delete_resolution(resolution_id)
|
|
408
|
+
|
|
409
|
+
return {
|
|
410
|
+
"success": deleted,
|
|
411
|
+
"message": "Merge aborted successfully" if deleted else "Failed to abort merge",
|
|
412
|
+
"resolution_id": resolution_id,
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
except Exception as e:
|
|
416
|
+
logger.exception(f"Error aborting merge: {e}")
|
|
417
|
+
return {
|
|
418
|
+
"success": False,
|
|
419
|
+
"error": str(e),
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return registry
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Internal MCP tools for Tool Metrics.
|
|
3
|
+
|
|
4
|
+
Exposes functionality for:
|
|
5
|
+
- Querying tool call metrics (get_tool_metrics)
|
|
6
|
+
- Getting top performing tools (get_top_tools)
|
|
7
|
+
|
|
8
|
+
These tools are registered with the InternalToolRegistry and accessed
|
|
9
|
+
via the downstream proxy pattern (call_tool).
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from gobby.mcp_proxy.metrics import ToolMetricsManager
|
|
15
|
+
from gobby.mcp_proxy.tools.internal import InternalToolRegistry
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def create_metrics_registry(metrics_manager: ToolMetricsManager) -> InternalToolRegistry:
|
|
19
|
+
"""
|
|
20
|
+
Create a metrics tool registry with all metrics-related tools.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
metrics_manager: ToolMetricsManager instance
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
InternalToolRegistry with metrics tools registered
|
|
27
|
+
"""
|
|
28
|
+
registry = InternalToolRegistry(
|
|
29
|
+
name="gobby-metrics",
|
|
30
|
+
description="Tool metrics - query call counts, success rates, latency",
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
@registry.tool(
|
|
34
|
+
name="get_tool_metrics",
|
|
35
|
+
description="Get metrics for MCP tools including call count, success rate, and latency.",
|
|
36
|
+
)
|
|
37
|
+
def get_tool_metrics(
|
|
38
|
+
server_name: str | None = None,
|
|
39
|
+
tool_name: str | None = None,
|
|
40
|
+
project_id: str | None = None,
|
|
41
|
+
) -> dict[str, Any]:
|
|
42
|
+
"""
|
|
43
|
+
Get metrics for MCP tools.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
server_name: Optional server name to filter by
|
|
47
|
+
tool_name: Optional tool name to filter by
|
|
48
|
+
project_id: Optional project ID to filter by
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Dictionary with tool metrics including call counts, success rates, and latency
|
|
52
|
+
"""
|
|
53
|
+
try:
|
|
54
|
+
result = metrics_manager.get_metrics(
|
|
55
|
+
project_id=project_id,
|
|
56
|
+
server_name=server_name,
|
|
57
|
+
tool_name=tool_name,
|
|
58
|
+
)
|
|
59
|
+
return {
|
|
60
|
+
"success": True,
|
|
61
|
+
"metrics": result,
|
|
62
|
+
}
|
|
63
|
+
except Exception as e:
|
|
64
|
+
return {"success": False, "error": str(e)}
|
|
65
|
+
|
|
66
|
+
@registry.tool(
|
|
67
|
+
name="get_top_tools",
|
|
68
|
+
description="Get top tools by usage, success rate, or latency.",
|
|
69
|
+
)
|
|
70
|
+
def get_top_tools(
|
|
71
|
+
project_id: str | None = None,
|
|
72
|
+
limit: int = 10,
|
|
73
|
+
order_by: str = "call_count",
|
|
74
|
+
) -> dict[str, Any]:
|
|
75
|
+
"""
|
|
76
|
+
Get top tools by various metrics.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
project_id: Optional project ID to filter by
|
|
80
|
+
limit: Maximum number of tools to return (default: 10)
|
|
81
|
+
order_by: Sort criteria - "call_count", "success_count", or "avg_latency_ms"
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
List of top tools with their metrics
|
|
85
|
+
"""
|
|
86
|
+
try:
|
|
87
|
+
tools = metrics_manager.get_top_tools(
|
|
88
|
+
project_id=project_id,
|
|
89
|
+
limit=limit,
|
|
90
|
+
order_by=order_by,
|
|
91
|
+
)
|
|
92
|
+
return {
|
|
93
|
+
"success": True,
|
|
94
|
+
"tools": tools,
|
|
95
|
+
"count": len(tools),
|
|
96
|
+
}
|
|
97
|
+
except Exception as e:
|
|
98
|
+
return {"success": False, "error": str(e)}
|
|
99
|
+
|
|
100
|
+
@registry.tool(
|
|
101
|
+
name="get_failing_tools",
|
|
102
|
+
description="Get tools with high failure rates above a threshold.",
|
|
103
|
+
)
|
|
104
|
+
def get_failing_tools(
|
|
105
|
+
project_id: str | None = None,
|
|
106
|
+
threshold: float = 0.5,
|
|
107
|
+
limit: int = 10,
|
|
108
|
+
) -> dict[str, Any]:
|
|
109
|
+
"""
|
|
110
|
+
Get tools with failure rate above a threshold.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
project_id: Optional project ID to filter by
|
|
114
|
+
threshold: Minimum failure rate (0.0-1.0) to include a tool (default: 0.5)
|
|
115
|
+
limit: Maximum number of tools to return (default: 10)
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
List of failing tools sorted by failure rate descending
|
|
119
|
+
"""
|
|
120
|
+
try:
|
|
121
|
+
tools = metrics_manager.get_failing_tools(
|
|
122
|
+
project_id=project_id,
|
|
123
|
+
threshold=threshold,
|
|
124
|
+
limit=limit,
|
|
125
|
+
)
|
|
126
|
+
return {
|
|
127
|
+
"success": True,
|
|
128
|
+
"tools": tools,
|
|
129
|
+
"count": len(tools),
|
|
130
|
+
"threshold": threshold,
|
|
131
|
+
}
|
|
132
|
+
except Exception as e:
|
|
133
|
+
return {"success": False, "error": str(e)}
|
|
134
|
+
|
|
135
|
+
@registry.tool(
|
|
136
|
+
name="get_tool_success_rate",
|
|
137
|
+
description="Get success rate for a specific tool.",
|
|
138
|
+
)
|
|
139
|
+
def get_tool_success_rate(
|
|
140
|
+
server_name: str,
|
|
141
|
+
tool_name: str,
|
|
142
|
+
project_id: str,
|
|
143
|
+
) -> dict[str, Any]:
|
|
144
|
+
"""
|
|
145
|
+
Get success rate for a specific tool.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
server_name: Name of the MCP server
|
|
149
|
+
tool_name: Name of the tool
|
|
150
|
+
project_id: Project ID
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
Success rate as a float between 0 and 1
|
|
154
|
+
"""
|
|
155
|
+
try:
|
|
156
|
+
rate = metrics_manager.get_tool_success_rate(
|
|
157
|
+
server_name=server_name,
|
|
158
|
+
tool_name=tool_name,
|
|
159
|
+
project_id=project_id,
|
|
160
|
+
)
|
|
161
|
+
return {
|
|
162
|
+
"success": True,
|
|
163
|
+
"server_name": server_name,
|
|
164
|
+
"tool_name": tool_name,
|
|
165
|
+
"success_rate": rate,
|
|
166
|
+
}
|
|
167
|
+
except Exception as e:
|
|
168
|
+
return {"success": False, "error": str(e)}
|
|
169
|
+
|
|
170
|
+
@registry.tool(
|
|
171
|
+
name="reset_metrics",
|
|
172
|
+
description="Reset/delete metrics for a project, server, or specific tool.",
|
|
173
|
+
)
|
|
174
|
+
def reset_metrics(
|
|
175
|
+
project_id: str | None = None,
|
|
176
|
+
server_name: str | None = None,
|
|
177
|
+
tool_name: str | None = None,
|
|
178
|
+
) -> dict[str, Any]:
|
|
179
|
+
"""
|
|
180
|
+
Reset/delete metrics.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
project_id: Reset only for this project
|
|
184
|
+
server_name: Reset only for this server
|
|
185
|
+
tool_name: Reset only for this specific tool
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
Number of rows deleted
|
|
189
|
+
"""
|
|
190
|
+
try:
|
|
191
|
+
deleted = metrics_manager.reset_metrics(
|
|
192
|
+
project_id=project_id,
|
|
193
|
+
server_name=server_name,
|
|
194
|
+
tool_name=tool_name,
|
|
195
|
+
)
|
|
196
|
+
return {
|
|
197
|
+
"success": True,
|
|
198
|
+
"deleted_count": deleted,
|
|
199
|
+
}
|
|
200
|
+
except Exception as e:
|
|
201
|
+
return {"success": False, "error": str(e)}
|
|
202
|
+
|
|
203
|
+
@registry.tool(
|
|
204
|
+
name="reset_tool_metrics",
|
|
205
|
+
description="Admin tool to reset/delete metrics for a specific tool.",
|
|
206
|
+
)
|
|
207
|
+
def reset_tool_metrics(
|
|
208
|
+
server_name: str | None = None,
|
|
209
|
+
tool_name: str | None = None,
|
|
210
|
+
) -> dict[str, Any]:
|
|
211
|
+
"""
|
|
212
|
+
Reset/delete metrics for a specific tool (admin operation).
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
server_name: Server containing the tool
|
|
216
|
+
tool_name: Specific tool to reset metrics for
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
Number of rows deleted
|
|
220
|
+
"""
|
|
221
|
+
try:
|
|
222
|
+
deleted = metrics_manager.reset_metrics(
|
|
223
|
+
server_name=server_name,
|
|
224
|
+
tool_name=tool_name,
|
|
225
|
+
)
|
|
226
|
+
return {
|
|
227
|
+
"success": True,
|
|
228
|
+
"deleted_count": deleted,
|
|
229
|
+
"server_name": server_name,
|
|
230
|
+
"tool_name": tool_name,
|
|
231
|
+
}
|
|
232
|
+
except Exception as e:
|
|
233
|
+
return {"success": False, "error": str(e)}
|
|
234
|
+
|
|
235
|
+
@registry.tool(
|
|
236
|
+
name="cleanup_old_metrics",
|
|
237
|
+
description="Delete metrics older than retention period (default 7 days).",
|
|
238
|
+
)
|
|
239
|
+
def cleanup_old_metrics(
|
|
240
|
+
retention_days: int = 7,
|
|
241
|
+
) -> dict[str, Any]:
|
|
242
|
+
"""
|
|
243
|
+
Delete metrics older than the retention period.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
retention_days: Number of days to retain metrics (default: 7)
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
Number of rows deleted
|
|
250
|
+
"""
|
|
251
|
+
try:
|
|
252
|
+
deleted = metrics_manager.cleanup_old_metrics(
|
|
253
|
+
retention_days=retention_days,
|
|
254
|
+
)
|
|
255
|
+
return {
|
|
256
|
+
"success": True,
|
|
257
|
+
"deleted_count": deleted,
|
|
258
|
+
"retention_days": retention_days,
|
|
259
|
+
}
|
|
260
|
+
except Exception as e:
|
|
261
|
+
return {"success": False, "error": str(e)}
|
|
262
|
+
|
|
263
|
+
@registry.tool(
|
|
264
|
+
name="get_retention_stats",
|
|
265
|
+
description="Get statistics about metrics retention and age.",
|
|
266
|
+
)
|
|
267
|
+
def get_retention_stats() -> dict[str, Any]:
|
|
268
|
+
"""
|
|
269
|
+
Get statistics about metrics retention.
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
Dictionary with retention statistics
|
|
273
|
+
"""
|
|
274
|
+
try:
|
|
275
|
+
stats = metrics_manager.get_retention_stats()
|
|
276
|
+
return {
|
|
277
|
+
"success": True,
|
|
278
|
+
"stats": stats,
|
|
279
|
+
}
|
|
280
|
+
except Exception as e:
|
|
281
|
+
return {"success": False, "error": str(e)}
|
|
282
|
+
|
|
283
|
+
return registry
|