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
gobby/cli/tasks/deps.py
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Dependency management commands for tasks.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Literal
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
from gobby.cli.tasks._utils import get_task_manager, resolve_task_id
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@click.group("dep")
|
|
13
|
+
def dep_cmd() -> None:
|
|
14
|
+
"""Manage task dependencies."""
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
DependencyType = Literal["blocks", "related", "discovered-from"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dep_cmd.command("add")
|
|
22
|
+
@click.argument("task_id", metavar="TASK")
|
|
23
|
+
@click.argument("blocker_id", metavar="BLOCKER")
|
|
24
|
+
@click.option(
|
|
25
|
+
"--type",
|
|
26
|
+
"-t",
|
|
27
|
+
"dep_type",
|
|
28
|
+
default="blocks",
|
|
29
|
+
help="Dependency type (blocks, related, discovered-from)",
|
|
30
|
+
)
|
|
31
|
+
def dep_add(task_id: str, blocker_id: str, dep_type: DependencyType) -> None:
|
|
32
|
+
"""Add a dependency: BLOCKER blocks TASK.
|
|
33
|
+
|
|
34
|
+
TASK/BLOCKER can be: #N (e.g., #1, #47), path (e.g., 1.2.3), or UUID.
|
|
35
|
+
|
|
36
|
+
Example: gobby tasks dep add #3 #1
|
|
37
|
+
means #1 blocks #3 (task #3 depends on task #1)
|
|
38
|
+
"""
|
|
39
|
+
from gobby.storage.task_dependencies import TaskDependencyManager
|
|
40
|
+
|
|
41
|
+
manager = get_task_manager()
|
|
42
|
+
resolved = resolve_task_id(manager, task_id)
|
|
43
|
+
if not resolved:
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
blocker = resolve_task_id(manager, blocker_id)
|
|
47
|
+
if not blocker:
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
dep_manager = TaskDependencyManager(manager.db)
|
|
51
|
+
try:
|
|
52
|
+
dep_manager.add_dependency(resolved.id, blocker.id, dep_type)
|
|
53
|
+
click.echo(f"Added dependency: {blocker.id[:8]} {dep_type} {resolved.id[:8]}")
|
|
54
|
+
except ValueError as e:
|
|
55
|
+
click.echo(f"Error: {e}", err=True)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dep_cmd.command("remove")
|
|
59
|
+
@click.argument("task_id", metavar="TASK")
|
|
60
|
+
@click.argument("blocker_id", metavar="BLOCKER")
|
|
61
|
+
def dep_remove(task_id: str, blocker_id: str) -> None:
|
|
62
|
+
"""Remove a dependency between tasks.
|
|
63
|
+
|
|
64
|
+
TASK/BLOCKER can be: #N (e.g., #1, #47), path (e.g., 1.2.3), or UUID.
|
|
65
|
+
"""
|
|
66
|
+
from gobby.storage.task_dependencies import TaskDependencyManager
|
|
67
|
+
|
|
68
|
+
manager = get_task_manager()
|
|
69
|
+
resolved = resolve_task_id(manager, task_id)
|
|
70
|
+
if not resolved:
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
blocker = resolve_task_id(manager, blocker_id)
|
|
74
|
+
if not blocker:
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
dep_manager = TaskDependencyManager(manager.db)
|
|
78
|
+
dep_manager.remove_dependency(resolved.id, blocker.id)
|
|
79
|
+
click.echo(f"Removed dependency between {resolved.id[:8]} and {blocker.id[:8]}")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dep_cmd.command("tree")
|
|
83
|
+
@click.argument("task_id", metavar="TASK")
|
|
84
|
+
def dep_tree(task_id: str) -> None:
|
|
85
|
+
"""Show dependency tree for a task.
|
|
86
|
+
|
|
87
|
+
TASK can be: #N (e.g., #1, #47), path (e.g., 1.2.3), or UUID.
|
|
88
|
+
"""
|
|
89
|
+
from gobby.storage.task_dependencies import TaskDependencyManager
|
|
90
|
+
|
|
91
|
+
manager = get_task_manager()
|
|
92
|
+
resolved = resolve_task_id(manager, task_id)
|
|
93
|
+
if not resolved:
|
|
94
|
+
return
|
|
95
|
+
|
|
96
|
+
dep_manager = TaskDependencyManager(manager.db)
|
|
97
|
+
tree = dep_manager.get_dependency_tree(resolved.id)
|
|
98
|
+
|
|
99
|
+
click.echo(f"Dependency tree for {resolved.id[:8]} ({resolved.title}):")
|
|
100
|
+
click.echo("")
|
|
101
|
+
|
|
102
|
+
# Show blockers (what this task depends on)
|
|
103
|
+
if tree.get("blockers"):
|
|
104
|
+
click.echo("Blocked by:")
|
|
105
|
+
for b in tree["blockers"]:
|
|
106
|
+
status_icon = "✓" if b.get("status") == "closed" else "○"
|
|
107
|
+
click.echo(f" {status_icon} {b['id'][:8]}: {b.get('title', 'Unknown')}")
|
|
108
|
+
else:
|
|
109
|
+
click.echo("Blocked by: (none)")
|
|
110
|
+
|
|
111
|
+
# Show blocking (what depends on this task)
|
|
112
|
+
if tree.get("blocking"):
|
|
113
|
+
click.echo("\nBlocking:")
|
|
114
|
+
for b in tree["blocking"]:
|
|
115
|
+
status_icon = "✓" if b.get("status") == "closed" else "○"
|
|
116
|
+
click.echo(f" {status_icon} {b['id'][:8]}: {b.get('title', 'Unknown')}")
|
|
117
|
+
else:
|
|
118
|
+
click.echo("\nBlocking: (none)")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@dep_cmd.command("cycles")
|
|
122
|
+
def dep_cycles() -> None:
|
|
123
|
+
"""Check for dependency cycles."""
|
|
124
|
+
from gobby.storage.task_dependencies import TaskDependencyManager
|
|
125
|
+
|
|
126
|
+
manager = get_task_manager()
|
|
127
|
+
dep_manager = TaskDependencyManager(manager.db)
|
|
128
|
+
cycles = dep_manager.check_cycles()
|
|
129
|
+
|
|
130
|
+
if cycles:
|
|
131
|
+
click.echo(f"Found {len(cycles)} dependency cycles:", err=True)
|
|
132
|
+
for cycle in cycles:
|
|
133
|
+
click.echo(f" {' -> '.join(c[:8] for c in cycle)}", err=True)
|
|
134
|
+
else:
|
|
135
|
+
click.echo("✓ No dependency cycles found")
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Label management commands for tasks.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from gobby.cli.tasks._utils import get_task_manager, resolve_task_id
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@click.group("label")
|
|
11
|
+
def label_cmd() -> None:
|
|
12
|
+
"""Manage task labels."""
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@label_cmd.command("add")
|
|
17
|
+
@click.argument("task_id", metavar="TASK")
|
|
18
|
+
@click.argument("label")
|
|
19
|
+
def add_label(task_id: str, label: str) -> None:
|
|
20
|
+
"""Add a label to a task.
|
|
21
|
+
|
|
22
|
+
TASK can be: #N (e.g., #1, #47), path (e.g., 1.2.3), or UUID.
|
|
23
|
+
"""
|
|
24
|
+
manager = get_task_manager()
|
|
25
|
+
resolved = resolve_task_id(manager, task_id)
|
|
26
|
+
if not resolved:
|
|
27
|
+
click.secho(f"Error: Could not resolve task '{task_id}'", fg="red", err=True)
|
|
28
|
+
raise SystemExit(1)
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
manager.add_label(resolved.id, label)
|
|
32
|
+
except ValueError as e:
|
|
33
|
+
click.secho(f"Error: {e}", fg="red", err=True)
|
|
34
|
+
raise SystemExit(1) from None
|
|
35
|
+
except Exception as e:
|
|
36
|
+
click.secho(f"Unexpected error adding label: {e}", fg="red", err=True)
|
|
37
|
+
raise SystemExit(1) from None
|
|
38
|
+
click.echo(f"Added label '{label}' to task {resolved.id}")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@label_cmd.command("remove")
|
|
42
|
+
@click.argument("task_id", metavar="TASK")
|
|
43
|
+
@click.argument("label")
|
|
44
|
+
def remove_label(task_id: str, label: str) -> None:
|
|
45
|
+
"""Remove a label from a task.
|
|
46
|
+
|
|
47
|
+
TASK can be: #N (e.g., #1, #47), path (e.g., 1.2.3), or UUID.
|
|
48
|
+
"""
|
|
49
|
+
manager = get_task_manager()
|
|
50
|
+
resolved = resolve_task_id(manager, task_id)
|
|
51
|
+
if not resolved:
|
|
52
|
+
click.secho(f"Error: Could not resolve task '{task_id}'", fg="red", err=True)
|
|
53
|
+
raise SystemExit(1)
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
manager.remove_label(resolved.id, label)
|
|
57
|
+
except ValueError as e:
|
|
58
|
+
click.secho(f"Error: {e}", fg="red", err=True)
|
|
59
|
+
raise SystemExit(1) from None
|
|
60
|
+
except Exception as e:
|
|
61
|
+
click.secho(f"Unexpected error removing label: {e}", fg="red", err=True)
|
|
62
|
+
raise SystemExit(1) from None
|
|
63
|
+
click.echo(f"Removed label '{label}' from task {resolved.id}")
|
gobby/cli/tasks/main.py
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Task management commands - entry point and misc utilities.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
|
|
10
|
+
from gobby.cli.tasks._utils import (
|
|
11
|
+
check_tasks_enabled,
|
|
12
|
+
get_sync_manager,
|
|
13
|
+
get_task_manager,
|
|
14
|
+
)
|
|
15
|
+
from gobby.cli.tasks.ai import (
|
|
16
|
+
complexity_cmd,
|
|
17
|
+
expand_all_cmd,
|
|
18
|
+
expand_task_cmd,
|
|
19
|
+
generate_criteria_cmd,
|
|
20
|
+
suggest_cmd,
|
|
21
|
+
validate_task_cmd,
|
|
22
|
+
)
|
|
23
|
+
from gobby.cli.tasks.commits import commit_cmd, diff_cmd
|
|
24
|
+
from gobby.cli.tasks.crud import (
|
|
25
|
+
blocked_tasks,
|
|
26
|
+
close_task_cmd,
|
|
27
|
+
create_task,
|
|
28
|
+
de_escalate_cmd,
|
|
29
|
+
delete_task,
|
|
30
|
+
list_tasks,
|
|
31
|
+
ready_tasks,
|
|
32
|
+
reopen_task_cmd,
|
|
33
|
+
show_task,
|
|
34
|
+
task_stats,
|
|
35
|
+
update_task,
|
|
36
|
+
validation_history_cmd,
|
|
37
|
+
)
|
|
38
|
+
from gobby.cli.tasks.deps import dep_cmd
|
|
39
|
+
from gobby.cli.tasks.labels import label_cmd
|
|
40
|
+
from gobby.cli.tasks.search import reindex_tasks, search_tasks
|
|
41
|
+
|
|
42
|
+
logger = logging.getLogger(__name__)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@click.group()
|
|
46
|
+
def tasks() -> None:
|
|
47
|
+
"""Manage development tasks."""
|
|
48
|
+
check_tasks_enabled()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# Register CRUD commands from extracted module
|
|
52
|
+
tasks.add_command(list_tasks)
|
|
53
|
+
tasks.add_command(ready_tasks)
|
|
54
|
+
tasks.add_command(blocked_tasks)
|
|
55
|
+
tasks.add_command(task_stats)
|
|
56
|
+
tasks.add_command(create_task)
|
|
57
|
+
tasks.add_command(show_task)
|
|
58
|
+
tasks.add_command(update_task)
|
|
59
|
+
tasks.add_command(close_task_cmd)
|
|
60
|
+
tasks.add_command(reopen_task_cmd)
|
|
61
|
+
tasks.add_command(delete_task)
|
|
62
|
+
tasks.add_command(de_escalate_cmd)
|
|
63
|
+
tasks.add_command(validation_history_cmd)
|
|
64
|
+
|
|
65
|
+
# Register AI-powered commands from extracted module
|
|
66
|
+
tasks.add_command(validate_task_cmd)
|
|
67
|
+
tasks.add_command(generate_criteria_cmd)
|
|
68
|
+
tasks.add_command(expand_task_cmd)
|
|
69
|
+
tasks.add_command(complexity_cmd)
|
|
70
|
+
tasks.add_command(expand_all_cmd)
|
|
71
|
+
tasks.add_command(suggest_cmd)
|
|
72
|
+
|
|
73
|
+
# Register search commands
|
|
74
|
+
tasks.add_command(search_tasks)
|
|
75
|
+
tasks.add_command(reindex_tasks)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@tasks.command("sync")
|
|
79
|
+
@click.option("--import", "do_import", is_flag=True, help="Import tasks from JSONL")
|
|
80
|
+
@click.option("--export", "do_export", is_flag=True, help="Export tasks to JSONL")
|
|
81
|
+
@click.option("--quiet", "-q", is_flag=True, help="Suppress output")
|
|
82
|
+
def sync_tasks(do_import: bool, do_export: bool, quiet: bool) -> None:
|
|
83
|
+
"""Sync tasks with .gobby/tasks.jsonl.
|
|
84
|
+
|
|
85
|
+
If neither --import nor --export specified, does both.
|
|
86
|
+
"""
|
|
87
|
+
manager = get_sync_manager()
|
|
88
|
+
|
|
89
|
+
# Default to both if neither specified
|
|
90
|
+
if not do_import and not do_export:
|
|
91
|
+
do_import = True
|
|
92
|
+
do_export = True
|
|
93
|
+
|
|
94
|
+
if do_import:
|
|
95
|
+
if not quiet:
|
|
96
|
+
click.echo("Importing tasks...")
|
|
97
|
+
manager.import_from_jsonl()
|
|
98
|
+
|
|
99
|
+
if do_export:
|
|
100
|
+
if not quiet:
|
|
101
|
+
click.echo("Exporting tasks...")
|
|
102
|
+
manager.export_to_jsonl()
|
|
103
|
+
|
|
104
|
+
if not quiet:
|
|
105
|
+
click.echo("Sync completed")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@tasks.group("compact")
|
|
109
|
+
def compact_cmd() -> None:
|
|
110
|
+
"""Task compaction commands."""
|
|
111
|
+
pass
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@compact_cmd.command("analyze")
|
|
115
|
+
@click.option("--days", type=int, default=30, help="Days blocked threshold")
|
|
116
|
+
def compact_analyze(days: int) -> None:
|
|
117
|
+
"""Find tasks eligible for compaction."""
|
|
118
|
+
manager = get_task_manager()
|
|
119
|
+
from gobby.storage.compaction import TaskCompactor
|
|
120
|
+
|
|
121
|
+
compactor = TaskCompactor(manager)
|
|
122
|
+
candidates = compactor.find_candidates(days_closed=days)
|
|
123
|
+
|
|
124
|
+
if not candidates:
|
|
125
|
+
click.echo("No compaction candidates found.")
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
click.echo(f"Found {len(candidates)} candidates closed > {days} days:")
|
|
129
|
+
for task in candidates:
|
|
130
|
+
click.echo(f" {task['id']}: {task['title']} (Updated: {task['updated_at']})")
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@compact_cmd.command("apply")
|
|
134
|
+
@click.option("--id", "task_id", required=True, help="Task ID to compact")
|
|
135
|
+
@click.option("--summary", required=True, help="Summary text or file path (@path)")
|
|
136
|
+
def compact_apply(task_id: str, summary: str) -> None:
|
|
137
|
+
"""Compact a task with a summary."""
|
|
138
|
+
manager = get_task_manager()
|
|
139
|
+
from gobby.storage.compaction import TaskCompactor
|
|
140
|
+
|
|
141
|
+
# Handle file input for summary
|
|
142
|
+
if summary.startswith("@"):
|
|
143
|
+
path = summary[1:]
|
|
144
|
+
try:
|
|
145
|
+
with open(path) as f:
|
|
146
|
+
summary_content = f.read()
|
|
147
|
+
except Exception as e:
|
|
148
|
+
click.echo(f"Error reading summary file: {e}", err=True)
|
|
149
|
+
return
|
|
150
|
+
else:
|
|
151
|
+
summary_content = summary
|
|
152
|
+
|
|
153
|
+
compactor = TaskCompactor(manager)
|
|
154
|
+
try:
|
|
155
|
+
compactor.compact_task(task_id, summary_content)
|
|
156
|
+
click.echo(f"Compacted task {task_id}.")
|
|
157
|
+
except Exception as e:
|
|
158
|
+
click.echo(f"Error compacting task: {e}", err=True)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@compact_cmd.command("stats")
|
|
162
|
+
def compact_stats() -> None:
|
|
163
|
+
"""Show compaction statistics."""
|
|
164
|
+
manager = get_task_manager()
|
|
165
|
+
from gobby.storage.compaction import TaskCompactor
|
|
166
|
+
|
|
167
|
+
compactor = TaskCompactor(manager)
|
|
168
|
+
stats = compactor.get_stats()
|
|
169
|
+
|
|
170
|
+
click.echo("Compaction Statistics:")
|
|
171
|
+
click.echo(f" Total Closed: {stats['total_closed']}")
|
|
172
|
+
click.echo(f" Compacted: {stats['compacted']}")
|
|
173
|
+
click.echo(f" Rate: {stats['rate']}%")
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
# Register subgroups from extracted modules
|
|
177
|
+
tasks.add_command(dep_cmd)
|
|
178
|
+
tasks.add_command(label_cmd)
|
|
179
|
+
tasks.add_command(commit_cmd)
|
|
180
|
+
tasks.add_command(diff_cmd)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
@tasks.group("import")
|
|
184
|
+
def import_cmd() -> None:
|
|
185
|
+
"""Import tasks from external sources."""
|
|
186
|
+
pass
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@import_cmd.command("github")
|
|
190
|
+
@click.argument("url")
|
|
191
|
+
@click.option("--limit", default=50, help="Max issues to import")
|
|
192
|
+
def import_github(url: str, limit: int) -> None:
|
|
193
|
+
"""Import open issues from GitHub."""
|
|
194
|
+
import asyncio
|
|
195
|
+
|
|
196
|
+
manager = get_sync_manager()
|
|
197
|
+
|
|
198
|
+
# We need to run async method
|
|
199
|
+
async def run() -> dict[str, Any]:
|
|
200
|
+
result: dict[str, Any] = await manager.import_from_github_issues(url, limit=limit)
|
|
201
|
+
return result
|
|
202
|
+
|
|
203
|
+
try:
|
|
204
|
+
result = asyncio.run(run())
|
|
205
|
+
|
|
206
|
+
if result["success"]:
|
|
207
|
+
click.echo(result["message"])
|
|
208
|
+
for issue_id in result["imported"]:
|
|
209
|
+
click.echo(f" Imported {issue_id}")
|
|
210
|
+
else:
|
|
211
|
+
click.echo(f"Error: {result['error']}", err=True)
|
|
212
|
+
except Exception as e:
|
|
213
|
+
click.echo(f"Failed to run import: {e}", err=True)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
@tasks.command("doctor")
|
|
217
|
+
def doctor_cmd() -> None:
|
|
218
|
+
"""Validate task data integrity."""
|
|
219
|
+
manager = get_task_manager()
|
|
220
|
+
from gobby.utils.validation import TaskValidator
|
|
221
|
+
|
|
222
|
+
validator = TaskValidator(manager)
|
|
223
|
+
results = validator.validate_all()
|
|
224
|
+
|
|
225
|
+
issues_found = False
|
|
226
|
+
|
|
227
|
+
orphans = results["orphan_dependencies"]
|
|
228
|
+
if orphans:
|
|
229
|
+
issues_found = True
|
|
230
|
+
click.echo(f"Found {len(orphans)} orphan dependencies:", err=True)
|
|
231
|
+
for d in orphans:
|
|
232
|
+
click.echo(f" Dependency {d['id']}: {d['task_id']} -> {d['depends_on']}", err=True)
|
|
233
|
+
else:
|
|
234
|
+
click.echo("✓ No orphan dependencies")
|
|
235
|
+
|
|
236
|
+
invalid_projects = results["invalid_projects"]
|
|
237
|
+
if invalid_projects:
|
|
238
|
+
issues_found = True
|
|
239
|
+
click.echo(f"Found {len(invalid_projects)} tasks with invalid projects:", err=True)
|
|
240
|
+
for t in invalid_projects:
|
|
241
|
+
click.echo(f" Task {t['id']}: {t['title']} (Project ID: {t['project_id']})", err=True)
|
|
242
|
+
else:
|
|
243
|
+
click.echo("✓ No invalid projects")
|
|
244
|
+
|
|
245
|
+
cycles = results["cycles"]
|
|
246
|
+
if cycles:
|
|
247
|
+
issues_found = True
|
|
248
|
+
click.echo(f"Found {len(cycles)} dependency cycles:", err=True)
|
|
249
|
+
for cycle in cycles:
|
|
250
|
+
click.echo(f" Cycle: {' -> '.join(cycle)}", err=True)
|
|
251
|
+
else:
|
|
252
|
+
click.echo("✓ No dependency cycles")
|
|
253
|
+
|
|
254
|
+
if issues_found:
|
|
255
|
+
click.echo("\nIssues found. Run 'gobby tasks clean' to fix fixable issues.")
|
|
256
|
+
# Exit with error code if issues found
|
|
257
|
+
# (Click handles exit code but we can explicitly exit if needed, usually just return is fine unless we want non-zero)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
@tasks.command("clean")
|
|
261
|
+
@click.confirmation_option(prompt="This will remove orphaned dependencies. Are you sure?")
|
|
262
|
+
def clean_cmd() -> None:
|
|
263
|
+
"""Fix data integrity issues (remove orphans)."""
|
|
264
|
+
manager = get_task_manager()
|
|
265
|
+
from gobby.utils.validation import TaskValidator
|
|
266
|
+
|
|
267
|
+
validator = TaskValidator(manager)
|
|
268
|
+
count = validator.clean_orphans()
|
|
269
|
+
|
|
270
|
+
if count > 0:
|
|
271
|
+
click.echo(f"Removed {count} orphan dependencies.")
|
|
272
|
+
else:
|
|
273
|
+
click.echo("No orphan dependencies found.")
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Search commands for task management.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
from gobby.cli.tasks._utils import (
|
|
10
|
+
get_task_manager,
|
|
11
|
+
normalize_status,
|
|
12
|
+
)
|
|
13
|
+
from gobby.cli.utils import resolve_project_ref
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@click.command("search")
|
|
17
|
+
@click.argument("query")
|
|
18
|
+
@click.option(
|
|
19
|
+
"--status",
|
|
20
|
+
"-s",
|
|
21
|
+
help="Filter by status (open, in_progress, review, closed). Comma-separated for multiple.",
|
|
22
|
+
)
|
|
23
|
+
@click.option(
|
|
24
|
+
"--type",
|
|
25
|
+
"-t",
|
|
26
|
+
"task_type",
|
|
27
|
+
help="Filter by task type (task, bug, feature, epic)",
|
|
28
|
+
)
|
|
29
|
+
@click.option(
|
|
30
|
+
"--priority",
|
|
31
|
+
"-p",
|
|
32
|
+
type=int,
|
|
33
|
+
help="Filter by priority (1=High, 2=Medium, 3=Low)",
|
|
34
|
+
)
|
|
35
|
+
@click.option(
|
|
36
|
+
"--project",
|
|
37
|
+
"project_ref",
|
|
38
|
+
help="Filter by project (name or UUID). Default: current project.",
|
|
39
|
+
)
|
|
40
|
+
@click.option(
|
|
41
|
+
"--all-projects",
|
|
42
|
+
"-a",
|
|
43
|
+
is_flag=True,
|
|
44
|
+
help="Search all projects instead of just the current project",
|
|
45
|
+
)
|
|
46
|
+
@click.option(
|
|
47
|
+
"--limit",
|
|
48
|
+
"-n",
|
|
49
|
+
default=20,
|
|
50
|
+
help="Maximum number of results (default: 20)",
|
|
51
|
+
)
|
|
52
|
+
@click.option(
|
|
53
|
+
"--min-score",
|
|
54
|
+
type=float,
|
|
55
|
+
default=0.0,
|
|
56
|
+
help="Minimum similarity score threshold (0.0-1.0)",
|
|
57
|
+
)
|
|
58
|
+
@click.option(
|
|
59
|
+
"--json",
|
|
60
|
+
"json_format",
|
|
61
|
+
is_flag=True,
|
|
62
|
+
help="Output as JSON",
|
|
63
|
+
)
|
|
64
|
+
def search_tasks(
|
|
65
|
+
query: str,
|
|
66
|
+
status: str | None,
|
|
67
|
+
task_type: str | None,
|
|
68
|
+
priority: int | None,
|
|
69
|
+
project_ref: str | None,
|
|
70
|
+
all_projects: bool,
|
|
71
|
+
limit: int,
|
|
72
|
+
min_score: float,
|
|
73
|
+
json_format: bool,
|
|
74
|
+
) -> None:
|
|
75
|
+
"""Search tasks using semantic TF-IDF search.
|
|
76
|
+
|
|
77
|
+
QUERY is the natural language search query.
|
|
78
|
+
|
|
79
|
+
Examples:
|
|
80
|
+
|
|
81
|
+
gobby tasks search "authentication"
|
|
82
|
+
|
|
83
|
+
gobby tasks search "database migration" --status open
|
|
84
|
+
|
|
85
|
+
gobby tasks search "refactor" --type bug --limit 10
|
|
86
|
+
"""
|
|
87
|
+
if not query.strip():
|
|
88
|
+
click.echo("Error: Query cannot be empty.", err=True)
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
# Parse comma-separated statuses
|
|
92
|
+
status_filter: str | list[str] | None = None
|
|
93
|
+
if status:
|
|
94
|
+
if "," in status:
|
|
95
|
+
status_filter = [normalize_status(s.strip()) for s in status.split(",")]
|
|
96
|
+
else:
|
|
97
|
+
status_filter = normalize_status(status)
|
|
98
|
+
|
|
99
|
+
# Resolve project
|
|
100
|
+
project_id = None
|
|
101
|
+
if not all_projects:
|
|
102
|
+
project_id = resolve_project_ref(project_ref)
|
|
103
|
+
|
|
104
|
+
manager = get_task_manager()
|
|
105
|
+
|
|
106
|
+
# Perform search
|
|
107
|
+
results = manager.search_tasks(
|
|
108
|
+
query=query.strip(),
|
|
109
|
+
project_id=project_id,
|
|
110
|
+
status=status_filter,
|
|
111
|
+
task_type=task_type,
|
|
112
|
+
priority=priority,
|
|
113
|
+
limit=limit,
|
|
114
|
+
min_score=min_score,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
if json_format:
|
|
118
|
+
output = {
|
|
119
|
+
"query": query.strip(),
|
|
120
|
+
"count": len(results),
|
|
121
|
+
"tasks": [
|
|
122
|
+
{
|
|
123
|
+
**task.to_dict(),
|
|
124
|
+
"score": round(score, 4),
|
|
125
|
+
}
|
|
126
|
+
for task, score in results
|
|
127
|
+
],
|
|
128
|
+
}
|
|
129
|
+
click.echo(json.dumps(output, indent=2, default=str))
|
|
130
|
+
return
|
|
131
|
+
|
|
132
|
+
if not results:
|
|
133
|
+
click.echo(f"No tasks found matching '{query}'.")
|
|
134
|
+
return
|
|
135
|
+
|
|
136
|
+
click.echo(f"Found {len(results)} task(s) matching '{query}':\n")
|
|
137
|
+
|
|
138
|
+
# Print header
|
|
139
|
+
click.echo(f"{'#':<6} {'Score':<7} {'Status':<12} {'Pri':<4} {'Title'}")
|
|
140
|
+
click.echo("-" * 70)
|
|
141
|
+
|
|
142
|
+
for task, score in results:
|
|
143
|
+
# Format similar to list_tasks but with score
|
|
144
|
+
seq_ref = f"#{task.seq_num}" if task.seq_num else task.id[:8]
|
|
145
|
+
status_display = task.status[:11] if task.status else ""
|
|
146
|
+
pri_display = str(task.priority) if task.priority else ""
|
|
147
|
+
title_display = task.title[:45] if task.title else ""
|
|
148
|
+
|
|
149
|
+
click.echo(
|
|
150
|
+
f"{seq_ref:<6} {score:<7.3f} {status_display:<12} {pri_display:<4} {title_display}"
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@click.command("reindex")
|
|
155
|
+
@click.option(
|
|
156
|
+
"--all-projects",
|
|
157
|
+
"-a",
|
|
158
|
+
is_flag=True,
|
|
159
|
+
help="Reindex all projects instead of just the current project",
|
|
160
|
+
)
|
|
161
|
+
def reindex_tasks(all_projects: bool) -> None:
|
|
162
|
+
"""Rebuild the task search index.
|
|
163
|
+
|
|
164
|
+
Use this after bulk operations or if search results seem stale.
|
|
165
|
+
"""
|
|
166
|
+
# Resolve project
|
|
167
|
+
project_id = None
|
|
168
|
+
if not all_projects:
|
|
169
|
+
project_id = resolve_project_ref(None)
|
|
170
|
+
|
|
171
|
+
manager = get_task_manager()
|
|
172
|
+
|
|
173
|
+
click.echo("Rebuilding task search index...")
|
|
174
|
+
stats = manager.reindex_search(project_id)
|
|
175
|
+
|
|
176
|
+
click.echo(f"Search index rebuilt with {stats.get('item_count', 0)} tasks.")
|
|
177
|
+
if stats.get("vocabulary_size"):
|
|
178
|
+
click.echo(f"Vocabulary size: {stats['vocabulary_size']}")
|
gobby/cli/tui.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""TUI command for launching the Gobby dashboard."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@click.command()
|
|
7
|
+
@click.option(
|
|
8
|
+
"--port",
|
|
9
|
+
"-p",
|
|
10
|
+
default=8765,
|
|
11
|
+
help="Daemon HTTP port",
|
|
12
|
+
show_default=True,
|
|
13
|
+
)
|
|
14
|
+
@click.option(
|
|
15
|
+
"--ws-port",
|
|
16
|
+
"-w",
|
|
17
|
+
default=8766,
|
|
18
|
+
help="Daemon WebSocket port",
|
|
19
|
+
show_default=True,
|
|
20
|
+
)
|
|
21
|
+
def ui(port: int, ws_port: int) -> None:
|
|
22
|
+
"""Launch the Gobby TUI dashboard.
|
|
23
|
+
|
|
24
|
+
The TUI provides a terminal-based interface for monitoring and managing
|
|
25
|
+
Gobby sessions, tasks, agents, and more.
|
|
26
|
+
|
|
27
|
+
Requires the Gobby daemon to be running (gobby start).
|
|
28
|
+
"""
|
|
29
|
+
from gobby.tui.app import run_tui
|
|
30
|
+
|
|
31
|
+
daemon_url = f"http://localhost:{port}"
|
|
32
|
+
ws_url = f"ws://localhost:{ws_port}"
|
|
33
|
+
|
|
34
|
+
run_tui(daemon_url=daemon_url, ws_url=ws_url)
|