@fifine/aim-studio 0.0.1
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.
- package/LICENSE +21 -0
- package/README.md +159 -0
- package/bin/aim.js +3 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +89 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/commands/init.d.ts +13 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +513 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/update.d.ts +27 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +1275 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/configurators/claude.d.ts +32 -0
- package/dist/configurators/claude.d.ts.map +1 -0
- package/dist/configurators/claude.js +98 -0
- package/dist/configurators/claude.js.map +1 -0
- package/dist/configurators/index.d.ts +51 -0
- package/dist/configurators/index.d.ts.map +1 -0
- package/dist/configurators/index.js +113 -0
- package/dist/configurators/index.js.map +1 -0
- package/dist/configurators/shared.d.ts +12 -0
- package/dist/configurators/shared.d.ts.map +1 -0
- package/dist/configurators/shared.js +21 -0
- package/dist/configurators/shared.js.map +1 -0
- package/dist/configurators/workflow.d.ts +28 -0
- package/dist/configurators/workflow.d.ts.map +1 -0
- package/dist/configurators/workflow.js +147 -0
- package/dist/configurators/workflow.js.map +1 -0
- package/dist/constants/paths.d.ts +68 -0
- package/dist/constants/paths.d.ts.map +1 -0
- package/dist/constants/paths.js +77 -0
- package/dist/constants/paths.js.map +1 -0
- package/dist/constants/version.d.ts +9 -0
- package/dist/constants/version.d.ts.map +1 -0
- package/dist/constants/version.js +15 -0
- package/dist/constants/version.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/migrations/index.d.ts +54 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +160 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/migrations/manifests/0.0.1.json +9 -0
- package/dist/migrations/manifests/0.1.9.json +30 -0
- package/dist/migrations/manifests/0.2.0.json +49 -0
- package/dist/migrations/manifests/0.2.12.json +9 -0
- package/dist/migrations/manifests/0.2.13.json +9 -0
- package/dist/migrations/manifests/0.2.14.json +175 -0
- package/dist/migrations/manifests/0.2.15.json +33 -0
- package/dist/migrations/manifests/0.3.0-beta.0.json +278 -0
- package/dist/migrations/manifests/0.3.0-beta.1.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.10.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.11.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.12.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.13.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.14.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.15.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.16.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.2.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.3.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.4.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.5.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.6.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.7.json +11 -0
- package/dist/migrations/manifests/0.3.0-beta.8.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.9.json +9 -0
- package/dist/migrations/manifests/0.3.0-rc.0.json +9 -0
- package/dist/migrations/manifests/0.3.0-rc.1.json +9 -0
- package/dist/migrations/manifests/0.3.0-rc.2.json +9 -0
- package/dist/templates/CLAUDE.md +71 -0
- package/dist/templates/aim/gitignore.txt +29 -0
- package/dist/templates/aim/index.d.ts +49 -0
- package/dist/templates/aim/index.d.ts.map +1 -0
- package/dist/templates/aim/index.js +92 -0
- package/dist/templates/aim/index.js.map +1 -0
- package/dist/templates/aim/scripts/__init__.py +5 -0
- package/dist/templates/aim/scripts/add_session.py +392 -0
- package/dist/templates/aim/scripts/common/__init__.py +80 -0
- package/dist/templates/aim/scripts/common/cli_adapter.py +435 -0
- package/dist/templates/aim/scripts/common/developer.py +190 -0
- package/dist/templates/aim/scripts/common/git_context.py +383 -0
- package/dist/templates/aim/scripts/common/paths.py +347 -0
- package/dist/templates/aim/scripts/common/phase.py +253 -0
- package/dist/templates/aim/scripts/common/registry.py +366 -0
- package/dist/templates/aim/scripts/common/task_queue.py +255 -0
- package/dist/templates/aim/scripts/common/task_utils.py +178 -0
- package/dist/templates/aim/scripts/common/worktree.py +219 -0
- package/dist/templates/aim/scripts/create_bootstrap.py +290 -0
- package/dist/templates/aim/scripts/get_context.py +16 -0
- package/dist/templates/aim/scripts/get_developer.py +26 -0
- package/dist/templates/aim/scripts/init_developer.py +51 -0
- package/dist/templates/aim/scripts/multi_agent/__init__.py +5 -0
- package/dist/templates/aim/scripts/multi_agent/cleanup.py +403 -0
- package/dist/templates/aim/scripts/multi_agent/create_pr.py +329 -0
- package/dist/templates/aim/scripts/multi_agent/plan.py +233 -0
- package/dist/templates/aim/scripts/multi_agent/start.py +461 -0
- package/dist/templates/aim/scripts/multi_agent/status.py +817 -0
- package/dist/templates/aim/scripts/task.py +1068 -0
- package/dist/templates/aim/scripts-shell-archive/add-session.sh +384 -0
- package/dist/templates/aim/scripts-shell-archive/common/developer.sh +129 -0
- package/dist/templates/aim/scripts-shell-archive/common/git-context.sh +263 -0
- package/dist/templates/aim/scripts-shell-archive/common/paths.sh +208 -0
- package/dist/templates/aim/scripts-shell-archive/common/phase.sh +150 -0
- package/dist/templates/aim/scripts-shell-archive/common/registry.sh +247 -0
- package/dist/templates/aim/scripts-shell-archive/common/task-queue.sh +142 -0
- package/dist/templates/aim/scripts-shell-archive/common/task-utils.sh +151 -0
- package/dist/templates/aim/scripts-shell-archive/common/worktree.sh +128 -0
- package/dist/templates/aim/scripts-shell-archive/create-bootstrap.sh +299 -0
- package/dist/templates/aim/scripts-shell-archive/get-context.sh +7 -0
- package/dist/templates/aim/scripts-shell-archive/get-developer.sh +15 -0
- package/dist/templates/aim/scripts-shell-archive/init-developer.sh +34 -0
- package/dist/templates/aim/scripts-shell-archive/multi-agent/cleanup.sh +396 -0
- package/dist/templates/aim/scripts-shell-archive/multi-agent/create-pr.sh +241 -0
- package/dist/templates/aim/scripts-shell-archive/multi-agent/plan.sh +207 -0
- package/dist/templates/aim/scripts-shell-archive/multi-agent/start.sh +317 -0
- package/dist/templates/aim/scripts-shell-archive/multi-agent/status.sh +828 -0
- package/dist/templates/aim/scripts-shell-archive/task.sh +1204 -0
- package/dist/templates/aim/tasks/.gitkeep +0 -0
- package/dist/templates/aim/workflow.md +258 -0
- package/dist/templates/aim/worktree.yaml +47 -0
- package/dist/templates/claude/agents/check.md +122 -0
- package/dist/templates/claude/agents/debug.md +106 -0
- package/dist/templates/claude/agents/dispatch.md +230 -0
- package/dist/templates/claude/agents/implement.md +96 -0
- package/dist/templates/claude/agents/plan.md +396 -0
- package/dist/templates/claude/agents/research.md +120 -0
- package/dist/templates/claude/agents/story.md +53 -0
- package/dist/templates/claude/commands/aim/before-backend-dev.md +13 -0
- package/dist/templates/claude/commands/aim/before-frontend-dev.md +13 -0
- package/dist/templates/claude/commands/aim/break-loop.md +153 -0
- package/dist/templates/claude/commands/aim/check-backend.md +13 -0
- package/dist/templates/claude/commands/aim/check-cross-layer.md +153 -0
- package/dist/templates/claude/commands/aim/check-frontend.md +13 -0
- package/dist/templates/claude/commands/aim/check-story.md +59 -0
- package/dist/templates/claude/commands/aim/create-command.md +154 -0
- package/dist/templates/claude/commands/aim/export.md +187 -0
- package/dist/templates/claude/commands/aim/finish-work.md +104 -0
- package/dist/templates/claude/commands/aim/integrate-skill.md +219 -0
- package/dist/templates/claude/commands/aim/onboard.md +358 -0
- package/dist/templates/claude/commands/aim/parallel.md +217 -0
- package/dist/templates/claude/commands/aim/portrait.md +170 -0
- package/dist/templates/claude/commands/aim/record-session.md +92 -0
- package/dist/templates/claude/commands/aim/start.md +112 -0
- package/dist/templates/claude/commands/aim/story.md +140 -0
- package/dist/templates/claude/commands/aim/update-spec.md +285 -0
- package/dist/templates/claude/commands/aim/visualize.md +182 -0
- package/dist/templates/claude/commands/trellis/before-backend-dev.md +13 -0
- package/dist/templates/claude/commands/trellis/before-frontend-dev.md +13 -0
- package/dist/templates/claude/commands/trellis/break-loop.md +125 -0
- package/dist/templates/claude/commands/trellis/check-backend.md +13 -0
- package/dist/templates/claude/commands/trellis/check-cross-layer.md +153 -0
- package/dist/templates/claude/commands/trellis/check-frontend.md +13 -0
- package/dist/templates/claude/commands/trellis/create-command.md +154 -0
- package/dist/templates/claude/commands/trellis/finish-work.md +129 -0
- package/dist/templates/claude/commands/trellis/integrate-skill.md +219 -0
- package/dist/templates/claude/commands/trellis/onboard.md +358 -0
- package/dist/templates/claude/commands/trellis/parallel.md +193 -0
- package/dist/templates/claude/commands/trellis/record-session.md +62 -0
- package/dist/templates/claude/commands/trellis/start.md +280 -0
- package/dist/templates/claude/commands/trellis/update-spec.md +285 -0
- package/dist/templates/claude/hooks/inject-subagent-context.py +772 -0
- package/dist/templates/claude/hooks/ralph-loop.py +388 -0
- package/dist/templates/claude/hooks/session-start.py +142 -0
- package/dist/templates/claude/index.d.ts +54 -0
- package/dist/templates/claude/index.d.ts.map +1 -0
- package/dist/templates/claude/index.js +85 -0
- package/dist/templates/claude/index.js.map +1 -0
- package/dist/templates/claude/settings.json +41 -0
- package/dist/templates/extract.d.ts +68 -0
- package/dist/templates/extract.d.ts.map +1 -0
- package/dist/templates/extract.js +128 -0
- package/dist/templates/extract.js.map +1 -0
- package/dist/templates/markdown/agents.md +25 -0
- package/dist/templates/markdown/gitignore.txt +12 -0
- package/dist/templates/markdown/index.d.ts +32 -0
- package/dist/templates/markdown/index.d.ts.map +1 -0
- package/dist/templates/markdown/index.js +58 -0
- package/dist/templates/markdown/index.js.map +1 -0
- package/dist/templates/markdown/spec/backend/database-guidelines.md.txt +51 -0
- package/dist/templates/markdown/spec/backend/directory-structure.md.txt +54 -0
- package/dist/templates/markdown/spec/backend/error-handling.md.txt +51 -0
- package/dist/templates/markdown/spec/backend/index.md +40 -0
- package/dist/templates/markdown/spec/backend/index.md.txt +38 -0
- package/dist/templates/markdown/spec/backend/logging-guidelines.md.txt +51 -0
- package/dist/templates/markdown/spec/backend/quality-guidelines.md.txt +51 -0
- package/dist/templates/markdown/spec/backend/script-conventions.md +467 -0
- package/dist/templates/markdown/spec/frontend/component-guidelines.md.txt +59 -0
- package/dist/templates/markdown/spec/frontend/directory-structure.md.txt +54 -0
- package/dist/templates/markdown/spec/frontend/hook-guidelines.md.txt +51 -0
- package/dist/templates/markdown/spec/frontend/index.md.txt +39 -0
- package/dist/templates/markdown/spec/frontend/quality-guidelines.md.txt +51 -0
- package/dist/templates/markdown/spec/frontend/state-management.md.txt +51 -0
- package/dist/templates/markdown/spec/frontend/type-safety.md.txt +51 -0
- package/dist/templates/markdown/spec/guides/code-reuse-thinking-guide.md +118 -0
- package/dist/templates/markdown/spec/guides/code-reuse-thinking-guide.md.txt +92 -0
- package/dist/templates/markdown/spec/guides/cross-layer-thinking-guide.md.txt +94 -0
- package/dist/templates/markdown/spec/guides/cross-platform-thinking-guide.md +394 -0
- package/dist/templates/markdown/spec/guides/cross-platform-thinking-guide.md.txt +319 -0
- package/dist/templates/markdown/spec/guides/index.md.txt +89 -0
- package/dist/templates/markdown/spec/story/character.md.txt +95 -0
- package/dist/templates/markdown/spec/story/index.md.txt +31 -0
- package/dist/templates/markdown/spec/story/script.md.txt +313 -0
- package/dist/templates/markdown/spec/story/world.md.txt +92 -0
- package/dist/templates/markdown/workspace-index.md +123 -0
- package/dist/templates/markdown/worktree.yaml.txt +58 -0
- package/dist/templates/trellis/gitignore.txt +29 -0
- package/dist/templates/trellis/index.d.ts +49 -0
- package/dist/templates/trellis/index.d.ts.map +1 -0
- package/dist/templates/trellis/index.js +92 -0
- package/dist/templates/trellis/index.js.map +1 -0
- package/dist/templates/trellis/scripts/__init__.py +5 -0
- package/dist/templates/trellis/scripts/add_session.py +392 -0
- package/dist/templates/trellis/scripts/common/__init__.py +80 -0
- package/dist/templates/trellis/scripts/common/cli_adapter.py +435 -0
- package/dist/templates/trellis/scripts/common/developer.py +190 -0
- package/dist/templates/trellis/scripts/common/git_context.py +383 -0
- package/dist/templates/trellis/scripts/common/paths.py +347 -0
- package/dist/templates/trellis/scripts/common/phase.py +253 -0
- package/dist/templates/trellis/scripts/common/registry.py +366 -0
- package/dist/templates/trellis/scripts/common/task_queue.py +255 -0
- package/dist/templates/trellis/scripts/common/task_utils.py +178 -0
- package/dist/templates/trellis/scripts/common/worktree.py +219 -0
- package/dist/templates/trellis/scripts/create_bootstrap.py +290 -0
- package/dist/templates/trellis/scripts/get_context.py +16 -0
- package/dist/templates/trellis/scripts/get_developer.py +26 -0
- package/dist/templates/trellis/scripts/init_developer.py +51 -0
- package/dist/templates/trellis/scripts/multi_agent/__init__.py +5 -0
- package/dist/templates/trellis/scripts/multi_agent/cleanup.py +403 -0
- package/dist/templates/trellis/scripts/multi_agent/create_pr.py +329 -0
- package/dist/templates/trellis/scripts/multi_agent/plan.py +233 -0
- package/dist/templates/trellis/scripts/multi_agent/start.py +461 -0
- package/dist/templates/trellis/scripts/multi_agent/status.py +817 -0
- package/dist/templates/trellis/scripts/task.py +1056 -0
- package/dist/templates/trellis/scripts-shell-archive/add-session.sh +384 -0
- package/dist/templates/trellis/scripts-shell-archive/common/developer.sh +129 -0
- package/dist/templates/trellis/scripts-shell-archive/common/git-context.sh +263 -0
- package/dist/templates/trellis/scripts-shell-archive/common/paths.sh +208 -0
- package/dist/templates/trellis/scripts-shell-archive/common/phase.sh +150 -0
- package/dist/templates/trellis/scripts-shell-archive/common/registry.sh +247 -0
- package/dist/templates/trellis/scripts-shell-archive/common/task-queue.sh +142 -0
- package/dist/templates/trellis/scripts-shell-archive/common/task-utils.sh +151 -0
- package/dist/templates/trellis/scripts-shell-archive/common/worktree.sh +128 -0
- package/dist/templates/trellis/scripts-shell-archive/create-bootstrap.sh +299 -0
- package/dist/templates/trellis/scripts-shell-archive/get-context.sh +7 -0
- package/dist/templates/trellis/scripts-shell-archive/get-developer.sh +15 -0
- package/dist/templates/trellis/scripts-shell-archive/init-developer.sh +34 -0
- package/dist/templates/trellis/scripts-shell-archive/multi-agent/cleanup.sh +396 -0
- package/dist/templates/trellis/scripts-shell-archive/multi-agent/create-pr.sh +241 -0
- package/dist/templates/trellis/scripts-shell-archive/multi-agent/plan.sh +207 -0
- package/dist/templates/trellis/scripts-shell-archive/multi-agent/start.sh +317 -0
- package/dist/templates/trellis/scripts-shell-archive/multi-agent/status.sh +828 -0
- package/dist/templates/trellis/scripts-shell-archive/task.sh +1204 -0
- package/dist/templates/trellis/tasks/.gitkeep +0 -0
- package/dist/templates/trellis/workflow.md +416 -0
- package/dist/templates/trellis/worktree.yaml +47 -0
- package/dist/types/ai-tools.d.ts +48 -0
- package/dist/types/ai-tools.d.ts.map +1 -0
- package/dist/types/ai-tools.js +32 -0
- package/dist/types/ai-tools.js.map +1 -0
- package/dist/types/migration.d.ts +86 -0
- package/dist/types/migration.d.ts.map +1 -0
- package/dist/types/migration.js +8 -0
- package/dist/types/migration.js.map +1 -0
- package/dist/utils/compare-versions.d.ts +12 -0
- package/dist/utils/compare-versions.d.ts.map +1 -0
- package/dist/utils/compare-versions.js +76 -0
- package/dist/utils/compare-versions.js.map +1 -0
- package/dist/utils/file-writer.d.ts +23 -0
- package/dist/utils/file-writer.d.ts.map +1 -0
- package/dist/utils/file-writer.js +140 -0
- package/dist/utils/file-writer.js.map +1 -0
- package/dist/utils/project-detector.d.ts +16 -0
- package/dist/utils/project-detector.d.ts.map +1 -0
- package/dist/utils/project-detector.js +188 -0
- package/dist/utils/project-detector.js.map +1 -0
- package/dist/utils/template-fetcher.d.ts +51 -0
- package/dist/utils/template-fetcher.d.ts.map +1 -0
- package/dist/utils/template-fetcher.js +174 -0
- package/dist/utils/template-fetcher.js.map +1 -0
- package/dist/utils/template-hash.d.ts +78 -0
- package/dist/utils/template-hash.d.ts.map +1 -0
- package/dist/utils/template-hash.js +239 -0
- package/dist/utils/template-hash.js.map +1 -0
- package/package.json +87 -0
|
@@ -0,0 +1,1056 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Task Management Script for Multi-Agent Pipeline.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
python3 task.py create "<title>" [--slug <name>] [--assignee <dev>] [--priority P0|P1|P2|P3]
|
|
8
|
+
python3 task.py init-context <dir> <type> # Initialize jsonl files
|
|
9
|
+
python3 task.py add-context <dir> <file> <path> [reason] # Add jsonl entry
|
|
10
|
+
python3 task.py validate <dir> # Validate jsonl files
|
|
11
|
+
python3 task.py list-context <dir> # List jsonl entries
|
|
12
|
+
python3 task.py start <dir> # Set as current task
|
|
13
|
+
python3 task.py finish # Clear current task
|
|
14
|
+
python3 task.py set-branch <dir> <branch> # Set git branch
|
|
15
|
+
python3 task.py set-base-branch <dir> <branch> # Set PR target branch
|
|
16
|
+
python3 task.py set-scope <dir> <scope> # Set scope for PR title
|
|
17
|
+
python3 task.py create-pr [dir] [--dry-run] # Create PR from task
|
|
18
|
+
python3 task.py archive <task-name> # Archive completed task
|
|
19
|
+
python3 task.py list # List active tasks
|
|
20
|
+
python3 task.py list-archive [month] # List archived tasks
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import sys
|
|
26
|
+
|
|
27
|
+
# IMPORTANT: Force stdout to use UTF-8 on Windows
|
|
28
|
+
# This fixes UnicodeEncodeError when outputting non-ASCII characters
|
|
29
|
+
if sys.platform == "win32":
|
|
30
|
+
import io as _io
|
|
31
|
+
if hasattr(sys.stdout, "reconfigure"):
|
|
32
|
+
sys.stdout.reconfigure(encoding="utf-8", errors="replace") # type: ignore[union-attr]
|
|
33
|
+
elif hasattr(sys.stdout, "detach"):
|
|
34
|
+
sys.stdout = _io.TextIOWrapper(sys.stdout.detach(), encoding="utf-8", errors="replace") # type: ignore[union-attr]
|
|
35
|
+
|
|
36
|
+
import argparse
|
|
37
|
+
import json
|
|
38
|
+
import re
|
|
39
|
+
import sys
|
|
40
|
+
from datetime import datetime
|
|
41
|
+
from pathlib import Path
|
|
42
|
+
|
|
43
|
+
from common.cli_adapter import get_cli_adapter_auto
|
|
44
|
+
from common.git_context import _run_git_command
|
|
45
|
+
from common.paths import (
|
|
46
|
+
DIR_WORKFLOW,
|
|
47
|
+
DIR_TASKS,
|
|
48
|
+
DIR_SPEC,
|
|
49
|
+
DIR_ARCHIVE,
|
|
50
|
+
FILE_TASK_JSON,
|
|
51
|
+
get_repo_root,
|
|
52
|
+
get_developer,
|
|
53
|
+
get_tasks_dir,
|
|
54
|
+
get_current_task,
|
|
55
|
+
set_current_task,
|
|
56
|
+
clear_current_task,
|
|
57
|
+
generate_task_date_prefix,
|
|
58
|
+
)
|
|
59
|
+
from common.task_utils import (
|
|
60
|
+
find_task_by_name,
|
|
61
|
+
archive_task_complete,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# =============================================================================
|
|
66
|
+
# Colors
|
|
67
|
+
# =============================================================================
|
|
68
|
+
|
|
69
|
+
class Colors:
|
|
70
|
+
RED = "\033[0;31m"
|
|
71
|
+
GREEN = "\033[0;32m"
|
|
72
|
+
YELLOW = "\033[1;33m"
|
|
73
|
+
BLUE = "\033[0;34m"
|
|
74
|
+
CYAN = "\033[0;36m"
|
|
75
|
+
NC = "\033[0m"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def colored(text: str, color: str) -> str:
|
|
79
|
+
"""Apply color to text."""
|
|
80
|
+
return f"{color}{text}{Colors.NC}"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# =============================================================================
|
|
84
|
+
# Helper Functions
|
|
85
|
+
# =============================================================================
|
|
86
|
+
|
|
87
|
+
def _read_json_file(path: Path) -> dict | None:
|
|
88
|
+
"""Read and parse a JSON file."""
|
|
89
|
+
try:
|
|
90
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
91
|
+
except (FileNotFoundError, json.JSONDecodeError, OSError):
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _write_json_file(path: Path, data: dict) -> bool:
|
|
96
|
+
"""Write dict to JSON file."""
|
|
97
|
+
try:
|
|
98
|
+
path.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8")
|
|
99
|
+
return True
|
|
100
|
+
except (OSError, IOError):
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _slugify(title: str) -> str:
|
|
105
|
+
"""Convert title to slug (only works with ASCII)."""
|
|
106
|
+
result = title.lower()
|
|
107
|
+
result = re.sub(r"[^a-z0-9]", "-", result)
|
|
108
|
+
result = re.sub(r"-+", "-", result)
|
|
109
|
+
result = result.strip("-")
|
|
110
|
+
return result
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _resolve_task_dir(target_dir: str, repo_root: Path) -> Path:
|
|
114
|
+
"""Resolve task directory to absolute path.
|
|
115
|
+
|
|
116
|
+
Supports:
|
|
117
|
+
- Absolute path: /path/to/task
|
|
118
|
+
- Relative path: .trellis/tasks/01-31-my-task
|
|
119
|
+
- Task name: my-task (uses find_task_by_name for lookup)
|
|
120
|
+
"""
|
|
121
|
+
if not target_dir:
|
|
122
|
+
return Path()
|
|
123
|
+
|
|
124
|
+
# Absolute path
|
|
125
|
+
if target_dir.startswith("/"):
|
|
126
|
+
return Path(target_dir)
|
|
127
|
+
|
|
128
|
+
# Relative path (contains path separator or starts with .trellis)
|
|
129
|
+
if "/" in target_dir or target_dir.startswith(".trellis"):
|
|
130
|
+
return repo_root / target_dir
|
|
131
|
+
|
|
132
|
+
# Task name - try to find in tasks directory
|
|
133
|
+
tasks_dir = get_tasks_dir(repo_root)
|
|
134
|
+
found = find_task_by_name(target_dir, tasks_dir)
|
|
135
|
+
if found:
|
|
136
|
+
return found
|
|
137
|
+
|
|
138
|
+
# Fallback to treating as relative path
|
|
139
|
+
return repo_root / target_dir
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
# =============================================================================
|
|
143
|
+
# JSONL Default Content Generators
|
|
144
|
+
# =============================================================================
|
|
145
|
+
|
|
146
|
+
def get_implement_base() -> list[dict]:
|
|
147
|
+
"""Get base implement context entries."""
|
|
148
|
+
return [
|
|
149
|
+
{"file": f"{DIR_WORKFLOW}/workflow.md", "reason": "Project workflow and conventions"},
|
|
150
|
+
{"file": f"{DIR_WORKFLOW}/{DIR_SPEC}/shared/index.md", "reason": "Shared coding standards"},
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def get_implement_backend() -> list[dict]:
|
|
155
|
+
"""Get backend implement context entries."""
|
|
156
|
+
return [
|
|
157
|
+
{"file": f"{DIR_WORKFLOW}/{DIR_SPEC}/backend/index.md", "reason": "Backend development guide"},
|
|
158
|
+
{"file": f"{DIR_WORKFLOW}/{DIR_SPEC}/backend/api-module.md", "reason": "API module conventions"},
|
|
159
|
+
{"file": f"{DIR_WORKFLOW}/{DIR_SPEC}/backend/quality.md", "reason": "Code quality requirements"},
|
|
160
|
+
]
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def get_implement_frontend() -> list[dict]:
|
|
164
|
+
"""Get frontend implement context entries."""
|
|
165
|
+
return [
|
|
166
|
+
{"file": f"{DIR_WORKFLOW}/{DIR_SPEC}/frontend/index.md", "reason": "Frontend development guide"},
|
|
167
|
+
{"file": f"{DIR_WORKFLOW}/{DIR_SPEC}/frontend/components.md", "reason": "Component conventions"},
|
|
168
|
+
]
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def get_check_context(dev_type: str, repo_root: Path) -> list[dict]:
|
|
172
|
+
"""Get check context entries."""
|
|
173
|
+
adapter = get_cli_adapter_auto(repo_root)
|
|
174
|
+
|
|
175
|
+
entries = [
|
|
176
|
+
{"file": adapter.get_trellis_command_path("finish-work"), "reason": "Finish work checklist"},
|
|
177
|
+
{"file": f"{DIR_WORKFLOW}/{DIR_SPEC}/shared/index.md", "reason": "Shared coding standards"},
|
|
178
|
+
]
|
|
179
|
+
|
|
180
|
+
if dev_type in ("backend", "fullstack"):
|
|
181
|
+
entries.append({"file": adapter.get_trellis_command_path("check-backend"), "reason": "Backend check spec"})
|
|
182
|
+
if dev_type in ("frontend", "fullstack"):
|
|
183
|
+
entries.append({"file": adapter.get_trellis_command_path("check-frontend"), "reason": "Frontend check spec"})
|
|
184
|
+
|
|
185
|
+
return entries
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def get_debug_context(dev_type: str, repo_root: Path) -> list[dict]:
|
|
189
|
+
"""Get debug context entries."""
|
|
190
|
+
adapter = get_cli_adapter_auto(repo_root)
|
|
191
|
+
|
|
192
|
+
entries = [
|
|
193
|
+
{"file": f"{DIR_WORKFLOW}/{DIR_SPEC}/shared/index.md", "reason": "Shared coding standards"},
|
|
194
|
+
]
|
|
195
|
+
|
|
196
|
+
if dev_type in ("backend", "fullstack"):
|
|
197
|
+
entries.append({"file": adapter.get_trellis_command_path("check-backend"), "reason": "Backend check spec"})
|
|
198
|
+
if dev_type in ("frontend", "fullstack"):
|
|
199
|
+
entries.append({"file": adapter.get_trellis_command_path("check-frontend"), "reason": "Frontend check spec"})
|
|
200
|
+
|
|
201
|
+
return entries
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _write_jsonl(path: Path, entries: list[dict]) -> None:
|
|
205
|
+
"""Write entries to JSONL file."""
|
|
206
|
+
lines = [json.dumps(entry, ensure_ascii=False) for entry in entries]
|
|
207
|
+
path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
# =============================================================================
|
|
211
|
+
# Task Operations
|
|
212
|
+
# =============================================================================
|
|
213
|
+
|
|
214
|
+
def ensure_tasks_dir(repo_root: Path) -> Path:
|
|
215
|
+
"""Ensure tasks directory exists."""
|
|
216
|
+
tasks_dir = get_tasks_dir(repo_root)
|
|
217
|
+
archive_dir = tasks_dir / "archive"
|
|
218
|
+
|
|
219
|
+
if not tasks_dir.exists():
|
|
220
|
+
tasks_dir.mkdir(parents=True)
|
|
221
|
+
print(colored(f"Created tasks directory: {tasks_dir}", Colors.GREEN), file=sys.stderr)
|
|
222
|
+
|
|
223
|
+
if not archive_dir.exists():
|
|
224
|
+
archive_dir.mkdir(parents=True)
|
|
225
|
+
|
|
226
|
+
return tasks_dir
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
# =============================================================================
|
|
230
|
+
# Command: create
|
|
231
|
+
# =============================================================================
|
|
232
|
+
|
|
233
|
+
def cmd_create(args: argparse.Namespace) -> int:
|
|
234
|
+
"""Create a new task."""
|
|
235
|
+
repo_root = get_repo_root()
|
|
236
|
+
|
|
237
|
+
if not args.title:
|
|
238
|
+
print(colored("Error: title is required", Colors.RED), file=sys.stderr)
|
|
239
|
+
return 1
|
|
240
|
+
|
|
241
|
+
# Default assignee to current developer
|
|
242
|
+
assignee = args.assignee
|
|
243
|
+
if not assignee:
|
|
244
|
+
assignee = get_developer(repo_root)
|
|
245
|
+
if not assignee:
|
|
246
|
+
print(colored("Error: No developer set. Run init_developer.py first or use --assignee", Colors.RED), file=sys.stderr)
|
|
247
|
+
return 1
|
|
248
|
+
|
|
249
|
+
ensure_tasks_dir(repo_root)
|
|
250
|
+
|
|
251
|
+
# Get current developer as creator
|
|
252
|
+
creator = get_developer(repo_root) or assignee
|
|
253
|
+
|
|
254
|
+
# Generate slug if not provided
|
|
255
|
+
slug = args.slug or _slugify(args.title)
|
|
256
|
+
if not slug:
|
|
257
|
+
print(colored("Error: could not generate slug from title", Colors.RED), file=sys.stderr)
|
|
258
|
+
return 1
|
|
259
|
+
|
|
260
|
+
# Create task directory with MM-DD-slug format
|
|
261
|
+
tasks_dir = get_tasks_dir(repo_root)
|
|
262
|
+
date_prefix = generate_task_date_prefix()
|
|
263
|
+
dir_name = f"{date_prefix}-{slug}"
|
|
264
|
+
task_dir = tasks_dir / dir_name
|
|
265
|
+
task_json_path = task_dir / FILE_TASK_JSON
|
|
266
|
+
|
|
267
|
+
if task_dir.exists():
|
|
268
|
+
print(colored(f"Warning: Task directory already exists: {dir_name}", Colors.YELLOW), file=sys.stderr)
|
|
269
|
+
else:
|
|
270
|
+
task_dir.mkdir(parents=True)
|
|
271
|
+
|
|
272
|
+
today = datetime.now().strftime("%Y-%m-%d")
|
|
273
|
+
|
|
274
|
+
# Record current branch as base_branch (PR target)
|
|
275
|
+
_, branch_out, _ = _run_git_command(["branch", "--show-current"], cwd=repo_root)
|
|
276
|
+
current_branch = branch_out.strip() or "main"
|
|
277
|
+
|
|
278
|
+
task_data = {
|
|
279
|
+
"id": slug,
|
|
280
|
+
"name": slug,
|
|
281
|
+
"title": args.title,
|
|
282
|
+
"description": args.description or "",
|
|
283
|
+
"status": "planning",
|
|
284
|
+
"dev_type": None,
|
|
285
|
+
"scope": None,
|
|
286
|
+
"priority": args.priority,
|
|
287
|
+
"creator": creator,
|
|
288
|
+
"assignee": assignee,
|
|
289
|
+
"createdAt": today,
|
|
290
|
+
"completedAt": None,
|
|
291
|
+
"branch": None,
|
|
292
|
+
"base_branch": current_branch,
|
|
293
|
+
"worktree_path": None,
|
|
294
|
+
"current_phase": 0,
|
|
295
|
+
"next_action": [
|
|
296
|
+
{"phase": 1, "action": "implement"},
|
|
297
|
+
{"phase": 2, "action": "check"},
|
|
298
|
+
{"phase": 3, "action": "finish"},
|
|
299
|
+
{"phase": 4, "action": "create-pr"},
|
|
300
|
+
],
|
|
301
|
+
"commit": None,
|
|
302
|
+
"pr_url": None,
|
|
303
|
+
"subtasks": [],
|
|
304
|
+
"relatedFiles": [],
|
|
305
|
+
"notes": "",
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
_write_json_file(task_json_path, task_data)
|
|
309
|
+
|
|
310
|
+
print(colored(f"Created task: {dir_name}", Colors.GREEN), file=sys.stderr)
|
|
311
|
+
print("", file=sys.stderr)
|
|
312
|
+
print(colored("Next steps:", Colors.BLUE), file=sys.stderr)
|
|
313
|
+
print(" 1. Create prd.md with requirements", file=sys.stderr)
|
|
314
|
+
print(" 2. Run: python3 task.py init-context <dir> <dev_type>", file=sys.stderr)
|
|
315
|
+
print(" 3. Run: python3 task.py start <dir>", file=sys.stderr)
|
|
316
|
+
print("", file=sys.stderr)
|
|
317
|
+
|
|
318
|
+
# Output relative path for script chaining
|
|
319
|
+
print(f"{DIR_WORKFLOW}/{DIR_TASKS}/{dir_name}")
|
|
320
|
+
return 0
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
# =============================================================================
|
|
324
|
+
# Command: init-context
|
|
325
|
+
# =============================================================================
|
|
326
|
+
|
|
327
|
+
def cmd_init_context(args: argparse.Namespace) -> int:
|
|
328
|
+
"""Initialize JSONL context files for a task."""
|
|
329
|
+
repo_root = get_repo_root()
|
|
330
|
+
target_dir = _resolve_task_dir(args.dir, repo_root)
|
|
331
|
+
dev_type = args.type
|
|
332
|
+
|
|
333
|
+
if not dev_type:
|
|
334
|
+
print(colored("Error: Missing arguments", Colors.RED))
|
|
335
|
+
print("Usage: python3 task.py init-context <task-dir> <dev_type>")
|
|
336
|
+
print(" dev_type: backend | frontend | fullstack | test | docs")
|
|
337
|
+
return 1
|
|
338
|
+
|
|
339
|
+
if not target_dir.is_dir():
|
|
340
|
+
print(colored(f"Error: Directory not found: {target_dir}", Colors.RED))
|
|
341
|
+
return 1
|
|
342
|
+
|
|
343
|
+
print(colored("=== Initializing Agent Context Files ===", Colors.BLUE))
|
|
344
|
+
print(f"Target dir: {target_dir}")
|
|
345
|
+
print(f"Dev type: {dev_type}")
|
|
346
|
+
print()
|
|
347
|
+
|
|
348
|
+
# implement.jsonl
|
|
349
|
+
print(colored("Creating implement.jsonl...", Colors.CYAN))
|
|
350
|
+
implement_entries = get_implement_base()
|
|
351
|
+
if dev_type in ("backend", "test"):
|
|
352
|
+
implement_entries.extend(get_implement_backend())
|
|
353
|
+
elif dev_type == "frontend":
|
|
354
|
+
implement_entries.extend(get_implement_frontend())
|
|
355
|
+
elif dev_type == "fullstack":
|
|
356
|
+
implement_entries.extend(get_implement_backend())
|
|
357
|
+
implement_entries.extend(get_implement_frontend())
|
|
358
|
+
|
|
359
|
+
implement_file = target_dir / "implement.jsonl"
|
|
360
|
+
_write_jsonl(implement_file, implement_entries)
|
|
361
|
+
print(f" {colored('✓', Colors.GREEN)} {len(implement_entries)} entries")
|
|
362
|
+
|
|
363
|
+
# check.jsonl
|
|
364
|
+
print(colored("Creating check.jsonl...", Colors.CYAN))
|
|
365
|
+
check_entries = get_check_context(dev_type, repo_root)
|
|
366
|
+
check_file = target_dir / "check.jsonl"
|
|
367
|
+
_write_jsonl(check_file, check_entries)
|
|
368
|
+
print(f" {colored('✓', Colors.GREEN)} {len(check_entries)} entries")
|
|
369
|
+
|
|
370
|
+
# debug.jsonl
|
|
371
|
+
print(colored("Creating debug.jsonl...", Colors.CYAN))
|
|
372
|
+
debug_entries = get_debug_context(dev_type, repo_root)
|
|
373
|
+
debug_file = target_dir / "debug.jsonl"
|
|
374
|
+
_write_jsonl(debug_file, debug_entries)
|
|
375
|
+
print(f" {colored('✓', Colors.GREEN)} {len(debug_entries)} entries")
|
|
376
|
+
|
|
377
|
+
print()
|
|
378
|
+
print(colored("✓ All context files created", Colors.GREEN))
|
|
379
|
+
print()
|
|
380
|
+
print(colored("Next steps:", Colors.BLUE))
|
|
381
|
+
print(" 1. Add task-specific specs: python3 task.py add-context <dir> <jsonl> <path>")
|
|
382
|
+
print(" 2. Set as current: python3 task.py start <dir>")
|
|
383
|
+
|
|
384
|
+
return 0
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
# =============================================================================
|
|
388
|
+
# Command: add-context
|
|
389
|
+
# =============================================================================
|
|
390
|
+
|
|
391
|
+
def cmd_add_context(args: argparse.Namespace) -> int:
|
|
392
|
+
"""Add entry to JSONL context file."""
|
|
393
|
+
repo_root = get_repo_root()
|
|
394
|
+
target_dir = _resolve_task_dir(args.dir, repo_root)
|
|
395
|
+
|
|
396
|
+
jsonl_name = args.file
|
|
397
|
+
path = args.path
|
|
398
|
+
reason = args.reason or "Added manually"
|
|
399
|
+
|
|
400
|
+
if not target_dir.is_dir():
|
|
401
|
+
print(colored(f"Error: Directory not found: {target_dir}", Colors.RED))
|
|
402
|
+
return 1
|
|
403
|
+
|
|
404
|
+
# Support shorthand
|
|
405
|
+
if not jsonl_name.endswith(".jsonl"):
|
|
406
|
+
jsonl_name = f"{jsonl_name}.jsonl"
|
|
407
|
+
|
|
408
|
+
jsonl_file = target_dir / jsonl_name
|
|
409
|
+
full_path = repo_root / path
|
|
410
|
+
|
|
411
|
+
entry_type = "file"
|
|
412
|
+
if full_path.is_dir():
|
|
413
|
+
entry_type = "directory"
|
|
414
|
+
if not path.endswith("/"):
|
|
415
|
+
path = f"{path}/"
|
|
416
|
+
elif not full_path.is_file():
|
|
417
|
+
print(colored(f"Error: Path not found: {path}", Colors.RED))
|
|
418
|
+
return 1
|
|
419
|
+
|
|
420
|
+
# Check if already exists
|
|
421
|
+
if jsonl_file.is_file():
|
|
422
|
+
content = jsonl_file.read_text(encoding="utf-8")
|
|
423
|
+
if f'"{path}"' in content:
|
|
424
|
+
print(colored(f"Warning: Entry already exists for {path}", Colors.YELLOW))
|
|
425
|
+
return 0
|
|
426
|
+
|
|
427
|
+
# Add entry
|
|
428
|
+
entry: dict
|
|
429
|
+
if entry_type == "directory":
|
|
430
|
+
entry = {"file": path, "type": "directory", "reason": reason}
|
|
431
|
+
else:
|
|
432
|
+
entry = {"file": path, "reason": reason}
|
|
433
|
+
|
|
434
|
+
with jsonl_file.open("a", encoding="utf-8") as f:
|
|
435
|
+
f.write(json.dumps(entry, ensure_ascii=False) + "\n")
|
|
436
|
+
|
|
437
|
+
print(colored(f"Added {entry_type}: {path}", Colors.GREEN))
|
|
438
|
+
return 0
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
# =============================================================================
|
|
442
|
+
# Command: validate
|
|
443
|
+
# =============================================================================
|
|
444
|
+
|
|
445
|
+
def cmd_validate(args: argparse.Namespace) -> int:
|
|
446
|
+
"""Validate JSONL context files."""
|
|
447
|
+
repo_root = get_repo_root()
|
|
448
|
+
target_dir = _resolve_task_dir(args.dir, repo_root)
|
|
449
|
+
|
|
450
|
+
if not target_dir.is_dir():
|
|
451
|
+
print(colored("Error: task directory required", Colors.RED))
|
|
452
|
+
return 1
|
|
453
|
+
|
|
454
|
+
print(colored("=== Validating Context Files ===", Colors.BLUE))
|
|
455
|
+
print(f"Target dir: {target_dir}")
|
|
456
|
+
print()
|
|
457
|
+
|
|
458
|
+
total_errors = 0
|
|
459
|
+
for jsonl_name in ["implement.jsonl", "check.jsonl", "debug.jsonl"]:
|
|
460
|
+
jsonl_file = target_dir / jsonl_name
|
|
461
|
+
errors = _validate_jsonl(jsonl_file, repo_root)
|
|
462
|
+
total_errors += errors
|
|
463
|
+
|
|
464
|
+
print()
|
|
465
|
+
if total_errors == 0:
|
|
466
|
+
print(colored("✓ All validations passed", Colors.GREEN))
|
|
467
|
+
return 0
|
|
468
|
+
else:
|
|
469
|
+
print(colored(f"✗ Validation failed ({total_errors} errors)", Colors.RED))
|
|
470
|
+
return 1
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
def _validate_jsonl(jsonl_file: Path, repo_root: Path) -> int:
|
|
474
|
+
"""Validate a single JSONL file."""
|
|
475
|
+
file_name = jsonl_file.name
|
|
476
|
+
errors = 0
|
|
477
|
+
|
|
478
|
+
if not jsonl_file.is_file():
|
|
479
|
+
print(f" {colored(f'{file_name}: not found (skipped)', Colors.YELLOW)}")
|
|
480
|
+
return 0
|
|
481
|
+
|
|
482
|
+
line_num = 0
|
|
483
|
+
for line in jsonl_file.read_text(encoding="utf-8").splitlines():
|
|
484
|
+
line_num += 1
|
|
485
|
+
if not line.strip():
|
|
486
|
+
continue
|
|
487
|
+
|
|
488
|
+
try:
|
|
489
|
+
data = json.loads(line)
|
|
490
|
+
except json.JSONDecodeError:
|
|
491
|
+
print(f" {colored(f'{file_name}:{line_num}: Invalid JSON', Colors.RED)}")
|
|
492
|
+
errors += 1
|
|
493
|
+
continue
|
|
494
|
+
|
|
495
|
+
file_path = data.get("file")
|
|
496
|
+
entry_type = data.get("type", "file")
|
|
497
|
+
|
|
498
|
+
if not file_path:
|
|
499
|
+
print(f" {colored(f'{file_name}:{line_num}: Missing file field', Colors.RED)}")
|
|
500
|
+
errors += 1
|
|
501
|
+
continue
|
|
502
|
+
|
|
503
|
+
full_path = repo_root / file_path
|
|
504
|
+
if entry_type == "directory":
|
|
505
|
+
if not full_path.is_dir():
|
|
506
|
+
print(f" {colored(f'{file_name}:{line_num}: Directory not found: {file_path}', Colors.RED)}")
|
|
507
|
+
errors += 1
|
|
508
|
+
else:
|
|
509
|
+
if not full_path.is_file():
|
|
510
|
+
print(f" {colored(f'{file_name}:{line_num}: File not found: {file_path}', Colors.RED)}")
|
|
511
|
+
errors += 1
|
|
512
|
+
|
|
513
|
+
if errors == 0:
|
|
514
|
+
print(f" {colored(f'{file_name}: ✓ ({line_num} entries)', Colors.GREEN)}")
|
|
515
|
+
else:
|
|
516
|
+
print(f" {colored(f'{file_name}: ✗ ({errors} errors)', Colors.RED)}")
|
|
517
|
+
|
|
518
|
+
return errors
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
# =============================================================================
|
|
522
|
+
# Command: list-context
|
|
523
|
+
# =============================================================================
|
|
524
|
+
|
|
525
|
+
def cmd_list_context(args: argparse.Namespace) -> int:
|
|
526
|
+
"""List JSONL context entries."""
|
|
527
|
+
repo_root = get_repo_root()
|
|
528
|
+
target_dir = _resolve_task_dir(args.dir, repo_root)
|
|
529
|
+
|
|
530
|
+
if not target_dir.is_dir():
|
|
531
|
+
print(colored("Error: task directory required", Colors.RED))
|
|
532
|
+
return 1
|
|
533
|
+
|
|
534
|
+
print(colored("=== Context Files ===", Colors.BLUE))
|
|
535
|
+
print()
|
|
536
|
+
|
|
537
|
+
for jsonl_name in ["implement.jsonl", "check.jsonl", "debug.jsonl"]:
|
|
538
|
+
jsonl_file = target_dir / jsonl_name
|
|
539
|
+
if not jsonl_file.is_file():
|
|
540
|
+
continue
|
|
541
|
+
|
|
542
|
+
print(colored(f"[{jsonl_name}]", Colors.CYAN))
|
|
543
|
+
|
|
544
|
+
count = 0
|
|
545
|
+
for line in jsonl_file.read_text(encoding="utf-8").splitlines():
|
|
546
|
+
if not line.strip():
|
|
547
|
+
continue
|
|
548
|
+
|
|
549
|
+
try:
|
|
550
|
+
data = json.loads(line)
|
|
551
|
+
except json.JSONDecodeError:
|
|
552
|
+
continue
|
|
553
|
+
|
|
554
|
+
count += 1
|
|
555
|
+
file_path = data.get("file", "?")
|
|
556
|
+
entry_type = data.get("type", "file")
|
|
557
|
+
reason = data.get("reason", "-")
|
|
558
|
+
|
|
559
|
+
if entry_type == "directory":
|
|
560
|
+
print(f" {colored(f'{count}.', Colors.GREEN)} [DIR] {file_path}")
|
|
561
|
+
else:
|
|
562
|
+
print(f" {colored(f'{count}.', Colors.GREEN)} {file_path}")
|
|
563
|
+
print(f" {colored('→', Colors.YELLOW)} {reason}")
|
|
564
|
+
|
|
565
|
+
print()
|
|
566
|
+
|
|
567
|
+
return 0
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
# =============================================================================
|
|
571
|
+
# Command: start / finish
|
|
572
|
+
# =============================================================================
|
|
573
|
+
|
|
574
|
+
def cmd_start(args: argparse.Namespace) -> int:
|
|
575
|
+
"""Set current task."""
|
|
576
|
+
repo_root = get_repo_root()
|
|
577
|
+
task_input = args.dir
|
|
578
|
+
|
|
579
|
+
if not task_input:
|
|
580
|
+
print(colored("Error: task directory or name required", Colors.RED))
|
|
581
|
+
return 1
|
|
582
|
+
|
|
583
|
+
# Resolve task directory (supports task name, relative path, or absolute path)
|
|
584
|
+
full_path = _resolve_task_dir(task_input, repo_root)
|
|
585
|
+
|
|
586
|
+
if not full_path.is_dir():
|
|
587
|
+
print(colored(f"Error: Task not found: {task_input}", Colors.RED))
|
|
588
|
+
print("Hint: Use task name (e.g., 'my-task') or full path (e.g., '.trellis/tasks/01-31-my-task')")
|
|
589
|
+
return 1
|
|
590
|
+
|
|
591
|
+
# Convert to relative path for storage
|
|
592
|
+
try:
|
|
593
|
+
task_dir = str(full_path.relative_to(repo_root))
|
|
594
|
+
except ValueError:
|
|
595
|
+
task_dir = str(full_path)
|
|
596
|
+
|
|
597
|
+
if set_current_task(task_dir, repo_root):
|
|
598
|
+
print(colored(f"✓ Current task set to: {task_dir}", Colors.GREEN))
|
|
599
|
+
print()
|
|
600
|
+
print(colored("The hook will now inject context from this task's jsonl files.", Colors.BLUE))
|
|
601
|
+
return 0
|
|
602
|
+
else:
|
|
603
|
+
print(colored("Error: Failed to set current task", Colors.RED))
|
|
604
|
+
return 1
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
def cmd_finish(args: argparse.Namespace) -> int:
|
|
608
|
+
"""Clear current task."""
|
|
609
|
+
repo_root = get_repo_root()
|
|
610
|
+
current = get_current_task(repo_root)
|
|
611
|
+
|
|
612
|
+
if not current:
|
|
613
|
+
print(colored("No current task set", Colors.YELLOW))
|
|
614
|
+
return 0
|
|
615
|
+
|
|
616
|
+
clear_current_task(repo_root)
|
|
617
|
+
print(colored(f"✓ Cleared current task (was: {current})", Colors.GREEN))
|
|
618
|
+
return 0
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
# =============================================================================
|
|
622
|
+
# Command: archive
|
|
623
|
+
# =============================================================================
|
|
624
|
+
|
|
625
|
+
def cmd_archive(args: argparse.Namespace) -> int:
|
|
626
|
+
"""Archive completed task."""
|
|
627
|
+
repo_root = get_repo_root()
|
|
628
|
+
task_name = args.name
|
|
629
|
+
|
|
630
|
+
if not task_name:
|
|
631
|
+
print(colored("Error: Task name is required", Colors.RED), file=sys.stderr)
|
|
632
|
+
return 1
|
|
633
|
+
|
|
634
|
+
tasks_dir = get_tasks_dir(repo_root)
|
|
635
|
+
|
|
636
|
+
# Find task directory
|
|
637
|
+
task_dir = find_task_by_name(task_name, tasks_dir)
|
|
638
|
+
|
|
639
|
+
if not task_dir or not task_dir.is_dir():
|
|
640
|
+
print(colored(f"Error: Task not found: {task_name}", Colors.RED), file=sys.stderr)
|
|
641
|
+
print("Active tasks:", file=sys.stderr)
|
|
642
|
+
cmd_list(argparse.Namespace(mine=False, status=None))
|
|
643
|
+
return 1
|
|
644
|
+
|
|
645
|
+
dir_name = task_dir.name
|
|
646
|
+
task_json_path = task_dir / FILE_TASK_JSON
|
|
647
|
+
|
|
648
|
+
# Update status before archiving
|
|
649
|
+
today = datetime.now().strftime("%Y-%m-%d")
|
|
650
|
+
if task_json_path.is_file():
|
|
651
|
+
data = _read_json_file(task_json_path)
|
|
652
|
+
if data:
|
|
653
|
+
data["status"] = "completed"
|
|
654
|
+
data["completedAt"] = today
|
|
655
|
+
_write_json_file(task_json_path, data)
|
|
656
|
+
|
|
657
|
+
# Clear if current task
|
|
658
|
+
current = get_current_task(repo_root)
|
|
659
|
+
if current and dir_name in current:
|
|
660
|
+
clear_current_task(repo_root)
|
|
661
|
+
|
|
662
|
+
# Archive
|
|
663
|
+
result = archive_task_complete(task_dir, repo_root)
|
|
664
|
+
if "archived_to" in result:
|
|
665
|
+
archive_dest = Path(result["archived_to"])
|
|
666
|
+
year_month = archive_dest.parent.name
|
|
667
|
+
print(colored(f"Archived: {dir_name} -> archive/{year_month}/", Colors.GREEN), file=sys.stderr)
|
|
668
|
+
|
|
669
|
+
# Return the archive path
|
|
670
|
+
print(f"{DIR_WORKFLOW}/{DIR_TASKS}/{DIR_ARCHIVE}/{year_month}/{dir_name}")
|
|
671
|
+
return 0
|
|
672
|
+
|
|
673
|
+
return 1
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
# =============================================================================
|
|
677
|
+
# Command: list
|
|
678
|
+
# =============================================================================
|
|
679
|
+
|
|
680
|
+
def cmd_list(args: argparse.Namespace) -> int:
|
|
681
|
+
"""List active tasks."""
|
|
682
|
+
repo_root = get_repo_root()
|
|
683
|
+
tasks_dir = get_tasks_dir(repo_root)
|
|
684
|
+
current_task = get_current_task(repo_root)
|
|
685
|
+
developer = get_developer(repo_root)
|
|
686
|
+
filter_mine = args.mine
|
|
687
|
+
filter_status = args.status
|
|
688
|
+
|
|
689
|
+
if filter_mine:
|
|
690
|
+
if not developer:
|
|
691
|
+
print(colored("Error: No developer set. Run init_developer.py first", Colors.RED), file=sys.stderr)
|
|
692
|
+
return 1
|
|
693
|
+
print(colored(f"My tasks (assignee: {developer}):", Colors.BLUE))
|
|
694
|
+
else:
|
|
695
|
+
print(colored("All active tasks:", Colors.BLUE))
|
|
696
|
+
print()
|
|
697
|
+
|
|
698
|
+
count = 0
|
|
699
|
+
if tasks_dir.is_dir():
|
|
700
|
+
for d in sorted(tasks_dir.iterdir()):
|
|
701
|
+
if not d.is_dir() or d.name == "archive":
|
|
702
|
+
continue
|
|
703
|
+
|
|
704
|
+
dir_name = d.name
|
|
705
|
+
task_json = d / FILE_TASK_JSON
|
|
706
|
+
status = "unknown"
|
|
707
|
+
assignee = "-"
|
|
708
|
+
relative_path = f"{DIR_WORKFLOW}/{DIR_TASKS}/{dir_name}"
|
|
709
|
+
|
|
710
|
+
if task_json.is_file():
|
|
711
|
+
data = _read_json_file(task_json)
|
|
712
|
+
if data:
|
|
713
|
+
status = data.get("status", "unknown")
|
|
714
|
+
assignee = data.get("assignee", "-")
|
|
715
|
+
|
|
716
|
+
# Apply --mine filter
|
|
717
|
+
if filter_mine and assignee != developer:
|
|
718
|
+
continue
|
|
719
|
+
|
|
720
|
+
# Apply --status filter
|
|
721
|
+
if filter_status and status != filter_status:
|
|
722
|
+
continue
|
|
723
|
+
|
|
724
|
+
marker = ""
|
|
725
|
+
if relative_path == current_task:
|
|
726
|
+
marker = f" {colored('<- current', Colors.GREEN)}"
|
|
727
|
+
|
|
728
|
+
if filter_mine:
|
|
729
|
+
print(f" - {dir_name}/ ({status}){marker}")
|
|
730
|
+
else:
|
|
731
|
+
print(f" - {dir_name}/ ({status}) [{colored(assignee, Colors.CYAN)}]{marker}")
|
|
732
|
+
count += 1
|
|
733
|
+
|
|
734
|
+
if count == 0:
|
|
735
|
+
if filter_mine:
|
|
736
|
+
print(" (no tasks assigned to you)")
|
|
737
|
+
else:
|
|
738
|
+
print(" (no active tasks)")
|
|
739
|
+
|
|
740
|
+
print()
|
|
741
|
+
print(f"Total: {count} task(s)")
|
|
742
|
+
return 0
|
|
743
|
+
|
|
744
|
+
|
|
745
|
+
# =============================================================================
|
|
746
|
+
# Command: list-archive
|
|
747
|
+
# =============================================================================
|
|
748
|
+
|
|
749
|
+
def cmd_list_archive(args: argparse.Namespace) -> int:
|
|
750
|
+
"""List archived tasks."""
|
|
751
|
+
repo_root = get_repo_root()
|
|
752
|
+
tasks_dir = get_tasks_dir(repo_root)
|
|
753
|
+
archive_dir = tasks_dir / "archive"
|
|
754
|
+
month = args.month
|
|
755
|
+
|
|
756
|
+
print(colored("Archived tasks:", Colors.BLUE))
|
|
757
|
+
print()
|
|
758
|
+
|
|
759
|
+
if month:
|
|
760
|
+
month_dir = archive_dir / month
|
|
761
|
+
if month_dir.is_dir():
|
|
762
|
+
print(f"[{month}]")
|
|
763
|
+
for d in sorted(month_dir.iterdir()):
|
|
764
|
+
if d.is_dir():
|
|
765
|
+
print(f" - {d.name}/")
|
|
766
|
+
else:
|
|
767
|
+
print(f" No archives for {month}")
|
|
768
|
+
else:
|
|
769
|
+
if archive_dir.is_dir():
|
|
770
|
+
for month_dir in sorted(archive_dir.iterdir()):
|
|
771
|
+
if month_dir.is_dir():
|
|
772
|
+
month_name = month_dir.name
|
|
773
|
+
count = sum(1 for d in month_dir.iterdir() if d.is_dir())
|
|
774
|
+
print(f"[{month_name}] - {count} task(s)")
|
|
775
|
+
|
|
776
|
+
return 0
|
|
777
|
+
|
|
778
|
+
|
|
779
|
+
# =============================================================================
|
|
780
|
+
# Command: set-branch
|
|
781
|
+
# =============================================================================
|
|
782
|
+
|
|
783
|
+
def cmd_set_branch(args: argparse.Namespace) -> int:
|
|
784
|
+
"""Set git branch for task."""
|
|
785
|
+
repo_root = get_repo_root()
|
|
786
|
+
target_dir = _resolve_task_dir(args.dir, repo_root)
|
|
787
|
+
branch = args.branch
|
|
788
|
+
|
|
789
|
+
if not branch:
|
|
790
|
+
print(colored("Error: Missing arguments", Colors.RED))
|
|
791
|
+
print("Usage: python3 task.py set-branch <task-dir> <branch-name>")
|
|
792
|
+
return 1
|
|
793
|
+
|
|
794
|
+
task_json = target_dir / FILE_TASK_JSON
|
|
795
|
+
if not task_json.is_file():
|
|
796
|
+
print(colored(f"Error: task.json not found at {target_dir}", Colors.RED))
|
|
797
|
+
return 1
|
|
798
|
+
|
|
799
|
+
data = _read_json_file(task_json)
|
|
800
|
+
if not data:
|
|
801
|
+
return 1
|
|
802
|
+
|
|
803
|
+
data["branch"] = branch
|
|
804
|
+
_write_json_file(task_json, data)
|
|
805
|
+
|
|
806
|
+
print(colored(f"✓ Branch set to: {branch}", Colors.GREEN))
|
|
807
|
+
print()
|
|
808
|
+
print(colored("Now you can start the multi-agent pipeline:", Colors.BLUE))
|
|
809
|
+
print(f" python3 ./.trellis/scripts/multi_agent/start.py {args.dir}")
|
|
810
|
+
return 0
|
|
811
|
+
|
|
812
|
+
|
|
813
|
+
# =============================================================================
|
|
814
|
+
# Command: set-base-branch
|
|
815
|
+
# =============================================================================
|
|
816
|
+
|
|
817
|
+
def cmd_set_base_branch(args: argparse.Namespace) -> int:
|
|
818
|
+
"""Set the base branch (PR target) for task."""
|
|
819
|
+
repo_root = get_repo_root()
|
|
820
|
+
target_dir = _resolve_task_dir(args.dir, repo_root)
|
|
821
|
+
base_branch = args.base_branch
|
|
822
|
+
|
|
823
|
+
if not base_branch:
|
|
824
|
+
print(colored("Error: Missing arguments", Colors.RED))
|
|
825
|
+
print("Usage: python3 task.py set-base-branch <task-dir> <base-branch>")
|
|
826
|
+
print("Example: python3 task.py set-base-branch <dir> develop")
|
|
827
|
+
print()
|
|
828
|
+
print("This sets the target branch for PR (the branch your feature will merge into).")
|
|
829
|
+
return 1
|
|
830
|
+
|
|
831
|
+
task_json = target_dir / FILE_TASK_JSON
|
|
832
|
+
if not task_json.is_file():
|
|
833
|
+
print(colored(f"Error: task.json not found at {target_dir}", Colors.RED))
|
|
834
|
+
return 1
|
|
835
|
+
|
|
836
|
+
data = _read_json_file(task_json)
|
|
837
|
+
if not data:
|
|
838
|
+
return 1
|
|
839
|
+
|
|
840
|
+
data["base_branch"] = base_branch
|
|
841
|
+
_write_json_file(task_json, data)
|
|
842
|
+
|
|
843
|
+
print(colored(f"✓ Base branch set to: {base_branch}", Colors.GREEN))
|
|
844
|
+
print(f" PR will target: {base_branch}")
|
|
845
|
+
return 0
|
|
846
|
+
|
|
847
|
+
|
|
848
|
+
# =============================================================================
|
|
849
|
+
# Command: set-scope
|
|
850
|
+
# =============================================================================
|
|
851
|
+
|
|
852
|
+
def cmd_set_scope(args: argparse.Namespace) -> int:
|
|
853
|
+
"""Set scope for PR title."""
|
|
854
|
+
repo_root = get_repo_root()
|
|
855
|
+
target_dir = _resolve_task_dir(args.dir, repo_root)
|
|
856
|
+
scope = args.scope
|
|
857
|
+
|
|
858
|
+
if not scope:
|
|
859
|
+
print(colored("Error: Missing arguments", Colors.RED))
|
|
860
|
+
print("Usage: python3 task.py set-scope <task-dir> <scope>")
|
|
861
|
+
return 1
|
|
862
|
+
|
|
863
|
+
task_json = target_dir / FILE_TASK_JSON
|
|
864
|
+
if not task_json.is_file():
|
|
865
|
+
print(colored(f"Error: task.json not found at {target_dir}", Colors.RED))
|
|
866
|
+
return 1
|
|
867
|
+
|
|
868
|
+
data = _read_json_file(task_json)
|
|
869
|
+
if not data:
|
|
870
|
+
return 1
|
|
871
|
+
|
|
872
|
+
data["scope"] = scope
|
|
873
|
+
_write_json_file(task_json, data)
|
|
874
|
+
|
|
875
|
+
print(colored(f"✓ Scope set to: {scope}", Colors.GREEN))
|
|
876
|
+
return 0
|
|
877
|
+
|
|
878
|
+
|
|
879
|
+
# =============================================================================
|
|
880
|
+
# Command: create-pr (delegates to multi-agent script)
|
|
881
|
+
# =============================================================================
|
|
882
|
+
|
|
883
|
+
def cmd_create_pr(args: argparse.Namespace) -> int:
|
|
884
|
+
"""Create PR from task - delegates to multi_agent/create_pr.py."""
|
|
885
|
+
import subprocess
|
|
886
|
+
script_dir = Path(__file__).parent
|
|
887
|
+
create_pr_script = script_dir / "multi_agent" / "create_pr.py"
|
|
888
|
+
|
|
889
|
+
cmd = [sys.executable, str(create_pr_script)]
|
|
890
|
+
if args.dir:
|
|
891
|
+
cmd.append(args.dir)
|
|
892
|
+
if args.dry_run:
|
|
893
|
+
cmd.append("--dry-run")
|
|
894
|
+
|
|
895
|
+
result = subprocess.run(cmd)
|
|
896
|
+
return result.returncode
|
|
897
|
+
|
|
898
|
+
|
|
899
|
+
# =============================================================================
|
|
900
|
+
# Help
|
|
901
|
+
# =============================================================================
|
|
902
|
+
|
|
903
|
+
def show_usage() -> None:
|
|
904
|
+
"""Show usage help."""
|
|
905
|
+
print("""Task Management Script for Multi-Agent Pipeline
|
|
906
|
+
|
|
907
|
+
Usage:
|
|
908
|
+
python3 task.py create <title> Create new task directory
|
|
909
|
+
python3 task.py init-context <dir> <dev_type> Initialize jsonl files
|
|
910
|
+
python3 task.py add-context <dir> <jsonl> <path> [reason] Add entry to jsonl
|
|
911
|
+
python3 task.py validate <dir> Validate jsonl files
|
|
912
|
+
python3 task.py list-context <dir> List jsonl entries
|
|
913
|
+
python3 task.py start <dir> Set as current task
|
|
914
|
+
python3 task.py finish Clear current task
|
|
915
|
+
python3 task.py set-branch <dir> <branch> Set git branch for multi-agent
|
|
916
|
+
python3 task.py set-scope <dir> <scope> Set scope for PR title
|
|
917
|
+
python3 task.py create-pr [dir] [--dry-run] Create PR from task
|
|
918
|
+
python3 task.py archive <task-name> Archive completed task
|
|
919
|
+
python3 task.py list [--mine] [--status <status>] List tasks
|
|
920
|
+
python3 task.py list-archive [YYYY-MM] List archived tasks
|
|
921
|
+
|
|
922
|
+
Arguments:
|
|
923
|
+
dev_type: backend | frontend | fullstack | test | docs
|
|
924
|
+
|
|
925
|
+
List options:
|
|
926
|
+
--mine, -m Show only tasks assigned to current developer
|
|
927
|
+
--status, -s <s> Filter by status (planning, in_progress, review, completed)
|
|
928
|
+
|
|
929
|
+
Examples:
|
|
930
|
+
python3 task.py create "Add login feature" --slug add-login
|
|
931
|
+
python3 task.py init-context .trellis/tasks/01-21-add-login backend
|
|
932
|
+
python3 task.py add-context <dir> implement .trellis/spec/backend/auth.md "Auth guidelines"
|
|
933
|
+
python3 task.py set-branch <dir> task/add-login
|
|
934
|
+
python3 task.py start .trellis/tasks/01-21-add-login
|
|
935
|
+
python3 task.py create-pr # Uses current task
|
|
936
|
+
python3 task.py create-pr <dir> --dry-run # Preview without changes
|
|
937
|
+
python3 task.py finish
|
|
938
|
+
python3 task.py archive add-login
|
|
939
|
+
python3 task.py list # List all active tasks
|
|
940
|
+
python3 task.py list --mine # List my tasks only
|
|
941
|
+
python3 task.py list --mine --status in_progress # List my in-progress tasks
|
|
942
|
+
""")
|
|
943
|
+
|
|
944
|
+
|
|
945
|
+
# =============================================================================
|
|
946
|
+
# Main Entry
|
|
947
|
+
# =============================================================================
|
|
948
|
+
|
|
949
|
+
def main() -> int:
|
|
950
|
+
"""CLI entry point."""
|
|
951
|
+
parser = argparse.ArgumentParser(
|
|
952
|
+
description="Task Management Script for Multi-Agent Pipeline",
|
|
953
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
954
|
+
)
|
|
955
|
+
subparsers = parser.add_subparsers(dest="command", help="Commands")
|
|
956
|
+
|
|
957
|
+
# create
|
|
958
|
+
p_create = subparsers.add_parser("create", help="Create new task")
|
|
959
|
+
p_create.add_argument("title", help="Task title")
|
|
960
|
+
p_create.add_argument("--slug", "-s", help="Task slug")
|
|
961
|
+
p_create.add_argument("--assignee", "-a", help="Assignee developer")
|
|
962
|
+
p_create.add_argument("--priority", "-p", default="P2", help="Priority (P0-P3)")
|
|
963
|
+
p_create.add_argument("--description", "-d", help="Task description")
|
|
964
|
+
|
|
965
|
+
# init-context
|
|
966
|
+
p_init = subparsers.add_parser("init-context", help="Initialize context files")
|
|
967
|
+
p_init.add_argument("dir", help="Task directory")
|
|
968
|
+
p_init.add_argument("type", help="Dev type: backend|frontend|fullstack|test|docs")
|
|
969
|
+
|
|
970
|
+
# add-context
|
|
971
|
+
p_add = subparsers.add_parser("add-context", help="Add context entry")
|
|
972
|
+
p_add.add_argument("dir", help="Task directory")
|
|
973
|
+
p_add.add_argument("file", help="JSONL file (implement|check|debug)")
|
|
974
|
+
p_add.add_argument("path", help="File path to add")
|
|
975
|
+
p_add.add_argument("reason", nargs="?", help="Reason for adding")
|
|
976
|
+
|
|
977
|
+
# validate
|
|
978
|
+
p_validate = subparsers.add_parser("validate", help="Validate context files")
|
|
979
|
+
p_validate.add_argument("dir", help="Task directory")
|
|
980
|
+
|
|
981
|
+
# list-context
|
|
982
|
+
p_listctx = subparsers.add_parser("list-context", help="List context entries")
|
|
983
|
+
p_listctx.add_argument("dir", help="Task directory")
|
|
984
|
+
|
|
985
|
+
# start
|
|
986
|
+
p_start = subparsers.add_parser("start", help="Set current task")
|
|
987
|
+
p_start.add_argument("dir", help="Task directory")
|
|
988
|
+
|
|
989
|
+
# finish
|
|
990
|
+
subparsers.add_parser("finish", help="Clear current task")
|
|
991
|
+
|
|
992
|
+
# set-branch
|
|
993
|
+
p_branch = subparsers.add_parser("set-branch", help="Set git branch")
|
|
994
|
+
p_branch.add_argument("dir", help="Task directory")
|
|
995
|
+
p_branch.add_argument("branch", help="Branch name")
|
|
996
|
+
|
|
997
|
+
# set-base-branch
|
|
998
|
+
p_base = subparsers.add_parser("set-base-branch", help="Set PR target branch")
|
|
999
|
+
p_base.add_argument("dir", help="Task directory")
|
|
1000
|
+
p_base.add_argument("base_branch", help="Base branch name (PR target)")
|
|
1001
|
+
|
|
1002
|
+
# set-scope
|
|
1003
|
+
p_scope = subparsers.add_parser("set-scope", help="Set scope")
|
|
1004
|
+
p_scope.add_argument("dir", help="Task directory")
|
|
1005
|
+
p_scope.add_argument("scope", help="Scope name")
|
|
1006
|
+
|
|
1007
|
+
# create-pr
|
|
1008
|
+
p_pr = subparsers.add_parser("create-pr", help="Create PR")
|
|
1009
|
+
p_pr.add_argument("dir", nargs="?", help="Task directory")
|
|
1010
|
+
p_pr.add_argument("--dry-run", action="store_true", help="Dry run mode")
|
|
1011
|
+
|
|
1012
|
+
# archive
|
|
1013
|
+
p_archive = subparsers.add_parser("archive", help="Archive task")
|
|
1014
|
+
p_archive.add_argument("name", help="Task name")
|
|
1015
|
+
|
|
1016
|
+
# list
|
|
1017
|
+
p_list = subparsers.add_parser("list", help="List tasks")
|
|
1018
|
+
p_list.add_argument("--mine", "-m", action="store_true", help="My tasks only")
|
|
1019
|
+
p_list.add_argument("--status", "-s", help="Filter by status")
|
|
1020
|
+
|
|
1021
|
+
# list-archive
|
|
1022
|
+
p_listarch = subparsers.add_parser("list-archive", help="List archived tasks")
|
|
1023
|
+
p_listarch.add_argument("month", nargs="?", help="Month (YYYY-MM)")
|
|
1024
|
+
|
|
1025
|
+
args = parser.parse_args()
|
|
1026
|
+
|
|
1027
|
+
if not args.command:
|
|
1028
|
+
show_usage()
|
|
1029
|
+
return 1
|
|
1030
|
+
|
|
1031
|
+
commands = {
|
|
1032
|
+
"create": cmd_create,
|
|
1033
|
+
"init-context": cmd_init_context,
|
|
1034
|
+
"add-context": cmd_add_context,
|
|
1035
|
+
"validate": cmd_validate,
|
|
1036
|
+
"list-context": cmd_list_context,
|
|
1037
|
+
"start": cmd_start,
|
|
1038
|
+
"finish": cmd_finish,
|
|
1039
|
+
"set-branch": cmd_set_branch,
|
|
1040
|
+
"set-base-branch": cmd_set_base_branch,
|
|
1041
|
+
"set-scope": cmd_set_scope,
|
|
1042
|
+
"create-pr": cmd_create_pr,
|
|
1043
|
+
"archive": cmd_archive,
|
|
1044
|
+
"list": cmd_list,
|
|
1045
|
+
"list-archive": cmd_list_archive,
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
if args.command in commands:
|
|
1049
|
+
return commands[args.command](args)
|
|
1050
|
+
else:
|
|
1051
|
+
show_usage()
|
|
1052
|
+
return 1
|
|
1053
|
+
|
|
1054
|
+
|
|
1055
|
+
if __name__ == "__main__":
|
|
1056
|
+
sys.exit(main())
|