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,167 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Hook extensions configuration module.
|
|
3
|
+
|
|
4
|
+
Contains hook extension Pydantic config models:
|
|
5
|
+
- WebSocketBroadcastConfig: WebSocket event broadcasting
|
|
6
|
+
- WebhookEndpointConfig: Single webhook endpoint
|
|
7
|
+
- WebhooksConfig: Webhook dispatching settings
|
|
8
|
+
- PluginItemConfig: Individual plugin settings
|
|
9
|
+
- PluginsConfig: Plugin system settings
|
|
10
|
+
- HookExtensionsConfig: Combined extension settings
|
|
11
|
+
|
|
12
|
+
Extracted from app.py using Strangler Fig pattern for code decomposition.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from pydantic import BaseModel, Field
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"WebSocketBroadcastConfig",
|
|
21
|
+
"WebhookEndpointConfig",
|
|
22
|
+
"WebhooksConfig",
|
|
23
|
+
"PluginItemConfig",
|
|
24
|
+
"PluginsConfig",
|
|
25
|
+
"HookExtensionsConfig",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class WebSocketBroadcastConfig(BaseModel):
|
|
30
|
+
"""Configuration for WebSocket event broadcasting."""
|
|
31
|
+
|
|
32
|
+
enabled: bool = Field(
|
|
33
|
+
default=True,
|
|
34
|
+
description="Enable broadcasting hook events to WebSocket clients",
|
|
35
|
+
)
|
|
36
|
+
broadcast_events: list[str] = Field(
|
|
37
|
+
default=[
|
|
38
|
+
"session-start",
|
|
39
|
+
"session-end",
|
|
40
|
+
"pre-tool-use",
|
|
41
|
+
"post-tool-use",
|
|
42
|
+
],
|
|
43
|
+
description="List of hook event types to broadcast",
|
|
44
|
+
)
|
|
45
|
+
include_payload: bool = Field(
|
|
46
|
+
default=True,
|
|
47
|
+
description="Include event payload data in broadcast messages",
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class WebhookEndpointConfig(BaseModel):
|
|
52
|
+
"""Configuration for a single webhook endpoint."""
|
|
53
|
+
|
|
54
|
+
name: str = Field(
|
|
55
|
+
description="Unique name for this webhook endpoint",
|
|
56
|
+
)
|
|
57
|
+
url: str = Field(
|
|
58
|
+
description="URL to POST webhook payloads to (supports ${ENV_VAR} substitution)",
|
|
59
|
+
)
|
|
60
|
+
events: list[str] = Field(
|
|
61
|
+
default_factory=list,
|
|
62
|
+
description="List of hook event types to trigger this webhook (empty = all events)",
|
|
63
|
+
)
|
|
64
|
+
headers: dict[str, str] = Field(
|
|
65
|
+
default_factory=dict,
|
|
66
|
+
description="Custom HTTP headers to include (supports ${ENV_VAR} substitution)",
|
|
67
|
+
)
|
|
68
|
+
timeout: float = Field(
|
|
69
|
+
default=10.0,
|
|
70
|
+
ge=1.0,
|
|
71
|
+
le=60.0,
|
|
72
|
+
description="Request timeout in seconds",
|
|
73
|
+
)
|
|
74
|
+
retry_count: int = Field(
|
|
75
|
+
default=3,
|
|
76
|
+
ge=0,
|
|
77
|
+
le=10,
|
|
78
|
+
description="Number of retries on failure",
|
|
79
|
+
)
|
|
80
|
+
retry_delay: float = Field(
|
|
81
|
+
default=1.0,
|
|
82
|
+
ge=0.1,
|
|
83
|
+
le=30.0,
|
|
84
|
+
description="Initial retry delay in seconds (doubles each retry)",
|
|
85
|
+
)
|
|
86
|
+
can_block: bool = Field(
|
|
87
|
+
default=False,
|
|
88
|
+
description="If True, webhook can block the action via response decision field",
|
|
89
|
+
)
|
|
90
|
+
enabled: bool = Field(
|
|
91
|
+
default=True,
|
|
92
|
+
description="Enable or disable this webhook",
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class WebhooksConfig(BaseModel):
|
|
97
|
+
"""Configuration for HTTP webhooks triggered on hook events."""
|
|
98
|
+
|
|
99
|
+
enabled: bool = Field(
|
|
100
|
+
default=True,
|
|
101
|
+
description="Enable webhook dispatching",
|
|
102
|
+
)
|
|
103
|
+
endpoints: list[WebhookEndpointConfig] = Field(
|
|
104
|
+
default_factory=list,
|
|
105
|
+
description="List of webhook endpoint configurations",
|
|
106
|
+
)
|
|
107
|
+
default_timeout: float = Field(
|
|
108
|
+
default=10.0,
|
|
109
|
+
ge=1.0,
|
|
110
|
+
le=60.0,
|
|
111
|
+
description="Default timeout for webhook requests",
|
|
112
|
+
)
|
|
113
|
+
async_dispatch: bool = Field(
|
|
114
|
+
default=True,
|
|
115
|
+
description="Dispatch webhooks asynchronously (non-blocking except for can_block)",
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class PluginItemConfig(BaseModel):
|
|
120
|
+
"""Configuration for an individual plugin."""
|
|
121
|
+
|
|
122
|
+
enabled: bool = Field(
|
|
123
|
+
default=True,
|
|
124
|
+
description="Enable or disable this plugin",
|
|
125
|
+
)
|
|
126
|
+
config: dict[str, Any] = Field(
|
|
127
|
+
default_factory=dict,
|
|
128
|
+
description="Plugin-specific configuration passed to on_load()",
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class PluginsConfig(BaseModel):
|
|
133
|
+
"""Configuration for Python plugin system."""
|
|
134
|
+
|
|
135
|
+
enabled: bool = Field(
|
|
136
|
+
default=False,
|
|
137
|
+
description="Enable plugin system (disabled by default for security)",
|
|
138
|
+
)
|
|
139
|
+
plugin_dirs: list[str] = Field(
|
|
140
|
+
default_factory=lambda: ["~/.gobby/plugins", ".gobby/plugins"],
|
|
141
|
+
description="Directories to scan for plugins (supports ~ expansion)",
|
|
142
|
+
)
|
|
143
|
+
auto_discover: bool = Field(
|
|
144
|
+
default=True,
|
|
145
|
+
description="Automatically discover and load plugins from plugin_dirs",
|
|
146
|
+
)
|
|
147
|
+
plugins: dict[str, PluginItemConfig] = Field(
|
|
148
|
+
default_factory=dict,
|
|
149
|
+
description="Per-plugin configuration keyed by plugin name",
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class HookExtensionsConfig(BaseModel):
|
|
154
|
+
"""Configuration for hook extensions (broadcasting, webhooks, plugins)."""
|
|
155
|
+
|
|
156
|
+
websocket: WebSocketBroadcastConfig = Field(
|
|
157
|
+
default_factory=WebSocketBroadcastConfig,
|
|
158
|
+
description="WebSocket broadcasting configuration",
|
|
159
|
+
)
|
|
160
|
+
webhooks: WebhooksConfig = Field(
|
|
161
|
+
default_factory=WebhooksConfig,
|
|
162
|
+
description="HTTP webhook configuration",
|
|
163
|
+
)
|
|
164
|
+
plugins: PluginsConfig = Field(
|
|
165
|
+
default_factory=PluginsConfig,
|
|
166
|
+
description="Python plugin system configuration",
|
|
167
|
+
)
|
gobby/config/features.py
ADDED
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Feature configuration module.
|
|
3
|
+
|
|
4
|
+
Contains MCP proxy and tool feature Pydantic config models:
|
|
5
|
+
- ToolSummarizerConfig: Tool description summarization settings
|
|
6
|
+
- RecommendToolsConfig: Tool recommendation settings
|
|
7
|
+
- ImportMCPServerConfig: MCP server import settings
|
|
8
|
+
- MetricsConfig: Metrics endpoint settings
|
|
9
|
+
- ProjectVerificationConfig: Project verification command settings
|
|
10
|
+
- TaskDescriptionConfig: LLM-based task description generation settings
|
|
11
|
+
|
|
12
|
+
Extracted from app.py using Strangler Fig pattern for code decomposition.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"ToolSummarizerConfig",
|
|
19
|
+
"RecommendToolsConfig",
|
|
20
|
+
"ImportMCPServerConfig",
|
|
21
|
+
"MetricsConfig",
|
|
22
|
+
"ProjectVerificationConfig",
|
|
23
|
+
"HookStageConfig",
|
|
24
|
+
"HooksConfig",
|
|
25
|
+
"TaskDescriptionConfig",
|
|
26
|
+
"DEFAULT_IMPORT_MCP_SERVER_PROMPT",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ToolSummarizerConfig(BaseModel):
|
|
31
|
+
"""Tool description summarization configuration."""
|
|
32
|
+
|
|
33
|
+
enabled: bool = Field(
|
|
34
|
+
default=True,
|
|
35
|
+
description="Enable LLM-based tool description summarization",
|
|
36
|
+
)
|
|
37
|
+
provider: str = Field(
|
|
38
|
+
default="claude",
|
|
39
|
+
description="LLM provider to use for summarization",
|
|
40
|
+
)
|
|
41
|
+
model: str = Field(
|
|
42
|
+
default="claude-haiku-4-5",
|
|
43
|
+
description="Model to use for summarization (fast/cheap recommended)",
|
|
44
|
+
)
|
|
45
|
+
prompt: str = Field(
|
|
46
|
+
default="""Summarize this MCP tool description in 180 characters or less.
|
|
47
|
+
Keep it to three sentences or less. Be concise and preserve the key functionality.
|
|
48
|
+
Do not add quotes, extra formatting, or code examples.
|
|
49
|
+
|
|
50
|
+
Description: {description}
|
|
51
|
+
|
|
52
|
+
Summary:""",
|
|
53
|
+
description="DEPRECATED: Use prompt_path instead. Prompt template for tool description summarization",
|
|
54
|
+
)
|
|
55
|
+
prompt_path: str | None = Field(
|
|
56
|
+
default=None,
|
|
57
|
+
description="Path to custom tool summary prompt template (e.g., 'features/tool_summary')",
|
|
58
|
+
)
|
|
59
|
+
system_prompt: str = Field(
|
|
60
|
+
default="You are a technical summarizer. Create concise tool descriptions.",
|
|
61
|
+
description="DEPRECATED: Use system_prompt_path instead. System prompt for tool description summarization",
|
|
62
|
+
)
|
|
63
|
+
system_prompt_path: str | None = Field(
|
|
64
|
+
default=None,
|
|
65
|
+
description="Path to custom tool summary system prompt (e.g., 'features/tool_summary_system')",
|
|
66
|
+
)
|
|
67
|
+
server_description_prompt: str = Field(
|
|
68
|
+
default="""Write a single concise sentence describing what the '{server_name}' MCP server does based on its tools.
|
|
69
|
+
|
|
70
|
+
Tools:
|
|
71
|
+
{tools_list}
|
|
72
|
+
|
|
73
|
+
Description (1 sentence, try to keep under 100 characters):""",
|
|
74
|
+
description="DEPRECATED: Use server_description_prompt_path instead. Prompt template for server description generation",
|
|
75
|
+
)
|
|
76
|
+
server_description_prompt_path: str | None = Field(
|
|
77
|
+
default=None,
|
|
78
|
+
description="Path to custom server description prompt (e.g., 'features/server_description')",
|
|
79
|
+
)
|
|
80
|
+
server_description_system_prompt: str = Field(
|
|
81
|
+
default="You write concise technical descriptions.",
|
|
82
|
+
description="DEPRECATED: Use server_description_system_prompt_path instead. System prompt for server description generation",
|
|
83
|
+
)
|
|
84
|
+
server_description_system_prompt_path: str | None = Field(
|
|
85
|
+
default=None,
|
|
86
|
+
description="Path to custom server description system prompt (e.g., 'features/server_description_system')",
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class TaskDescriptionConfig(BaseModel):
|
|
91
|
+
"""Task description generation configuration.
|
|
92
|
+
|
|
93
|
+
Controls LLM-based description generation for tasks created from specs.
|
|
94
|
+
Used when structured extraction yields minimal results.
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
enabled: bool = Field(
|
|
98
|
+
default=True,
|
|
99
|
+
description="Enable LLM-based task description generation",
|
|
100
|
+
)
|
|
101
|
+
provider: str = Field(
|
|
102
|
+
default="claude",
|
|
103
|
+
description="LLM provider to use for description generation",
|
|
104
|
+
)
|
|
105
|
+
model: str = Field(
|
|
106
|
+
default="claude-haiku-4-5-20251001",
|
|
107
|
+
description="Model to use for description generation (fast/cheap recommended)",
|
|
108
|
+
)
|
|
109
|
+
min_structured_length: int = Field(
|
|
110
|
+
default=50,
|
|
111
|
+
description="Minimum length of structured extraction before LLM fallback triggers",
|
|
112
|
+
)
|
|
113
|
+
prompt: str = Field(
|
|
114
|
+
default="""Generate a concise task description for this task from a spec document.
|
|
115
|
+
|
|
116
|
+
Task title: {task_title}
|
|
117
|
+
Section: {section_title}
|
|
118
|
+
Section content: {section_content}
|
|
119
|
+
Existing context: {existing_context}
|
|
120
|
+
|
|
121
|
+
Write a 1-2 sentence description focusing on the goal and deliverable.
|
|
122
|
+
Do not add quotes, extra formatting, or implementation details.""",
|
|
123
|
+
description="DEPRECATED: Use prompt_path instead. Prompt template for task description generation",
|
|
124
|
+
)
|
|
125
|
+
prompt_path: str | None = Field(
|
|
126
|
+
default=None,
|
|
127
|
+
description="Path to custom task description prompt (e.g., 'features/task_description')",
|
|
128
|
+
)
|
|
129
|
+
system_prompt: str = Field(
|
|
130
|
+
default="You are a technical writer creating concise task descriptions for developers.",
|
|
131
|
+
description="DEPRECATED: Use system_prompt_path instead. System prompt for task description generation",
|
|
132
|
+
)
|
|
133
|
+
system_prompt_path: str | None = Field(
|
|
134
|
+
default=None,
|
|
135
|
+
description="Path to custom task description system prompt (e.g., 'features/task_description_system')",
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
@field_validator("min_structured_length")
|
|
139
|
+
@classmethod
|
|
140
|
+
def validate_min_structured_length(cls, v: int) -> int:
|
|
141
|
+
"""Validate min_structured_length is non-negative."""
|
|
142
|
+
if v < 0:
|
|
143
|
+
raise ValueError("min_structured_length must be non-negative")
|
|
144
|
+
return v
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class RecommendToolsConfig(BaseModel):
|
|
148
|
+
"""Tool recommendation configuration."""
|
|
149
|
+
|
|
150
|
+
enabled: bool = Field(
|
|
151
|
+
default=True,
|
|
152
|
+
description="Enable tool recommendation MCP tool",
|
|
153
|
+
)
|
|
154
|
+
provider: str = Field(
|
|
155
|
+
default="claude",
|
|
156
|
+
description="LLM provider to use for tool recommendations",
|
|
157
|
+
)
|
|
158
|
+
model: str = Field(
|
|
159
|
+
default="claude-sonnet-4-5",
|
|
160
|
+
description="Model to use for tool recommendations",
|
|
161
|
+
)
|
|
162
|
+
prompt: str = Field(
|
|
163
|
+
default="""You are a tool recommendation assistant for Claude Code with access to MCP servers.
|
|
164
|
+
|
|
165
|
+
CRITICAL PRIORITIZATION RULES:
|
|
166
|
+
1. Analyze the task type (code navigation, docs lookup, database query, planning, data processing, etc.)
|
|
167
|
+
2. Check available MCP server DESCRIPTIONS for capability matches
|
|
168
|
+
3. If ANY MCP server's description matches the task type -> recommend those tools FIRST
|
|
169
|
+
4. Only recommend built-in Claude Code tools (Grep, Read, Bash, WebSearch) if NO suitable MCP server exists
|
|
170
|
+
|
|
171
|
+
TASK TYPE MATCHING GUIDELINES:
|
|
172
|
+
- Task needs library/framework documentation -> Look for MCP servers describing "documentation", "library docs", "API reference"
|
|
173
|
+
- Task needs code navigation/architecture understanding -> Look for MCP servers describing "code analysis", "symbols", "semantic search"
|
|
174
|
+
- Task needs database operations -> Look for MCP servers describing "database", "PostgreSQL", "SQL"
|
|
175
|
+
- Task needs complex reasoning/planning -> Look for MCP servers describing "problem-solving", "thinking", "reasoning"
|
|
176
|
+
- Task needs data processing/large datasets -> Look for MCP servers describing "code execution", "data processing", "token optimization"
|
|
177
|
+
|
|
178
|
+
ANTI-PATTERNS (What NOT to recommend):
|
|
179
|
+
- Don't recommend WebSearch when an MCP server provides library/framework documentation
|
|
180
|
+
- Don't recommend Grep/Read for code architecture questions when an MCP server does semantic code analysis
|
|
181
|
+
- Don't recommend Bash for database queries when an MCP server provides database tools
|
|
182
|
+
- Don't recommend direct implementation when an MCP server provides structured reasoning
|
|
183
|
+
|
|
184
|
+
OUTPUT FORMAT:
|
|
185
|
+
Be concise and specific. Recommend 1-3 tools maximum with:
|
|
186
|
+
1. Which MCP server and tools to use (if applicable)
|
|
187
|
+
2. Brief rationale based on server description matching task type
|
|
188
|
+
3. Suggested workflow (e.g., "First call X, then use result with Y")
|
|
189
|
+
4. Only mention built-in tools if no MCP server is suitable""",
|
|
190
|
+
description="DEPRECATED: Use prompt_path instead. System prompt for recommend_tools() MCP tool.",
|
|
191
|
+
)
|
|
192
|
+
prompt_path: str | None = Field(
|
|
193
|
+
default=None,
|
|
194
|
+
description="Path to custom recommend tools system prompt (e.g., 'features/recommend_tools')",
|
|
195
|
+
)
|
|
196
|
+
hybrid_rerank_prompt: str = Field(
|
|
197
|
+
default="""You are an expert at selecting tools for tasks.
|
|
198
|
+
Task: {task_description}
|
|
199
|
+
|
|
200
|
+
Candidate tools (ranked by semantic similarity):
|
|
201
|
+
{candidate_list}
|
|
202
|
+
|
|
203
|
+
Re-rank these tools by relevance to the task and provide reasoning.
|
|
204
|
+
Return the top {top_k} most relevant as JSON:
|
|
205
|
+
{{
|
|
206
|
+
"recommendations": [
|
|
207
|
+
{{
|
|
208
|
+
"server": "server_name",
|
|
209
|
+
"tool": "tool_name",
|
|
210
|
+
"reason": "Why this tool is the best choice"
|
|
211
|
+
}}
|
|
212
|
+
]
|
|
213
|
+
}}""",
|
|
214
|
+
description="DEPRECATED: Use hybrid_rerank_prompt_path instead. Prompt template for hybrid mode re-ranking",
|
|
215
|
+
)
|
|
216
|
+
hybrid_rerank_prompt_path: str | None = Field(
|
|
217
|
+
default=None,
|
|
218
|
+
description="Path to custom hybrid re-rank prompt (e.g., 'features/recommend_tools_hybrid')",
|
|
219
|
+
)
|
|
220
|
+
llm_prompt: str = Field(
|
|
221
|
+
default="""You are an expert at selecting the right tools for a given task.
|
|
222
|
+
Task: {task_description}
|
|
223
|
+
|
|
224
|
+
Available Servers: {available_servers}
|
|
225
|
+
|
|
226
|
+
Please recommend which tools from these servers would be most useful for this task.
|
|
227
|
+
Return a JSON object with this structure:
|
|
228
|
+
{{
|
|
229
|
+
"recommendations": [
|
|
230
|
+
{{
|
|
231
|
+
"server": "server_name",
|
|
232
|
+
"tool": "tool_name",
|
|
233
|
+
"reason": "Why this tool is useful"
|
|
234
|
+
}}
|
|
235
|
+
]
|
|
236
|
+
}}""",
|
|
237
|
+
description="DEPRECATED: Use llm_prompt_path instead. Prompt template for LLM mode recommendations",
|
|
238
|
+
)
|
|
239
|
+
llm_prompt_path: str | None = Field(
|
|
240
|
+
default=None,
|
|
241
|
+
description="Path to custom LLM recommendation prompt (e.g., 'features/recommend_tools_llm')",
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
DEFAULT_IMPORT_MCP_SERVER_PROMPT = """You are an MCP server configuration extractor. Given documentation for an MCP server, extract the configuration needed to connect to it.
|
|
246
|
+
|
|
247
|
+
Return ONLY a valid JSON object (no markdown, no code blocks) with these fields:
|
|
248
|
+
- name: Server name (lowercase, no spaces, use hyphens)
|
|
249
|
+
- transport: "http", "stdio", or "websocket"
|
|
250
|
+
- url: Server URL (required for http/websocket transports)
|
|
251
|
+
- command: Command to run (required for stdio, e.g., "npx", "uv", "node")
|
|
252
|
+
- args: Array of command arguments (for stdio)
|
|
253
|
+
- env: Object of environment variables needed (use placeholder "<YOUR_KEY_NAME>" for secrets)
|
|
254
|
+
- headers: Object of HTTP headers needed (use placeholder "<YOUR_KEY_NAME>" for secrets)
|
|
255
|
+
- instructions: How to obtain any required API keys or setup steps
|
|
256
|
+
|
|
257
|
+
Example stdio server:
|
|
258
|
+
{"name": "filesystem", "transport": "stdio", "command": "npx", "args": ["-y", "@anthropic-ai/filesystem-mcp"], "env": {}, "instructions": "No setup required"}
|
|
259
|
+
|
|
260
|
+
Example http server with API key:
|
|
261
|
+
{"name": "exa", "transport": "http", "url": "https://mcp.exa.ai/mcp", "headers": {"EXA_API_KEY": "<YOUR_EXA_API_KEY>"}, "instructions": "Get your API key from https://exa.ai/dashboard"}"""
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
class ImportMCPServerConfig(BaseModel):
|
|
265
|
+
"""MCP server import configuration."""
|
|
266
|
+
|
|
267
|
+
enabled: bool = Field(
|
|
268
|
+
default=True,
|
|
269
|
+
description="Enable MCP server import tool",
|
|
270
|
+
)
|
|
271
|
+
provider: str = Field(
|
|
272
|
+
default="claude",
|
|
273
|
+
description="LLM provider to use for config extraction",
|
|
274
|
+
)
|
|
275
|
+
model: str = Field(
|
|
276
|
+
default="claude-haiku-4-5",
|
|
277
|
+
description="Model to use for config extraction",
|
|
278
|
+
)
|
|
279
|
+
prompt: str = Field(
|
|
280
|
+
default=DEFAULT_IMPORT_MCP_SERVER_PROMPT,
|
|
281
|
+
description="DEPRECATED: Use prompt_path instead. System prompt for MCP server config extraction",
|
|
282
|
+
)
|
|
283
|
+
prompt_path: str | None = Field(
|
|
284
|
+
default=None,
|
|
285
|
+
description="Path to custom import MCP system prompt (e.g., 'features/import_mcp')",
|
|
286
|
+
)
|
|
287
|
+
github_fetch_prompt: str = Field(
|
|
288
|
+
default="""Fetch the README from this GitHub repository and extract MCP server configuration:
|
|
289
|
+
|
|
290
|
+
{github_url}
|
|
291
|
+
|
|
292
|
+
If the URL doesn't point directly to a README, try to find and fetch the README.md file.
|
|
293
|
+
|
|
294
|
+
After reading the documentation, extract the MCP server configuration as a JSON object.""",
|
|
295
|
+
description="DEPRECATED: Use github_fetch_prompt_path instead. User prompt template for GitHub import",
|
|
296
|
+
)
|
|
297
|
+
github_fetch_prompt_path: str | None = Field(
|
|
298
|
+
default=None,
|
|
299
|
+
description="Path to custom GitHub fetch prompt (e.g., 'features/import_mcp_github')",
|
|
300
|
+
)
|
|
301
|
+
search_fetch_prompt: str = Field(
|
|
302
|
+
default="""Search for MCP server: {search_query}
|
|
303
|
+
|
|
304
|
+
Find the official documentation or GitHub repository for this MCP server.
|
|
305
|
+
Then fetch and read the README or installation docs.
|
|
306
|
+
|
|
307
|
+
After reading the documentation, extract the MCP server configuration as a JSON object.""",
|
|
308
|
+
description="DEPRECATED: Use search_fetch_prompt_path instead. User prompt template for search-based import",
|
|
309
|
+
)
|
|
310
|
+
search_fetch_prompt_path: str | None = Field(
|
|
311
|
+
default=None,
|
|
312
|
+
description="Path to custom search fetch prompt (e.g., 'features/import_mcp_search')",
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
class MetricsConfig(BaseModel):
|
|
317
|
+
"""Configuration for metrics and status endpoints."""
|
|
318
|
+
|
|
319
|
+
list_limit: int = Field(
|
|
320
|
+
default=10000,
|
|
321
|
+
description="Maximum items to fetch when counting sessions/tasks for metrics. "
|
|
322
|
+
"Set higher for large installs to avoid underreporting. "
|
|
323
|
+
"Use 0 for unbounded (uses COUNT queries instead of list).",
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
@field_validator("list_limit")
|
|
327
|
+
@classmethod
|
|
328
|
+
def validate_list_limit(cls, v: int) -> int:
|
|
329
|
+
"""Validate list_limit is non-negative."""
|
|
330
|
+
if v < 0:
|
|
331
|
+
raise ValueError("list_limit must be non-negative")
|
|
332
|
+
return v
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
class ProjectVerificationConfig(BaseModel):
|
|
336
|
+
"""Project verification commands configuration.
|
|
337
|
+
|
|
338
|
+
Stores project-specific commands for running tests, type checking, linting, etc.
|
|
339
|
+
Used by task expansion to generate precise validation criteria with actual commands.
|
|
340
|
+
Also used by git hooks to run verification commands at pre-commit, pre-push, etc.
|
|
341
|
+
"""
|
|
342
|
+
|
|
343
|
+
unit_tests: str | None = Field(
|
|
344
|
+
default=None,
|
|
345
|
+
description="Command to run unit tests (e.g., 'uv run pytest tests/ -v')",
|
|
346
|
+
)
|
|
347
|
+
type_check: str | None = Field(
|
|
348
|
+
default=None,
|
|
349
|
+
description="Command to run type checking (e.g., 'uv run mypy src/')",
|
|
350
|
+
)
|
|
351
|
+
lint: str | None = Field(
|
|
352
|
+
default=None,
|
|
353
|
+
description="Command to run linting (e.g., 'uv run ruff check src/')",
|
|
354
|
+
)
|
|
355
|
+
format: str | None = Field(
|
|
356
|
+
default=None,
|
|
357
|
+
description="Command to check formatting (e.g., 'uv run ruff format --check src/')",
|
|
358
|
+
)
|
|
359
|
+
integration: str | None = Field(
|
|
360
|
+
default=None,
|
|
361
|
+
description="Command to run integration tests",
|
|
362
|
+
)
|
|
363
|
+
security: str | None = Field(
|
|
364
|
+
default=None,
|
|
365
|
+
description="Command to run security scanning (e.g., 'bandit -r src/')",
|
|
366
|
+
)
|
|
367
|
+
code_review: str | None = Field(
|
|
368
|
+
default=None,
|
|
369
|
+
description="Command to run AI/automated code review (e.g., 'coderabbit review --ci')",
|
|
370
|
+
)
|
|
371
|
+
custom: dict[str, str] = Field(
|
|
372
|
+
default_factory=dict,
|
|
373
|
+
description="Custom verification commands (name -> command)",
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
# Standard field names for lookup
|
|
377
|
+
_standard_fields: tuple[str, ...] = (
|
|
378
|
+
"unit_tests",
|
|
379
|
+
"type_check",
|
|
380
|
+
"lint",
|
|
381
|
+
"format",
|
|
382
|
+
"integration",
|
|
383
|
+
"security",
|
|
384
|
+
"code_review",
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
def get_command(self, name: str) -> str | None:
|
|
388
|
+
"""Get a command by name, checking both standard and custom fields.
|
|
389
|
+
|
|
390
|
+
Args:
|
|
391
|
+
name: Command name (e.g., 'lint', 'unit_tests', or custom name)
|
|
392
|
+
|
|
393
|
+
Returns:
|
|
394
|
+
The command string if found, None otherwise
|
|
395
|
+
"""
|
|
396
|
+
# Check standard fields first
|
|
397
|
+
if name in self._standard_fields:
|
|
398
|
+
return getattr(self, name, None)
|
|
399
|
+
# Check custom commands
|
|
400
|
+
return self.custom.get(name)
|
|
401
|
+
|
|
402
|
+
def all_commands(self) -> dict[str, str]:
|
|
403
|
+
"""Return all defined commands as a dict.
|
|
404
|
+
|
|
405
|
+
Returns:
|
|
406
|
+
Dict mapping command names to command strings (only non-None values)
|
|
407
|
+
"""
|
|
408
|
+
result: dict[str, str] = {}
|
|
409
|
+
for field in self._standard_fields:
|
|
410
|
+
if cmd := getattr(self, field, None):
|
|
411
|
+
result[field] = cmd
|
|
412
|
+
result.update(self.custom)
|
|
413
|
+
return result
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
class HookStageConfig(BaseModel):
|
|
417
|
+
"""Configuration for a single git hook stage."""
|
|
418
|
+
|
|
419
|
+
run: list[str] = Field(
|
|
420
|
+
default_factory=list,
|
|
421
|
+
description="List of verification command names to run (e.g., ['lint', 'format'])",
|
|
422
|
+
)
|
|
423
|
+
fail_fast: bool = Field(
|
|
424
|
+
default=True,
|
|
425
|
+
description="Stop on first failure (exit 1) vs run all and report",
|
|
426
|
+
)
|
|
427
|
+
timeout: int = Field(
|
|
428
|
+
default=300,
|
|
429
|
+
description="Timeout in seconds for each command",
|
|
430
|
+
)
|
|
431
|
+
enabled: bool = Field(
|
|
432
|
+
default=True,
|
|
433
|
+
description="Whether this hook stage is active",
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
class HooksConfig(BaseModel):
|
|
438
|
+
"""Git hooks configuration for verification commands.
|
|
439
|
+
|
|
440
|
+
Maps git hook stages to verification commands defined in ProjectVerificationConfig.
|
|
441
|
+
"""
|
|
442
|
+
|
|
443
|
+
pre_commit: HookStageConfig = Field(
|
|
444
|
+
default_factory=HookStageConfig,
|
|
445
|
+
alias="pre-commit",
|
|
446
|
+
description="Pre-commit hook configuration",
|
|
447
|
+
)
|
|
448
|
+
pre_push: HookStageConfig = Field(
|
|
449
|
+
default_factory=HookStageConfig,
|
|
450
|
+
alias="pre-push",
|
|
451
|
+
description="Pre-push hook configuration",
|
|
452
|
+
)
|
|
453
|
+
pre_merge: HookStageConfig = Field(
|
|
454
|
+
default_factory=HookStageConfig,
|
|
455
|
+
alias="pre-merge",
|
|
456
|
+
description="Pre-merge hook configuration (runs before merge commits)",
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
460
|
+
|
|
461
|
+
def get_stage(self, stage: str) -> HookStageConfig:
|
|
462
|
+
"""Get configuration for a hook stage.
|
|
463
|
+
|
|
464
|
+
Args:
|
|
465
|
+
stage: Hook stage name (e.g., 'pre-commit', 'pre-push', 'pre-merge')
|
|
466
|
+
|
|
467
|
+
Returns:
|
|
468
|
+
HookStageConfig for the stage
|
|
469
|
+
"""
|
|
470
|
+
# Normalize stage name (pre-commit -> pre_commit)
|
|
471
|
+
attr_name = stage.replace("-", "_")
|
|
472
|
+
return getattr(self, attr_name, HookStageConfig())
|