@mindfoldhq/trellis 0.5.0-beta.8 → 0.5.0-rc.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/README.md +60 -95
- package/dist/cli/index.js +7 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/init.d.ts +13 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +474 -210
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +295 -54
- package/dist/commands/update.js.map +1 -1
- package/dist/configurators/antigravity.d.ts.map +1 -1
- package/dist/configurators/antigravity.js +2 -8
- package/dist/configurators/antigravity.js.map +1 -1
- package/dist/configurators/claude.d.ts.map +1 -1
- package/dist/configurators/claude.js +4 -10
- package/dist/configurators/claude.js.map +1 -1
- package/dist/configurators/codebuddy.d.ts.map +1 -1
- package/dist/configurators/codebuddy.js +3 -3
- package/dist/configurators/codebuddy.js.map +1 -1
- package/dist/configurators/codex.d.ts.map +1 -1
- package/dist/configurators/codex.js +5 -13
- package/dist/configurators/codex.js.map +1 -1
- package/dist/configurators/copilot.d.ts.map +1 -1
- package/dist/configurators/copilot.js +5 -19
- package/dist/configurators/copilot.js.map +1 -1
- package/dist/configurators/cursor.d.ts.map +1 -1
- package/dist/configurators/cursor.js +3 -3
- package/dist/configurators/cursor.js.map +1 -1
- package/dist/configurators/droid.d.ts.map +1 -1
- package/dist/configurators/droid.js +3 -3
- package/dist/configurators/droid.js.map +1 -1
- package/dist/configurators/gemini.d.ts.map +1 -1
- package/dist/configurators/gemini.js +3 -5
- package/dist/configurators/gemini.js.map +1 -1
- package/dist/configurators/index.d.ts.map +1 -1
- package/dist/configurators/index.js +44 -55
- package/dist/configurators/index.js.map +1 -1
- package/dist/configurators/kilo.d.ts.map +1 -1
- package/dist/configurators/kilo.js +2 -8
- package/dist/configurators/kilo.js.map +1 -1
- package/dist/configurators/kiro.d.ts.map +1 -1
- package/dist/configurators/kiro.js +3 -3
- package/dist/configurators/kiro.js.map +1 -1
- package/dist/configurators/opencode.d.ts.map +1 -1
- package/dist/configurators/opencode.js +7 -4
- package/dist/configurators/opencode.js.map +1 -1
- package/dist/configurators/pi.d.ts +3 -0
- package/dist/configurators/pi.d.ts.map +1 -0
- package/dist/configurators/pi.js +44 -0
- package/dist/configurators/pi.js.map +1 -0
- package/dist/configurators/qoder.d.ts +7 -6
- package/dist/configurators/qoder.d.ts.map +1 -1
- package/dist/configurators/qoder.js +18 -12
- package/dist/configurators/qoder.js.map +1 -1
- package/dist/configurators/shared.d.ts +30 -6
- package/dist/configurators/shared.d.ts.map +1 -1
- package/dist/configurators/shared.js +65 -15
- package/dist/configurators/shared.js.map +1 -1
- package/dist/configurators/windsurf.d.ts.map +1 -1
- package/dist/configurators/windsurf.js +2 -8
- package/dist/configurators/windsurf.js.map +1 -1
- package/dist/constants/paths.d.ts +2 -0
- package/dist/constants/paths.d.ts.map +1 -1
- package/dist/constants/paths.js +2 -0
- package/dist/constants/paths.js.map +1 -1
- package/dist/migrations/manifests/0.5.0-beta.0.json +2 -0
- package/dist/migrations/manifests/0.5.0-beta.10.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.11.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.12.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.13.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.14.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.15.json +116 -0
- package/dist/migrations/manifests/0.5.0-beta.16.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.17.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.18.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.19.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.5.json +2 -0
- package/dist/migrations/manifests/0.5.0-beta.9.json +48 -0
- package/dist/migrations/manifests/0.5.0-rc.0.json +9 -0
- package/dist/templates/claude/agents/trellis-research.md +1 -1
- package/dist/templates/claude/settings.json +0 -4
- package/dist/templates/codebuddy/agents/trellis-research.md +1 -1
- package/dist/templates/codex/agents/trellis-research.toml +3 -2
- package/dist/templates/codex/hooks/session-start.py +126 -26
- package/dist/templates/codex/skills/finish-work/SKILL.md +41 -109
- package/dist/templates/codex/skills/start/SKILL.md +12 -9
- package/dist/templates/common/bundled-skills/trellis-meta/SKILL.md +73 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/add-project-local-conventions.md +83 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-agents.md +54 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-context-loading.md +81 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-hooks.md +57 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-skills-or-commands.md +78 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-spec-structure.md +83 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-task-lifecycle.md +90 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-workflow.md +64 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/overview.md +55 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/context-injection.md +68 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/generated-files.md +80 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/overview.md +51 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/spec-system.md +102 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/task-system.md +101 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/workflow.md +75 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/workspace-memory.md +71 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/agents.md +79 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/hooks-and-settings.md +69 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/overview.md +59 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/platform-map.md +74 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/skills-and-commands.md +83 -0
- package/dist/templates/common/commands/continue.md +9 -5
- package/dist/templates/common/commands/finish-work.md +34 -10
- package/dist/templates/common/index.d.ts +22 -2
- package/dist/templates/common/index.d.ts.map +1 -1
- package/dist/templates/common/index.js +53 -4
- package/dist/templates/common/index.js.map +1 -1
- package/dist/templates/common/skills/brainstorm.md +50 -4
- package/dist/templates/copilot/hooks/session-start.py +127 -30
- package/dist/templates/copilot/prompts/finish-work.prompt.md +44 -112
- package/dist/templates/copilot/prompts/start.prompt.md +12 -9
- package/dist/templates/cursor/agents/trellis-check.md +1 -1
- package/dist/templates/cursor/agents/trellis-implement.md +1 -1
- package/dist/templates/cursor/agents/trellis-research.md +2 -2
- package/dist/templates/cursor/hooks.json +7 -1
- package/dist/templates/droid/droids/trellis-research.md +1 -1
- package/dist/templates/extract.d.ts +6 -0
- package/dist/templates/extract.d.ts.map +1 -1
- package/dist/templates/extract.js +14 -0
- package/dist/templates/extract.js.map +1 -1
- package/dist/templates/gemini/agents/trellis-research.md +1 -1
- package/dist/templates/kiro/agents/trellis-research.json +1 -1
- package/dist/templates/markdown/agents.md +19 -12
- package/dist/templates/markdown/gitignore.txt +3 -0
- package/dist/templates/markdown/spec/guides/cross-platform-thinking-guide.md.txt +24 -0
- package/dist/templates/opencode/agents/trellis-check.md +1 -1
- package/dist/templates/opencode/agents/trellis-implement.md +7 -4
- package/dist/templates/opencode/agents/trellis-research.md +2 -2
- package/dist/templates/opencode/lib/trellis-context.js +100 -13
- package/dist/templates/opencode/plugins/inject-subagent-context.js +70 -5
- package/dist/templates/opencode/plugins/inject-workflow-state.js +38 -44
- package/dist/templates/opencode/plugins/session-start.js +76 -31
- package/dist/templates/pi/agents/trellis-check.md +28 -0
- package/dist/templates/pi/agents/trellis-implement.md +33 -0
- package/dist/templates/pi/agents/trellis-research.md +25 -0
- package/dist/templates/pi/extensions/trellis/index.ts.txt +997 -0
- package/dist/templates/pi/index.d.ts +5 -0
- package/dist/templates/pi/index.d.ts.map +1 -0
- package/dist/templates/pi/index.js +12 -0
- package/dist/templates/pi/index.js.map +1 -0
- package/dist/templates/pi/settings.json +12 -0
- package/dist/templates/qoder/agents/trellis-research.md +1 -1
- package/dist/templates/shared-hooks/index.d.ts +31 -0
- package/dist/templates/shared-hooks/index.d.ts.map +1 -1
- package/dist/templates/shared-hooks/index.js +59 -0
- package/dist/templates/shared-hooks/index.js.map +1 -1
- package/dist/templates/shared-hooks/inject-shell-session-context.py +180 -0
- package/dist/templates/shared-hooks/inject-subagent-context.py +156 -27
- package/dist/templates/shared-hooks/inject-workflow-state.py +85 -92
- package/dist/templates/shared-hooks/session-start.py +232 -36
- package/dist/templates/trellis/config.yaml +6 -0
- package/dist/templates/trellis/gitignore.txt +3 -0
- package/dist/templates/trellis/index.d.ts +1 -1
- package/dist/templates/trellis/index.d.ts.map +1 -1
- package/dist/templates/trellis/index.js +2 -2
- package/dist/templates/trellis/index.js.map +1 -1
- package/dist/templates/trellis/scripts/common/__init__.py +8 -0
- package/dist/templates/trellis/scripts/common/active_task.py +593 -0
- package/dist/templates/trellis/scripts/common/cli_adapter.py +72 -14
- package/dist/templates/trellis/scripts/common/paths.py +61 -58
- package/dist/templates/trellis/scripts/common/session_context.py +12 -0
- package/dist/templates/trellis/scripts/common/task_context.py +27 -194
- package/dist/templates/trellis/scripts/common/task_store.py +102 -26
- package/dist/templates/trellis/scripts/common/tasks.py +4 -1
- package/dist/templates/trellis/scripts/common/types.py +0 -2
- package/dist/templates/trellis/scripts/common/workflow_phase.py +15 -3
- package/dist/templates/trellis/scripts/task.py +99 -34
- package/dist/templates/trellis/workflow.md +332 -64
- package/dist/types/ai-tools.d.ts +12 -3
- package/dist/types/ai-tools.d.ts.map +1 -1
- package/dist/types/ai-tools.js +29 -0
- package/dist/types/ai-tools.js.map +1 -1
- package/dist/utils/file-writer.d.ts.map +1 -1
- package/dist/utils/file-writer.js +7 -2
- package/dist/utils/file-writer.js.map +1 -1
- package/dist/utils/posix.d.ts +13 -0
- package/dist/utils/posix.d.ts.map +1 -0
- package/dist/utils/posix.js +15 -0
- package/dist/utils/posix.js.map +1 -0
- package/dist/utils/project-detector.d.ts +2 -0
- package/dist/utils/project-detector.d.ts.map +1 -1
- package/dist/utils/project-detector.js +120 -11
- package/dist/utils/project-detector.js.map +1 -1
- package/dist/utils/task-json.d.ts +46 -0
- package/dist/utils/task-json.d.ts.map +1 -0
- package/dist/utils/task-json.js +49 -0
- package/dist/utils/task-json.js.map +1 -0
- package/dist/utils/template-fetcher.d.ts +22 -6
- package/dist/utils/template-fetcher.d.ts.map +1 -1
- package/dist/utils/template-fetcher.js +405 -27
- package/dist/utils/template-fetcher.js.map +1 -1
- package/dist/utils/template-hash.d.ts +22 -3
- package/dist/utils/template-hash.d.ts.map +1 -1
- package/dist/utils/template-hash.js +99 -19
- package/dist/utils/template-hash.js.map +1 -1
- package/package.json +7 -7
- package/dist/templates/markdown/spec/backend/directory-structure.md +0 -292
- package/dist/templates/markdown/spec/backend/index.md +0 -40
- package/dist/templates/markdown/spec/backend/script-conventions.md +0 -742
- package/dist/templates/markdown/spec/guides/code-reuse-thinking-guide.md +0 -118
- package/dist/templates/markdown/spec/guides/cross-platform-thinking-guide.md +0 -394
- package/dist/templates/shared-hooks/statusline.py +0 -218
- package/dist/templates/trellis/scripts/create_bootstrap.py +0 -298
|
@@ -3,210 +3,29 @@
|
|
|
3
3
|
Task JSONL context management.
|
|
4
4
|
|
|
5
5
|
Provides:
|
|
6
|
-
cmd_init_context - Initialize JSONL context files for a task
|
|
7
6
|
cmd_add_context - Add entry to JSONL context file
|
|
8
7
|
cmd_validate - Validate JSONL context files
|
|
9
8
|
cmd_list_context - List JSONL context entries
|
|
9
|
+
|
|
10
|
+
Note:
|
|
11
|
+
``cmd_init_context`` was removed in v0.5.0-beta.12. JSONL context files
|
|
12
|
+
are now seeded at ``task.py create`` time with a self-describing
|
|
13
|
+
``_example`` line; the AI agent curates real entries during Phase 1.3 of
|
|
14
|
+
the workflow. See ``.trellis/workflow.md`` Phase 1.3 for the current
|
|
15
|
+
instructions.
|
|
10
16
|
"""
|
|
11
17
|
|
|
12
18
|
from __future__ import annotations
|
|
13
19
|
|
|
14
20
|
import argparse
|
|
15
21
|
import json
|
|
16
|
-
import sys
|
|
17
22
|
from pathlib import Path
|
|
18
23
|
|
|
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
24
|
from .log import Colors, colored
|
|
28
|
-
from .paths import
|
|
29
|
-
DIR_SPEC,
|
|
30
|
-
DIR_WORKFLOW,
|
|
31
|
-
FILE_TASK_JSON,
|
|
32
|
-
get_repo_root,
|
|
33
|
-
)
|
|
25
|
+
from .paths import get_repo_root
|
|
34
26
|
from .task_utils import resolve_task_dir
|
|
35
27
|
|
|
36
28
|
|
|
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
|
-
return [
|
|
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
|
-
|
|
74
|
-
def _write_jsonl(path: Path, entries: list[dict]) -> None:
|
|
75
|
-
"""Write entries to JSONL file."""
|
|
76
|
-
lines = [json.dumps(entry, ensure_ascii=False) for entry in entries]
|
|
77
|
-
path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
# =============================================================================
|
|
81
|
-
# Command: init-context
|
|
82
|
-
# =============================================================================
|
|
83
|
-
|
|
84
|
-
def cmd_init_context(args: argparse.Namespace) -> int:
|
|
85
|
-
"""Initialize JSONL context files for a task."""
|
|
86
|
-
repo_root = get_repo_root()
|
|
87
|
-
target_dir = resolve_task_dir(args.dir, repo_root)
|
|
88
|
-
dev_type = args.type
|
|
89
|
-
|
|
90
|
-
if not dev_type:
|
|
91
|
-
print(colored("Error: Missing arguments", Colors.RED))
|
|
92
|
-
print("Usage: python3 task.py init-context <task-dir> <dev_type>")
|
|
93
|
-
print(" dev_type: backend | frontend | fullstack | test | docs")
|
|
94
|
-
return 1
|
|
95
|
-
|
|
96
|
-
if not target_dir.is_dir():
|
|
97
|
-
print(colored(f"Error: Directory not found: {target_dir}", Colors.RED))
|
|
98
|
-
return 1
|
|
99
|
-
|
|
100
|
-
# Resolve package: --package CLI → task.json.package → default_package
|
|
101
|
-
cli_package: str | None = getattr(args, "package", None)
|
|
102
|
-
package: str | None = None
|
|
103
|
-
if not is_monorepo(repo_root):
|
|
104
|
-
# Single-repo: ignore --package, no package prefix
|
|
105
|
-
if cli_package:
|
|
106
|
-
print(colored("Warning: --package ignored in single-repo project", Colors.YELLOW), file=sys.stderr)
|
|
107
|
-
elif cli_package:
|
|
108
|
-
if not validate_package(cli_package, repo_root):
|
|
109
|
-
packages = get_packages(repo_root)
|
|
110
|
-
available = ", ".join(sorted(packages.keys())) if packages else "(none)"
|
|
111
|
-
print(colored(f"Error: unknown package '{cli_package}'. Available: {available}", Colors.RED), file=sys.stderr)
|
|
112
|
-
return 1
|
|
113
|
-
package = cli_package
|
|
114
|
-
else:
|
|
115
|
-
# Read task.json.package as inferred source
|
|
116
|
-
task_json_path = target_dir / FILE_TASK_JSON
|
|
117
|
-
task_pkg_value = None
|
|
118
|
-
if task_json_path.is_file():
|
|
119
|
-
task_data = read_json(task_json_path)
|
|
120
|
-
if isinstance(task_data, dict):
|
|
121
|
-
task_pkg_value = task_data.get("package")
|
|
122
|
-
# Only pass string values to resolve_package (guard against malformed JSON)
|
|
123
|
-
task_package = task_pkg_value if isinstance(task_pkg_value, str) else None
|
|
124
|
-
package = resolve_package(task_package=task_package, repo_root=repo_root)
|
|
125
|
-
|
|
126
|
-
# Monorepo fallback prohibition
|
|
127
|
-
if package is None:
|
|
128
|
-
packages = get_packages(repo_root)
|
|
129
|
-
available = ", ".join(sorted(packages.keys())) if packages else "(none)"
|
|
130
|
-
print(colored(
|
|
131
|
-
f"Error: monorepo project requires --package (or set default_package in config.yaml). Available: {available}",
|
|
132
|
-
Colors.RED,
|
|
133
|
-
), file=sys.stderr)
|
|
134
|
-
return 1
|
|
135
|
-
|
|
136
|
-
print(colored("=== Initializing Agent Context Files ===", Colors.BLUE))
|
|
137
|
-
print(f"Target dir: {target_dir}")
|
|
138
|
-
print(f"Dev type: {dev_type}")
|
|
139
|
-
if package:
|
|
140
|
-
print(f"Package: {package}")
|
|
141
|
-
print()
|
|
142
|
-
|
|
143
|
-
# implement.jsonl
|
|
144
|
-
print(colored("Creating implement.jsonl...", Colors.CYAN))
|
|
145
|
-
implement_entries = get_implement_base()
|
|
146
|
-
if dev_type in ("backend", "test"):
|
|
147
|
-
implement_entries.extend(get_implement_backend(package))
|
|
148
|
-
elif dev_type == "frontend":
|
|
149
|
-
implement_entries.extend(get_implement_frontend(package))
|
|
150
|
-
elif dev_type == "fullstack":
|
|
151
|
-
implement_entries.extend(get_implement_backend(package))
|
|
152
|
-
implement_entries.extend(get_implement_frontend(package))
|
|
153
|
-
|
|
154
|
-
implement_file = target_dir / "implement.jsonl"
|
|
155
|
-
_write_jsonl(implement_file, implement_entries)
|
|
156
|
-
print(f" {colored('✓', Colors.GREEN)} {len(implement_entries)} entries")
|
|
157
|
-
|
|
158
|
-
# check.jsonl
|
|
159
|
-
print(colored("Creating check.jsonl...", Colors.CYAN))
|
|
160
|
-
check_entries = get_check_context(repo_root)
|
|
161
|
-
check_file = target_dir / "check.jsonl"
|
|
162
|
-
_write_jsonl(check_file, check_entries)
|
|
163
|
-
print(f" {colored('✓', Colors.GREEN)} {len(check_entries)} entries")
|
|
164
|
-
|
|
165
|
-
# Update task.json dev_type and package
|
|
166
|
-
task_json_path = target_dir / FILE_TASK_JSON
|
|
167
|
-
if task_json_path.is_file():
|
|
168
|
-
task_data = read_json(task_json_path)
|
|
169
|
-
if isinstance(task_data, dict):
|
|
170
|
-
task_data["dev_type"] = dev_type
|
|
171
|
-
task_data["package"] = package # Always sync to match resolved value
|
|
172
|
-
write_json(task_json_path, task_data)
|
|
173
|
-
|
|
174
|
-
print()
|
|
175
|
-
print(colored("✓ All context files created", Colors.GREEN))
|
|
176
|
-
print()
|
|
177
|
-
|
|
178
|
-
# Show what was auto-injected
|
|
179
|
-
all_injected = [e["file"] for e in implement_entries]
|
|
180
|
-
print(colored("Auto-injected (defaults only):", Colors.YELLOW))
|
|
181
|
-
for f in all_injected:
|
|
182
|
-
print(f" - {f}")
|
|
183
|
-
print()
|
|
184
|
-
|
|
185
|
-
# Scan spec directory for available spec files the AI should consider
|
|
186
|
-
spec_base = repo_root / DIR_WORKFLOW / DIR_SPEC
|
|
187
|
-
if package:
|
|
188
|
-
spec_base = spec_base / package
|
|
189
|
-
available_specs: list[str] = []
|
|
190
|
-
if spec_base.is_dir():
|
|
191
|
-
for md_file in sorted(spec_base.rglob("*.md")):
|
|
192
|
-
rel = str(md_file.relative_to(repo_root))
|
|
193
|
-
if rel not in all_injected:
|
|
194
|
-
available_specs.append(rel)
|
|
195
|
-
|
|
196
|
-
if available_specs:
|
|
197
|
-
print(colored("Available spec files (not yet injected):", Colors.BLUE))
|
|
198
|
-
for spec in available_specs:
|
|
199
|
-
print(f" - {spec}")
|
|
200
|
-
print()
|
|
201
|
-
|
|
202
|
-
print(colored("Next steps:", Colors.BLUE))
|
|
203
|
-
print(" 1. Review the spec files above and add relevant ones for your task:")
|
|
204
|
-
print(f" python3 task.py add-context <dir> implement <spec-path> \"<reason>\"")
|
|
205
|
-
print(" 2. Set as current: python3 task.py start <dir>")
|
|
206
|
-
|
|
207
|
-
return 0
|
|
208
|
-
|
|
209
|
-
|
|
210
29
|
# =============================================================================
|
|
211
30
|
# Command: add-context
|
|
212
31
|
# =============================================================================
|
|
@@ -294,7 +113,11 @@ def cmd_validate(args: argparse.Namespace) -> int:
|
|
|
294
113
|
|
|
295
114
|
|
|
296
115
|
def _validate_jsonl(jsonl_file: Path, repo_root: Path) -> int:
|
|
297
|
-
"""Validate a single JSONL file.
|
|
116
|
+
"""Validate a single JSONL file.
|
|
117
|
+
|
|
118
|
+
Seed rows (no ``file`` field — typically ``{"_example": "..."}``) are
|
|
119
|
+
skipped silently; they are self-describing comments, not real entries.
|
|
120
|
+
"""
|
|
298
121
|
file_name = jsonl_file.name
|
|
299
122
|
errors = 0
|
|
300
123
|
|
|
@@ -303,6 +126,7 @@ def _validate_jsonl(jsonl_file: Path, repo_root: Path) -> int:
|
|
|
303
126
|
return 0
|
|
304
127
|
|
|
305
128
|
line_num = 0
|
|
129
|
+
real_entries = 0
|
|
306
130
|
for line in jsonl_file.read_text(encoding="utf-8").splitlines():
|
|
307
131
|
line_num += 1
|
|
308
132
|
if not line.strip():
|
|
@@ -319,10 +143,10 @@ def _validate_jsonl(jsonl_file: Path, repo_root: Path) -> int:
|
|
|
319
143
|
entry_type = data.get("type", "file")
|
|
320
144
|
|
|
321
145
|
if not file_path:
|
|
322
|
-
|
|
323
|
-
errors += 1
|
|
146
|
+
# Seed / comment row — skip silently
|
|
324
147
|
continue
|
|
325
148
|
|
|
149
|
+
real_entries += 1
|
|
326
150
|
full_path = repo_root / file_path
|
|
327
151
|
if entry_type == "directory":
|
|
328
152
|
if not full_path.is_dir():
|
|
@@ -334,7 +158,7 @@ def _validate_jsonl(jsonl_file: Path, repo_root: Path) -> int:
|
|
|
334
158
|
errors += 1
|
|
335
159
|
|
|
336
160
|
if errors == 0:
|
|
337
|
-
print(f" {colored(f'{file_name}: ✓ ({
|
|
161
|
+
print(f" {colored(f'{file_name}: ✓ ({real_entries} entries)', Colors.GREEN)}")
|
|
338
162
|
else:
|
|
339
163
|
print(f" {colored(f'{file_name}: ✗ ({errors} errors)', Colors.RED)}")
|
|
340
164
|
|
|
@@ -365,6 +189,7 @@ def cmd_list_context(args: argparse.Namespace) -> int:
|
|
|
365
189
|
print(colored(f"[{jsonl_name}]", Colors.CYAN))
|
|
366
190
|
|
|
367
191
|
count = 0
|
|
192
|
+
seed_only = True
|
|
368
193
|
for line in jsonl_file.read_text(encoding="utf-8").splitlines():
|
|
369
194
|
if not line.strip():
|
|
370
195
|
continue
|
|
@@ -374,8 +199,13 @@ def cmd_list_context(args: argparse.Namespace) -> int:
|
|
|
374
199
|
except json.JSONDecodeError:
|
|
375
200
|
continue
|
|
376
201
|
|
|
202
|
+
file_path = data.get("file")
|
|
203
|
+
if not file_path:
|
|
204
|
+
# Seed / comment row — don't count as a real entry
|
|
205
|
+
continue
|
|
206
|
+
seed_only = False
|
|
207
|
+
|
|
377
208
|
count += 1
|
|
378
|
-
file_path = data.get("file", "?")
|
|
379
209
|
entry_type = data.get("type", "file")
|
|
380
210
|
reason = data.get("reason", "-")
|
|
381
211
|
|
|
@@ -385,6 +215,9 @@ def cmd_list_context(args: argparse.Namespace) -> int:
|
|
|
385
215
|
print(f" {colored(f'{count}.', Colors.GREEN)} {file_path}")
|
|
386
216
|
print(f" {colored('→', Colors.YELLOW)} {reason}")
|
|
387
217
|
|
|
218
|
+
if seed_only:
|
|
219
|
+
print(f" {colored('(no curated entries yet — only seed row)', Colors.YELLOW)}")
|
|
220
|
+
|
|
388
221
|
print()
|
|
389
222
|
|
|
390
223
|
return 0
|
|
@@ -16,6 +16,7 @@ Provides:
|
|
|
16
16
|
from __future__ import annotations
|
|
17
17
|
|
|
18
18
|
import argparse
|
|
19
|
+
import json
|
|
19
20
|
import re
|
|
20
21
|
import sys
|
|
21
22
|
from datetime import datetime
|
|
@@ -35,9 +36,7 @@ from .paths import (
|
|
|
35
36
|
DIR_TASKS,
|
|
36
37
|
DIR_WORKFLOW,
|
|
37
38
|
FILE_TASK_JSON,
|
|
38
|
-
clear_current_task,
|
|
39
39
|
generate_task_date_prefix,
|
|
40
|
-
get_current_task,
|
|
41
40
|
get_developer,
|
|
42
41
|
get_repo_root,
|
|
43
42
|
get_tasks_dir,
|
|
@@ -78,6 +77,61 @@ def ensure_tasks_dir(repo_root: Path) -> Path:
|
|
|
78
77
|
return tasks_dir
|
|
79
78
|
|
|
80
79
|
|
|
80
|
+
# =============================================================================
|
|
81
|
+
# Sub-agent platform detection + JSONL seeding
|
|
82
|
+
# =============================================================================
|
|
83
|
+
|
|
84
|
+
# Config directories of platforms that consume implement.jsonl / check.jsonl.
|
|
85
|
+
# Keep in sync with src/types/ai-tools.ts AI_TOOLS entries — these are the
|
|
86
|
+
# platforms listed in workflow.md's "agent-capable" Skill Routing block
|
|
87
|
+
# (Class-1 hook-inject + Class-2 pull-based preludes). Kilo / Antigravity /
|
|
88
|
+
# Windsurf are NOT in this list: they do not consume JSONL.
|
|
89
|
+
_SUBAGENT_CONFIG_DIRS: tuple[str, ...] = (
|
|
90
|
+
".claude",
|
|
91
|
+
".cursor",
|
|
92
|
+
".codex",
|
|
93
|
+
".kiro",
|
|
94
|
+
".gemini",
|
|
95
|
+
".opencode",
|
|
96
|
+
".qoder",
|
|
97
|
+
".codebuddy",
|
|
98
|
+
".factory", # Factory Droid
|
|
99
|
+
".github/copilot",
|
|
100
|
+
".pi", # Pi Agent
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
_SEED_EXAMPLE = (
|
|
104
|
+
"Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. "
|
|
105
|
+
"Put spec/research files only — no code paths. "
|
|
106
|
+
"Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. "
|
|
107
|
+
"Delete this line once real entries are added."
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _has_subagent_platform(repo_root: Path) -> bool:
|
|
112
|
+
"""Return True if any sub-agent-capable platform is configured.
|
|
113
|
+
|
|
114
|
+
Detected by probing well-known config directories at the repo root. Used
|
|
115
|
+
only to decide whether ``task.py create`` should seed empty
|
|
116
|
+
``implement.jsonl`` / ``check.jsonl`` files.
|
|
117
|
+
"""
|
|
118
|
+
for config_dir in _SUBAGENT_CONFIG_DIRS:
|
|
119
|
+
if (repo_root / config_dir).is_dir():
|
|
120
|
+
return True
|
|
121
|
+
return False
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _write_seed_jsonl(path: Path) -> None:
|
|
125
|
+
"""Write a one-line seed JSONL file with a self-describing ``_example``.
|
|
126
|
+
|
|
127
|
+
The seed row has no ``file`` field, so downstream consumers (hooks +
|
|
128
|
+
preludes) that iterate entries via ``item.get("file")`` naturally skip
|
|
129
|
+
it. The row exists purely as an in-file prompt for the AI curator.
|
|
130
|
+
"""
|
|
131
|
+
seed = {"_example": _SEED_EXAMPLE}
|
|
132
|
+
path.write_text(json.dumps(seed, ensure_ascii=False) + "\n", encoding="utf-8")
|
|
133
|
+
|
|
134
|
+
|
|
81
135
|
# =============================================================================
|
|
82
136
|
# Command: create
|
|
83
137
|
# =============================================================================
|
|
@@ -173,6 +227,18 @@ def cmd_create(args: argparse.Namespace) -> int:
|
|
|
173
227
|
|
|
174
228
|
write_json(task_json_path, task_data)
|
|
175
229
|
|
|
230
|
+
# Seed implement.jsonl / check.jsonl for sub-agent-capable platforms.
|
|
231
|
+
# Agent curates real entries in Phase 1.3 (see .trellis/workflow.md).
|
|
232
|
+
# Agent-less platforms (Kilo / Antigravity / Windsurf) skip this — they
|
|
233
|
+
# load specs via the trellis-before-dev skill instead of JSONL.
|
|
234
|
+
seeded_jsonl = False
|
|
235
|
+
if _has_subagent_platform(repo_root):
|
|
236
|
+
for jsonl_name in ("implement.jsonl", "check.jsonl"):
|
|
237
|
+
jsonl_path = task_dir / jsonl_name
|
|
238
|
+
if not jsonl_path.exists():
|
|
239
|
+
_write_seed_jsonl(jsonl_path)
|
|
240
|
+
seeded_jsonl = True
|
|
241
|
+
|
|
176
242
|
# Handle --parent: establish bidirectional link
|
|
177
243
|
if args.parent:
|
|
178
244
|
parent_dir = resolve_task_dir(args.parent, repo_root)
|
|
@@ -195,12 +261,35 @@ def cmd_create(args: argparse.Namespace) -> int:
|
|
|
195
261
|
|
|
196
262
|
print(colored(f"Linked as child of: {parent_dir.name}", Colors.GREEN), file=sys.stderr)
|
|
197
263
|
|
|
264
|
+
# Auto-activate the new task so the per-turn breadcrumb fires planning
|
|
265
|
+
# state. Best-effort: gracefully degrade if no session identity (CLI run
|
|
266
|
+
# outside an AI session) — the task is still created, the user can run
|
|
267
|
+
# task.py start later. Pointer is session-scoped so this never affects
|
|
268
|
+
# other AI sessions.
|
|
269
|
+
try:
|
|
270
|
+
from .active_task import resolve_context_key, set_active_task
|
|
271
|
+
if resolve_context_key():
|
|
272
|
+
try:
|
|
273
|
+
rel_dir = task_dir.relative_to(repo_root).as_posix()
|
|
274
|
+
except ValueError:
|
|
275
|
+
rel_dir = str(task_dir)
|
|
276
|
+
set_active_task(rel_dir, repo_root)
|
|
277
|
+
except Exception:
|
|
278
|
+
pass
|
|
279
|
+
|
|
198
280
|
print(colored(f"Created task: {dir_name}", Colors.GREEN), file=sys.stderr)
|
|
199
281
|
print("", file=sys.stderr)
|
|
200
282
|
print(colored("Next steps:", Colors.BLUE), file=sys.stderr)
|
|
201
283
|
print(" 1. Create prd.md with requirements", file=sys.stderr)
|
|
202
|
-
|
|
203
|
-
|
|
284
|
+
if seeded_jsonl:
|
|
285
|
+
print(
|
|
286
|
+
" 2. Curate implement.jsonl / check.jsonl (spec + research files only — "
|
|
287
|
+
"see .trellis/workflow.md Phase 1.3)",
|
|
288
|
+
file=sys.stderr,
|
|
289
|
+
)
|
|
290
|
+
print(" 3. Run: python3 task.py start <dir>", file=sys.stderr)
|
|
291
|
+
else:
|
|
292
|
+
print(" 2. Run: python3 task.py start <dir>", file=sys.stderr)
|
|
204
293
|
print("", file=sys.stderr)
|
|
205
294
|
|
|
206
295
|
# Output relative path for script chaining
|
|
@@ -225,8 +314,8 @@ def cmd_archive(args: argparse.Namespace) -> int:
|
|
|
225
314
|
|
|
226
315
|
tasks_dir = get_tasks_dir(repo_root)
|
|
227
316
|
|
|
228
|
-
#
|
|
229
|
-
task_dir =
|
|
317
|
+
# Resolve task directory (supports task name, relative path, or absolute path)
|
|
318
|
+
task_dir = resolve_task_dir(task_name, repo_root)
|
|
230
319
|
|
|
231
320
|
if not task_dir or not task_dir.is_dir():
|
|
232
321
|
print(colored(f"Error: Task not found: {task_name}", Colors.RED), file=sys.stderr)
|
|
@@ -249,24 +338,12 @@ def cmd_archive(args: argparse.Namespace) -> int:
|
|
|
249
338
|
data["completedAt"] = today
|
|
250
339
|
write_json(task_json_path, data)
|
|
251
340
|
|
|
252
|
-
# Handle subtask relationships on archive
|
|
253
|
-
|
|
341
|
+
# Handle subtask relationships on archive.
|
|
342
|
+
# Keep this task in its parent's children list so progress
|
|
343
|
+
# counters (children_progress) stay consistent — children
|
|
344
|
+
# missing from the active set are treated as completed.
|
|
254
345
|
task_children = data.get("children", [])
|
|
255
346
|
|
|
256
|
-
# If this is a child, remove from parent's children list
|
|
257
|
-
if task_parent:
|
|
258
|
-
parent_dir = find_task_by_name(task_parent, tasks_dir)
|
|
259
|
-
if parent_dir:
|
|
260
|
-
parent_json = parent_dir / FILE_TASK_JSON
|
|
261
|
-
if parent_json.is_file():
|
|
262
|
-
parent_data = read_json(parent_json)
|
|
263
|
-
if parent_data:
|
|
264
|
-
parent_children = parent_data.get("children", [])
|
|
265
|
-
if dir_name in parent_children:
|
|
266
|
-
parent_children.remove(dir_name)
|
|
267
|
-
parent_data["children"] = parent_children
|
|
268
|
-
write_json(parent_json, parent_data)
|
|
269
|
-
|
|
270
347
|
# If this is a parent, clear parent field in all children
|
|
271
348
|
if task_children:
|
|
272
349
|
for child_name in task_children:
|
|
@@ -279,10 +356,9 @@ def cmd_archive(args: argparse.Namespace) -> int:
|
|
|
279
356
|
child_data["parent"] = None
|
|
280
357
|
write_json(child_json, child_data)
|
|
281
358
|
|
|
282
|
-
# Clear
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
clear_current_task(repo_root)
|
|
359
|
+
# Clear any session that still points at this task before the path moves.
|
|
360
|
+
from .active_task import clear_task_from_sessions
|
|
361
|
+
clear_task_from_sessions(str(task_dir), repo_root)
|
|
286
362
|
|
|
287
363
|
# Archive
|
|
288
364
|
result = archive_task_complete(task_dir, repo_root)
|
|
@@ -102,8 +102,11 @@ def children_progress(
|
|
|
102
102
|
"""
|
|
103
103
|
if not children:
|
|
104
104
|
return ""
|
|
105
|
+
# A child missing from active statuses has been archived (cmd_archive
|
|
106
|
+
# sets status=completed before moving the dir). Count it as done so
|
|
107
|
+
# parent progress doesn't regress when children are archived.
|
|
105
108
|
done = sum(
|
|
106
109
|
1 for c in children
|
|
107
|
-
if all_statuses.get(c) in ("completed", "done")
|
|
110
|
+
if c not in all_statuses or all_statuses.get(c) in ("completed", "done")
|
|
108
111
|
)
|
|
109
112
|
return f" [{done}/{len(children)} done]"
|
|
@@ -65,7 +65,10 @@ def get_phase_index() -> str:
|
|
|
65
65
|
Matches what the SessionStart hook injects into the `<workflow>` block:
|
|
66
66
|
starts at `## Phase Index`, continues through `## Phase 1: Plan`,
|
|
67
67
|
`## Phase 2: Execute`, `## Phase 3: Finish`, stops at
|
|
68
|
-
`##
|
|
68
|
+
`## Customizing Trellis (for forks)` (the docs-for-forks footer).
|
|
69
|
+
`[workflow-state:STATUS]` tag blocks (now embedded in Phase Index since
|
|
70
|
+
v0.5.0-rc.0) are consumed by the UserPromptSubmit hook so they're
|
|
71
|
+
stripped from this output.
|
|
69
72
|
"""
|
|
70
73
|
text = _read_workflow()
|
|
71
74
|
lines = text.splitlines()
|
|
@@ -77,7 +80,7 @@ def get_phase_index() -> str:
|
|
|
77
80
|
if start is None and stripped == _PHASE_INDEX_HEADING:
|
|
78
81
|
start = i
|
|
79
82
|
continue
|
|
80
|
-
if start is not None and stripped == "##
|
|
83
|
+
if start is not None and stripped == "## Customizing Trellis (for forks)":
|
|
81
84
|
end = i
|
|
82
85
|
break
|
|
83
86
|
|
|
@@ -85,7 +88,16 @@ def get_phase_index() -> str:
|
|
|
85
88
|
return ""
|
|
86
89
|
if end is None:
|
|
87
90
|
end = len(lines)
|
|
88
|
-
|
|
91
|
+
|
|
92
|
+
section = "\n".join(lines[start:end]).rstrip()
|
|
93
|
+
# Strip [workflow-state:STATUS]...[/workflow-state:STATUS] blocks since
|
|
94
|
+
# they're injected separately by inject-workflow-state.py per-turn.
|
|
95
|
+
import re as _re
|
|
96
|
+
tag_re = _re.compile(
|
|
97
|
+
r"\[workflow-state:([A-Za-z0-9_-]+)\]\s*\n.*?\n\s*\[/workflow-state:\1\]\n?",
|
|
98
|
+
_re.DOTALL,
|
|
99
|
+
)
|
|
100
|
+
return tag_re.sub("", section).rstrip() + "\n"
|
|
89
101
|
|
|
90
102
|
|
|
91
103
|
def get_step(step_id: str) -> str:
|