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