@mindfoldhq/trellis 0.4.0-beta.8 → 0.4.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 +10 -5
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +165 -13
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +14 -2
- package/dist/commands/update.js.map +1 -1
- package/dist/configurators/codex.d.ts.map +1 -1
- package/dist/configurators/codex.js +2 -1
- package/dist/configurators/codex.js.map +1 -1
- package/dist/configurators/copilot.d.ts +9 -0
- package/dist/configurators/copilot.d.ts.map +1 -0
- package/dist/configurators/copilot.js +34 -0
- package/dist/configurators/copilot.js.map +1 -0
- package/dist/configurators/index.d.ts.map +1 -1
- package/dist/configurators/index.js +32 -1
- package/dist/configurators/index.js.map +1 -1
- package/dist/configurators/windsurf.d.ts +8 -0
- package/dist/configurators/windsurf.d.ts.map +1 -0
- package/dist/configurators/windsurf.js +18 -0
- package/dist/configurators/windsurf.js.map +1 -0
- package/dist/migrations/manifests/0.4.0-beta.10.json +9 -0
- package/dist/migrations/manifests/0.4.0-beta.9.json +9 -0
- package/dist/migrations/manifests/0.4.0-rc.0.json +9 -0
- package/dist/templates/claude/hooks/inject-subagent-context.py +8 -1
- package/dist/templates/claude/hooks/ralph-loop.py +18 -10
- package/dist/templates/claude/hooks/session-start.py +60 -19
- package/dist/templates/claude/hooks/statusline.py +218 -0
- package/dist/templates/claude/settings.json +4 -0
- package/dist/templates/codex/hooks/session-start.py +60 -21
- package/dist/templates/codex/hooks.json +1 -1
- package/dist/templates/copilot/hooks/session-start.py +243 -0
- package/dist/templates/copilot/hooks.json +11 -0
- package/dist/templates/copilot/index.d.ts +23 -0
- package/dist/templates/copilot/index.d.ts.map +1 -0
- package/dist/templates/copilot/index.js +54 -0
- package/dist/templates/copilot/index.js.map +1 -0
- package/dist/templates/copilot/prompts/before-dev.prompt.md +33 -0
- package/dist/templates/copilot/prompts/brainstorm.prompt.md +491 -0
- package/dist/templates/copilot/prompts/break-loop.prompt.md +129 -0
- package/dist/templates/copilot/prompts/check-cross-layer.prompt.md +157 -0
- package/dist/templates/copilot/prompts/check.prompt.md +29 -0
- package/dist/templates/copilot/prompts/create-command.prompt.md +116 -0
- package/dist/templates/copilot/prompts/finish-work.prompt.md +157 -0
- package/dist/templates/copilot/prompts/integrate-skill.prompt.md +223 -0
- package/dist/templates/copilot/prompts/onboard.prompt.md +362 -0
- package/dist/templates/copilot/prompts/parallel.prompt.md +196 -0
- package/dist/templates/copilot/prompts/record-session.prompt.md +66 -0
- package/dist/templates/copilot/prompts/start.prompt.md +397 -0
- package/dist/templates/copilot/prompts/update-spec.prompt.md +358 -0
- package/dist/templates/extract.d.ts +18 -0
- package/dist/templates/extract.d.ts.map +1 -1
- package/dist/templates/extract.js +32 -0
- package/dist/templates/extract.js.map +1 -1
- package/dist/templates/iflow/hooks/inject-subagent-context.py +8 -1
- package/dist/templates/iflow/hooks/ralph-loop.py +8 -1
- package/dist/templates/iflow/hooks/session-start.py +60 -19
- package/dist/templates/markdown/spec/backend/directory-structure.md +1 -1
- package/dist/templates/opencode/agents/dispatch.md +20 -19
- package/dist/templates/opencode/lib/trellis-context.js +35 -239
- package/dist/templates/opencode/plugins/inject-subagent-context.js +71 -121
- package/dist/templates/opencode/plugins/session-start.js +150 -146
- package/dist/templates/trellis/scripts/add_session.py +6 -1
- package/dist/templates/trellis/scripts/common/__init__.py +2 -0
- package/dist/templates/trellis/scripts/common/cli_adapter.py +87 -9
- package/dist/templates/trellis/scripts/common/paths.py +57 -6
- package/dist/templates/trellis/scripts/common/task_store.py +6 -4
- package/dist/templates/trellis/scripts/common/task_utils.py +14 -8
- package/dist/templates/trellis/scripts/multi_agent/start.py +9 -5
- package/dist/templates/trellis/scripts/task.py +1 -1
- package/dist/templates/trellis/workflow.md +17 -4
- package/dist/templates/windsurf/index.d.ts +21 -0
- package/dist/templates/windsurf/index.d.ts.map +1 -0
- package/dist/templates/windsurf/index.js +44 -0
- package/dist/templates/windsurf/index.js.map +1 -0
- package/dist/templates/windsurf/workflows/trellis-before-dev.md +31 -0
- package/dist/templates/windsurf/workflows/trellis-brainstorm.md +491 -0
- package/dist/templates/windsurf/workflows/trellis-break-loop.md +111 -0
- package/dist/templates/windsurf/workflows/trellis-check-cross-layer.md +157 -0
- package/dist/templates/windsurf/workflows/trellis-check.md +27 -0
- package/dist/templates/windsurf/workflows/trellis-create-command.md +154 -0
- package/dist/templates/windsurf/workflows/trellis-finish-work.md +147 -0
- package/dist/templates/windsurf/workflows/trellis-integrate-skill.md +220 -0
- package/dist/templates/windsurf/workflows/trellis-onboard.md +362 -0
- package/dist/templates/windsurf/workflows/trellis-record-session.md +66 -0
- package/dist/templates/windsurf/workflows/trellis-start.md +373 -0
- package/dist/templates/windsurf/workflows/trellis-update-spec.md +358 -0
- package/dist/types/ai-tools.d.ts +5 -3
- package/dist/types/ai-tools.d.ts.map +1 -1
- package/dist/types/ai-tools.js +21 -1
- package/dist/types/ai-tools.js.map +1 -1
- package/dist/utils/template-fetcher.d.ts +17 -4
- package/dist/utils/template-fetcher.d.ts.map +1 -1
- package/dist/utils/template-fetcher.js +94 -12
- package/dist/utils/template-fetcher.js.map +1 -1
- package/package.json +1 -1
|
@@ -65,23 +65,47 @@ def run_script(script_path: Path) -> str:
|
|
|
65
65
|
return "No context available"
|
|
66
66
|
|
|
67
67
|
|
|
68
|
+
def _normalize_task_ref(task_ref: str) -> str:
|
|
69
|
+
normalized = task_ref.strip()
|
|
70
|
+
if not normalized:
|
|
71
|
+
return ""
|
|
72
|
+
|
|
73
|
+
path_obj = Path(normalized)
|
|
74
|
+
if path_obj.is_absolute():
|
|
75
|
+
return str(path_obj)
|
|
76
|
+
|
|
77
|
+
normalized = normalized.replace("\\", "/")
|
|
78
|
+
while normalized.startswith("./"):
|
|
79
|
+
normalized = normalized[2:]
|
|
80
|
+
|
|
81
|
+
if normalized.startswith("tasks/"):
|
|
82
|
+
return f".trellis/{normalized}"
|
|
83
|
+
|
|
84
|
+
return normalized
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _resolve_task_dir(trellis_dir: Path, task_ref: str) -> Path:
|
|
88
|
+
normalized = _normalize_task_ref(task_ref)
|
|
89
|
+
path_obj = Path(normalized)
|
|
90
|
+
if path_obj.is_absolute():
|
|
91
|
+
return path_obj
|
|
92
|
+
if normalized.startswith(".trellis/"):
|
|
93
|
+
return trellis_dir.parent / path_obj
|
|
94
|
+
return trellis_dir / "tasks" / path_obj
|
|
95
|
+
|
|
96
|
+
|
|
68
97
|
def _get_task_status(trellis_dir: Path) -> str:
|
|
69
98
|
"""Check current task status and return structured status string."""
|
|
70
99
|
current_task_file = trellis_dir / ".current-task"
|
|
71
100
|
if not current_task_file.is_file():
|
|
72
101
|
return "Status: NO ACTIVE TASK\nNext: Describe what you want to work on"
|
|
73
102
|
|
|
74
|
-
task_ref = current_task_file.read_text(encoding="utf-8").strip()
|
|
103
|
+
task_ref = _normalize_task_ref(current_task_file.read_text(encoding="utf-8").strip())
|
|
75
104
|
if not task_ref:
|
|
76
105
|
return "Status: NO ACTIVE TASK\nNext: Describe what you want to work on"
|
|
77
106
|
|
|
78
107
|
# Resolve task directory
|
|
79
|
-
|
|
80
|
-
task_dir = Path(task_ref)
|
|
81
|
-
elif task_ref.startswith(".trellis/"):
|
|
82
|
-
task_dir = trellis_dir.parent / task_ref
|
|
83
|
-
else:
|
|
84
|
-
task_dir = trellis_dir / "tasks" / task_ref
|
|
108
|
+
task_dir = _resolve_task_dir(trellis_dir, task_ref)
|
|
85
109
|
if not task_dir.is_dir():
|
|
86
110
|
return f"Status: STALE POINTER\nTask: {task_ref}\nNext: Task directory not found. Run: python3 ./.trellis/scripts/task.py finish"
|
|
87
111
|
|
|
@@ -264,13 +288,38 @@ def _resolve_spec_scope(
|
|
|
264
288
|
return None # Unknown scope type: full scan
|
|
265
289
|
|
|
266
290
|
|
|
291
|
+
def _build_workflow_toc(workflow_path: Path) -> str:
|
|
292
|
+
"""Build a compact section index for workflow.md (lazy-load the full file on demand).
|
|
293
|
+
|
|
294
|
+
Replaces full-file injection to keep additionalContext payload small.
|
|
295
|
+
The full file is accessible via: Read tool on .trellis/workflow.md
|
|
296
|
+
"""
|
|
297
|
+
content = read_file(workflow_path)
|
|
298
|
+
if not content:
|
|
299
|
+
return "No workflow.md found"
|
|
300
|
+
|
|
301
|
+
toc_lines = [
|
|
302
|
+
"# Development Workflow — Section Index",
|
|
303
|
+
"Full guide: .trellis/workflow.md (read on demand)",
|
|
304
|
+
"",
|
|
305
|
+
]
|
|
306
|
+
for line in content.splitlines():
|
|
307
|
+
if line.startswith("## "):
|
|
308
|
+
toc_lines.append(line)
|
|
309
|
+
|
|
310
|
+
toc_lines += [
|
|
311
|
+
"",
|
|
312
|
+
"To read a section: use the Read tool on .trellis/workflow.md",
|
|
313
|
+
]
|
|
314
|
+
return "\n".join(toc_lines)
|
|
315
|
+
|
|
316
|
+
|
|
267
317
|
def main():
|
|
268
318
|
if should_skip_injection():
|
|
269
319
|
sys.exit(0)
|
|
270
320
|
|
|
271
321
|
project_dir = Path(os.environ.get("CLAUDE_PROJECT_DIR", ".")).resolve()
|
|
272
322
|
trellis_dir = project_dir / ".trellis"
|
|
273
|
-
claude_dir = project_dir / ".claude"
|
|
274
323
|
|
|
275
324
|
# Load config for scope filtering and legacy detection
|
|
276
325
|
is_mono, packages, scope_config, task_pkg, default_pkg = _load_trellis_config(trellis_dir)
|
|
@@ -296,8 +345,7 @@ Read and follow all instructions below carefully.
|
|
|
296
345
|
output.write("\n</current-state>\n\n")
|
|
297
346
|
|
|
298
347
|
output.write("<workflow>\n")
|
|
299
|
-
|
|
300
|
-
output.write(workflow_content)
|
|
348
|
+
output.write(_build_workflow_toc(trellis_dir / "workflow.md"))
|
|
301
349
|
output.write("\n</workflow>\n\n")
|
|
302
350
|
|
|
303
351
|
output.write("<guidelines>\n")
|
|
@@ -341,20 +389,13 @@ Read and follow all instructions below carefully.
|
|
|
341
389
|
|
|
342
390
|
output.write("</guidelines>\n\n")
|
|
343
391
|
|
|
344
|
-
output.write("<instructions>\n")
|
|
345
|
-
start_md = read_file(
|
|
346
|
-
claude_dir / "commands" / "trellis" / "start.md", "No start.md found"
|
|
347
|
-
)
|
|
348
|
-
output.write(start_md)
|
|
349
|
-
output.write("\n</instructions>\n\n")
|
|
350
|
-
|
|
351
392
|
# Check task status and inject structured tag
|
|
352
393
|
task_status = _get_task_status(trellis_dir)
|
|
353
394
|
output.write(f"<task-status>\n{task_status}\n</task-status>\n\n")
|
|
354
395
|
|
|
355
396
|
output.write("""<ready>
|
|
356
|
-
Context loaded.
|
|
357
|
-
|
|
397
|
+
Context loaded. Workflow index, project state, and guidelines are already injected above — do NOT re-read them.
|
|
398
|
+
Wait for the user's first message, then handle it following the workflow guide.
|
|
358
399
|
If there is an active task, ask whether to continue it.
|
|
359
400
|
</ready>""")
|
|
360
401
|
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Trellis StatusLine — project-level status display for Claude Code.
|
|
5
|
+
|
|
6
|
+
Reads Claude Code session JSON from stdin + Trellis task data from filesystem.
|
|
7
|
+
Outputs 1-2 lines:
|
|
8
|
+
With active task: [P1] Task title (status) + info line
|
|
9
|
+
Without task: info line only
|
|
10
|
+
Info line: model · ctx% · branch · duration · developer · tasks · rate limits
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import io
|
|
15
|
+
import json
|
|
16
|
+
import re
|
|
17
|
+
import subprocess
|
|
18
|
+
import sys
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
# Fix: Windows Python defaults to GBK encoding, which corrupts UTF-8
|
|
22
|
+
# characters like the middle dot (·). Wrap stdout/stderr with UTF-8.
|
|
23
|
+
if sys.platform == "win32":
|
|
24
|
+
sys.stdout = io.TextIOWrapper(sys.stdout.detach(), encoding="utf-8")
|
|
25
|
+
sys.stderr = io.TextIOWrapper(sys.stderr.detach(), encoding="utf-8")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _read_text(path: Path) -> str:
|
|
29
|
+
try:
|
|
30
|
+
return path.read_text(encoding="utf-8").strip()
|
|
31
|
+
except (FileNotFoundError, PermissionError, OSError):
|
|
32
|
+
return ""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _read_json(path: Path) -> dict:
|
|
36
|
+
text = _read_text(path)
|
|
37
|
+
if not text:
|
|
38
|
+
return {}
|
|
39
|
+
try:
|
|
40
|
+
return json.loads(text)
|
|
41
|
+
except (json.JSONDecodeError, ValueError):
|
|
42
|
+
return {}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _normalize_task_ref(task_ref: str) -> str:
|
|
46
|
+
normalized = task_ref.strip()
|
|
47
|
+
if not normalized:
|
|
48
|
+
return ""
|
|
49
|
+
|
|
50
|
+
path_obj = Path(normalized)
|
|
51
|
+
if path_obj.is_absolute():
|
|
52
|
+
return str(path_obj)
|
|
53
|
+
|
|
54
|
+
normalized = normalized.replace("\\", "/")
|
|
55
|
+
while normalized.startswith("./"):
|
|
56
|
+
normalized = normalized[2:]
|
|
57
|
+
|
|
58
|
+
if normalized.startswith("tasks/"):
|
|
59
|
+
return f".trellis/{normalized}"
|
|
60
|
+
|
|
61
|
+
return normalized
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _resolve_task_dir(trellis_dir: Path, task_ref: str) -> Path:
|
|
65
|
+
normalized = _normalize_task_ref(task_ref)
|
|
66
|
+
path_obj = Path(normalized)
|
|
67
|
+
if path_obj.is_absolute():
|
|
68
|
+
return path_obj
|
|
69
|
+
if normalized.startswith(".trellis/"):
|
|
70
|
+
return trellis_dir.parent / path_obj
|
|
71
|
+
return trellis_dir / "tasks" / path_obj
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _find_trellis_dir() -> Path | None:
|
|
75
|
+
"""Walk up from cwd to find .trellis/ directory."""
|
|
76
|
+
current = Path.cwd()
|
|
77
|
+
for parent in [current, *current.parents]:
|
|
78
|
+
candidate = parent / ".trellis"
|
|
79
|
+
if candidate.is_dir():
|
|
80
|
+
return candidate
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _get_current_task(trellis_dir: Path) -> dict | None:
|
|
85
|
+
"""Load current task info. Returns dict with title/status/priority or None."""
|
|
86
|
+
task_ref = _normalize_task_ref(_read_text(trellis_dir / ".current-task"))
|
|
87
|
+
if not task_ref:
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
# Resolve task directory
|
|
91
|
+
task_path = _resolve_task_dir(trellis_dir, task_ref)
|
|
92
|
+
task_data = _read_json(task_path / "task.json")
|
|
93
|
+
if not task_data:
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
"title": task_data.get("title") or task_data.get("name") or "unknown",
|
|
98
|
+
"status": task_data.get("status", "unknown"),
|
|
99
|
+
"priority": task_data.get("priority", "P2"),
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _count_active_tasks(trellis_dir: Path) -> int:
|
|
104
|
+
"""Count non-archived task directories with valid task.json."""
|
|
105
|
+
tasks_dir = trellis_dir / "tasks"
|
|
106
|
+
if not tasks_dir.is_dir():
|
|
107
|
+
return 0
|
|
108
|
+
count = 0
|
|
109
|
+
for d in tasks_dir.iterdir():
|
|
110
|
+
if d.is_dir() and d.name != "archive" and (d / "task.json").is_file():
|
|
111
|
+
count += 1
|
|
112
|
+
return count
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _get_developer(trellis_dir: Path) -> str:
|
|
116
|
+
content = _read_text(trellis_dir / ".developer")
|
|
117
|
+
if not content:
|
|
118
|
+
return "unknown"
|
|
119
|
+
for line in content.splitlines():
|
|
120
|
+
if line.startswith("name="):
|
|
121
|
+
return line[5:].strip()
|
|
122
|
+
return content.splitlines()[0].strip() or "unknown"
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _get_git_branch() -> str:
|
|
126
|
+
try:
|
|
127
|
+
result = subprocess.run(
|
|
128
|
+
["git", "branch", "--show-current"],
|
|
129
|
+
capture_output=True, text=True, timeout=3,
|
|
130
|
+
)
|
|
131
|
+
return result.stdout.strip() if result.returncode == 0 else ""
|
|
132
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
133
|
+
return ""
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _format_ctx_size(size: int) -> str:
|
|
137
|
+
if size >= 1_000_000:
|
|
138
|
+
return f"{size // 1_000_000}M"
|
|
139
|
+
if size >= 1_000:
|
|
140
|
+
return f"{size // 1_000}K"
|
|
141
|
+
return str(size)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _format_duration(ms: int) -> str:
|
|
145
|
+
secs = ms // 1000
|
|
146
|
+
hours, remainder = divmod(secs, 3600)
|
|
147
|
+
mins = remainder // 60
|
|
148
|
+
if hours > 0:
|
|
149
|
+
return f"{hours}h{mins}m"
|
|
150
|
+
return f"{mins}m"
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def main() -> None:
|
|
154
|
+
# Read Claude Code session JSON from stdin
|
|
155
|
+
try:
|
|
156
|
+
cc_data = json.loads(sys.stdin.read())
|
|
157
|
+
except (json.JSONDecodeError, ValueError):
|
|
158
|
+
cc_data = {}
|
|
159
|
+
|
|
160
|
+
trellis_dir = _find_trellis_dir()
|
|
161
|
+
SEP = " \033[90m·\033[0m "
|
|
162
|
+
|
|
163
|
+
# --- Trellis data ---
|
|
164
|
+
task = _get_current_task(trellis_dir) if trellis_dir else None
|
|
165
|
+
dev = _get_developer(trellis_dir) if trellis_dir else ""
|
|
166
|
+
task_count = _count_active_tasks(trellis_dir) if trellis_dir else 0
|
|
167
|
+
|
|
168
|
+
# --- CC session data ---
|
|
169
|
+
model = cc_data.get("model", {}).get("display_name", "?")
|
|
170
|
+
ctx_pct = int(cc_data.get("context_window", {}).get("used_percentage") or 0)
|
|
171
|
+
ctx_size = _format_ctx_size(cc_data.get("context_window", {}).get("context_window_size") or 0)
|
|
172
|
+
duration = _format_duration(cc_data.get("cost", {}).get("total_duration_ms") or 0)
|
|
173
|
+
branch = _get_git_branch()
|
|
174
|
+
|
|
175
|
+
# Avoid "Opus 4.6 (1M context) (1M)"
|
|
176
|
+
if re.search(r"\d+[KMG]\b", model, re.IGNORECASE):
|
|
177
|
+
model_label = model
|
|
178
|
+
else:
|
|
179
|
+
model_label = f"{model} ({ctx_size})"
|
|
180
|
+
|
|
181
|
+
# Context % with color
|
|
182
|
+
if ctx_pct >= 90:
|
|
183
|
+
ctx_color = "\033[31m"
|
|
184
|
+
elif ctx_pct >= 70:
|
|
185
|
+
ctx_color = "\033[33m"
|
|
186
|
+
else:
|
|
187
|
+
ctx_color = "\033[32m"
|
|
188
|
+
|
|
189
|
+
# Build info line: model · ctx · branch · duration · dev · tasks [· rate limits]
|
|
190
|
+
parts = [
|
|
191
|
+
model_label,
|
|
192
|
+
f"ctx {ctx_color}{ctx_pct}%\033[0m",
|
|
193
|
+
]
|
|
194
|
+
if branch:
|
|
195
|
+
parts.append(f"\033[35m{branch}\033[0m")
|
|
196
|
+
parts.append(duration)
|
|
197
|
+
if dev:
|
|
198
|
+
parts.append(f"\033[32m{dev}\033[0m")
|
|
199
|
+
if task_count:
|
|
200
|
+
parts.append(f"{task_count} task(s)")
|
|
201
|
+
|
|
202
|
+
five_hr = cc_data.get("rate_limits", {}).get("five_hour", {}).get("used_percentage")
|
|
203
|
+
if five_hr is not None:
|
|
204
|
+
parts.append(f"5h {int(five_hr)}%")
|
|
205
|
+
seven_day = cc_data.get("rate_limits", {}).get("seven_day", {}).get("used_percentage")
|
|
206
|
+
if seven_day is not None:
|
|
207
|
+
parts.append(f"7d {int(seven_day)}%")
|
|
208
|
+
|
|
209
|
+
info_line = SEP.join(parts)
|
|
210
|
+
|
|
211
|
+
# Output: task line (only if active) + info line
|
|
212
|
+
if task:
|
|
213
|
+
print(f"\033[36m[{task['priority']}]\033[0m {task['title']} \033[33m({task['status']})\033[0m")
|
|
214
|
+
print(info_line)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
if __name__ == "__main__":
|
|
218
|
+
main()
|
|
@@ -51,21 +51,45 @@ def run_script(script_path: Path) -> str:
|
|
|
51
51
|
return "No context available"
|
|
52
52
|
|
|
53
53
|
|
|
54
|
+
def _normalize_task_ref(task_ref: str) -> str:
|
|
55
|
+
normalized = task_ref.strip()
|
|
56
|
+
if not normalized:
|
|
57
|
+
return ""
|
|
58
|
+
|
|
59
|
+
path_obj = Path(normalized)
|
|
60
|
+
if path_obj.is_absolute():
|
|
61
|
+
return str(path_obj)
|
|
62
|
+
|
|
63
|
+
normalized = normalized.replace("\\", "/")
|
|
64
|
+
while normalized.startswith("./"):
|
|
65
|
+
normalized = normalized[2:]
|
|
66
|
+
|
|
67
|
+
if normalized.startswith("tasks/"):
|
|
68
|
+
return f".trellis/{normalized}"
|
|
69
|
+
|
|
70
|
+
return normalized
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _resolve_task_dir(trellis_dir: Path, task_ref: str) -> Path:
|
|
74
|
+
normalized = _normalize_task_ref(task_ref)
|
|
75
|
+
path_obj = Path(normalized)
|
|
76
|
+
if path_obj.is_absolute():
|
|
77
|
+
return path_obj
|
|
78
|
+
if normalized.startswith(".trellis/"):
|
|
79
|
+
return trellis_dir.parent / path_obj
|
|
80
|
+
return trellis_dir / "tasks" / path_obj
|
|
81
|
+
|
|
82
|
+
|
|
54
83
|
def _get_task_status(trellis_dir: Path) -> str:
|
|
55
84
|
current_task_file = trellis_dir / ".current-task"
|
|
56
85
|
if not current_task_file.is_file():
|
|
57
86
|
return "Status: NO ACTIVE TASK\nNext: Describe what you want to work on"
|
|
58
87
|
|
|
59
|
-
task_ref = current_task_file.read_text(encoding="utf-8").strip()
|
|
88
|
+
task_ref = _normalize_task_ref(current_task_file.read_text(encoding="utf-8").strip())
|
|
60
89
|
if not task_ref:
|
|
61
90
|
return "Status: NO ACTIVE TASK\nNext: Describe what you want to work on"
|
|
62
91
|
|
|
63
|
-
|
|
64
|
-
task_dir = Path(task_ref)
|
|
65
|
-
elif task_ref.startswith(".trellis/"):
|
|
66
|
-
task_dir = trellis_dir.parent / task_ref
|
|
67
|
-
else:
|
|
68
|
-
task_dir = trellis_dir / "tasks" / task_ref
|
|
92
|
+
task_dir = _resolve_task_dir(trellis_dir, task_ref)
|
|
69
93
|
if not task_dir.is_dir():
|
|
70
94
|
return f"Status: STALE POINTER\nTask: {task_ref}\nNext: Task directory not found. Run: python3 ./.trellis/scripts/task.py finish"
|
|
71
95
|
|
|
@@ -101,6 +125,32 @@ def _get_task_status(trellis_dir: Path) -> str:
|
|
|
101
125
|
return f"Status: READY\nTask: {task_title}\nNext: Continue with implement or check"
|
|
102
126
|
|
|
103
127
|
|
|
128
|
+
def _build_workflow_toc(workflow_path: Path) -> str:
|
|
129
|
+
"""Build a compact section index for workflow.md (lazy-load the full file on demand).
|
|
130
|
+
|
|
131
|
+
Replaces full-file injection to keep additionalContext payload small.
|
|
132
|
+
The full file is accessible via: Read tool on .trellis/workflow.md
|
|
133
|
+
"""
|
|
134
|
+
content = read_file(workflow_path)
|
|
135
|
+
if not content:
|
|
136
|
+
return "No workflow.md found"
|
|
137
|
+
|
|
138
|
+
toc_lines = [
|
|
139
|
+
"# Development Workflow — Section Index",
|
|
140
|
+
"Full guide: .trellis/workflow.md (read on demand)",
|
|
141
|
+
"",
|
|
142
|
+
]
|
|
143
|
+
for line in content.splitlines():
|
|
144
|
+
if line.startswith("## "):
|
|
145
|
+
toc_lines.append(line)
|
|
146
|
+
|
|
147
|
+
toc_lines += [
|
|
148
|
+
"",
|
|
149
|
+
"To read a section: use the Read tool on .trellis/workflow.md",
|
|
150
|
+
]
|
|
151
|
+
return "\n".join(toc_lines)
|
|
152
|
+
|
|
153
|
+
|
|
104
154
|
def main() -> None:
|
|
105
155
|
if should_skip_injection():
|
|
106
156
|
sys.exit(0)
|
|
@@ -113,7 +163,6 @@ def main() -> None:
|
|
|
113
163
|
project_dir = Path(".").resolve()
|
|
114
164
|
|
|
115
165
|
trellis_dir = project_dir / ".trellis"
|
|
116
|
-
codex_dir = project_dir / ".codex"
|
|
117
166
|
|
|
118
167
|
output = StringIO()
|
|
119
168
|
|
|
@@ -130,8 +179,7 @@ Read and follow all instructions below carefully.
|
|
|
130
179
|
output.write("\n</current-state>\n\n")
|
|
131
180
|
|
|
132
181
|
output.write("<workflow>\n")
|
|
133
|
-
|
|
134
|
-
output.write(workflow_content)
|
|
182
|
+
output.write(_build_workflow_toc(trellis_dir / "workflow.md"))
|
|
135
183
|
output.write("\n</workflow>\n\n")
|
|
136
184
|
|
|
137
185
|
output.write("<guidelines>\n")
|
|
@@ -169,21 +217,12 @@ Read and follow all instructions below carefully.
|
|
|
169
217
|
|
|
170
218
|
output.write("</guidelines>\n\n")
|
|
171
219
|
|
|
172
|
-
# Inject start skill as instructions (Codex uses skills, not slash commands)
|
|
173
|
-
start_skill = codex_dir / "skills" / "start" / "SKILL.md"
|
|
174
|
-
if not start_skill.is_file():
|
|
175
|
-
start_skill = project_dir / ".agents" / "skills" / "start" / "SKILL.md"
|
|
176
|
-
if start_skill.is_file():
|
|
177
|
-
output.write("<instructions>\n")
|
|
178
|
-
output.write(read_file(start_skill))
|
|
179
|
-
output.write("\n</instructions>\n\n")
|
|
180
|
-
|
|
181
220
|
task_status = _get_task_status(trellis_dir)
|
|
182
221
|
output.write(f"<task-status>\n{task_status}\n</task-status>\n\n")
|
|
183
222
|
|
|
184
223
|
output.write("""<ready>
|
|
185
|
-
Context loaded.
|
|
186
|
-
|
|
224
|
+
Context loaded. Workflow index, project state, and guidelines are already injected above — do NOT re-read them.
|
|
225
|
+
Wait for the user's first message, then handle it following the workflow guide.
|
|
187
226
|
If there is an active task, ask whether to continue it.
|
|
188
227
|
</ready>""")
|
|
189
228
|
|