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,207 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Internal MCP tools for Gobby Artifacts System.
|
|
3
|
+
|
|
4
|
+
Exposes functionality for:
|
|
5
|
+
- search_artifacts: Full-text search across artifact content
|
|
6
|
+
- list_artifacts: List artifacts with session_id and type filters
|
|
7
|
+
- get_artifact: Get a single artifact by ID
|
|
8
|
+
- get_timeline: Get artifacts for a session in chronological order
|
|
9
|
+
|
|
10
|
+
These tools are registered with the InternalToolRegistry and accessed
|
|
11
|
+
via the downstream proxy pattern (call_tool, list_tools, get_tool_schema).
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from typing import TYPE_CHECKING, Any
|
|
17
|
+
|
|
18
|
+
from gobby.mcp_proxy.tools.internal import InternalToolRegistry
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from gobby.storage.artifacts import LocalArtifactManager
|
|
22
|
+
from gobby.storage.database import LocalDatabase
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def create_artifacts_registry(
|
|
26
|
+
db: LocalDatabase | None = None,
|
|
27
|
+
artifact_manager: LocalArtifactManager | None = None,
|
|
28
|
+
) -> InternalToolRegistry:
|
|
29
|
+
"""
|
|
30
|
+
Create an artifacts tool registry with all artifact-related tools.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
db: LocalDatabase instance (used to create artifact_manager if not provided)
|
|
34
|
+
artifact_manager: LocalArtifactManager instance
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
InternalToolRegistry with artifact tools registered
|
|
38
|
+
"""
|
|
39
|
+
# Create artifact manager if not provided
|
|
40
|
+
if artifact_manager is None:
|
|
41
|
+
if db is None:
|
|
42
|
+
from gobby.storage.database import LocalDatabase
|
|
43
|
+
|
|
44
|
+
db = LocalDatabase()
|
|
45
|
+
from gobby.storage.artifacts import LocalArtifactManager
|
|
46
|
+
|
|
47
|
+
artifact_manager = LocalArtifactManager(db)
|
|
48
|
+
|
|
49
|
+
_artifact_manager = artifact_manager
|
|
50
|
+
|
|
51
|
+
registry = InternalToolRegistry(
|
|
52
|
+
name="gobby-artifacts",
|
|
53
|
+
description="Artifact management - search, list, get, timeline",
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
@registry.tool(
|
|
57
|
+
name="search_artifacts",
|
|
58
|
+
description="Search artifacts by content using full-text search.",
|
|
59
|
+
)
|
|
60
|
+
def search_artifacts(
|
|
61
|
+
query: str,
|
|
62
|
+
session_id: str | None = None,
|
|
63
|
+
artifact_type: str | None = None,
|
|
64
|
+
limit: int = 50,
|
|
65
|
+
) -> dict[str, Any]:
|
|
66
|
+
"""
|
|
67
|
+
Search artifacts by content using FTS5 full-text search.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
query: Search query text
|
|
71
|
+
session_id: Optional session ID to filter by
|
|
72
|
+
artifact_type: Optional artifact type to filter by (code, diff, error, etc.)
|
|
73
|
+
limit: Maximum number of results (default: 50)
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Dict with success status and list of matching artifacts
|
|
77
|
+
"""
|
|
78
|
+
if not query or not query.strip():
|
|
79
|
+
return {"success": True, "artifacts": [], "count": 0}
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
artifacts = _artifact_manager.search_artifacts(
|
|
83
|
+
query_text=query,
|
|
84
|
+
session_id=session_id,
|
|
85
|
+
artifact_type=artifact_type,
|
|
86
|
+
limit=limit,
|
|
87
|
+
)
|
|
88
|
+
return {
|
|
89
|
+
"success": True,
|
|
90
|
+
"artifacts": [a.to_dict() for a in artifacts],
|
|
91
|
+
"count": len(artifacts),
|
|
92
|
+
}
|
|
93
|
+
except Exception as e:
|
|
94
|
+
return {"success": False, "error": str(e), "artifacts": []}
|
|
95
|
+
|
|
96
|
+
@registry.tool(
|
|
97
|
+
name="list_artifacts",
|
|
98
|
+
description="List artifacts with optional filters.",
|
|
99
|
+
)
|
|
100
|
+
def list_artifacts(
|
|
101
|
+
session_id: str | None = None,
|
|
102
|
+
artifact_type: str | None = None,
|
|
103
|
+
limit: int = 100,
|
|
104
|
+
offset: int = 0,
|
|
105
|
+
) -> dict[str, Any]:
|
|
106
|
+
"""
|
|
107
|
+
List artifacts with optional filters.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
session_id: Optional session ID to filter by
|
|
111
|
+
artifact_type: Optional artifact type to filter by
|
|
112
|
+
limit: Maximum number of results (default: 100)
|
|
113
|
+
offset: Offset for pagination (default: 0)
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
Dict with success status and list of artifacts
|
|
117
|
+
"""
|
|
118
|
+
try:
|
|
119
|
+
artifacts = _artifact_manager.list_artifacts(
|
|
120
|
+
session_id=session_id,
|
|
121
|
+
artifact_type=artifact_type,
|
|
122
|
+
limit=limit,
|
|
123
|
+
offset=offset,
|
|
124
|
+
)
|
|
125
|
+
return {
|
|
126
|
+
"success": True,
|
|
127
|
+
"artifacts": [a.to_dict() for a in artifacts],
|
|
128
|
+
"count": len(artifacts),
|
|
129
|
+
}
|
|
130
|
+
except Exception as e:
|
|
131
|
+
return {"success": False, "error": str(e), "artifacts": []}
|
|
132
|
+
|
|
133
|
+
@registry.tool(
|
|
134
|
+
name="get_artifact",
|
|
135
|
+
description="Get a single artifact by ID.",
|
|
136
|
+
)
|
|
137
|
+
def get_artifact(artifact_id: str) -> dict[str, Any]:
|
|
138
|
+
"""
|
|
139
|
+
Get a single artifact by its ID.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
artifact_id: The artifact ID to retrieve
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Dict with success status and artifact data
|
|
146
|
+
"""
|
|
147
|
+
try:
|
|
148
|
+
artifact = _artifact_manager.get_artifact(artifact_id)
|
|
149
|
+
if artifact is None:
|
|
150
|
+
return {
|
|
151
|
+
"success": False,
|
|
152
|
+
"error": f"Artifact '{artifact_id}' not found",
|
|
153
|
+
"artifact": None,
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
"success": True,
|
|
157
|
+
"artifact": artifact.to_dict(),
|
|
158
|
+
}
|
|
159
|
+
except Exception as e:
|
|
160
|
+
return {"success": False, "error": str(e), "artifact": None}
|
|
161
|
+
|
|
162
|
+
@registry.tool(
|
|
163
|
+
name="get_timeline",
|
|
164
|
+
description="Get artifacts for a session in chronological order.",
|
|
165
|
+
)
|
|
166
|
+
def get_timeline(
|
|
167
|
+
session_id: str | None = None,
|
|
168
|
+
artifact_type: str | None = None,
|
|
169
|
+
limit: int = 100,
|
|
170
|
+
) -> dict[str, Any]:
|
|
171
|
+
"""
|
|
172
|
+
Get artifacts for a session in chronological order (oldest first).
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
session_id: Required session ID to get timeline for
|
|
176
|
+
artifact_type: Optional artifact type to filter by
|
|
177
|
+
limit: Maximum number of results (default: 100)
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
Dict with success status and chronologically ordered artifacts
|
|
181
|
+
"""
|
|
182
|
+
if not session_id:
|
|
183
|
+
return {
|
|
184
|
+
"success": False,
|
|
185
|
+
"error": "session_id is required for timeline",
|
|
186
|
+
"artifacts": [],
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
# Get artifacts (list_artifacts returns newest first by default)
|
|
191
|
+
artifacts = _artifact_manager.list_artifacts(
|
|
192
|
+
session_id=session_id,
|
|
193
|
+
artifact_type=artifact_type,
|
|
194
|
+
limit=limit,
|
|
195
|
+
offset=0,
|
|
196
|
+
)
|
|
197
|
+
# Reverse to get chronological order (oldest first)
|
|
198
|
+
artifacts = list(reversed(artifacts))
|
|
199
|
+
return {
|
|
200
|
+
"success": True,
|
|
201
|
+
"artifacts": [a.to_dict() for a in artifacts],
|
|
202
|
+
"count": len(artifacts),
|
|
203
|
+
}
|
|
204
|
+
except Exception as e:
|
|
205
|
+
return {"success": False, "error": str(e), "artifacts": []}
|
|
206
|
+
|
|
207
|
+
return registry
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Internal MCP tools for Hub (cross-project) queries.
|
|
3
|
+
|
|
4
|
+
Exposes functionality for:
|
|
5
|
+
- list_all_projects(): List all unique projects in hub database
|
|
6
|
+
- list_cross_project_tasks(status?): Query tasks across all projects
|
|
7
|
+
- list_cross_project_sessions(limit?): Recent sessions across all projects
|
|
8
|
+
- hub_stats(): Aggregate statistics from hub database
|
|
9
|
+
|
|
10
|
+
These tools query the hub database directly (not the project db).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from collections.abc import Callable
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
from gobby.mcp_proxy.tools.internal import InternalToolRegistry
|
|
20
|
+
from gobby.storage.database import LocalDatabase
|
|
21
|
+
|
|
22
|
+
__all__ = ["create_hub_registry", "HubToolRegistry"]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class HubToolRegistry(InternalToolRegistry):
|
|
26
|
+
"""Registry for hub query tools with test-friendly get_tool method."""
|
|
27
|
+
|
|
28
|
+
def get_tool(self, name: str) -> Callable[..., Any] | None:
|
|
29
|
+
"""Get a tool function by name (for testing)."""
|
|
30
|
+
tool = self._tools.get(name)
|
|
31
|
+
return tool.func if tool else None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def create_hub_registry(
|
|
35
|
+
hub_db_path: Path,
|
|
36
|
+
) -> HubToolRegistry:
|
|
37
|
+
"""
|
|
38
|
+
Create a hub query tool registry with cross-project tools.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
hub_db_path: Path to the hub database file
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
InternalToolRegistry with hub query tools registered
|
|
45
|
+
"""
|
|
46
|
+
registry = HubToolRegistry(
|
|
47
|
+
name="gobby-hub",
|
|
48
|
+
description="Hub (cross-project) queries - list_all_projects, list_cross_project_tasks, list_cross_project_sessions, hub_stats",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
def _get_hub_db() -> LocalDatabase | None:
|
|
52
|
+
"""Get hub database connection if it exists."""
|
|
53
|
+
if not hub_db_path.exists():
|
|
54
|
+
return None
|
|
55
|
+
return LocalDatabase(hub_db_path)
|
|
56
|
+
|
|
57
|
+
@registry.tool(
|
|
58
|
+
name="list_all_projects",
|
|
59
|
+
description="List all unique projects in the hub database.",
|
|
60
|
+
)
|
|
61
|
+
async def list_all_projects() -> dict[str, Any]:
|
|
62
|
+
"""
|
|
63
|
+
List all unique projects stored in the hub database.
|
|
64
|
+
|
|
65
|
+
Returns project IDs with task and session counts.
|
|
66
|
+
"""
|
|
67
|
+
hub_db = _get_hub_db()
|
|
68
|
+
if hub_db is None:
|
|
69
|
+
return {
|
|
70
|
+
"success": False,
|
|
71
|
+
"error": f"Hub database not found: {hub_db_path}",
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
# Query unique projects from tasks table
|
|
76
|
+
task_projects = hub_db.fetchall(
|
|
77
|
+
"""
|
|
78
|
+
SELECT project_id, COUNT(*) as task_count
|
|
79
|
+
FROM tasks
|
|
80
|
+
WHERE project_id IS NOT NULL
|
|
81
|
+
GROUP BY project_id
|
|
82
|
+
"""
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# Query unique projects from sessions table
|
|
86
|
+
session_projects = hub_db.fetchall(
|
|
87
|
+
"""
|
|
88
|
+
SELECT project_id, COUNT(*) as session_count
|
|
89
|
+
FROM sessions
|
|
90
|
+
WHERE project_id IS NOT NULL
|
|
91
|
+
GROUP BY project_id
|
|
92
|
+
"""
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Merge results
|
|
96
|
+
projects: dict[str, dict[str, int]] = {}
|
|
97
|
+
for row in task_projects:
|
|
98
|
+
project_id = row["project_id"]
|
|
99
|
+
if project_id:
|
|
100
|
+
projects[project_id] = {
|
|
101
|
+
"task_count": row["task_count"],
|
|
102
|
+
"session_count": 0,
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
for row in session_projects:
|
|
106
|
+
project_id = row["project_id"]
|
|
107
|
+
if project_id:
|
|
108
|
+
if project_id in projects:
|
|
109
|
+
projects[project_id]["session_count"] = row["session_count"]
|
|
110
|
+
else:
|
|
111
|
+
projects[project_id] = {
|
|
112
|
+
"task_count": 0,
|
|
113
|
+
"session_count": row["session_count"],
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
"success": True,
|
|
118
|
+
"project_count": len(projects),
|
|
119
|
+
"projects": [
|
|
120
|
+
{
|
|
121
|
+
"project_id": pid,
|
|
122
|
+
"task_count": data["task_count"],
|
|
123
|
+
"session_count": data["session_count"],
|
|
124
|
+
}
|
|
125
|
+
for pid, data in sorted(projects.items())
|
|
126
|
+
],
|
|
127
|
+
}
|
|
128
|
+
except Exception as e:
|
|
129
|
+
return {
|
|
130
|
+
"success": False,
|
|
131
|
+
"error": str(e),
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
@registry.tool(
|
|
135
|
+
name="list_cross_project_tasks",
|
|
136
|
+
description="Query tasks across all projects in the hub database.",
|
|
137
|
+
)
|
|
138
|
+
async def list_cross_project_tasks(
|
|
139
|
+
status: str | None = None,
|
|
140
|
+
limit: int = 50,
|
|
141
|
+
) -> dict[str, Any]:
|
|
142
|
+
"""
|
|
143
|
+
List tasks across all projects in the hub.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
status: Optional status filter (open, closed, in_progress)
|
|
147
|
+
limit: Maximum number of tasks to return (default 50)
|
|
148
|
+
"""
|
|
149
|
+
hub_db = _get_hub_db()
|
|
150
|
+
if hub_db is None:
|
|
151
|
+
return {
|
|
152
|
+
"success": False,
|
|
153
|
+
"error": f"Hub database not found: {hub_db_path}",
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
try:
|
|
157
|
+
if status:
|
|
158
|
+
rows = hub_db.fetchall(
|
|
159
|
+
"""
|
|
160
|
+
SELECT id, project_id, title, status, task_type, priority, created_at, updated_at
|
|
161
|
+
FROM tasks
|
|
162
|
+
WHERE status = ?
|
|
163
|
+
ORDER BY updated_at DESC
|
|
164
|
+
LIMIT ?
|
|
165
|
+
""",
|
|
166
|
+
(status, limit),
|
|
167
|
+
)
|
|
168
|
+
else:
|
|
169
|
+
rows = hub_db.fetchall(
|
|
170
|
+
"""
|
|
171
|
+
SELECT id, project_id, title, status, task_type, priority, created_at, updated_at
|
|
172
|
+
FROM tasks
|
|
173
|
+
ORDER BY updated_at DESC
|
|
174
|
+
LIMIT ?
|
|
175
|
+
""",
|
|
176
|
+
(limit,),
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
tasks = [
|
|
180
|
+
{
|
|
181
|
+
"id": row["id"],
|
|
182
|
+
"project_id": row["project_id"],
|
|
183
|
+
"title": row["title"],
|
|
184
|
+
"status": row["status"],
|
|
185
|
+
"task_type": row["task_type"],
|
|
186
|
+
"priority": row["priority"],
|
|
187
|
+
"created_at": row["created_at"],
|
|
188
|
+
"updated_at": row["updated_at"],
|
|
189
|
+
}
|
|
190
|
+
for row in rows
|
|
191
|
+
]
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
"success": True,
|
|
195
|
+
"count": len(tasks),
|
|
196
|
+
"tasks": tasks,
|
|
197
|
+
}
|
|
198
|
+
except Exception as e:
|
|
199
|
+
return {
|
|
200
|
+
"success": False,
|
|
201
|
+
"error": str(e),
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
@registry.tool(
|
|
205
|
+
name="list_cross_project_sessions",
|
|
206
|
+
description="List recent sessions across all projects in the hub database.",
|
|
207
|
+
)
|
|
208
|
+
async def list_cross_project_sessions(
|
|
209
|
+
limit: int = 20,
|
|
210
|
+
) -> dict[str, Any]:
|
|
211
|
+
"""
|
|
212
|
+
List recent sessions across all projects in the hub.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
limit: Maximum number of sessions to return (default 20)
|
|
216
|
+
"""
|
|
217
|
+
hub_db = _get_hub_db()
|
|
218
|
+
if hub_db is None:
|
|
219
|
+
return {
|
|
220
|
+
"success": False,
|
|
221
|
+
"error": f"Hub database not found: {hub_db_path}",
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
try:
|
|
225
|
+
rows = hub_db.fetchall(
|
|
226
|
+
"""
|
|
227
|
+
SELECT id, project_id, source, status, machine_id, created_at, updated_at
|
|
228
|
+
FROM sessions
|
|
229
|
+
ORDER BY created_at DESC
|
|
230
|
+
LIMIT ?
|
|
231
|
+
""",
|
|
232
|
+
(limit,),
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
sessions = [
|
|
236
|
+
{
|
|
237
|
+
"id": row["id"],
|
|
238
|
+
"project_id": row["project_id"],
|
|
239
|
+
"source": row["source"],
|
|
240
|
+
"status": row["status"],
|
|
241
|
+
"machine_id": row["machine_id"],
|
|
242
|
+
"created_at": row["created_at"],
|
|
243
|
+
"updated_at": row["updated_at"],
|
|
244
|
+
}
|
|
245
|
+
for row in rows
|
|
246
|
+
]
|
|
247
|
+
|
|
248
|
+
return {
|
|
249
|
+
"success": True,
|
|
250
|
+
"count": len(sessions),
|
|
251
|
+
"sessions": sessions,
|
|
252
|
+
}
|
|
253
|
+
except Exception as e:
|
|
254
|
+
return {
|
|
255
|
+
"success": False,
|
|
256
|
+
"error": str(e),
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
@registry.tool(
|
|
260
|
+
name="hub_stats",
|
|
261
|
+
description="Get aggregate statistics from the hub database.",
|
|
262
|
+
)
|
|
263
|
+
async def hub_stats() -> dict[str, Any]:
|
|
264
|
+
"""
|
|
265
|
+
Get aggregate statistics from the hub database.
|
|
266
|
+
|
|
267
|
+
Returns counts of projects, tasks, sessions, memories, etc.
|
|
268
|
+
"""
|
|
269
|
+
hub_db = _get_hub_db()
|
|
270
|
+
if hub_db is None:
|
|
271
|
+
return {
|
|
272
|
+
"success": False,
|
|
273
|
+
"error": f"Hub database not found: {hub_db_path}",
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
try:
|
|
277
|
+
stats: dict[str, Any] = {}
|
|
278
|
+
|
|
279
|
+
# Count unique projects
|
|
280
|
+
project_count_result = hub_db.fetchone(
|
|
281
|
+
"""
|
|
282
|
+
SELECT COUNT(DISTINCT project_id) as count
|
|
283
|
+
FROM (
|
|
284
|
+
SELECT project_id FROM tasks WHERE project_id IS NOT NULL
|
|
285
|
+
UNION
|
|
286
|
+
SELECT project_id FROM sessions WHERE project_id IS NOT NULL
|
|
287
|
+
)
|
|
288
|
+
"""
|
|
289
|
+
)
|
|
290
|
+
stats["project_count"] = project_count_result["count"] if project_count_result else 0
|
|
291
|
+
|
|
292
|
+
# Count tasks by status
|
|
293
|
+
task_stats = hub_db.fetchall(
|
|
294
|
+
"""
|
|
295
|
+
SELECT status, COUNT(*) as count
|
|
296
|
+
FROM tasks
|
|
297
|
+
GROUP BY status
|
|
298
|
+
"""
|
|
299
|
+
)
|
|
300
|
+
stats["tasks"] = {
|
|
301
|
+
"total": sum(row["count"] for row in task_stats),
|
|
302
|
+
"by_status": {row["status"]: row["count"] for row in task_stats},
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
# Count sessions by status
|
|
306
|
+
session_stats = hub_db.fetchall(
|
|
307
|
+
"""
|
|
308
|
+
SELECT status, COUNT(*) as count
|
|
309
|
+
FROM sessions
|
|
310
|
+
GROUP BY status
|
|
311
|
+
"""
|
|
312
|
+
)
|
|
313
|
+
stats["sessions"] = {
|
|
314
|
+
"total": sum(row["count"] for row in session_stats),
|
|
315
|
+
"by_status": {row["status"]: row["count"] for row in session_stats},
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
# Count memories if table exists
|
|
319
|
+
try:
|
|
320
|
+
memory_count = hub_db.fetchone("SELECT COUNT(*) as count FROM memories")
|
|
321
|
+
stats["memories"] = memory_count["count"] if memory_count else 0
|
|
322
|
+
except Exception:
|
|
323
|
+
stats["memories"] = 0
|
|
324
|
+
|
|
325
|
+
return {
|
|
326
|
+
"success": True,
|
|
327
|
+
"stats": stats,
|
|
328
|
+
}
|
|
329
|
+
except Exception as e:
|
|
330
|
+
return {
|
|
331
|
+
"success": False,
|
|
332
|
+
"error": str(e),
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return registry
|