@mindfoldhq/trellis 0.3.8 → 0.3.10-beta.0
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/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +203 -31
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +154 -6
- package/dist/commands/update.js.map +1 -1
- package/dist/configurators/workflow.d.ts +6 -2
- package/dist/configurators/workflow.d.ts.map +1 -1
- package/dist/configurators/workflow.js +88 -58
- package/dist/configurators/workflow.js.map +1 -1
- package/dist/migrations/index.d.ts +1 -0
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/migrations/index.js +2 -0
- package/dist/migrations/index.js.map +1 -1
- package/dist/migrations/manifests/0.3.9.json +9 -0
- package/dist/migrations/manifests/0.4.0-beta.1.json +228 -0
- package/dist/templates/claude/agents/dispatch.md +1 -2
- package/dist/templates/claude/agents/implement.md +2 -3
- package/dist/templates/claude/commands/trellis/before-dev.md +29 -0
- package/dist/templates/claude/commands/trellis/check.md +25 -0
- package/dist/templates/claude/commands/trellis/create-command.md +2 -2
- package/dist/templates/claude/commands/trellis/onboard.md +13 -13
- package/dist/templates/claude/commands/trellis/parallel.md +1 -2
- package/dist/templates/claude/commands/trellis/record-session.md +1 -1
- package/dist/templates/claude/commands/trellis/start.md +8 -4
- package/dist/templates/claude/hooks/inject-subagent-context.py +21 -13
- package/dist/templates/claude/hooks/session-start.py +170 -2
- package/dist/templates/codex/skills/before-dev/SKILL.md +34 -0
- package/dist/templates/codex/skills/check/SKILL.md +30 -0
- package/dist/templates/codex/skills/create-command/SKILL.md +2 -2
- package/dist/templates/codex/skills/onboard/SKILL.md +11 -11
- package/dist/templates/codex/skills/record-session/SKILL.md +1 -1
- package/dist/templates/codex/skills/start/SKILL.md +8 -3
- package/dist/templates/cursor/commands/trellis-before-dev.md +29 -0
- package/dist/templates/cursor/commands/trellis-check.md +25 -0
- package/dist/templates/cursor/commands/trellis-create-command.md +2 -2
- package/dist/templates/cursor/commands/trellis-onboard.md +13 -13
- package/dist/templates/cursor/commands/trellis-record-session.md +1 -1
- package/dist/templates/cursor/commands/trellis-start.md +7 -16
- package/dist/templates/gemini/commands/trellis/before-dev.toml +33 -0
- package/dist/templates/gemini/commands/trellis/check.toml +29 -0
- package/dist/templates/gemini/commands/trellis/create-command.toml +2 -2
- package/dist/templates/gemini/commands/trellis/onboard.toml +2 -2
- package/dist/templates/gemini/commands/trellis/record-session.toml +1 -1
- package/dist/templates/gemini/commands/trellis/start.toml +9 -4
- package/dist/templates/iflow/agents/dispatch.md +1 -2
- package/dist/templates/iflow/agents/implement.md +2 -3
- package/dist/templates/iflow/commands/trellis/before-dev.md +29 -0
- package/dist/templates/iflow/commands/trellis/check.md +25 -0
- package/dist/templates/iflow/commands/trellis/create-command.md +2 -2
- package/dist/templates/iflow/commands/trellis/onboard.md +13 -13
- package/dist/templates/iflow/commands/trellis/parallel.md +1 -2
- package/dist/templates/iflow/commands/trellis/record-session.md +1 -1
- package/dist/templates/iflow/commands/trellis/start.md +8 -4
- package/dist/templates/iflow/hooks/inject-subagent-context.py +21 -13
- package/dist/templates/iflow/hooks/session-start.py +156 -1
- package/dist/templates/iflow/settings.json +2 -2
- package/dist/templates/kilo/workflows/before-dev.md +29 -0
- package/dist/templates/kilo/workflows/check.md +25 -0
- package/dist/templates/kilo/workflows/create-command.md +2 -2
- package/dist/templates/kilo/workflows/onboard.md +13 -13
- package/dist/templates/kilo/workflows/parallel.md +1 -2
- package/dist/templates/kilo/workflows/record-session.md +1 -1
- package/dist/templates/kilo/workflows/start.md +8 -3
- package/dist/templates/kiro/skills/before-dev/SKILL.md +34 -0
- package/dist/templates/kiro/skills/check/SKILL.md +30 -0
- package/dist/templates/kiro/skills/create-command/SKILL.md +2 -2
- package/dist/templates/kiro/skills/onboard/SKILL.md +11 -11
- package/dist/templates/kiro/skills/record-session/SKILL.md +1 -1
- package/dist/templates/kiro/skills/start/SKILL.md +8 -3
- package/dist/templates/markdown/spec/backend/script-conventions.md +93 -0
- package/dist/templates/opencode/agents/dispatch.md +1 -2
- package/dist/templates/opencode/agents/implement.md +2 -2
- package/dist/templates/opencode/agents/research.md +1 -2
- package/dist/templates/opencode/commands/trellis/before-dev.md +29 -0
- package/dist/templates/opencode/commands/trellis/check.md +25 -0
- package/dist/templates/opencode/commands/trellis/create-command.md +2 -2
- package/dist/templates/opencode/commands/trellis/onboard.md +13 -13
- package/dist/templates/opencode/commands/trellis/parallel.md +1 -2
- package/dist/templates/opencode/commands/trellis/record-session.md +1 -1
- package/dist/templates/opencode/commands/trellis/start.md +8 -3
- package/dist/templates/opencode/plugin/inject-subagent-context.js +45 -18
- package/dist/templates/opencode/plugin/session-start.js +149 -1
- package/dist/templates/qoder/skills/before-dev/SKILL.md +34 -0
- package/dist/templates/qoder/skills/check/SKILL.md +30 -0
- package/dist/templates/qoder/skills/create-command/SKILL.md +2 -2
- package/dist/templates/qoder/skills/onboard/SKILL.md +13 -13
- package/dist/templates/qoder/skills/record-session/SKILL.md +1 -1
- package/dist/templates/qoder/skills/start/SKILL.md +8 -3
- package/dist/templates/trellis/config.yaml +20 -0
- package/dist/templates/trellis/index.d.ts +11 -0
- package/dist/templates/trellis/index.d.ts.map +1 -1
- package/dist/templates/trellis/index.js +22 -0
- package/dist/templates/trellis/index.js.map +1 -1
- package/dist/templates/trellis/scripts/add_session.py +52 -7
- package/dist/templates/trellis/scripts/common/cli_adapter.py +33 -45
- package/dist/templates/trellis/scripts/common/config.py +152 -0
- package/dist/templates/trellis/scripts/common/git.py +31 -0
- package/dist/templates/trellis/scripts/common/git_context.py +23 -586
- package/dist/templates/trellis/scripts/common/io.py +37 -0
- package/dist/templates/trellis/scripts/common/log.py +45 -0
- package/dist/templates/trellis/scripts/common/packages_context.py +233 -0
- package/dist/templates/trellis/scripts/common/paths.py +46 -0
- package/dist/templates/trellis/scripts/common/phase.py +50 -49
- package/dist/templates/trellis/scripts/common/registry.py +41 -72
- package/dist/templates/trellis/scripts/common/session_context.py +466 -0
- package/dist/templates/trellis/scripts/common/task_context.py +384 -0
- package/dist/templates/trellis/scripts/common/task_queue.py +27 -98
- package/dist/templates/trellis/scripts/common/task_store.py +534 -0
- package/dist/templates/trellis/scripts/common/task_utils.py +96 -6
- package/dist/templates/trellis/scripts/common/tasks.py +109 -0
- package/dist/templates/trellis/scripts/common/types.py +112 -0
- package/dist/templates/trellis/scripts/create_bootstrap.py +31 -26
- package/dist/templates/trellis/scripts/hooks/linear_sync.py +243 -0
- package/dist/templates/trellis/scripts/multi_agent/_bootstrap.py +17 -0
- package/dist/templates/trellis/scripts/multi_agent/cleanup.py +43 -48
- package/dist/templates/trellis/scripts/multi_agent/create_pr.py +336 -45
- package/dist/templates/trellis/scripts/multi_agent/plan.py +2 -26
- package/dist/templates/trellis/scripts/multi_agent/start.py +126 -57
- package/dist/templates/trellis/scripts/multi_agent/status.py +12 -753
- package/dist/templates/trellis/scripts/multi_agent/status_display.py +542 -0
- package/dist/templates/trellis/scripts/multi_agent/status_monitor.py +225 -0
- package/dist/templates/trellis/scripts/task.py +50 -975
- package/dist/templates/trellis/workflow.md +21 -34
- package/dist/types/migration.d.ts +3 -1
- package/dist/types/migration.d.ts.map +1 -1
- package/dist/utils/project-detector.d.ts +23 -0
- package/dist/utils/project-detector.d.ts.map +1 -1
- package/dist/utils/project-detector.js +364 -0
- package/dist/utils/project-detector.js.map +1 -1
- package/dist/utils/template-fetcher.d.ts +2 -2
- package/dist/utils/template-fetcher.d.ts.map +1 -1
- package/dist/utils/template-fetcher.js +5 -5
- package/dist/utils/template-fetcher.js.map +1 -1
- package/package.json +1 -1
- package/dist/templates/claude/commands/trellis/before-backend-dev.md +0 -13
- package/dist/templates/claude/commands/trellis/before-frontend-dev.md +0 -13
- package/dist/templates/claude/commands/trellis/check-backend.md +0 -13
- package/dist/templates/claude/commands/trellis/check-frontend.md +0 -13
- package/dist/templates/codex/skills/before-backend-dev/SKILL.md +0 -18
- package/dist/templates/codex/skills/before-frontend-dev/SKILL.md +0 -18
- package/dist/templates/codex/skills/check-backend/SKILL.md +0 -18
- package/dist/templates/codex/skills/check-frontend/SKILL.md +0 -18
- package/dist/templates/cursor/commands/trellis-before-backend-dev.md +0 -13
- package/dist/templates/cursor/commands/trellis-before-frontend-dev.md +0 -13
- package/dist/templates/cursor/commands/trellis-check-backend.md +0 -13
- package/dist/templates/cursor/commands/trellis-check-frontend.md +0 -13
- package/dist/templates/gemini/commands/trellis/before-backend-dev.toml +0 -17
- package/dist/templates/gemini/commands/trellis/before-frontend-dev.toml +0 -17
- package/dist/templates/gemini/commands/trellis/check-backend.toml +0 -17
- package/dist/templates/gemini/commands/trellis/check-frontend.toml +0 -17
- package/dist/templates/iflow/commands/trellis/before-backend-dev.md +0 -13
- package/dist/templates/iflow/commands/trellis/before-frontend-dev.md +0 -13
- package/dist/templates/iflow/commands/trellis/check-backend.md +0 -13
- package/dist/templates/iflow/commands/trellis/check-frontend.md +0 -13
- package/dist/templates/kilo/workflows/before-backend-dev.md +0 -13
- package/dist/templates/kilo/workflows/before-frontend-dev.md +0 -13
- package/dist/templates/kilo/workflows/check-backend.md +0 -13
- package/dist/templates/kilo/workflows/check-frontend.md +0 -13
- package/dist/templates/kiro/skills/before-backend-dev/SKILL.md +0 -18
- package/dist/templates/kiro/skills/before-frontend-dev/SKILL.md +0 -18
- package/dist/templates/kiro/skills/check-backend/SKILL.md +0 -18
- package/dist/templates/kiro/skills/check-frontend/SKILL.md +0 -18
- package/dist/templates/opencode/commands/trellis/before-backend-dev.md +0 -13
- package/dist/templates/opencode/commands/trellis/before-frontend-dev.md +0 -13
- package/dist/templates/opencode/commands/trellis/check-backend.md +0 -13
- package/dist/templates/opencode/commands/trellis/check-frontend.md +0 -13
- package/dist/templates/qoder/skills/before-backend-dev/SKILL.md +0 -18
- package/dist/templates/qoder/skills/before-frontend-dev/SKILL.md +0 -18
- package/dist/templates/qoder/skills/check-backend/SKILL.md +0 -18
- package/dist/templates/qoder/skills/check-frontend/SKILL.md +0 -18
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
Task Management Script for Multi-Agent Pipeline.
|
|
5
5
|
|
|
6
6
|
Usage:
|
|
7
|
-
python3 task.py create "<title>" [--slug <name>] [--assignee <dev>] [--priority P0|P1|P2|P3] [--parent <dir>]
|
|
8
|
-
python3 task.py init-context <dir> <type>
|
|
7
|
+
python3 task.py create "<title>" [--slug <name>] [--assignee <dev>] [--priority P0|P1|P2|P3] [--parent <dir>] [--package <pkg>]
|
|
8
|
+
python3 task.py init-context <dir> <type> [--package <pkg>] # Initialize jsonl files
|
|
9
9
|
python3 task.py add-context <dir> <file> <path> [reason] # Add jsonl entry
|
|
10
10
|
python3 task.py validate <dir> # Validate jsonl files
|
|
11
11
|
python3 task.py list-context <dir> # List jsonl entries
|
|
@@ -24,31 +24,14 @@ Usage:
|
|
|
24
24
|
|
|
25
25
|
from __future__ import annotations
|
|
26
26
|
|
|
27
|
-
import sys
|
|
28
|
-
|
|
29
|
-
# IMPORTANT: Force stdout to use UTF-8 on Windows
|
|
30
|
-
# This fixes UnicodeEncodeError when outputting non-ASCII characters
|
|
31
|
-
if sys.platform == "win32":
|
|
32
|
-
import io as _io
|
|
33
|
-
if hasattr(sys.stdout, "reconfigure"):
|
|
34
|
-
sys.stdout.reconfigure(encoding="utf-8", errors="replace") # type: ignore[union-attr]
|
|
35
|
-
elif hasattr(sys.stdout, "detach"):
|
|
36
|
-
sys.stdout = _io.TextIOWrapper(sys.stdout.detach(), encoding="utf-8", errors="replace") # type: ignore[union-attr]
|
|
37
|
-
|
|
38
27
|
import argparse
|
|
39
|
-
import json
|
|
40
|
-
import re
|
|
41
28
|
import sys
|
|
42
|
-
from datetime import datetime
|
|
43
29
|
from pathlib import Path
|
|
44
30
|
|
|
45
|
-
from common.
|
|
46
|
-
from common.git_context import _run_git_command
|
|
31
|
+
from common.log import Colors, colored
|
|
47
32
|
from common.paths import (
|
|
48
33
|
DIR_WORKFLOW,
|
|
49
34
|
DIR_TASKS,
|
|
50
|
-
DIR_SPEC,
|
|
51
|
-
DIR_ARCHIVE,
|
|
52
35
|
FILE_TASK_JSON,
|
|
53
36
|
get_repo_root,
|
|
54
37
|
get_developer,
|
|
@@ -56,585 +39,26 @@ from common.paths import (
|
|
|
56
39
|
get_current_task,
|
|
57
40
|
set_current_task,
|
|
58
41
|
clear_current_task,
|
|
59
|
-
generate_task_date_prefix,
|
|
60
42
|
)
|
|
61
|
-
from common.task_utils import
|
|
62
|
-
|
|
63
|
-
|
|
43
|
+
from common.task_utils import resolve_task_dir, run_task_hooks
|
|
44
|
+
from common.tasks import iter_active_tasks, children_progress
|
|
45
|
+
|
|
46
|
+
# Import command handlers from split modules (also re-exports for plan.py compatibility)
|
|
47
|
+
from common.task_store import (
|
|
48
|
+
cmd_create,
|
|
49
|
+
cmd_archive,
|
|
50
|
+
cmd_set_branch,
|
|
51
|
+
cmd_set_base_branch,
|
|
52
|
+
cmd_set_scope,
|
|
53
|
+
cmd_add_subtask,
|
|
54
|
+
cmd_remove_subtask,
|
|
55
|
+
)
|
|
56
|
+
from common.task_context import (
|
|
57
|
+
cmd_init_context,
|
|
58
|
+
cmd_add_context,
|
|
59
|
+
cmd_validate,
|
|
60
|
+
cmd_list_context,
|
|
64
61
|
)
|
|
65
|
-
from common.config import get_hooks
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
# =============================================================================
|
|
69
|
-
# Colors
|
|
70
|
-
# =============================================================================
|
|
71
|
-
|
|
72
|
-
class Colors:
|
|
73
|
-
RED = "\033[0;31m"
|
|
74
|
-
GREEN = "\033[0;32m"
|
|
75
|
-
YELLOW = "\033[1;33m"
|
|
76
|
-
BLUE = "\033[0;34m"
|
|
77
|
-
CYAN = "\033[0;36m"
|
|
78
|
-
NC = "\033[0m"
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def colored(text: str, color: str) -> str:
|
|
82
|
-
"""Apply color to text."""
|
|
83
|
-
return f"{color}{text}{Colors.NC}"
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
# =============================================================================
|
|
87
|
-
# Lifecycle Hooks
|
|
88
|
-
# =============================================================================
|
|
89
|
-
|
|
90
|
-
def _run_hooks(event: str, task_json_path: Path, repo_root: Path) -> None:
|
|
91
|
-
"""Run lifecycle hooks for an event.
|
|
92
|
-
|
|
93
|
-
Args:
|
|
94
|
-
event: Event name (e.g. "after_create").
|
|
95
|
-
task_json_path: Absolute path to the task's task.json.
|
|
96
|
-
repo_root: Repository root for cwd and config lookup.
|
|
97
|
-
"""
|
|
98
|
-
import os
|
|
99
|
-
import subprocess
|
|
100
|
-
|
|
101
|
-
commands = get_hooks(event, repo_root)
|
|
102
|
-
if not commands:
|
|
103
|
-
return
|
|
104
|
-
|
|
105
|
-
env = {**os.environ, "TASK_JSON_PATH": str(task_json_path)}
|
|
106
|
-
|
|
107
|
-
for cmd in commands:
|
|
108
|
-
try:
|
|
109
|
-
result = subprocess.run(
|
|
110
|
-
cmd,
|
|
111
|
-
shell=True,
|
|
112
|
-
cwd=repo_root,
|
|
113
|
-
env=env,
|
|
114
|
-
capture_output=True,
|
|
115
|
-
text=True,
|
|
116
|
-
encoding="utf-8",
|
|
117
|
-
errors="replace",
|
|
118
|
-
)
|
|
119
|
-
if result.returncode != 0:
|
|
120
|
-
print(
|
|
121
|
-
colored(f"[WARN] Hook failed ({event}): {cmd}", Colors.YELLOW),
|
|
122
|
-
file=sys.stderr,
|
|
123
|
-
)
|
|
124
|
-
if result.stderr.strip():
|
|
125
|
-
print(f" {result.stderr.strip()}", file=sys.stderr)
|
|
126
|
-
except Exception as e:
|
|
127
|
-
print(
|
|
128
|
-
colored(f"[WARN] Hook error ({event}): {cmd} — {e}", Colors.YELLOW),
|
|
129
|
-
file=sys.stderr,
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
# =============================================================================
|
|
134
|
-
# Helper Functions
|
|
135
|
-
# =============================================================================
|
|
136
|
-
|
|
137
|
-
def _read_json_file(path: Path) -> dict | None:
|
|
138
|
-
"""Read and parse a JSON file."""
|
|
139
|
-
try:
|
|
140
|
-
return json.loads(path.read_text(encoding="utf-8"))
|
|
141
|
-
except (FileNotFoundError, json.JSONDecodeError, OSError):
|
|
142
|
-
return None
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
def _write_json_file(path: Path, data: dict) -> bool:
|
|
146
|
-
"""Write dict to JSON file."""
|
|
147
|
-
try:
|
|
148
|
-
path.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8")
|
|
149
|
-
return True
|
|
150
|
-
except (OSError, IOError):
|
|
151
|
-
return False
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
def _slugify(title: str) -> str:
|
|
155
|
-
"""Convert title to slug (only works with ASCII)."""
|
|
156
|
-
result = title.lower()
|
|
157
|
-
result = re.sub(r"[^a-z0-9]", "-", result)
|
|
158
|
-
result = re.sub(r"-+", "-", result)
|
|
159
|
-
result = result.strip("-")
|
|
160
|
-
return result
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
def _resolve_task_dir(target_dir: str, repo_root: Path) -> Path:
|
|
164
|
-
"""Resolve task directory to absolute path.
|
|
165
|
-
|
|
166
|
-
Supports:
|
|
167
|
-
- Absolute path: /path/to/task
|
|
168
|
-
- Relative path: .trellis/tasks/01-31-my-task
|
|
169
|
-
- Task name: my-task (uses find_task_by_name for lookup)
|
|
170
|
-
"""
|
|
171
|
-
if not target_dir:
|
|
172
|
-
return Path()
|
|
173
|
-
|
|
174
|
-
# Absolute path
|
|
175
|
-
if target_dir.startswith("/"):
|
|
176
|
-
return Path(target_dir)
|
|
177
|
-
|
|
178
|
-
# Relative path (contains path separator or starts with .trellis)
|
|
179
|
-
if "/" in target_dir or target_dir.startswith(".trellis"):
|
|
180
|
-
return repo_root / target_dir
|
|
181
|
-
|
|
182
|
-
# Task name - try to find in tasks directory
|
|
183
|
-
tasks_dir = get_tasks_dir(repo_root)
|
|
184
|
-
found = find_task_by_name(target_dir, tasks_dir)
|
|
185
|
-
if found:
|
|
186
|
-
return found
|
|
187
|
-
|
|
188
|
-
# Fallback to treating as relative path
|
|
189
|
-
return repo_root / target_dir
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
# =============================================================================
|
|
193
|
-
# JSONL Default Content Generators
|
|
194
|
-
# =============================================================================
|
|
195
|
-
|
|
196
|
-
def get_implement_base() -> list[dict]:
|
|
197
|
-
"""Get base implement context entries."""
|
|
198
|
-
return [
|
|
199
|
-
{"file": f"{DIR_WORKFLOW}/workflow.md", "reason": "Project workflow and conventions"},
|
|
200
|
-
]
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
def get_implement_backend() -> list[dict]:
|
|
204
|
-
"""Get backend implement context entries."""
|
|
205
|
-
return [
|
|
206
|
-
{"file": f"{DIR_WORKFLOW}/{DIR_SPEC}/backend/index.md", "reason": "Backend development guide"},
|
|
207
|
-
]
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
def get_implement_frontend() -> list[dict]:
|
|
211
|
-
"""Get frontend implement context entries."""
|
|
212
|
-
return [
|
|
213
|
-
{"file": f"{DIR_WORKFLOW}/{DIR_SPEC}/frontend/index.md", "reason": "Frontend development guide"},
|
|
214
|
-
]
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
def get_check_context(dev_type: str, repo_root: Path) -> list[dict]:
|
|
218
|
-
"""Get check context entries."""
|
|
219
|
-
adapter = get_cli_adapter_auto(repo_root)
|
|
220
|
-
|
|
221
|
-
entries = [
|
|
222
|
-
{"file": adapter.get_trellis_command_path("finish-work"), "reason": "Finish work checklist"},
|
|
223
|
-
]
|
|
224
|
-
|
|
225
|
-
if dev_type in ("backend", "fullstack"):
|
|
226
|
-
entries.append({"file": adapter.get_trellis_command_path("check-backend"), "reason": "Backend check spec"})
|
|
227
|
-
if dev_type in ("frontend", "fullstack"):
|
|
228
|
-
entries.append({"file": adapter.get_trellis_command_path("check-frontend"), "reason": "Frontend check spec"})
|
|
229
|
-
|
|
230
|
-
return entries
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
def get_debug_context(dev_type: str, repo_root: Path) -> list[dict]:
|
|
234
|
-
"""Get debug context entries."""
|
|
235
|
-
adapter = get_cli_adapter_auto(repo_root)
|
|
236
|
-
|
|
237
|
-
entries: list[dict] = []
|
|
238
|
-
|
|
239
|
-
if dev_type in ("backend", "fullstack"):
|
|
240
|
-
entries.append({"file": adapter.get_trellis_command_path("check-backend"), "reason": "Backend check spec"})
|
|
241
|
-
if dev_type in ("frontend", "fullstack"):
|
|
242
|
-
entries.append({"file": adapter.get_trellis_command_path("check-frontend"), "reason": "Frontend check spec"})
|
|
243
|
-
|
|
244
|
-
return entries
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
def _write_jsonl(path: Path, entries: list[dict]) -> None:
|
|
248
|
-
"""Write entries to JSONL file."""
|
|
249
|
-
lines = [json.dumps(entry, ensure_ascii=False) for entry in entries]
|
|
250
|
-
path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
# =============================================================================
|
|
254
|
-
# Task Operations
|
|
255
|
-
# =============================================================================
|
|
256
|
-
|
|
257
|
-
def ensure_tasks_dir(repo_root: Path) -> Path:
|
|
258
|
-
"""Ensure tasks directory exists."""
|
|
259
|
-
tasks_dir = get_tasks_dir(repo_root)
|
|
260
|
-
archive_dir = tasks_dir / "archive"
|
|
261
|
-
|
|
262
|
-
if not tasks_dir.exists():
|
|
263
|
-
tasks_dir.mkdir(parents=True)
|
|
264
|
-
print(colored(f"Created tasks directory: {tasks_dir}", Colors.GREEN), file=sys.stderr)
|
|
265
|
-
|
|
266
|
-
if not archive_dir.exists():
|
|
267
|
-
archive_dir.mkdir(parents=True)
|
|
268
|
-
|
|
269
|
-
return tasks_dir
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
# =============================================================================
|
|
273
|
-
# Command: create
|
|
274
|
-
# =============================================================================
|
|
275
|
-
|
|
276
|
-
def cmd_create(args: argparse.Namespace) -> int:
|
|
277
|
-
"""Create a new task."""
|
|
278
|
-
repo_root = get_repo_root()
|
|
279
|
-
|
|
280
|
-
if not args.title:
|
|
281
|
-
print(colored("Error: title is required", Colors.RED), file=sys.stderr)
|
|
282
|
-
return 1
|
|
283
|
-
|
|
284
|
-
# Default assignee to current developer
|
|
285
|
-
assignee = args.assignee
|
|
286
|
-
if not assignee:
|
|
287
|
-
assignee = get_developer(repo_root)
|
|
288
|
-
if not assignee:
|
|
289
|
-
print(colored("Error: No developer set. Run init_developer.py first or use --assignee", Colors.RED), file=sys.stderr)
|
|
290
|
-
return 1
|
|
291
|
-
|
|
292
|
-
ensure_tasks_dir(repo_root)
|
|
293
|
-
|
|
294
|
-
# Get current developer as creator
|
|
295
|
-
creator = get_developer(repo_root) or assignee
|
|
296
|
-
|
|
297
|
-
# Generate slug if not provided
|
|
298
|
-
slug = args.slug or _slugify(args.title)
|
|
299
|
-
if not slug:
|
|
300
|
-
print(colored("Error: could not generate slug from title", Colors.RED), file=sys.stderr)
|
|
301
|
-
return 1
|
|
302
|
-
|
|
303
|
-
# Create task directory with MM-DD-slug format
|
|
304
|
-
tasks_dir = get_tasks_dir(repo_root)
|
|
305
|
-
date_prefix = generate_task_date_prefix()
|
|
306
|
-
dir_name = f"{date_prefix}-{slug}"
|
|
307
|
-
task_dir = tasks_dir / dir_name
|
|
308
|
-
task_json_path = task_dir / FILE_TASK_JSON
|
|
309
|
-
|
|
310
|
-
if task_dir.exists():
|
|
311
|
-
print(colored(f"Warning: Task directory already exists: {dir_name}", Colors.YELLOW), file=sys.stderr)
|
|
312
|
-
else:
|
|
313
|
-
task_dir.mkdir(parents=True)
|
|
314
|
-
|
|
315
|
-
today = datetime.now().strftime("%Y-%m-%d")
|
|
316
|
-
|
|
317
|
-
# Record current branch as base_branch (PR target)
|
|
318
|
-
_, branch_out, _ = _run_git_command(["branch", "--show-current"], cwd=repo_root)
|
|
319
|
-
current_branch = branch_out.strip() or "main"
|
|
320
|
-
|
|
321
|
-
task_data = {
|
|
322
|
-
"id": slug,
|
|
323
|
-
"name": slug,
|
|
324
|
-
"title": args.title,
|
|
325
|
-
"description": args.description or "",
|
|
326
|
-
"status": "planning",
|
|
327
|
-
"dev_type": None,
|
|
328
|
-
"scope": None,
|
|
329
|
-
"priority": args.priority,
|
|
330
|
-
"creator": creator,
|
|
331
|
-
"assignee": assignee,
|
|
332
|
-
"createdAt": today,
|
|
333
|
-
"completedAt": None,
|
|
334
|
-
"branch": None,
|
|
335
|
-
"base_branch": current_branch,
|
|
336
|
-
"worktree_path": None,
|
|
337
|
-
"current_phase": 0,
|
|
338
|
-
"next_action": [
|
|
339
|
-
{"phase": 1, "action": "implement"},
|
|
340
|
-
{"phase": 2, "action": "check"},
|
|
341
|
-
{"phase": 3, "action": "finish"},
|
|
342
|
-
{"phase": 4, "action": "create-pr"},
|
|
343
|
-
],
|
|
344
|
-
"commit": None,
|
|
345
|
-
"pr_url": None,
|
|
346
|
-
"subtasks": [],
|
|
347
|
-
"children": [],
|
|
348
|
-
"parent": None,
|
|
349
|
-
"relatedFiles": [],
|
|
350
|
-
"notes": "",
|
|
351
|
-
"meta": {},
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
_write_json_file(task_json_path, task_data)
|
|
355
|
-
|
|
356
|
-
# Handle --parent: establish bidirectional link
|
|
357
|
-
if args.parent:
|
|
358
|
-
parent_dir = _resolve_task_dir(args.parent, repo_root)
|
|
359
|
-
parent_json_path = parent_dir / FILE_TASK_JSON
|
|
360
|
-
if not parent_json_path.is_file():
|
|
361
|
-
print(colored(f"Warning: Parent task.json not found: {args.parent}", Colors.YELLOW), file=sys.stderr)
|
|
362
|
-
else:
|
|
363
|
-
parent_data = _read_json_file(parent_json_path)
|
|
364
|
-
if parent_data:
|
|
365
|
-
# Add child to parent's children list
|
|
366
|
-
parent_children = parent_data.get("children", [])
|
|
367
|
-
if dir_name not in parent_children:
|
|
368
|
-
parent_children.append(dir_name)
|
|
369
|
-
parent_data["children"] = parent_children
|
|
370
|
-
_write_json_file(parent_json_path, parent_data)
|
|
371
|
-
|
|
372
|
-
# Set parent in child's task.json
|
|
373
|
-
task_data["parent"] = parent_dir.name
|
|
374
|
-
_write_json_file(task_json_path, task_data)
|
|
375
|
-
|
|
376
|
-
print(colored(f"Linked as child of: {parent_dir.name}", Colors.GREEN), file=sys.stderr)
|
|
377
|
-
|
|
378
|
-
print(colored(f"Created task: {dir_name}", Colors.GREEN), file=sys.stderr)
|
|
379
|
-
print("", file=sys.stderr)
|
|
380
|
-
print(colored("Next steps:", Colors.BLUE), file=sys.stderr)
|
|
381
|
-
print(" 1. Create prd.md with requirements", file=sys.stderr)
|
|
382
|
-
print(" 2. Run: python3 task.py init-context <dir> <dev_type>", file=sys.stderr)
|
|
383
|
-
print(" 3. Run: python3 task.py start <dir>", file=sys.stderr)
|
|
384
|
-
print("", file=sys.stderr)
|
|
385
|
-
|
|
386
|
-
# Output relative path for script chaining
|
|
387
|
-
print(f"{DIR_WORKFLOW}/{DIR_TASKS}/{dir_name}")
|
|
388
|
-
|
|
389
|
-
_run_hooks("after_create", task_json_path, repo_root)
|
|
390
|
-
return 0
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
# =============================================================================
|
|
394
|
-
# Command: init-context
|
|
395
|
-
# =============================================================================
|
|
396
|
-
|
|
397
|
-
def cmd_init_context(args: argparse.Namespace) -> int:
|
|
398
|
-
"""Initialize JSONL context files for a task."""
|
|
399
|
-
repo_root = get_repo_root()
|
|
400
|
-
target_dir = _resolve_task_dir(args.dir, repo_root)
|
|
401
|
-
dev_type = args.type
|
|
402
|
-
|
|
403
|
-
if not dev_type:
|
|
404
|
-
print(colored("Error: Missing arguments", Colors.RED))
|
|
405
|
-
print("Usage: python3 task.py init-context <task-dir> <dev_type>")
|
|
406
|
-
print(" dev_type: backend | frontend | fullstack | test | docs")
|
|
407
|
-
return 1
|
|
408
|
-
|
|
409
|
-
if not target_dir.is_dir():
|
|
410
|
-
print(colored(f"Error: Directory not found: {target_dir}", Colors.RED))
|
|
411
|
-
return 1
|
|
412
|
-
|
|
413
|
-
print(colored("=== Initializing Agent Context Files ===", Colors.BLUE))
|
|
414
|
-
print(f"Target dir: {target_dir}")
|
|
415
|
-
print(f"Dev type: {dev_type}")
|
|
416
|
-
print()
|
|
417
|
-
|
|
418
|
-
# implement.jsonl
|
|
419
|
-
print(colored("Creating implement.jsonl...", Colors.CYAN))
|
|
420
|
-
implement_entries = get_implement_base()
|
|
421
|
-
if dev_type in ("backend", "test"):
|
|
422
|
-
implement_entries.extend(get_implement_backend())
|
|
423
|
-
elif dev_type == "frontend":
|
|
424
|
-
implement_entries.extend(get_implement_frontend())
|
|
425
|
-
elif dev_type == "fullstack":
|
|
426
|
-
implement_entries.extend(get_implement_backend())
|
|
427
|
-
implement_entries.extend(get_implement_frontend())
|
|
428
|
-
|
|
429
|
-
implement_file = target_dir / "implement.jsonl"
|
|
430
|
-
_write_jsonl(implement_file, implement_entries)
|
|
431
|
-
print(f" {colored('✓', Colors.GREEN)} {len(implement_entries)} entries")
|
|
432
|
-
|
|
433
|
-
# check.jsonl
|
|
434
|
-
print(colored("Creating check.jsonl...", Colors.CYAN))
|
|
435
|
-
check_entries = get_check_context(dev_type, repo_root)
|
|
436
|
-
check_file = target_dir / "check.jsonl"
|
|
437
|
-
_write_jsonl(check_file, check_entries)
|
|
438
|
-
print(f" {colored('✓', Colors.GREEN)} {len(check_entries)} entries")
|
|
439
|
-
|
|
440
|
-
# debug.jsonl
|
|
441
|
-
print(colored("Creating debug.jsonl...", Colors.CYAN))
|
|
442
|
-
debug_entries = get_debug_context(dev_type, repo_root)
|
|
443
|
-
debug_file = target_dir / "debug.jsonl"
|
|
444
|
-
_write_jsonl(debug_file, debug_entries)
|
|
445
|
-
print(f" {colored('✓', Colors.GREEN)} {len(debug_entries)} entries")
|
|
446
|
-
|
|
447
|
-
print()
|
|
448
|
-
print(colored("✓ All context files created", Colors.GREEN))
|
|
449
|
-
print()
|
|
450
|
-
print(colored("Next steps:", Colors.BLUE))
|
|
451
|
-
print(" 1. Add task-specific specs: python3 task.py add-context <dir> <jsonl> <path>")
|
|
452
|
-
print(" 2. Set as current: python3 task.py start <dir>")
|
|
453
|
-
|
|
454
|
-
return 0
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
# =============================================================================
|
|
458
|
-
# Command: add-context
|
|
459
|
-
# =============================================================================
|
|
460
|
-
|
|
461
|
-
def cmd_add_context(args: argparse.Namespace) -> int:
|
|
462
|
-
"""Add entry to JSONL context file."""
|
|
463
|
-
repo_root = get_repo_root()
|
|
464
|
-
target_dir = _resolve_task_dir(args.dir, repo_root)
|
|
465
|
-
|
|
466
|
-
jsonl_name = args.file
|
|
467
|
-
path = args.path
|
|
468
|
-
reason = args.reason or "Added manually"
|
|
469
|
-
|
|
470
|
-
if not target_dir.is_dir():
|
|
471
|
-
print(colored(f"Error: Directory not found: {target_dir}", Colors.RED))
|
|
472
|
-
return 1
|
|
473
|
-
|
|
474
|
-
# Support shorthand
|
|
475
|
-
if not jsonl_name.endswith(".jsonl"):
|
|
476
|
-
jsonl_name = f"{jsonl_name}.jsonl"
|
|
477
|
-
|
|
478
|
-
jsonl_file = target_dir / jsonl_name
|
|
479
|
-
full_path = repo_root / path
|
|
480
|
-
|
|
481
|
-
entry_type = "file"
|
|
482
|
-
if full_path.is_dir():
|
|
483
|
-
entry_type = "directory"
|
|
484
|
-
if not path.endswith("/"):
|
|
485
|
-
path = f"{path}/"
|
|
486
|
-
elif not full_path.is_file():
|
|
487
|
-
print(colored(f"Error: Path not found: {path}", Colors.RED))
|
|
488
|
-
return 1
|
|
489
|
-
|
|
490
|
-
# Check if already exists
|
|
491
|
-
if jsonl_file.is_file():
|
|
492
|
-
content = jsonl_file.read_text(encoding="utf-8")
|
|
493
|
-
if f'"{path}"' in content:
|
|
494
|
-
print(colored(f"Warning: Entry already exists for {path}", Colors.YELLOW))
|
|
495
|
-
return 0
|
|
496
|
-
|
|
497
|
-
# Add entry
|
|
498
|
-
entry: dict
|
|
499
|
-
if entry_type == "directory":
|
|
500
|
-
entry = {"file": path, "type": "directory", "reason": reason}
|
|
501
|
-
else:
|
|
502
|
-
entry = {"file": path, "reason": reason}
|
|
503
|
-
|
|
504
|
-
with jsonl_file.open("a", encoding="utf-8") as f:
|
|
505
|
-
f.write(json.dumps(entry, ensure_ascii=False) + "\n")
|
|
506
|
-
|
|
507
|
-
print(colored(f"Added {entry_type}: {path}", Colors.GREEN))
|
|
508
|
-
return 0
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
# =============================================================================
|
|
512
|
-
# Command: validate
|
|
513
|
-
# =============================================================================
|
|
514
|
-
|
|
515
|
-
def cmd_validate(args: argparse.Namespace) -> int:
|
|
516
|
-
"""Validate JSONL context files."""
|
|
517
|
-
repo_root = get_repo_root()
|
|
518
|
-
target_dir = _resolve_task_dir(args.dir, repo_root)
|
|
519
|
-
|
|
520
|
-
if not target_dir.is_dir():
|
|
521
|
-
print(colored("Error: task directory required", Colors.RED))
|
|
522
|
-
return 1
|
|
523
|
-
|
|
524
|
-
print(colored("=== Validating Context Files ===", Colors.BLUE))
|
|
525
|
-
print(f"Target dir: {target_dir}")
|
|
526
|
-
print()
|
|
527
|
-
|
|
528
|
-
total_errors = 0
|
|
529
|
-
for jsonl_name in ["implement.jsonl", "check.jsonl", "debug.jsonl"]:
|
|
530
|
-
jsonl_file = target_dir / jsonl_name
|
|
531
|
-
errors = _validate_jsonl(jsonl_file, repo_root)
|
|
532
|
-
total_errors += errors
|
|
533
|
-
|
|
534
|
-
print()
|
|
535
|
-
if total_errors == 0:
|
|
536
|
-
print(colored("✓ All validations passed", Colors.GREEN))
|
|
537
|
-
return 0
|
|
538
|
-
else:
|
|
539
|
-
print(colored(f"✗ Validation failed ({total_errors} errors)", Colors.RED))
|
|
540
|
-
return 1
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
def _validate_jsonl(jsonl_file: Path, repo_root: Path) -> int:
|
|
544
|
-
"""Validate a single JSONL file."""
|
|
545
|
-
file_name = jsonl_file.name
|
|
546
|
-
errors = 0
|
|
547
|
-
|
|
548
|
-
if not jsonl_file.is_file():
|
|
549
|
-
print(f" {colored(f'{file_name}: not found (skipped)', Colors.YELLOW)}")
|
|
550
|
-
return 0
|
|
551
|
-
|
|
552
|
-
line_num = 0
|
|
553
|
-
for line in jsonl_file.read_text(encoding="utf-8").splitlines():
|
|
554
|
-
line_num += 1
|
|
555
|
-
if not line.strip():
|
|
556
|
-
continue
|
|
557
|
-
|
|
558
|
-
try:
|
|
559
|
-
data = json.loads(line)
|
|
560
|
-
except json.JSONDecodeError:
|
|
561
|
-
print(f" {colored(f'{file_name}:{line_num}: Invalid JSON', Colors.RED)}")
|
|
562
|
-
errors += 1
|
|
563
|
-
continue
|
|
564
|
-
|
|
565
|
-
file_path = data.get("file")
|
|
566
|
-
entry_type = data.get("type", "file")
|
|
567
|
-
|
|
568
|
-
if not file_path:
|
|
569
|
-
print(f" {colored(f'{file_name}:{line_num}: Missing file field', Colors.RED)}")
|
|
570
|
-
errors += 1
|
|
571
|
-
continue
|
|
572
|
-
|
|
573
|
-
full_path = repo_root / file_path
|
|
574
|
-
if entry_type == "directory":
|
|
575
|
-
if not full_path.is_dir():
|
|
576
|
-
print(f" {colored(f'{file_name}:{line_num}: Directory not found: {file_path}', Colors.RED)}")
|
|
577
|
-
errors += 1
|
|
578
|
-
else:
|
|
579
|
-
if not full_path.is_file():
|
|
580
|
-
print(f" {colored(f'{file_name}:{line_num}: File not found: {file_path}', Colors.RED)}")
|
|
581
|
-
errors += 1
|
|
582
|
-
|
|
583
|
-
if errors == 0:
|
|
584
|
-
print(f" {colored(f'{file_name}: ✓ ({line_num} entries)', Colors.GREEN)}")
|
|
585
|
-
else:
|
|
586
|
-
print(f" {colored(f'{file_name}: ✗ ({errors} errors)', Colors.RED)}")
|
|
587
|
-
|
|
588
|
-
return errors
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
# =============================================================================
|
|
592
|
-
# Command: list-context
|
|
593
|
-
# =============================================================================
|
|
594
|
-
|
|
595
|
-
def cmd_list_context(args: argparse.Namespace) -> int:
|
|
596
|
-
"""List JSONL context entries."""
|
|
597
|
-
repo_root = get_repo_root()
|
|
598
|
-
target_dir = _resolve_task_dir(args.dir, repo_root)
|
|
599
|
-
|
|
600
|
-
if not target_dir.is_dir():
|
|
601
|
-
print(colored("Error: task directory required", Colors.RED))
|
|
602
|
-
return 1
|
|
603
|
-
|
|
604
|
-
print(colored("=== Context Files ===", Colors.BLUE))
|
|
605
|
-
print()
|
|
606
|
-
|
|
607
|
-
for jsonl_name in ["implement.jsonl", "check.jsonl", "debug.jsonl"]:
|
|
608
|
-
jsonl_file = target_dir / jsonl_name
|
|
609
|
-
if not jsonl_file.is_file():
|
|
610
|
-
continue
|
|
611
|
-
|
|
612
|
-
print(colored(f"[{jsonl_name}]", Colors.CYAN))
|
|
613
|
-
|
|
614
|
-
count = 0
|
|
615
|
-
for line in jsonl_file.read_text(encoding="utf-8").splitlines():
|
|
616
|
-
if not line.strip():
|
|
617
|
-
continue
|
|
618
|
-
|
|
619
|
-
try:
|
|
620
|
-
data = json.loads(line)
|
|
621
|
-
except json.JSONDecodeError:
|
|
622
|
-
continue
|
|
623
|
-
|
|
624
|
-
count += 1
|
|
625
|
-
file_path = data.get("file", "?")
|
|
626
|
-
entry_type = data.get("type", "file")
|
|
627
|
-
reason = data.get("reason", "-")
|
|
628
|
-
|
|
629
|
-
if entry_type == "directory":
|
|
630
|
-
print(f" {colored(f'{count}.', Colors.GREEN)} [DIR] {file_path}")
|
|
631
|
-
else:
|
|
632
|
-
print(f" {colored(f'{count}.', Colors.GREEN)} {file_path}")
|
|
633
|
-
print(f" {colored('→', Colors.YELLOW)} {reason}")
|
|
634
|
-
|
|
635
|
-
print()
|
|
636
|
-
|
|
637
|
-
return 0
|
|
638
62
|
|
|
639
63
|
|
|
640
64
|
# =============================================================================
|
|
@@ -651,7 +75,7 @@ def cmd_start(args: argparse.Namespace) -> int:
|
|
|
651
75
|
return 1
|
|
652
76
|
|
|
653
77
|
# Resolve task directory (supports task name, relative path, or absolute path)
|
|
654
|
-
full_path =
|
|
78
|
+
full_path = resolve_task_dir(task_input, repo_root)
|
|
655
79
|
|
|
656
80
|
if not full_path.is_dir():
|
|
657
81
|
print(colored(f"Error: Task not found: {task_input}", Colors.RED))
|
|
@@ -670,7 +94,7 @@ def cmd_start(args: argparse.Namespace) -> int:
|
|
|
670
94
|
print(colored("The hook will now inject context from this task's jsonl files.", Colors.BLUE))
|
|
671
95
|
|
|
672
96
|
task_json_path = full_path / FILE_TASK_JSON
|
|
673
|
-
|
|
97
|
+
run_task_hooks("after_start", task_json_path, repo_root)
|
|
674
98
|
return 0
|
|
675
99
|
else:
|
|
676
100
|
print(colored("Error: Failed to set current task", Colors.RED))
|
|
@@ -693,221 +117,7 @@ def cmd_finish(args: argparse.Namespace) -> int:
|
|
|
693
117
|
print(colored(f"✓ Cleared current task (was: {current})", Colors.GREEN))
|
|
694
118
|
|
|
695
119
|
if task_json_path.is_file():
|
|
696
|
-
|
|
697
|
-
return 0
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
# =============================================================================
|
|
701
|
-
# Command: archive
|
|
702
|
-
# =============================================================================
|
|
703
|
-
|
|
704
|
-
def cmd_archive(args: argparse.Namespace) -> int:
|
|
705
|
-
"""Archive completed task."""
|
|
706
|
-
repo_root = get_repo_root()
|
|
707
|
-
task_name = args.name
|
|
708
|
-
|
|
709
|
-
if not task_name:
|
|
710
|
-
print(colored("Error: Task name is required", Colors.RED), file=sys.stderr)
|
|
711
|
-
return 1
|
|
712
|
-
|
|
713
|
-
tasks_dir = get_tasks_dir(repo_root)
|
|
714
|
-
|
|
715
|
-
# Find task directory
|
|
716
|
-
task_dir = find_task_by_name(task_name, tasks_dir)
|
|
717
|
-
|
|
718
|
-
if not task_dir or not task_dir.is_dir():
|
|
719
|
-
print(colored(f"Error: Task not found: {task_name}", Colors.RED), file=sys.stderr)
|
|
720
|
-
print("Active tasks:", file=sys.stderr)
|
|
721
|
-
cmd_list(argparse.Namespace(mine=False, status=None))
|
|
722
|
-
return 1
|
|
723
|
-
|
|
724
|
-
dir_name = task_dir.name
|
|
725
|
-
task_json_path = task_dir / FILE_TASK_JSON
|
|
726
|
-
|
|
727
|
-
# Update status before archiving
|
|
728
|
-
today = datetime.now().strftime("%Y-%m-%d")
|
|
729
|
-
if task_json_path.is_file():
|
|
730
|
-
data = _read_json_file(task_json_path)
|
|
731
|
-
if data:
|
|
732
|
-
data["status"] = "completed"
|
|
733
|
-
data["completedAt"] = today
|
|
734
|
-
_write_json_file(task_json_path, data)
|
|
735
|
-
|
|
736
|
-
# Handle subtask relationships on archive
|
|
737
|
-
task_parent = data.get("parent")
|
|
738
|
-
task_children = data.get("children", [])
|
|
739
|
-
|
|
740
|
-
# If this is a child, remove from parent's children list
|
|
741
|
-
if task_parent:
|
|
742
|
-
parent_dir = find_task_by_name(task_parent, tasks_dir)
|
|
743
|
-
if parent_dir:
|
|
744
|
-
parent_json = parent_dir / FILE_TASK_JSON
|
|
745
|
-
if parent_json.is_file():
|
|
746
|
-
parent_data = _read_json_file(parent_json)
|
|
747
|
-
if parent_data:
|
|
748
|
-
parent_children = parent_data.get("children", [])
|
|
749
|
-
if dir_name in parent_children:
|
|
750
|
-
parent_children.remove(dir_name)
|
|
751
|
-
parent_data["children"] = parent_children
|
|
752
|
-
_write_json_file(parent_json, parent_data)
|
|
753
|
-
|
|
754
|
-
# If this is a parent, clear parent field in all children
|
|
755
|
-
if task_children:
|
|
756
|
-
for child_name in task_children:
|
|
757
|
-
child_dir_path = find_task_by_name(child_name, tasks_dir)
|
|
758
|
-
if child_dir_path:
|
|
759
|
-
child_json = child_dir_path / FILE_TASK_JSON
|
|
760
|
-
if child_json.is_file():
|
|
761
|
-
child_data = _read_json_file(child_json)
|
|
762
|
-
if child_data:
|
|
763
|
-
child_data["parent"] = None
|
|
764
|
-
_write_json_file(child_json, child_data)
|
|
765
|
-
|
|
766
|
-
# Clear if current task
|
|
767
|
-
current = get_current_task(repo_root)
|
|
768
|
-
if current and dir_name in current:
|
|
769
|
-
clear_current_task(repo_root)
|
|
770
|
-
|
|
771
|
-
# Archive
|
|
772
|
-
result = archive_task_complete(task_dir, repo_root)
|
|
773
|
-
if "archived_to" in result:
|
|
774
|
-
archive_dest = Path(result["archived_to"])
|
|
775
|
-
year_month = archive_dest.parent.name
|
|
776
|
-
print(colored(f"Archived: {dir_name} -> archive/{year_month}/", Colors.GREEN), file=sys.stderr)
|
|
777
|
-
|
|
778
|
-
# Auto-commit unless --no-commit
|
|
779
|
-
if not getattr(args, "no_commit", False):
|
|
780
|
-
_auto_commit_archive(dir_name, repo_root)
|
|
781
|
-
|
|
782
|
-
# Return the archive path
|
|
783
|
-
print(f"{DIR_WORKFLOW}/{DIR_TASKS}/{DIR_ARCHIVE}/{year_month}/{dir_name}")
|
|
784
|
-
|
|
785
|
-
# Run hooks with the archived path
|
|
786
|
-
archived_json = archive_dest / FILE_TASK_JSON
|
|
787
|
-
_run_hooks("after_archive", archived_json, repo_root)
|
|
788
|
-
return 0
|
|
789
|
-
|
|
790
|
-
return 1
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
def _auto_commit_archive(task_name: str, repo_root: Path) -> None:
|
|
794
|
-
"""Stage .trellis/tasks/ changes and commit after archive."""
|
|
795
|
-
tasks_rel = f"{DIR_WORKFLOW}/{DIR_TASKS}"
|
|
796
|
-
_run_git_command(["add", "-A", tasks_rel], cwd=repo_root)
|
|
797
|
-
|
|
798
|
-
# Check if there are staged changes
|
|
799
|
-
rc, _, _ = _run_git_command(
|
|
800
|
-
["diff", "--cached", "--quiet", "--", tasks_rel], cwd=repo_root
|
|
801
|
-
)
|
|
802
|
-
if rc == 0:
|
|
803
|
-
print("[OK] No task changes to commit.", file=sys.stderr)
|
|
804
|
-
return
|
|
805
|
-
|
|
806
|
-
commit_msg = f"chore(task): archive {task_name}"
|
|
807
|
-
rc, _, err = _run_git_command(["commit", "-m", commit_msg], cwd=repo_root)
|
|
808
|
-
if rc == 0:
|
|
809
|
-
print(f"[OK] Auto-committed: {commit_msg}", file=sys.stderr)
|
|
810
|
-
else:
|
|
811
|
-
print(f"[WARN] Auto-commit failed: {err.strip()}", file=sys.stderr)
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
# =============================================================================
|
|
815
|
-
# Command: add-subtask
|
|
816
|
-
# =============================================================================
|
|
817
|
-
|
|
818
|
-
def cmd_add_subtask(args: argparse.Namespace) -> int:
|
|
819
|
-
"""Link a child task to a parent task."""
|
|
820
|
-
repo_root = get_repo_root()
|
|
821
|
-
|
|
822
|
-
parent_dir = _resolve_task_dir(args.parent_dir, repo_root)
|
|
823
|
-
child_dir = _resolve_task_dir(args.child_dir, repo_root)
|
|
824
|
-
|
|
825
|
-
parent_json_path = parent_dir / FILE_TASK_JSON
|
|
826
|
-
child_json_path = child_dir / FILE_TASK_JSON
|
|
827
|
-
|
|
828
|
-
if not parent_json_path.is_file():
|
|
829
|
-
print(colored(f"Error: Parent task.json not found: {args.parent_dir}", Colors.RED), file=sys.stderr)
|
|
830
|
-
return 1
|
|
831
|
-
|
|
832
|
-
if not child_json_path.is_file():
|
|
833
|
-
print(colored(f"Error: Child task.json not found: {args.child_dir}", Colors.RED), file=sys.stderr)
|
|
834
|
-
return 1
|
|
835
|
-
|
|
836
|
-
parent_data = _read_json_file(parent_json_path)
|
|
837
|
-
child_data = _read_json_file(child_json_path)
|
|
838
|
-
|
|
839
|
-
if not parent_data or not child_data:
|
|
840
|
-
print(colored("Error: Failed to read task.json", Colors.RED), file=sys.stderr)
|
|
841
|
-
return 1
|
|
842
|
-
|
|
843
|
-
# Check if child already has a parent
|
|
844
|
-
existing_parent = child_data.get("parent")
|
|
845
|
-
if existing_parent:
|
|
846
|
-
print(colored(f"Error: Child task already has a parent: {existing_parent}", Colors.RED), file=sys.stderr)
|
|
847
|
-
return 1
|
|
848
|
-
|
|
849
|
-
# Add child to parent's children list
|
|
850
|
-
parent_children = parent_data.get("children", [])
|
|
851
|
-
child_dir_name = child_dir.name
|
|
852
|
-
if child_dir_name not in parent_children:
|
|
853
|
-
parent_children.append(child_dir_name)
|
|
854
|
-
parent_data["children"] = parent_children
|
|
855
|
-
|
|
856
|
-
# Set parent in child's task.json
|
|
857
|
-
child_data["parent"] = parent_dir.name
|
|
858
|
-
|
|
859
|
-
# Write both
|
|
860
|
-
_write_json_file(parent_json_path, parent_data)
|
|
861
|
-
_write_json_file(child_json_path, child_data)
|
|
862
|
-
|
|
863
|
-
print(colored(f"Linked: {child_dir.name} -> {parent_dir.name}", Colors.GREEN), file=sys.stderr)
|
|
864
|
-
return 0
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
# =============================================================================
|
|
868
|
-
# Command: remove-subtask
|
|
869
|
-
# =============================================================================
|
|
870
|
-
|
|
871
|
-
def cmd_remove_subtask(args: argparse.Namespace) -> int:
|
|
872
|
-
"""Unlink a child task from a parent task."""
|
|
873
|
-
repo_root = get_repo_root()
|
|
874
|
-
|
|
875
|
-
parent_dir = _resolve_task_dir(args.parent_dir, repo_root)
|
|
876
|
-
child_dir = _resolve_task_dir(args.child_dir, repo_root)
|
|
877
|
-
|
|
878
|
-
parent_json_path = parent_dir / FILE_TASK_JSON
|
|
879
|
-
child_json_path = child_dir / FILE_TASK_JSON
|
|
880
|
-
|
|
881
|
-
if not parent_json_path.is_file():
|
|
882
|
-
print(colored(f"Error: Parent task.json not found: {args.parent_dir}", Colors.RED), file=sys.stderr)
|
|
883
|
-
return 1
|
|
884
|
-
|
|
885
|
-
if not child_json_path.is_file():
|
|
886
|
-
print(colored(f"Error: Child task.json not found: {args.child_dir}", Colors.RED), file=sys.stderr)
|
|
887
|
-
return 1
|
|
888
|
-
|
|
889
|
-
parent_data = _read_json_file(parent_json_path)
|
|
890
|
-
child_data = _read_json_file(child_json_path)
|
|
891
|
-
|
|
892
|
-
if not parent_data or not child_data:
|
|
893
|
-
print(colored("Error: Failed to read task.json", Colors.RED), file=sys.stderr)
|
|
894
|
-
return 1
|
|
895
|
-
|
|
896
|
-
# Remove child from parent's children list
|
|
897
|
-
parent_children = parent_data.get("children", [])
|
|
898
|
-
child_dir_name = child_dir.name
|
|
899
|
-
if child_dir_name in parent_children:
|
|
900
|
-
parent_children.remove(child_dir_name)
|
|
901
|
-
parent_data["children"] = parent_children
|
|
902
|
-
|
|
903
|
-
# Clear parent in child's task.json
|
|
904
|
-
child_data["parent"] = None
|
|
905
|
-
|
|
906
|
-
# Write both
|
|
907
|
-
_write_json_file(parent_json_path, parent_data)
|
|
908
|
-
_write_json_file(child_json_path, child_data)
|
|
909
|
-
|
|
910
|
-
print(colored(f"Unlinked: {child_dir.name} from {parent_dir.name}", Colors.GREEN), file=sys.stderr)
|
|
120
|
+
run_task_hooks("after_finish", task_json_path, repo_root)
|
|
911
121
|
return 0
|
|
912
122
|
|
|
913
123
|
|
|
@@ -915,24 +125,6 @@ def cmd_remove_subtask(args: argparse.Namespace) -> int:
|
|
|
915
125
|
# Command: list
|
|
916
126
|
# =============================================================================
|
|
917
127
|
|
|
918
|
-
def _get_children_progress(children: list[str], tasks_dir: Path) -> str:
|
|
919
|
-
"""Get children progress summary like '[2/3 done]'."""
|
|
920
|
-
if not children:
|
|
921
|
-
return ""
|
|
922
|
-
done_count = 0
|
|
923
|
-
total = len(children)
|
|
924
|
-
for child_name in children:
|
|
925
|
-
child_dir = tasks_dir / child_name
|
|
926
|
-
child_json = child_dir / FILE_TASK_JSON
|
|
927
|
-
if child_json.is_file():
|
|
928
|
-
data = _read_json_file(child_json)
|
|
929
|
-
if data:
|
|
930
|
-
status = data.get("status", "")
|
|
931
|
-
if status in ("completed", "done"):
|
|
932
|
-
done_count += 1
|
|
933
|
-
return f" [{done_count}/{total} done]"
|
|
934
|
-
|
|
935
|
-
|
|
936
128
|
def cmd_list(args: argparse.Namespace) -> int:
|
|
937
129
|
"""List active tasks."""
|
|
938
130
|
repo_root = get_repo_root()
|
|
@@ -951,51 +143,23 @@ def cmd_list(args: argparse.Namespace) -> int:
|
|
|
951
143
|
print(colored("All active tasks:", Colors.BLUE))
|
|
952
144
|
print()
|
|
953
145
|
|
|
954
|
-
#
|
|
955
|
-
all_tasks:
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
continue
|
|
960
|
-
|
|
961
|
-
dir_name = d.name
|
|
962
|
-
task_json = d / FILE_TASK_JSON
|
|
963
|
-
status = "unknown"
|
|
964
|
-
assignee = "-"
|
|
965
|
-
children: list[str] = []
|
|
966
|
-
parent: str | None = None
|
|
967
|
-
|
|
968
|
-
if task_json.is_file():
|
|
969
|
-
data = _read_json_file(task_json)
|
|
970
|
-
if data:
|
|
971
|
-
status = data.get("status", "unknown")
|
|
972
|
-
assignee = data.get("assignee", "-")
|
|
973
|
-
children = data.get("children", [])
|
|
974
|
-
parent = data.get("parent")
|
|
975
|
-
|
|
976
|
-
all_tasks[dir_name] = {
|
|
977
|
-
"status": status,
|
|
978
|
-
"assignee": assignee,
|
|
979
|
-
"children": children,
|
|
980
|
-
"parent": parent,
|
|
981
|
-
}
|
|
982
|
-
|
|
983
|
-
# Second pass: display tasks hierarchically
|
|
146
|
+
# Single pass: collect all tasks via shared iterator
|
|
147
|
+
all_tasks = {t.dir_name: t for t in iter_active_tasks(tasks_dir)}
|
|
148
|
+
all_statuses = {name: t.status for name, t in all_tasks.items()}
|
|
149
|
+
|
|
150
|
+
# Display tasks hierarchically
|
|
984
151
|
count = 0
|
|
985
152
|
|
|
986
153
|
def _print_task(dir_name: str, indent: int = 0) -> None:
|
|
987
154
|
nonlocal count
|
|
988
|
-
|
|
989
|
-
status = info["status"]
|
|
990
|
-
assignee = info["assignee"]
|
|
991
|
-
children = info["children"]
|
|
155
|
+
t = all_tasks[dir_name]
|
|
992
156
|
|
|
993
157
|
# Apply --mine filter
|
|
994
|
-
if filter_mine and assignee != developer:
|
|
158
|
+
if filter_mine and (t.assignee or "-") != developer:
|
|
995
159
|
return
|
|
996
160
|
|
|
997
161
|
# Apply --status filter
|
|
998
|
-
if filter_status and status != filter_status:
|
|
162
|
+
if filter_status and t.status != filter_status:
|
|
999
163
|
return
|
|
1000
164
|
|
|
1001
165
|
relative_path = f"{DIR_WORKFLOW}/{DIR_TASKS}/{dir_name}"
|
|
@@ -1004,25 +168,27 @@ def cmd_list(args: argparse.Namespace) -> int:
|
|
|
1004
168
|
marker = f" {colored('<- current', Colors.GREEN)}"
|
|
1005
169
|
|
|
1006
170
|
# Children progress
|
|
1007
|
-
progress =
|
|
171
|
+
progress = children_progress(t.children, all_statuses)
|
|
172
|
+
|
|
173
|
+
# Package tag
|
|
174
|
+
pkg_tag = f" @{t.package}" if t.package else ""
|
|
1008
175
|
|
|
1009
176
|
prefix = " " * indent + " - "
|
|
1010
177
|
|
|
1011
178
|
if filter_mine:
|
|
1012
|
-
print(f"{prefix}{dir_name}/ ({status}){progress}{marker}")
|
|
179
|
+
print(f"{prefix}{dir_name}/ ({t.status}){pkg_tag}{progress}{marker}")
|
|
1013
180
|
else:
|
|
1014
|
-
print(f"{prefix}{dir_name}/ ({status}){progress} [{colored(assignee, Colors.CYAN)}]{marker}")
|
|
181
|
+
print(f"{prefix}{dir_name}/ ({t.status}){pkg_tag}{progress} [{colored(t.assignee or '-', Colors.CYAN)}]{marker}")
|
|
1015
182
|
count += 1
|
|
1016
183
|
|
|
1017
184
|
# Print children indented
|
|
1018
|
-
for child_name in children:
|
|
185
|
+
for child_name in t.children:
|
|
1019
186
|
if child_name in all_tasks:
|
|
1020
187
|
_print_task(child_name, indent + 1)
|
|
1021
188
|
|
|
1022
189
|
# Display only top-level tasks (those without a parent)
|
|
1023
190
|
for dir_name in sorted(all_tasks.keys()):
|
|
1024
|
-
|
|
1025
|
-
if not info["parent"]:
|
|
191
|
+
if not all_tasks[dir_name].parent:
|
|
1026
192
|
_print_task(dir_name)
|
|
1027
193
|
|
|
1028
194
|
if count == 0:
|
|
@@ -1070,106 +236,6 @@ def cmd_list_archive(args: argparse.Namespace) -> int:
|
|
|
1070
236
|
return 0
|
|
1071
237
|
|
|
1072
238
|
|
|
1073
|
-
# =============================================================================
|
|
1074
|
-
# Command: set-branch
|
|
1075
|
-
# =============================================================================
|
|
1076
|
-
|
|
1077
|
-
def cmd_set_branch(args: argparse.Namespace) -> int:
|
|
1078
|
-
"""Set git branch for task."""
|
|
1079
|
-
repo_root = get_repo_root()
|
|
1080
|
-
target_dir = _resolve_task_dir(args.dir, repo_root)
|
|
1081
|
-
branch = args.branch
|
|
1082
|
-
|
|
1083
|
-
if not branch:
|
|
1084
|
-
print(colored("Error: Missing arguments", Colors.RED))
|
|
1085
|
-
print("Usage: python3 task.py set-branch <task-dir> <branch-name>")
|
|
1086
|
-
return 1
|
|
1087
|
-
|
|
1088
|
-
task_json = target_dir / FILE_TASK_JSON
|
|
1089
|
-
if not task_json.is_file():
|
|
1090
|
-
print(colored(f"Error: task.json not found at {target_dir}", Colors.RED))
|
|
1091
|
-
return 1
|
|
1092
|
-
|
|
1093
|
-
data = _read_json_file(task_json)
|
|
1094
|
-
if not data:
|
|
1095
|
-
return 1
|
|
1096
|
-
|
|
1097
|
-
data["branch"] = branch
|
|
1098
|
-
_write_json_file(task_json, data)
|
|
1099
|
-
|
|
1100
|
-
print(colored(f"✓ Branch set to: {branch}", Colors.GREEN))
|
|
1101
|
-
print()
|
|
1102
|
-
print(colored("Now you can start the multi-agent pipeline:", Colors.BLUE))
|
|
1103
|
-
print(f" python3 ./.trellis/scripts/multi_agent/start.py {args.dir}")
|
|
1104
|
-
return 0
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
# =============================================================================
|
|
1108
|
-
# Command: set-base-branch
|
|
1109
|
-
# =============================================================================
|
|
1110
|
-
|
|
1111
|
-
def cmd_set_base_branch(args: argparse.Namespace) -> int:
|
|
1112
|
-
"""Set the base branch (PR target) for task."""
|
|
1113
|
-
repo_root = get_repo_root()
|
|
1114
|
-
target_dir = _resolve_task_dir(args.dir, repo_root)
|
|
1115
|
-
base_branch = args.base_branch
|
|
1116
|
-
|
|
1117
|
-
if not base_branch:
|
|
1118
|
-
print(colored("Error: Missing arguments", Colors.RED))
|
|
1119
|
-
print("Usage: python3 task.py set-base-branch <task-dir> <base-branch>")
|
|
1120
|
-
print("Example: python3 task.py set-base-branch <dir> develop")
|
|
1121
|
-
print()
|
|
1122
|
-
print("This sets the target branch for PR (the branch your feature will merge into).")
|
|
1123
|
-
return 1
|
|
1124
|
-
|
|
1125
|
-
task_json = target_dir / FILE_TASK_JSON
|
|
1126
|
-
if not task_json.is_file():
|
|
1127
|
-
print(colored(f"Error: task.json not found at {target_dir}", Colors.RED))
|
|
1128
|
-
return 1
|
|
1129
|
-
|
|
1130
|
-
data = _read_json_file(task_json)
|
|
1131
|
-
if not data:
|
|
1132
|
-
return 1
|
|
1133
|
-
|
|
1134
|
-
data["base_branch"] = base_branch
|
|
1135
|
-
_write_json_file(task_json, data)
|
|
1136
|
-
|
|
1137
|
-
print(colored(f"✓ Base branch set to: {base_branch}", Colors.GREEN))
|
|
1138
|
-
print(f" PR will target: {base_branch}")
|
|
1139
|
-
return 0
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
# =============================================================================
|
|
1143
|
-
# Command: set-scope
|
|
1144
|
-
# =============================================================================
|
|
1145
|
-
|
|
1146
|
-
def cmd_set_scope(args: argparse.Namespace) -> int:
|
|
1147
|
-
"""Set scope for PR title."""
|
|
1148
|
-
repo_root = get_repo_root()
|
|
1149
|
-
target_dir = _resolve_task_dir(args.dir, repo_root)
|
|
1150
|
-
scope = args.scope
|
|
1151
|
-
|
|
1152
|
-
if not scope:
|
|
1153
|
-
print(colored("Error: Missing arguments", Colors.RED))
|
|
1154
|
-
print("Usage: python3 task.py set-scope <task-dir> <scope>")
|
|
1155
|
-
return 1
|
|
1156
|
-
|
|
1157
|
-
task_json = target_dir / FILE_TASK_JSON
|
|
1158
|
-
if not task_json.is_file():
|
|
1159
|
-
print(colored(f"Error: task.json not found at {target_dir}", Colors.RED))
|
|
1160
|
-
return 1
|
|
1161
|
-
|
|
1162
|
-
data = _read_json_file(task_json)
|
|
1163
|
-
if not data:
|
|
1164
|
-
return 1
|
|
1165
|
-
|
|
1166
|
-
data["scope"] = scope
|
|
1167
|
-
_write_json_file(task_json, data)
|
|
1168
|
-
|
|
1169
|
-
print(colored(f"✓ Scope set to: {scope}", Colors.GREEN))
|
|
1170
|
-
return 0
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
239
|
# =============================================================================
|
|
1174
240
|
# Command: create-pr (delegates to multi-agent script)
|
|
1175
241
|
# =============================================================================
|
|
@@ -1200,8 +266,10 @@ def show_usage() -> None:
|
|
|
1200
266
|
|
|
1201
267
|
Usage:
|
|
1202
268
|
python3 task.py create <title> Create new task directory
|
|
269
|
+
python3 task.py create <title> --package <pkg> Create task for a specific package
|
|
1203
270
|
python3 task.py create <title> --parent <dir> Create task as child of parent
|
|
1204
271
|
python3 task.py init-context <dir> <dev_type> Initialize jsonl files
|
|
272
|
+
python3 task.py init-context <dir> <type> --package <pkg> With explicit package
|
|
1205
273
|
python3 task.py add-context <dir> <jsonl> <path> [reason] Add entry to jsonl
|
|
1206
274
|
python3 task.py validate <dir> Validate jsonl files
|
|
1207
275
|
python3 task.py list-context <dir> List jsonl entries
|
|
@@ -1219,15 +287,20 @@ Usage:
|
|
|
1219
287
|
Arguments:
|
|
1220
288
|
dev_type: backend | frontend | fullstack | test | docs
|
|
1221
289
|
|
|
290
|
+
Monorepo options:
|
|
291
|
+
--package <pkg> Package name (validated against config.yaml packages)
|
|
292
|
+
|
|
1222
293
|
List options:
|
|
1223
294
|
--mine, -m Show only tasks assigned to current developer
|
|
1224
295
|
--status, -s <s> Filter by status (planning, in_progress, review, completed)
|
|
1225
296
|
|
|
1226
297
|
Examples:
|
|
1227
298
|
python3 task.py create "Add login feature" --slug add-login
|
|
299
|
+
python3 task.py create "Add login feature" --slug add-login --package cli
|
|
1228
300
|
python3 task.py create "Child task" --slug child --parent .trellis/tasks/01-21-parent
|
|
1229
301
|
python3 task.py init-context .trellis/tasks/01-21-add-login backend
|
|
1230
|
-
python3 task.py
|
|
302
|
+
python3 task.py init-context .trellis/tasks/01-21-add-login backend --package cli
|
|
303
|
+
python3 task.py add-context <dir> implement .trellis/spec/cli/backend/auth.md "Auth guidelines"
|
|
1231
304
|
python3 task.py set-branch <dir> task/add-login
|
|
1232
305
|
python3 task.py start .trellis/tasks/01-21-add-login
|
|
1233
306
|
python3 task.py create-pr # Uses current task
|
|
@@ -1262,11 +335,13 @@ def main() -> int:
|
|
|
1262
335
|
p_create.add_argument("--priority", "-p", default="P2", help="Priority (P0-P3)")
|
|
1263
336
|
p_create.add_argument("--description", "-d", help="Task description")
|
|
1264
337
|
p_create.add_argument("--parent", help="Parent task directory (establishes subtask link)")
|
|
338
|
+
p_create.add_argument("--package", help="Package name for monorepo projects")
|
|
1265
339
|
|
|
1266
340
|
# init-context
|
|
1267
341
|
p_init = subparsers.add_parser("init-context", help="Initialize context files")
|
|
1268
342
|
p_init.add_argument("dir", help="Task directory")
|
|
1269
343
|
p_init.add_argument("type", help="Dev type: backend|frontend|fullstack|test|docs")
|
|
344
|
+
p_init.add_argument("--package", help="Package name for monorepo projects")
|
|
1270
345
|
|
|
1271
346
|
# add-context
|
|
1272
347
|
p_add = subparsers.add_parser("add-context", help="Add context entry")
|