@mindfoldhq/trellis 0.3.9 → 0.4.0-beta.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/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.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/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
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Task JSONL context management.
|
|
4
|
+
|
|
5
|
+
Provides:
|
|
6
|
+
cmd_init_context - Initialize JSONL context files for a task
|
|
7
|
+
cmd_add_context - Add entry to JSONL context file
|
|
8
|
+
cmd_validate - Validate JSONL context files
|
|
9
|
+
cmd_list_context - List JSONL context entries
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import argparse
|
|
15
|
+
import json
|
|
16
|
+
import sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
from .cli_adapter import get_cli_adapter_auto
|
|
20
|
+
from .config import (
|
|
21
|
+
get_packages,
|
|
22
|
+
is_monorepo,
|
|
23
|
+
resolve_package,
|
|
24
|
+
validate_package,
|
|
25
|
+
)
|
|
26
|
+
from .io import read_json, write_json
|
|
27
|
+
from .log import Colors, colored
|
|
28
|
+
from .paths import (
|
|
29
|
+
DIR_SPEC,
|
|
30
|
+
DIR_WORKFLOW,
|
|
31
|
+
FILE_TASK_JSON,
|
|
32
|
+
get_repo_root,
|
|
33
|
+
)
|
|
34
|
+
from .task_utils import resolve_task_dir
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# =============================================================================
|
|
38
|
+
# JSONL Default Content Generators
|
|
39
|
+
# =============================================================================
|
|
40
|
+
|
|
41
|
+
def get_implement_base() -> list[dict]:
|
|
42
|
+
"""Get base implement context entries."""
|
|
43
|
+
return [
|
|
44
|
+
{"file": f"{DIR_WORKFLOW}/workflow.md", "reason": "Project workflow and conventions"},
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def get_implement_backend(package: str | None = None) -> list[dict]:
|
|
49
|
+
"""Get backend implement context entries."""
|
|
50
|
+
spec_base = f"{DIR_SPEC}/{package}" if package else DIR_SPEC
|
|
51
|
+
return [
|
|
52
|
+
{"file": f"{DIR_WORKFLOW}/{spec_base}/backend/index.md", "reason": "Backend development guide"},
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def get_implement_frontend(package: str | None = None) -> list[dict]:
|
|
57
|
+
"""Get frontend implement context entries."""
|
|
58
|
+
spec_base = f"{DIR_SPEC}/{package}" if package else DIR_SPEC
|
|
59
|
+
return [
|
|
60
|
+
{"file": f"{DIR_WORKFLOW}/{spec_base}/frontend/index.md", "reason": "Frontend development guide"},
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def get_check_context(repo_root: Path) -> list[dict]:
|
|
65
|
+
"""Get check context entries."""
|
|
66
|
+
adapter = get_cli_adapter_auto(repo_root)
|
|
67
|
+
|
|
68
|
+
entries = [
|
|
69
|
+
{"file": adapter.get_trellis_command_path("finish-work"), "reason": "Finish work checklist"},
|
|
70
|
+
{"file": adapter.get_trellis_command_path("check"), "reason": "Code quality check spec"},
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
return entries
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def get_debug_context(repo_root: Path) -> list[dict]:
|
|
77
|
+
"""Get debug context entries."""
|
|
78
|
+
adapter = get_cli_adapter_auto(repo_root)
|
|
79
|
+
|
|
80
|
+
entries: list[dict] = [
|
|
81
|
+
{"file": adapter.get_trellis_command_path("check"), "reason": "Code quality check spec"},
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
return entries
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _write_jsonl(path: Path, entries: list[dict]) -> None:
|
|
88
|
+
"""Write entries to JSONL file."""
|
|
89
|
+
lines = [json.dumps(entry, ensure_ascii=False) for entry in entries]
|
|
90
|
+
path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# =============================================================================
|
|
94
|
+
# Command: init-context
|
|
95
|
+
# =============================================================================
|
|
96
|
+
|
|
97
|
+
def cmd_init_context(args: argparse.Namespace) -> int:
|
|
98
|
+
"""Initialize JSONL context files for a task."""
|
|
99
|
+
repo_root = get_repo_root()
|
|
100
|
+
target_dir = resolve_task_dir(args.dir, repo_root)
|
|
101
|
+
dev_type = args.type
|
|
102
|
+
|
|
103
|
+
if not dev_type:
|
|
104
|
+
print(colored("Error: Missing arguments", Colors.RED))
|
|
105
|
+
print("Usage: python3 task.py init-context <task-dir> <dev_type>")
|
|
106
|
+
print(" dev_type: backend | frontend | fullstack | test | docs")
|
|
107
|
+
return 1
|
|
108
|
+
|
|
109
|
+
if not target_dir.is_dir():
|
|
110
|
+
print(colored(f"Error: Directory not found: {target_dir}", Colors.RED))
|
|
111
|
+
return 1
|
|
112
|
+
|
|
113
|
+
# Resolve package: --package CLI → task.json.package → default_package
|
|
114
|
+
cli_package: str | None = getattr(args, "package", None)
|
|
115
|
+
package: str | None = None
|
|
116
|
+
if not is_monorepo(repo_root):
|
|
117
|
+
# Single-repo: ignore --package, no package prefix
|
|
118
|
+
if cli_package:
|
|
119
|
+
print(colored("Warning: --package ignored in single-repo project", Colors.YELLOW), file=sys.stderr)
|
|
120
|
+
elif cli_package:
|
|
121
|
+
if not validate_package(cli_package, repo_root):
|
|
122
|
+
packages = get_packages(repo_root)
|
|
123
|
+
available = ", ".join(sorted(packages.keys())) if packages else "(none)"
|
|
124
|
+
print(colored(f"Error: unknown package '{cli_package}'. Available: {available}", Colors.RED), file=sys.stderr)
|
|
125
|
+
return 1
|
|
126
|
+
package = cli_package
|
|
127
|
+
else:
|
|
128
|
+
# Read task.json.package as inferred source
|
|
129
|
+
task_json_path = target_dir / FILE_TASK_JSON
|
|
130
|
+
task_pkg_value = None
|
|
131
|
+
if task_json_path.is_file():
|
|
132
|
+
task_data = read_json(task_json_path)
|
|
133
|
+
if isinstance(task_data, dict):
|
|
134
|
+
task_pkg_value = task_data.get("package")
|
|
135
|
+
# Only pass string values to resolve_package (guard against malformed JSON)
|
|
136
|
+
task_package = task_pkg_value if isinstance(task_pkg_value, str) else None
|
|
137
|
+
package = resolve_package(task_package=task_package, repo_root=repo_root)
|
|
138
|
+
|
|
139
|
+
# Monorepo fallback prohibition
|
|
140
|
+
if package is None:
|
|
141
|
+
packages = get_packages(repo_root)
|
|
142
|
+
available = ", ".join(sorted(packages.keys())) if packages else "(none)"
|
|
143
|
+
print(colored(
|
|
144
|
+
f"Error: monorepo project requires --package (or set default_package in config.yaml). Available: {available}",
|
|
145
|
+
Colors.RED,
|
|
146
|
+
), file=sys.stderr)
|
|
147
|
+
return 1
|
|
148
|
+
|
|
149
|
+
print(colored("=== Initializing Agent Context Files ===", Colors.BLUE))
|
|
150
|
+
print(f"Target dir: {target_dir}")
|
|
151
|
+
print(f"Dev type: {dev_type}")
|
|
152
|
+
if package:
|
|
153
|
+
print(f"Package: {package}")
|
|
154
|
+
print()
|
|
155
|
+
|
|
156
|
+
# implement.jsonl
|
|
157
|
+
print(colored("Creating implement.jsonl...", Colors.CYAN))
|
|
158
|
+
implement_entries = get_implement_base()
|
|
159
|
+
if dev_type in ("backend", "test"):
|
|
160
|
+
implement_entries.extend(get_implement_backend(package))
|
|
161
|
+
elif dev_type == "frontend":
|
|
162
|
+
implement_entries.extend(get_implement_frontend(package))
|
|
163
|
+
elif dev_type == "fullstack":
|
|
164
|
+
implement_entries.extend(get_implement_backend(package))
|
|
165
|
+
implement_entries.extend(get_implement_frontend(package))
|
|
166
|
+
|
|
167
|
+
implement_file = target_dir / "implement.jsonl"
|
|
168
|
+
_write_jsonl(implement_file, implement_entries)
|
|
169
|
+
print(f" {colored('✓', Colors.GREEN)} {len(implement_entries)} entries")
|
|
170
|
+
|
|
171
|
+
# check.jsonl
|
|
172
|
+
print(colored("Creating check.jsonl...", Colors.CYAN))
|
|
173
|
+
check_entries = get_check_context(repo_root)
|
|
174
|
+
check_file = target_dir / "check.jsonl"
|
|
175
|
+
_write_jsonl(check_file, check_entries)
|
|
176
|
+
print(f" {colored('✓', Colors.GREEN)} {len(check_entries)} entries")
|
|
177
|
+
|
|
178
|
+
# debug.jsonl
|
|
179
|
+
print(colored("Creating debug.jsonl...", Colors.CYAN))
|
|
180
|
+
debug_entries = get_debug_context(repo_root)
|
|
181
|
+
debug_file = target_dir / "debug.jsonl"
|
|
182
|
+
_write_jsonl(debug_file, debug_entries)
|
|
183
|
+
print(f" {colored('✓', Colors.GREEN)} {len(debug_entries)} entries")
|
|
184
|
+
|
|
185
|
+
# Update task.json dev_type and package
|
|
186
|
+
task_json_path = target_dir / FILE_TASK_JSON
|
|
187
|
+
if task_json_path.is_file():
|
|
188
|
+
task_data = read_json(task_json_path)
|
|
189
|
+
if isinstance(task_data, dict):
|
|
190
|
+
task_data["dev_type"] = dev_type
|
|
191
|
+
task_data["package"] = package # Always sync to match resolved value
|
|
192
|
+
write_json(task_json_path, task_data)
|
|
193
|
+
|
|
194
|
+
print()
|
|
195
|
+
print(colored("✓ All context files created", Colors.GREEN))
|
|
196
|
+
print()
|
|
197
|
+
print(colored("Next steps:", Colors.BLUE))
|
|
198
|
+
print(" 1. Add task-specific specs: python3 task.py add-context <dir> <jsonl> <path>")
|
|
199
|
+
print(" 2. Set as current: python3 task.py start <dir>")
|
|
200
|
+
|
|
201
|
+
return 0
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
# =============================================================================
|
|
205
|
+
# Command: add-context
|
|
206
|
+
# =============================================================================
|
|
207
|
+
|
|
208
|
+
def cmd_add_context(args: argparse.Namespace) -> int:
|
|
209
|
+
"""Add entry to JSONL context file."""
|
|
210
|
+
repo_root = get_repo_root()
|
|
211
|
+
target_dir = resolve_task_dir(args.dir, repo_root)
|
|
212
|
+
|
|
213
|
+
jsonl_name = args.file
|
|
214
|
+
path = args.path
|
|
215
|
+
reason = args.reason or "Added manually"
|
|
216
|
+
|
|
217
|
+
if not target_dir.is_dir():
|
|
218
|
+
print(colored(f"Error: Directory not found: {target_dir}", Colors.RED))
|
|
219
|
+
return 1
|
|
220
|
+
|
|
221
|
+
# Support shorthand
|
|
222
|
+
if not jsonl_name.endswith(".jsonl"):
|
|
223
|
+
jsonl_name = f"{jsonl_name}.jsonl"
|
|
224
|
+
|
|
225
|
+
jsonl_file = target_dir / jsonl_name
|
|
226
|
+
full_path = repo_root / path
|
|
227
|
+
|
|
228
|
+
entry_type = "file"
|
|
229
|
+
if full_path.is_dir():
|
|
230
|
+
entry_type = "directory"
|
|
231
|
+
if not path.endswith("/"):
|
|
232
|
+
path = f"{path}/"
|
|
233
|
+
elif not full_path.is_file():
|
|
234
|
+
print(colored(f"Error: Path not found: {path}", Colors.RED))
|
|
235
|
+
return 1
|
|
236
|
+
|
|
237
|
+
# Check if already exists
|
|
238
|
+
if jsonl_file.is_file():
|
|
239
|
+
content = jsonl_file.read_text(encoding="utf-8")
|
|
240
|
+
if f'"{path}"' in content:
|
|
241
|
+
print(colored(f"Warning: Entry already exists for {path}", Colors.YELLOW))
|
|
242
|
+
return 0
|
|
243
|
+
|
|
244
|
+
# Add entry
|
|
245
|
+
entry: dict
|
|
246
|
+
if entry_type == "directory":
|
|
247
|
+
entry = {"file": path, "type": "directory", "reason": reason}
|
|
248
|
+
else:
|
|
249
|
+
entry = {"file": path, "reason": reason}
|
|
250
|
+
|
|
251
|
+
with jsonl_file.open("a", encoding="utf-8") as f:
|
|
252
|
+
f.write(json.dumps(entry, ensure_ascii=False) + "\n")
|
|
253
|
+
|
|
254
|
+
print(colored(f"Added {entry_type}: {path}", Colors.GREEN))
|
|
255
|
+
return 0
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
# =============================================================================
|
|
259
|
+
# Command: validate
|
|
260
|
+
# =============================================================================
|
|
261
|
+
|
|
262
|
+
def cmd_validate(args: argparse.Namespace) -> int:
|
|
263
|
+
"""Validate JSONL context files."""
|
|
264
|
+
repo_root = get_repo_root()
|
|
265
|
+
target_dir = resolve_task_dir(args.dir, repo_root)
|
|
266
|
+
|
|
267
|
+
if not target_dir.is_dir():
|
|
268
|
+
print(colored("Error: task directory required", Colors.RED))
|
|
269
|
+
return 1
|
|
270
|
+
|
|
271
|
+
print(colored("=== Validating Context Files ===", Colors.BLUE))
|
|
272
|
+
print(f"Target dir: {target_dir}")
|
|
273
|
+
print()
|
|
274
|
+
|
|
275
|
+
total_errors = 0
|
|
276
|
+
for jsonl_name in ["implement.jsonl", "check.jsonl", "debug.jsonl"]:
|
|
277
|
+
jsonl_file = target_dir / jsonl_name
|
|
278
|
+
errors = _validate_jsonl(jsonl_file, repo_root)
|
|
279
|
+
total_errors += errors
|
|
280
|
+
|
|
281
|
+
print()
|
|
282
|
+
if total_errors == 0:
|
|
283
|
+
print(colored("✓ All validations passed", Colors.GREEN))
|
|
284
|
+
return 0
|
|
285
|
+
else:
|
|
286
|
+
print(colored(f"✗ Validation failed ({total_errors} errors)", Colors.RED))
|
|
287
|
+
return 1
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _validate_jsonl(jsonl_file: Path, repo_root: Path) -> int:
|
|
291
|
+
"""Validate a single JSONL file."""
|
|
292
|
+
file_name = jsonl_file.name
|
|
293
|
+
errors = 0
|
|
294
|
+
|
|
295
|
+
if not jsonl_file.is_file():
|
|
296
|
+
print(f" {colored(f'{file_name}: not found (skipped)', Colors.YELLOW)}")
|
|
297
|
+
return 0
|
|
298
|
+
|
|
299
|
+
line_num = 0
|
|
300
|
+
for line in jsonl_file.read_text(encoding="utf-8").splitlines():
|
|
301
|
+
line_num += 1
|
|
302
|
+
if not line.strip():
|
|
303
|
+
continue
|
|
304
|
+
|
|
305
|
+
try:
|
|
306
|
+
data = json.loads(line)
|
|
307
|
+
except json.JSONDecodeError:
|
|
308
|
+
print(f" {colored(f'{file_name}:{line_num}: Invalid JSON', Colors.RED)}")
|
|
309
|
+
errors += 1
|
|
310
|
+
continue
|
|
311
|
+
|
|
312
|
+
file_path = data.get("file")
|
|
313
|
+
entry_type = data.get("type", "file")
|
|
314
|
+
|
|
315
|
+
if not file_path:
|
|
316
|
+
print(f" {colored(f'{file_name}:{line_num}: Missing file field', Colors.RED)}")
|
|
317
|
+
errors += 1
|
|
318
|
+
continue
|
|
319
|
+
|
|
320
|
+
full_path = repo_root / file_path
|
|
321
|
+
if entry_type == "directory":
|
|
322
|
+
if not full_path.is_dir():
|
|
323
|
+
print(f" {colored(f'{file_name}:{line_num}: Directory not found: {file_path}', Colors.RED)}")
|
|
324
|
+
errors += 1
|
|
325
|
+
else:
|
|
326
|
+
if not full_path.is_file():
|
|
327
|
+
print(f" {colored(f'{file_name}:{line_num}: File not found: {file_path}', Colors.RED)}")
|
|
328
|
+
errors += 1
|
|
329
|
+
|
|
330
|
+
if errors == 0:
|
|
331
|
+
print(f" {colored(f'{file_name}: ✓ ({line_num} entries)', Colors.GREEN)}")
|
|
332
|
+
else:
|
|
333
|
+
print(f" {colored(f'{file_name}: ✗ ({errors} errors)', Colors.RED)}")
|
|
334
|
+
|
|
335
|
+
return errors
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
# =============================================================================
|
|
339
|
+
# Command: list-context
|
|
340
|
+
# =============================================================================
|
|
341
|
+
|
|
342
|
+
def cmd_list_context(args: argparse.Namespace) -> int:
|
|
343
|
+
"""List JSONL context entries."""
|
|
344
|
+
repo_root = get_repo_root()
|
|
345
|
+
target_dir = resolve_task_dir(args.dir, repo_root)
|
|
346
|
+
|
|
347
|
+
if not target_dir.is_dir():
|
|
348
|
+
print(colored("Error: task directory required", Colors.RED))
|
|
349
|
+
return 1
|
|
350
|
+
|
|
351
|
+
print(colored("=== Context Files ===", Colors.BLUE))
|
|
352
|
+
print()
|
|
353
|
+
|
|
354
|
+
for jsonl_name in ["implement.jsonl", "check.jsonl", "debug.jsonl"]:
|
|
355
|
+
jsonl_file = target_dir / jsonl_name
|
|
356
|
+
if not jsonl_file.is_file():
|
|
357
|
+
continue
|
|
358
|
+
|
|
359
|
+
print(colored(f"[{jsonl_name}]", Colors.CYAN))
|
|
360
|
+
|
|
361
|
+
count = 0
|
|
362
|
+
for line in jsonl_file.read_text(encoding="utf-8").splitlines():
|
|
363
|
+
if not line.strip():
|
|
364
|
+
continue
|
|
365
|
+
|
|
366
|
+
try:
|
|
367
|
+
data = json.loads(line)
|
|
368
|
+
except json.JSONDecodeError:
|
|
369
|
+
continue
|
|
370
|
+
|
|
371
|
+
count += 1
|
|
372
|
+
file_path = data.get("file", "?")
|
|
373
|
+
entry_type = data.get("type", "file")
|
|
374
|
+
reason = data.get("reason", "-")
|
|
375
|
+
|
|
376
|
+
if entry_type == "directory":
|
|
377
|
+
print(f" {colored(f'{count}.', Colors.GREEN)} [DIR] {file_path}")
|
|
378
|
+
else:
|
|
379
|
+
print(f" {colored(f'{count}.', Colors.GREEN)} {file_path}")
|
|
380
|
+
print(f" {colored('→', Colors.YELLOW)} {reason}")
|
|
381
|
+
|
|
382
|
+
print()
|
|
383
|
+
|
|
384
|
+
return 0
|
|
@@ -12,23 +12,32 @@ Provides:
|
|
|
12
12
|
|
|
13
13
|
from __future__ import annotations
|
|
14
14
|
|
|
15
|
-
import json
|
|
16
15
|
from pathlib import Path
|
|
17
16
|
|
|
18
17
|
from .paths import (
|
|
19
|
-
FILE_TASK_JSON,
|
|
20
18
|
get_repo_root,
|
|
21
19
|
get_developer,
|
|
22
20
|
get_tasks_dir,
|
|
23
21
|
)
|
|
22
|
+
from .tasks import iter_active_tasks
|
|
24
23
|
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
# =============================================================================
|
|
26
|
+
# Internal helper
|
|
27
|
+
# =============================================================================
|
|
28
|
+
|
|
29
|
+
def _task_to_dict(t) -> dict:
|
|
30
|
+
"""Convert TaskInfo to the dict format callers expect."""
|
|
31
|
+
return {
|
|
32
|
+
"priority": t.priority,
|
|
33
|
+
"id": t.raw.get("id", ""),
|
|
34
|
+
"title": t.title,
|
|
35
|
+
"status": t.status,
|
|
36
|
+
"assignee": t.assignee or "-",
|
|
37
|
+
"dir": t.dir_name,
|
|
38
|
+
"children": list(t.children),
|
|
39
|
+
"parent": t.parent,
|
|
40
|
+
}
|
|
32
41
|
|
|
33
42
|
|
|
34
43
|
# =============================================================================
|
|
@@ -54,41 +63,10 @@ def list_tasks_by_status(
|
|
|
54
63
|
tasks_dir = get_tasks_dir(repo_root)
|
|
55
64
|
results = []
|
|
56
65
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
for d in tasks_dir.iterdir():
|
|
61
|
-
if not d.is_dir() or d.name == "archive":
|
|
62
|
-
continue
|
|
63
|
-
|
|
64
|
-
task_json = d / FILE_TASK_JSON
|
|
65
|
-
if not task_json.is_file():
|
|
66
|
-
continue
|
|
67
|
-
|
|
68
|
-
data = _read_json_file(task_json)
|
|
69
|
-
if not data:
|
|
66
|
+
for t in iter_active_tasks(tasks_dir):
|
|
67
|
+
if filter_status and t.status != filter_status:
|
|
70
68
|
continue
|
|
71
|
-
|
|
72
|
-
task_id = data.get("id", "")
|
|
73
|
-
title = data.get("title") or data.get("name", "")
|
|
74
|
-
priority = data.get("priority", "P2")
|
|
75
|
-
status = data.get("status", "planning")
|
|
76
|
-
assignee = data.get("assignee", "-")
|
|
77
|
-
|
|
78
|
-
# Apply filter
|
|
79
|
-
if filter_status and status != filter_status:
|
|
80
|
-
continue
|
|
81
|
-
|
|
82
|
-
results.append({
|
|
83
|
-
"priority": priority,
|
|
84
|
-
"id": task_id,
|
|
85
|
-
"title": title,
|
|
86
|
-
"status": status,
|
|
87
|
-
"assignee": assignee,
|
|
88
|
-
"dir": d.name,
|
|
89
|
-
"children": data.get("children", []),
|
|
90
|
-
"parent": data.get("parent"),
|
|
91
|
-
})
|
|
69
|
+
results.append(_task_to_dict(t))
|
|
92
70
|
|
|
93
71
|
return results
|
|
94
72
|
|
|
@@ -126,46 +104,12 @@ def list_tasks_by_assignee(
|
|
|
126
104
|
tasks_dir = get_tasks_dir(repo_root)
|
|
127
105
|
results = []
|
|
128
106
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
for d in tasks_dir.iterdir():
|
|
133
|
-
if not d.is_dir() or d.name == "archive":
|
|
134
|
-
continue
|
|
135
|
-
|
|
136
|
-
task_json = d / FILE_TASK_JSON
|
|
137
|
-
if not task_json.is_file():
|
|
107
|
+
for t in iter_active_tasks(tasks_dir):
|
|
108
|
+
if (t.assignee or "-") != assignee:
|
|
138
109
|
continue
|
|
139
|
-
|
|
140
|
-
data = _read_json_file(task_json)
|
|
141
|
-
if not data:
|
|
142
|
-
continue
|
|
143
|
-
|
|
144
|
-
task_assignee = data.get("assignee", "-")
|
|
145
|
-
|
|
146
|
-
# Apply assignee filter
|
|
147
|
-
if task_assignee != assignee:
|
|
110
|
+
if filter_status and t.status != filter_status:
|
|
148
111
|
continue
|
|
149
|
-
|
|
150
|
-
task_id = data.get("id", "")
|
|
151
|
-
title = data.get("title") or data.get("name", "")
|
|
152
|
-
priority = data.get("priority", "P2")
|
|
153
|
-
status = data.get("status", "planning")
|
|
154
|
-
|
|
155
|
-
# Apply status filter
|
|
156
|
-
if filter_status and status != filter_status:
|
|
157
|
-
continue
|
|
158
|
-
|
|
159
|
-
results.append({
|
|
160
|
-
"priority": priority,
|
|
161
|
-
"id": task_id,
|
|
162
|
-
"title": title,
|
|
163
|
-
"status": status,
|
|
164
|
-
"assignee": task_assignee,
|
|
165
|
-
"dir": d.name,
|
|
166
|
-
"children": data.get("children", []),
|
|
167
|
-
"parent": data.get("parent"),
|
|
168
|
-
})
|
|
112
|
+
results.append(_task_to_dict(t))
|
|
169
113
|
|
|
170
114
|
return results
|
|
171
115
|
|
|
@@ -211,24 +155,9 @@ def get_task_stats(repo_root: Path | None = None) -> dict[str, int]:
|
|
|
211
155
|
tasks_dir = get_tasks_dir(repo_root)
|
|
212
156
|
stats = {"P0": 0, "P1": 0, "P2": 0, "P3": 0, "Total": 0}
|
|
213
157
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
for d in tasks_dir.iterdir():
|
|
218
|
-
if not d.is_dir() or d.name == "archive":
|
|
219
|
-
continue
|
|
220
|
-
|
|
221
|
-
task_json = d / FILE_TASK_JSON
|
|
222
|
-
if not task_json.is_file():
|
|
223
|
-
continue
|
|
224
|
-
|
|
225
|
-
data = _read_json_file(task_json)
|
|
226
|
-
if not data:
|
|
227
|
-
continue
|
|
228
|
-
|
|
229
|
-
priority = data.get("priority", "P2")
|
|
230
|
-
if priority in stats:
|
|
231
|
-
stats[priority] += 1
|
|
158
|
+
for t in iter_active_tasks(tasks_dir):
|
|
159
|
+
if t.priority in stats:
|
|
160
|
+
stats[t.priority] += 1
|
|
232
161
|
stats["Total"] += 1
|
|
233
162
|
|
|
234
163
|
return stats
|