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,25 @@
|
|
|
1
|
+
"""Task tools package.
|
|
2
|
+
|
|
3
|
+
This package provides MCP tools for task management. Re-exports maintain
|
|
4
|
+
backwards compatibility with the original tasks.py module.
|
|
5
|
+
|
|
6
|
+
Public API:
|
|
7
|
+
- create_task_registry: Factory function to create the task tool registry
|
|
8
|
+
- resolve_task_id_for_mcp: Resolve task references to UUIDs
|
|
9
|
+
|
|
10
|
+
Internal (exported for test compatibility):
|
|
11
|
+
- SKIP_REASONS: Reasons that skip validation on close
|
|
12
|
+
- _infer_category: Infer task category from title/description
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from gobby.mcp_proxy.tools.tasks._factory import create_task_registry
|
|
16
|
+
from gobby.mcp_proxy.tools.tasks._helpers import SKIP_REASONS, _infer_category
|
|
17
|
+
from gobby.mcp_proxy.tools.tasks._resolution import resolve_task_id_for_mcp
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"create_task_registry",
|
|
21
|
+
"resolve_task_id_for_mcp",
|
|
22
|
+
# Internal exports for backward compatibility
|
|
23
|
+
"SKIP_REASONS",
|
|
24
|
+
"_infer_category",
|
|
25
|
+
]
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Registry context for task tools.
|
|
2
|
+
|
|
3
|
+
Provides RegistryContext dataclass that bundles shared state and helpers
|
|
4
|
+
used across task tool modules.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
from gobby.storage.projects import LocalProjectManager
|
|
11
|
+
from gobby.storage.session_tasks import SessionTaskManager
|
|
12
|
+
from gobby.storage.task_dependencies import TaskDependencyManager
|
|
13
|
+
from gobby.storage.tasks import LocalTaskManager
|
|
14
|
+
from gobby.utils.project_context import get_project_context
|
|
15
|
+
from gobby.workflows.definitions import WorkflowState
|
|
16
|
+
from gobby.workflows.state_manager import WorkflowStateManager
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from gobby.agents.runner import AgentRunner
|
|
20
|
+
from gobby.config.app import DaemonConfig
|
|
21
|
+
from gobby.config.tasks import TaskValidationConfig
|
|
22
|
+
from gobby.sync.tasks import TaskSyncManager
|
|
23
|
+
from gobby.tasks.expansion import TaskExpander
|
|
24
|
+
from gobby.tasks.validation import TaskValidator
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class RegistryContext:
|
|
29
|
+
"""Shared context for task tool registries.
|
|
30
|
+
|
|
31
|
+
Bundles managers, config, and helper methods used across all task tools.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
# Core managers
|
|
35
|
+
task_manager: LocalTaskManager
|
|
36
|
+
sync_manager: "TaskSyncManager"
|
|
37
|
+
|
|
38
|
+
# Optional managers
|
|
39
|
+
task_expander: "TaskExpander | None" = None
|
|
40
|
+
task_validator: "TaskValidator | None" = None
|
|
41
|
+
agent_runner: "AgentRunner | None" = None
|
|
42
|
+
config: "DaemonConfig | None" = None
|
|
43
|
+
|
|
44
|
+
# Derived managers (initialized in __post_init__)
|
|
45
|
+
dep_manager: TaskDependencyManager = field(init=False)
|
|
46
|
+
session_task_manager: SessionTaskManager = field(init=False)
|
|
47
|
+
workflow_state_manager: WorkflowStateManager = field(init=False)
|
|
48
|
+
project_manager: LocalProjectManager = field(init=False)
|
|
49
|
+
|
|
50
|
+
# Config settings (initialized in __post_init__)
|
|
51
|
+
show_result_on_create: bool = field(init=False)
|
|
52
|
+
auto_generate_on_expand: bool = field(init=False)
|
|
53
|
+
tdd_mode_config: bool = field(init=False)
|
|
54
|
+
validation_config: "TaskValidationConfig | None" = field(init=False)
|
|
55
|
+
|
|
56
|
+
def __post_init__(self) -> None:
|
|
57
|
+
"""Initialize derived managers and config settings."""
|
|
58
|
+
# Initialize managers from task_manager's database connection
|
|
59
|
+
db = self.task_manager.db
|
|
60
|
+
self.dep_manager = TaskDependencyManager(db)
|
|
61
|
+
self.session_task_manager = SessionTaskManager(db)
|
|
62
|
+
self.workflow_state_manager = WorkflowStateManager(db)
|
|
63
|
+
self.project_manager = LocalProjectManager(db)
|
|
64
|
+
|
|
65
|
+
# Initialize config settings
|
|
66
|
+
self.show_result_on_create = False
|
|
67
|
+
self.auto_generate_on_expand = True
|
|
68
|
+
self.tdd_mode_config = False
|
|
69
|
+
self.validation_config = None
|
|
70
|
+
|
|
71
|
+
if self.config is not None:
|
|
72
|
+
tasks_config = self.config.get_gobby_tasks_config()
|
|
73
|
+
self.show_result_on_create = tasks_config.show_result_on_create
|
|
74
|
+
self.validation_config = tasks_config.validation
|
|
75
|
+
self.auto_generate_on_expand = self.validation_config.auto_generate_on_expand
|
|
76
|
+
self.tdd_mode_config = tasks_config.expansion.tdd_mode
|
|
77
|
+
|
|
78
|
+
def get_project_repo_path(self, project_id: str | None) -> str | None:
|
|
79
|
+
"""Get the repo_path for a project by ID."""
|
|
80
|
+
if not project_id:
|
|
81
|
+
return None
|
|
82
|
+
project = self.project_manager.get(project_id)
|
|
83
|
+
return project.repo_path if project else None
|
|
84
|
+
|
|
85
|
+
def get_current_project_id(self) -> str | None:
|
|
86
|
+
"""Get the current project ID from context, or None if not in a project."""
|
|
87
|
+
ctx = get_project_context()
|
|
88
|
+
if ctx and ctx.get("id"):
|
|
89
|
+
project_id: str = ctx["id"]
|
|
90
|
+
return project_id
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
def get_workflow_state(self, session_id: str | None) -> WorkflowState | None:
|
|
94
|
+
"""Get workflow state for a session, if available."""
|
|
95
|
+
if not session_id:
|
|
96
|
+
return None
|
|
97
|
+
return self.workflow_state_manager.get_state(session_id)
|
|
98
|
+
|
|
99
|
+
def resolve_tdd_mode(self, session_id: str | None) -> bool:
|
|
100
|
+
"""
|
|
101
|
+
Resolve tdd_mode from workflow state > config hierarchy.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
True if TDD mode is enabled, False otherwise.
|
|
105
|
+
"""
|
|
106
|
+
# Check workflow state first (takes precedence)
|
|
107
|
+
state = self.get_workflow_state(session_id)
|
|
108
|
+
if state and "tdd_mode" in state.variables:
|
|
109
|
+
return bool(state.variables["tdd_mode"])
|
|
110
|
+
|
|
111
|
+
# Fall back to config
|
|
112
|
+
return self.tdd_mode_config
|
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
"""CRUD operations for task management.
|
|
2
|
+
|
|
3
|
+
Provides core task operations: create, get, update, list, and tree building.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from gobby.mcp_proxy.tools.internal import InternalToolRegistry
|
|
9
|
+
from gobby.mcp_proxy.tools.tasks._context import RegistryContext
|
|
10
|
+
from gobby.mcp_proxy.tools.tasks._helpers import _infer_category
|
|
11
|
+
from gobby.mcp_proxy.tools.tasks._resolution import resolve_task_id_for_mcp
|
|
12
|
+
from gobby.storage.tasks import TaskNotFoundError
|
|
13
|
+
from gobby.utils.project_context import get_project_context
|
|
14
|
+
from gobby.utils.project_init import initialize_project
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def create_crud_registry(ctx: RegistryContext) -> InternalToolRegistry:
|
|
18
|
+
"""Create a registry with task CRUD tools.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
ctx: Shared registry context
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
InternalToolRegistry with CRUD tools registered
|
|
25
|
+
"""
|
|
26
|
+
registry = InternalToolRegistry(
|
|
27
|
+
name="gobby-tasks-crud",
|
|
28
|
+
description="Task CRUD operations",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
async def create_task(
|
|
32
|
+
title: str,
|
|
33
|
+
session_id: str,
|
|
34
|
+
description: str | None = None,
|
|
35
|
+
priority: int = 2,
|
|
36
|
+
task_type: str = "task",
|
|
37
|
+
parent_task_id: str | None = None,
|
|
38
|
+
blocks: list[str] | None = None,
|
|
39
|
+
labels: list[str] | None = None,
|
|
40
|
+
category: str | None = None,
|
|
41
|
+
validation_criteria: str | None = None,
|
|
42
|
+
) -> dict[str, Any]:
|
|
43
|
+
"""Create a single task in the current project.
|
|
44
|
+
|
|
45
|
+
This tool creates exactly ONE task. Auto-decomposition of multi-step
|
|
46
|
+
descriptions is disabled. Use expand_task for complex decompositions.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
title: Task title
|
|
50
|
+
session_id: Your session ID for tracking (REQUIRED)
|
|
51
|
+
description: Detailed description
|
|
52
|
+
priority: Priority level (1=High, 2=Medium, 3=Low)
|
|
53
|
+
task_type: Task type (task, bug, feature, epic)
|
|
54
|
+
parent_task_id: Optional parent task ID
|
|
55
|
+
blocks: List of task IDs that this new task blocks
|
|
56
|
+
labels: List of labels
|
|
57
|
+
category: Task domain category (test, code, document, research, config, manual)
|
|
58
|
+
validation_criteria: Acceptance criteria for validating completion.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Created task dict with id (minimal) or full task details based on config.
|
|
62
|
+
"""
|
|
63
|
+
# Get current project context which is required for task creation
|
|
64
|
+
project_ctx = get_project_context()
|
|
65
|
+
if project_ctx and project_ctx.get("id"):
|
|
66
|
+
project_id = project_ctx["id"]
|
|
67
|
+
else:
|
|
68
|
+
init_result = initialize_project()
|
|
69
|
+
project_id = init_result.project_id
|
|
70
|
+
|
|
71
|
+
# Resolve parent_task_id if it's a reference format
|
|
72
|
+
if parent_task_id:
|
|
73
|
+
try:
|
|
74
|
+
parent_task_id = resolve_task_id_for_mcp(
|
|
75
|
+
ctx.task_manager, parent_task_id, project_id
|
|
76
|
+
)
|
|
77
|
+
except (TaskNotFoundError, ValueError) as e:
|
|
78
|
+
return {"error": f"Invalid parent_task_id: {e}"}
|
|
79
|
+
|
|
80
|
+
# Auto-infer category if not provided
|
|
81
|
+
effective_category = category
|
|
82
|
+
if effective_category is None:
|
|
83
|
+
effective_category = _infer_category(title, description)
|
|
84
|
+
|
|
85
|
+
# Create task
|
|
86
|
+
create_result = ctx.task_manager.create_task_with_decomposition(
|
|
87
|
+
project_id=project_id,
|
|
88
|
+
title=title,
|
|
89
|
+
description=description,
|
|
90
|
+
priority=priority,
|
|
91
|
+
task_type=task_type,
|
|
92
|
+
parent_task_id=parent_task_id,
|
|
93
|
+
labels=labels,
|
|
94
|
+
category=effective_category,
|
|
95
|
+
validation_criteria=validation_criteria,
|
|
96
|
+
created_in_session_id=session_id,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
task = ctx.task_manager.get_task(create_result["task"]["id"])
|
|
100
|
+
|
|
101
|
+
# Handle 'blocks' argument if provided (syntactic sugar)
|
|
102
|
+
if blocks:
|
|
103
|
+
for blocked_id in blocks:
|
|
104
|
+
ctx.dep_manager.add_dependency(task.id, blocked_id, "blocks")
|
|
105
|
+
|
|
106
|
+
# Return minimal or full result based on config
|
|
107
|
+
if ctx.show_result_on_create:
|
|
108
|
+
result = task.to_dict()
|
|
109
|
+
else:
|
|
110
|
+
result = {
|
|
111
|
+
"id": task.id,
|
|
112
|
+
"seq_num": task.seq_num,
|
|
113
|
+
"ref": f"#{task.seq_num}",
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return result
|
|
117
|
+
|
|
118
|
+
registry.register(
|
|
119
|
+
name="create_task",
|
|
120
|
+
description="Create a new task in the current project.",
|
|
121
|
+
input_schema={
|
|
122
|
+
"type": "object",
|
|
123
|
+
"properties": {
|
|
124
|
+
"title": {"type": "string", "description": "Task title"},
|
|
125
|
+
"description": {
|
|
126
|
+
"type": "string",
|
|
127
|
+
"description": "Detailed description",
|
|
128
|
+
"default": None,
|
|
129
|
+
},
|
|
130
|
+
"priority": {
|
|
131
|
+
"type": "integer",
|
|
132
|
+
"description": "Priority level (1=High, 2=Medium, 3=Low)",
|
|
133
|
+
"default": 2,
|
|
134
|
+
},
|
|
135
|
+
"task_type": {
|
|
136
|
+
"type": "string",
|
|
137
|
+
"description": "Task type (task, bug, feature, epic)",
|
|
138
|
+
"default": "task",
|
|
139
|
+
},
|
|
140
|
+
"parent_task_id": {
|
|
141
|
+
"type": "string",
|
|
142
|
+
"description": "Parent task reference: #N, N (seq_num), path (1.2.3), or UUID",
|
|
143
|
+
"default": None,
|
|
144
|
+
},
|
|
145
|
+
"blocks": {
|
|
146
|
+
"type": "array",
|
|
147
|
+
"items": {"type": "string"},
|
|
148
|
+
"description": "List of task IDs that this new task blocks (optional)",
|
|
149
|
+
"default": None,
|
|
150
|
+
},
|
|
151
|
+
"labels": {
|
|
152
|
+
"type": "array",
|
|
153
|
+
"items": {"type": "string"},
|
|
154
|
+
"description": "List of labels (optional)",
|
|
155
|
+
"default": None,
|
|
156
|
+
},
|
|
157
|
+
"category": {
|
|
158
|
+
"type": "string",
|
|
159
|
+
"description": "Task domain: 'code' (implementation), 'config' (configuration files), 'docs' (documentation), 'test' (test-writing), 'research' (investigation), 'planning' (design/architecture), or 'manual' (manual verification).",
|
|
160
|
+
"enum": ["code", "config", "docs", "test", "research", "planning", "manual"],
|
|
161
|
+
"default": None,
|
|
162
|
+
},
|
|
163
|
+
"validation_criteria": {
|
|
164
|
+
"type": "string",
|
|
165
|
+
"description": "Acceptance criteria for validating task completion (optional). If not provided and generate_validation is True, criteria will be auto-generated.",
|
|
166
|
+
"default": None,
|
|
167
|
+
},
|
|
168
|
+
"session_id": {
|
|
169
|
+
"type": "string",
|
|
170
|
+
"description": "Your session ID (from system context). Required to track which session created the task.",
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
"required": ["title", "session_id"],
|
|
174
|
+
},
|
|
175
|
+
func=create_task,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
def get_task(task_id: str) -> dict[str, Any]:
|
|
179
|
+
"""Get task details including dependencies."""
|
|
180
|
+
# Resolve task reference (supports #N, path, UUID formats)
|
|
181
|
+
try:
|
|
182
|
+
resolved_id = resolve_task_id_for_mcp(ctx.task_manager, task_id)
|
|
183
|
+
except TaskNotFoundError as e:
|
|
184
|
+
return {"error": str(e), "found": False}
|
|
185
|
+
except ValueError as e:
|
|
186
|
+
return {"error": str(e), "found": False}
|
|
187
|
+
|
|
188
|
+
task = ctx.task_manager.get_task(resolved_id)
|
|
189
|
+
if not task:
|
|
190
|
+
return {"error": f"Task {task_id} not found", "found": False}
|
|
191
|
+
|
|
192
|
+
result: dict[str, Any] = task.to_dict()
|
|
193
|
+
|
|
194
|
+
# Enrich with dependency info
|
|
195
|
+
blockers = ctx.dep_manager.get_blockers(resolved_id)
|
|
196
|
+
blocking = ctx.dep_manager.get_blocking(resolved_id)
|
|
197
|
+
|
|
198
|
+
result["dependencies"] = {
|
|
199
|
+
"blocked_by": [b.to_dict() for b in blockers],
|
|
200
|
+
"blocking": [b.to_dict() for b in blocking],
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return result
|
|
204
|
+
|
|
205
|
+
registry.register(
|
|
206
|
+
name="get_task",
|
|
207
|
+
description="Get task details including dependencies. Task ID can be #N (e.g., #1), path (e.g., 1.2.3), or UUID.",
|
|
208
|
+
input_schema={
|
|
209
|
+
"type": "object",
|
|
210
|
+
"properties": {
|
|
211
|
+
"task_id": {
|
|
212
|
+
"type": "string",
|
|
213
|
+
"description": "Task reference: #N (e.g., #1, #47), path (e.g., 1.2.3), or UUID",
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
"required": ["task_id"],
|
|
217
|
+
},
|
|
218
|
+
func=get_task,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
def update_task(
|
|
222
|
+
task_id: str,
|
|
223
|
+
title: str | None = None,
|
|
224
|
+
description: str | None = None,
|
|
225
|
+
status: str | None = None,
|
|
226
|
+
priority: int | None = None,
|
|
227
|
+
assignee: str | None = None,
|
|
228
|
+
labels: list[str] | None = None,
|
|
229
|
+
validation_criteria: str | None = None,
|
|
230
|
+
parent_task_id: str | None = None,
|
|
231
|
+
category: str | None = None,
|
|
232
|
+
workflow_name: str | None = None,
|
|
233
|
+
verification: str | None = None,
|
|
234
|
+
sequence_order: int | None = None,
|
|
235
|
+
) -> dict[str, Any]:
|
|
236
|
+
"""Update task fields."""
|
|
237
|
+
# Resolve task reference (supports #N, path, UUID formats)
|
|
238
|
+
try:
|
|
239
|
+
resolved_id = resolve_task_id_for_mcp(ctx.task_manager, task_id)
|
|
240
|
+
except TaskNotFoundError as e:
|
|
241
|
+
return {"error": str(e)}
|
|
242
|
+
except ValueError as e:
|
|
243
|
+
return {"error": str(e)}
|
|
244
|
+
|
|
245
|
+
# Build kwargs only for non-None values to avoid overwriting with NULL
|
|
246
|
+
kwargs: dict[str, Any] = {}
|
|
247
|
+
if title is not None:
|
|
248
|
+
kwargs["title"] = title
|
|
249
|
+
if description is not None:
|
|
250
|
+
kwargs["description"] = description
|
|
251
|
+
if status is not None:
|
|
252
|
+
kwargs["status"] = status
|
|
253
|
+
if priority is not None:
|
|
254
|
+
kwargs["priority"] = priority
|
|
255
|
+
if assignee is not None:
|
|
256
|
+
kwargs["assignee"] = assignee
|
|
257
|
+
if labels is not None:
|
|
258
|
+
kwargs["labels"] = labels
|
|
259
|
+
if validation_criteria is not None:
|
|
260
|
+
kwargs["validation_criteria"] = validation_criteria
|
|
261
|
+
if parent_task_id is not None:
|
|
262
|
+
# Empty string means "clear parent" - convert to None for storage layer
|
|
263
|
+
# Also resolve parent_task_id if it's a reference format
|
|
264
|
+
if parent_task_id:
|
|
265
|
+
try:
|
|
266
|
+
resolved_parent = resolve_task_id_for_mcp(ctx.task_manager, parent_task_id)
|
|
267
|
+
kwargs["parent_task_id"] = resolved_parent
|
|
268
|
+
except (TaskNotFoundError, ValueError):
|
|
269
|
+
kwargs["parent_task_id"] = parent_task_id # Fall back to original
|
|
270
|
+
else:
|
|
271
|
+
kwargs["parent_task_id"] = None
|
|
272
|
+
if category is not None:
|
|
273
|
+
kwargs["category"] = category
|
|
274
|
+
if workflow_name is not None:
|
|
275
|
+
kwargs["workflow_name"] = workflow_name
|
|
276
|
+
if verification is not None:
|
|
277
|
+
kwargs["verification"] = verification
|
|
278
|
+
if sequence_order is not None:
|
|
279
|
+
kwargs["sequence_order"] = sequence_order
|
|
280
|
+
|
|
281
|
+
task = ctx.task_manager.update_task(resolved_id, **kwargs)
|
|
282
|
+
if not task:
|
|
283
|
+
return {"error": f"Task {task_id} not found"}
|
|
284
|
+
return {}
|
|
285
|
+
|
|
286
|
+
registry.register(
|
|
287
|
+
name="update_task",
|
|
288
|
+
description="Update task fields.",
|
|
289
|
+
input_schema={
|
|
290
|
+
"type": "object",
|
|
291
|
+
"properties": {
|
|
292
|
+
"task_id": {
|
|
293
|
+
"type": "string",
|
|
294
|
+
"description": "Task reference: #N (e.g., #1, #47), path (e.g., 1.2.3), or UUID",
|
|
295
|
+
},
|
|
296
|
+
"title": {"type": "string", "description": "New title", "default": None},
|
|
297
|
+
"description": {
|
|
298
|
+
"type": "string",
|
|
299
|
+
"description": "New description",
|
|
300
|
+
"default": None,
|
|
301
|
+
},
|
|
302
|
+
"status": {
|
|
303
|
+
"type": "string",
|
|
304
|
+
"description": "New status (open, in_progress, review, closed)",
|
|
305
|
+
"default": None,
|
|
306
|
+
},
|
|
307
|
+
"priority": {"type": "integer", "description": "New priority", "default": None},
|
|
308
|
+
"assignee": {"type": "string", "description": "New assignee", "default": None},
|
|
309
|
+
"labels": {
|
|
310
|
+
"type": "array",
|
|
311
|
+
"items": {"type": "string"},
|
|
312
|
+
"description": "New labels list",
|
|
313
|
+
"default": None,
|
|
314
|
+
},
|
|
315
|
+
"validation_criteria": {
|
|
316
|
+
"type": "string",
|
|
317
|
+
"description": "Acceptance criteria for validating task completion",
|
|
318
|
+
"default": None,
|
|
319
|
+
},
|
|
320
|
+
"parent_task_id": {
|
|
321
|
+
"type": "string",
|
|
322
|
+
"description": "Parent task reference: #N, N (seq_num), path (1.2.3), or UUID. Empty string clears parent.",
|
|
323
|
+
"default": None,
|
|
324
|
+
},
|
|
325
|
+
"category": {
|
|
326
|
+
"type": "string",
|
|
327
|
+
"description": "Task domain: 'code' (implementation), 'config' (configuration files), 'docs' (documentation), 'test' (test-writing), 'research' (investigation), 'planning' (design/architecture), or 'manual' (manual verification).",
|
|
328
|
+
"enum": ["code", "config", "docs", "test", "research", "planning", "manual"],
|
|
329
|
+
"default": None,
|
|
330
|
+
},
|
|
331
|
+
"workflow_name": {
|
|
332
|
+
"type": "string",
|
|
333
|
+
"description": "Workflow name for execution context",
|
|
334
|
+
"default": None,
|
|
335
|
+
},
|
|
336
|
+
"verification": {
|
|
337
|
+
"type": "string",
|
|
338
|
+
"description": "Verification steps or notes",
|
|
339
|
+
"default": None,
|
|
340
|
+
},
|
|
341
|
+
"sequence_order": {
|
|
342
|
+
"type": "integer",
|
|
343
|
+
"description": "Order in a sequence of tasks",
|
|
344
|
+
"default": None,
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
"required": ["task_id"],
|
|
348
|
+
},
|
|
349
|
+
func=update_task,
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
def list_tasks(
|
|
353
|
+
status: str | list[str] | None = None,
|
|
354
|
+
priority: int | None = None,
|
|
355
|
+
task_type: str | None = None,
|
|
356
|
+
assignee: str | None = None,
|
|
357
|
+
label: str | None = None,
|
|
358
|
+
parent_task_id: str | None = None,
|
|
359
|
+
title_like: str | None = None,
|
|
360
|
+
limit: int = 50,
|
|
361
|
+
all_projects: bool = False,
|
|
362
|
+
) -> dict[str, Any]:
|
|
363
|
+
"""List tasks with optional filters."""
|
|
364
|
+
# Filter by current project unless all_projects is True
|
|
365
|
+
project_id = None if all_projects else ctx.get_current_project_id()
|
|
366
|
+
|
|
367
|
+
# Resolve parent_task_id if it's a reference format
|
|
368
|
+
if parent_task_id:
|
|
369
|
+
try:
|
|
370
|
+
parent_task_id = resolve_task_id_for_mcp(
|
|
371
|
+
ctx.task_manager, parent_task_id, project_id
|
|
372
|
+
)
|
|
373
|
+
except (TaskNotFoundError, ValueError) as e:
|
|
374
|
+
return {"error": f"Invalid parent_task_id: {e}", "tasks": [], "count": 0}
|
|
375
|
+
|
|
376
|
+
# Handle comma-separated status string
|
|
377
|
+
status_filter: str | list[str] | None = status
|
|
378
|
+
if isinstance(status, str) and "," in status:
|
|
379
|
+
status_filter = [s.strip() for s in status.split(",")]
|
|
380
|
+
|
|
381
|
+
tasks = ctx.task_manager.list_tasks(
|
|
382
|
+
status=status_filter,
|
|
383
|
+
priority=priority,
|
|
384
|
+
task_type=task_type,
|
|
385
|
+
assignee=assignee,
|
|
386
|
+
label=label,
|
|
387
|
+
parent_task_id=parent_task_id,
|
|
388
|
+
title_like=title_like,
|
|
389
|
+
limit=limit,
|
|
390
|
+
project_id=project_id,
|
|
391
|
+
)
|
|
392
|
+
return {"tasks": [t.to_brief() for t in tasks], "count": len(tasks)}
|
|
393
|
+
|
|
394
|
+
registry.register(
|
|
395
|
+
name="list_tasks",
|
|
396
|
+
description="List tasks with optional filters.",
|
|
397
|
+
input_schema={
|
|
398
|
+
"type": "object",
|
|
399
|
+
"properties": {
|
|
400
|
+
"status": {
|
|
401
|
+
"oneOf": [{"type": "string"}, {"type": "array", "items": {"type": "string"}}],
|
|
402
|
+
"description": "Filter by status. Can be a single status, array of statuses, or comma-separated string (e.g., 'open,in_progress')",
|
|
403
|
+
"default": None,
|
|
404
|
+
},
|
|
405
|
+
"priority": {
|
|
406
|
+
"type": "integer",
|
|
407
|
+
"description": "Filter by priority",
|
|
408
|
+
"default": None,
|
|
409
|
+
},
|
|
410
|
+
"task_type": {
|
|
411
|
+
"type": "string",
|
|
412
|
+
"description": "Filter by task type",
|
|
413
|
+
"default": None,
|
|
414
|
+
},
|
|
415
|
+
"assignee": {
|
|
416
|
+
"type": "string",
|
|
417
|
+
"description": "Filter by assignee",
|
|
418
|
+
"default": None,
|
|
419
|
+
},
|
|
420
|
+
"label": {
|
|
421
|
+
"type": "string",
|
|
422
|
+
"description": "Filter by label presence",
|
|
423
|
+
"default": None,
|
|
424
|
+
},
|
|
425
|
+
"parent_task_id": {
|
|
426
|
+
"type": "string",
|
|
427
|
+
"description": "Filter by parent task: #N, N (seq_num), path (1.2.3), or UUID",
|
|
428
|
+
"default": None,
|
|
429
|
+
},
|
|
430
|
+
"title_like": {
|
|
431
|
+
"type": "string",
|
|
432
|
+
"description": "Filter by title (fuzzy match)",
|
|
433
|
+
"default": None,
|
|
434
|
+
},
|
|
435
|
+
"limit": {
|
|
436
|
+
"type": "integer",
|
|
437
|
+
"description": "Max number of tasks to return",
|
|
438
|
+
"default": 50,
|
|
439
|
+
},
|
|
440
|
+
"all_projects": {
|
|
441
|
+
"type": "boolean",
|
|
442
|
+
"description": "If true, list tasks from all projects instead of just the current project",
|
|
443
|
+
"default": False,
|
|
444
|
+
},
|
|
445
|
+
},
|
|
446
|
+
},
|
|
447
|
+
func=list_tasks,
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
return registry
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def build_task_tree(
|
|
454
|
+
ctx: RegistryContext,
|
|
455
|
+
tree: dict[str, Any],
|
|
456
|
+
session_id: str,
|
|
457
|
+
) -> dict[str, Any]:
|
|
458
|
+
"""Create an entire task tree in one call.
|
|
459
|
+
|
|
460
|
+
Creates tasks with parent-child relationships and wires dependencies
|
|
461
|
+
based on `depends_on` title references within siblings.
|
|
462
|
+
|
|
463
|
+
This is an internal helper function, NOT registered as an MCP tool.
|
|
464
|
+
Agents should use expand_task with iterative mode for tree expansion.
|
|
465
|
+
|
|
466
|
+
Args:
|
|
467
|
+
ctx: Registry context
|
|
468
|
+
tree: JSON tree structure with title, task_type, children, depends_on
|
|
469
|
+
session_id: Your session ID for tracking (REQUIRED)
|
|
470
|
+
|
|
471
|
+
Returns:
|
|
472
|
+
Dict with success status, tasks_created count, epic_ref, task_refs
|
|
473
|
+
|
|
474
|
+
Example tree:
|
|
475
|
+
{
|
|
476
|
+
"title": "Epic Title",
|
|
477
|
+
"task_type": "epic",
|
|
478
|
+
"children": [
|
|
479
|
+
{
|
|
480
|
+
"title": "Phase 1",
|
|
481
|
+
"children": [
|
|
482
|
+
{"title": "Task A", "category": "code"},
|
|
483
|
+
{"title": "Task B", "category": "code", "depends_on": ["Task A"]}
|
|
484
|
+
]
|
|
485
|
+
}
|
|
486
|
+
]
|
|
487
|
+
}
|
|
488
|
+
"""
|
|
489
|
+
from gobby.tasks.tree_builder import TaskTreeBuilder
|
|
490
|
+
|
|
491
|
+
# Get current project context
|
|
492
|
+
project_ctx = get_project_context()
|
|
493
|
+
if project_ctx and project_ctx.get("id"):
|
|
494
|
+
project_id = project_ctx["id"]
|
|
495
|
+
else:
|
|
496
|
+
init_result = initialize_project()
|
|
497
|
+
project_id = init_result.project_id
|
|
498
|
+
|
|
499
|
+
# Build the tree
|
|
500
|
+
builder = TaskTreeBuilder(
|
|
501
|
+
task_manager=ctx.task_manager,
|
|
502
|
+
project_id=project_id,
|
|
503
|
+
session_id=session_id,
|
|
504
|
+
)
|
|
505
|
+
result = builder.build(tree)
|
|
506
|
+
|
|
507
|
+
response: dict[str, Any] = {
|
|
508
|
+
"success": len(result.errors) == 0,
|
|
509
|
+
"tasks_created": result.tasks_created,
|
|
510
|
+
"epic_ref": result.epic_ref,
|
|
511
|
+
"task_refs": result.task_refs,
|
|
512
|
+
}
|
|
513
|
+
if result.errors:
|
|
514
|
+
response["errors"] = result.errors
|
|
515
|
+
|
|
516
|
+
return response
|