@jahanxu/trellis 0.4.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/configurators/workflow.d.ts.map +1 -1
- package/dist/configurators/workflow.js +58 -1
- package/dist/configurators/workflow.js.map +1 -1
- package/dist/constants/paths.d.ts +17 -0
- package/dist/constants/paths.d.ts.map +1 -1
- package/dist/constants/paths.js +19 -0
- package/dist/constants/paths.js.map +1 -1
- package/dist/templates/claude/commands/trellis/handoff.md +90 -387
- package/dist/templates/claude/commands/trellis/pick-task.md +74 -444
- package/dist/templates/claude/hooks/inject-subagent-context.py +17 -101
- package/dist/templates/claude/hooks/ralph-loop.py +1 -0
- package/dist/templates/claude/hooks/session-start.py +170 -54
- package/dist/templates/iflow/commands/trellis/handoff.md +148 -0
- package/dist/templates/iflow/commands/trellis/pick-task.md +145 -0
- package/dist/templates/iflow/hooks/inject-subagent-context.py +1 -0
- package/dist/templates/iflow/hooks/ralph-loop.py +1 -0
- package/dist/templates/iflow/hooks/session-start.py +171 -0
- package/dist/templates/markdown/index.d.ts +9 -0
- package/dist/templates/markdown/index.d.ts.map +1 -1
- package/dist/templates/markdown/index.js +10 -0
- package/dist/templates/markdown/index.js.map +1 -1
- package/dist/templates/markdown/spec/roles/designer/index.md.txt +57 -0
- package/dist/templates/markdown/spec/roles/designer/mock-data-standards.md.txt +63 -0
- package/dist/templates/markdown/spec/roles/designer/prototype-guidelines.md.txt +49 -0
- package/dist/templates/markdown/spec/roles/frontend-impl/api-integration.md.txt +63 -0
- package/dist/templates/markdown/spec/roles/frontend-impl/index.md.txt +57 -0
- package/dist/templates/markdown/spec/roles/frontend-impl/prototype-to-production.md.txt +57 -0
- package/dist/templates/markdown/spec/roles/pm/index.md.txt +45 -0
- package/dist/templates/markdown/spec/roles/pm/prd-template.md.txt +64 -0
- package/dist/templates/markdown/spec/roles/pm/requirement-checklist.md.txt +43 -0
- package/dist/templates/trellis/index.d.ts +1 -0
- package/dist/templates/trellis/index.d.ts.map +1 -1
- package/dist/templates/trellis/index.js +2 -0
- package/dist/templates/trellis/index.js.map +1 -1
- package/dist/templates/trellis/scripts/add_session.py +3 -2
- package/dist/templates/trellis/scripts/common/cli_adapter.py +4 -4
- package/dist/templates/trellis/scripts/common/developer.py +4 -4
- package/dist/templates/trellis/scripts/common/git_context.py +7 -7
- package/dist/templates/trellis/scripts/common/paths.py +64 -14
- package/dist/templates/trellis/scripts/common/phase.py +2 -2
- package/dist/templates/trellis/scripts/common/registry.py +16 -16
- package/dist/templates/trellis/scripts/common/task_queue.py +10 -10
- package/dist/templates/trellis/scripts/common/task_utils.py +5 -5
- package/dist/templates/trellis/scripts/common/worktree.py +8 -8
- package/dist/templates/trellis/scripts/pool.py +214 -265
- package/dist/templates/trellis/scripts/task.py +3 -116
- package/package.json +3 -3
- package/dist/templates/claude/commands/trellis/before-role-work.md +0 -364
- package/dist/templates/trellis/VERSION +0 -1
- package/dist/templates/trellis/deliverables/README.md +0 -51
- package/dist/templates/trellis/paths.README.md +0 -277
- package/dist/templates/trellis/paths.yaml +0 -41
- package/dist/templates/trellis/pool/implementations.json +0 -5
- package/dist/templates/trellis/pool/prototypes.json +0 -5
- package/dist/templates/trellis/pool/requirements.json +0 -5
- package/dist/templates/trellis/scripts/common/project_paths.py +0 -189
- package/dist/templates/trellis/scripts/handoff_generator.py +0 -380
- package/dist/templates/trellis/spec/roles/designer/index.md +0 -243
- package/dist/templates/trellis/spec/roles/designer/mock-data-standards.md +0 -481
- package/dist/templates/trellis/spec/roles/designer/prototype-guidelines.md +0 -429
- package/dist/templates/trellis/spec/roles/frontend-impl/api-integration.md +0 -565
- package/dist/templates/trellis/spec/roles/frontend-impl/index.md +0 -321
- package/dist/templates/trellis/spec/roles/frontend-impl/state-management.md +0 -599
- package/dist/templates/trellis/spec/roles/pm/index.md +0 -112
- package/dist/templates/trellis/spec/roles/pm/prd-template.md +0 -124
|
@@ -21,6 +21,7 @@ Context Source: .trellis/.current-task points to task directory
|
|
|
21
21
|
- info.md - Technical design
|
|
22
22
|
- codex-review-output.txt - Code Review results
|
|
23
23
|
"""
|
|
24
|
+
from __future__ import annotations
|
|
24
25
|
|
|
25
26
|
# IMPORTANT: Suppress all warnings FIRST
|
|
26
27
|
import warnings
|
|
@@ -30,7 +31,6 @@ import json
|
|
|
30
31
|
import os
|
|
31
32
|
import sys
|
|
32
33
|
from pathlib import Path
|
|
33
|
-
from typing import Optional, List, Tuple
|
|
34
34
|
|
|
35
35
|
# IMPORTANT: Force stdout to use UTF-8 on Windows
|
|
36
36
|
# This fixes UnicodeEncodeError when outputting non-ASCII characters
|
|
@@ -70,7 +70,7 @@ AGENTS_REQUIRE_TASK = (AGENT_IMPLEMENT, AGENT_CHECK, AGENT_DEBUG)
|
|
|
70
70
|
AGENTS_ALL = (AGENT_IMPLEMENT, AGENT_CHECK, AGENT_DEBUG, AGENT_RESEARCH)
|
|
71
71
|
|
|
72
72
|
|
|
73
|
-
def find_repo_root(start_path: str) ->
|
|
73
|
+
def find_repo_root(start_path: str) -> str | None:
|
|
74
74
|
"""
|
|
75
75
|
Find git repo root from start_path upwards
|
|
76
76
|
|
|
@@ -85,7 +85,7 @@ def find_repo_root(start_path: str) -> Optional[str]:
|
|
|
85
85
|
return None
|
|
86
86
|
|
|
87
87
|
|
|
88
|
-
def get_current_task(repo_root: str) ->
|
|
88
|
+
def get_current_task(repo_root: str) -> str | None:
|
|
89
89
|
"""
|
|
90
90
|
Read current task directory path from .trellis/.current-task
|
|
91
91
|
|
|
@@ -162,7 +162,7 @@ def update_current_phase(repo_root: str, task_dir: str, subagent_type: str) -> N
|
|
|
162
162
|
pass
|
|
163
163
|
|
|
164
164
|
|
|
165
|
-
def read_file_content(base_path: str, file_path: str) ->
|
|
165
|
+
def read_file_content(base_path: str, file_path: str) -> str | None:
|
|
166
166
|
"""Read file content, return None if file doesn't exist"""
|
|
167
167
|
full_path = os.path.join(base_path, file_path)
|
|
168
168
|
if os.path.exists(full_path) and os.path.isfile(full_path):
|
|
@@ -265,58 +265,6 @@ def read_jsonl_entries(base_path: str, jsonl_path: str) -> list[tuple[str, str]]
|
|
|
265
265
|
return results
|
|
266
266
|
|
|
267
267
|
|
|
268
|
-
def read_source_json(repo_root: str, task_dir: str) -> Optional[dict]:
|
|
269
|
-
"""
|
|
270
|
-
Read source.json from task directory (三角色协作上游引用)
|
|
271
|
-
|
|
272
|
-
Returns:
|
|
273
|
-
source.json data or None if not exists
|
|
274
|
-
"""
|
|
275
|
-
source_json_path = os.path.join(repo_root, task_dir, "source.json")
|
|
276
|
-
if not os.path.exists(source_json_path):
|
|
277
|
-
return None
|
|
278
|
-
|
|
279
|
-
try:
|
|
280
|
-
with open(source_json_path, "r", encoding="utf-8") as f:
|
|
281
|
-
return json.load(f)
|
|
282
|
-
except Exception:
|
|
283
|
-
return None
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
def inject_upstream_context(repo_root: str, task_dir: str) -> list[tuple[str, str]]:
|
|
287
|
-
"""
|
|
288
|
-
Inject upstream outputs for three-role collaboration
|
|
289
|
-
|
|
290
|
-
Reads source.json and injects:
|
|
291
|
-
1. Upstream HANDOFF document
|
|
292
|
-
2. Upstream deliverables (requirements/prototypes)
|
|
293
|
-
|
|
294
|
-
Returns:
|
|
295
|
-
[(path, content), ...]
|
|
296
|
-
"""
|
|
297
|
-
source_data = read_source_json(repo_root, task_dir)
|
|
298
|
-
if not source_data or "based_on" not in source_data:
|
|
299
|
-
return []
|
|
300
|
-
|
|
301
|
-
results = []
|
|
302
|
-
based_on = source_data["based_on"]
|
|
303
|
-
|
|
304
|
-
# 1. Inject upstream HANDOFF document
|
|
305
|
-
handoff_doc = based_on.get("handoff_doc")
|
|
306
|
-
if handoff_doc:
|
|
307
|
-
handoff_content = read_file_content(repo_root, handoff_doc)
|
|
308
|
-
if handoff_content:
|
|
309
|
-
results.append((handoff_doc, handoff_content))
|
|
310
|
-
|
|
311
|
-
# 2. Inject upstream deliverables directory (all .md files)
|
|
312
|
-
upstream_path = based_on.get("path")
|
|
313
|
-
if upstream_path:
|
|
314
|
-
upstream_contents = read_directory_contents(repo_root, upstream_path)
|
|
315
|
-
results.extend(upstream_contents)
|
|
316
|
-
|
|
317
|
-
return results
|
|
318
|
-
|
|
319
|
-
|
|
320
268
|
def get_agent_context(repo_root: str, task_dir: str, agent_type: str) -> str:
|
|
321
269
|
"""
|
|
322
270
|
Get complete context for specified agent
|
|
@@ -346,9 +294,8 @@ def get_implement_context(repo_root: str, task_dir: str) -> str:
|
|
|
346
294
|
|
|
347
295
|
Read order:
|
|
348
296
|
1. All files in implement.jsonl (dev specs)
|
|
349
|
-
2.
|
|
350
|
-
3.
|
|
351
|
-
4. info.md (technical design)
|
|
297
|
+
2. prd.md (requirements)
|
|
298
|
+
3. info.md (technical design)
|
|
352
299
|
"""
|
|
353
300
|
context_parts = []
|
|
354
301
|
|
|
@@ -357,19 +304,12 @@ def get_implement_context(repo_root: str, task_dir: str) -> str:
|
|
|
357
304
|
if base_context:
|
|
358
305
|
context_parts.append(base_context)
|
|
359
306
|
|
|
360
|
-
# 2.
|
|
361
|
-
upstream_entries = inject_upstream_context(repo_root, task_dir)
|
|
362
|
-
if upstream_entries:
|
|
363
|
-
context_parts.append("=== Upstream Outputs (三角色协作上游产出) ===")
|
|
364
|
-
for file_path, content in upstream_entries:
|
|
365
|
-
context_parts.append(f"=== {file_path} ===\n{content}")
|
|
366
|
-
|
|
367
|
-
# 3. Requirements document
|
|
307
|
+
# 2. Requirements document
|
|
368
308
|
prd_content = read_file_content(repo_root, f"{task_dir}/prd.md")
|
|
369
309
|
if prd_content:
|
|
370
310
|
context_parts.append(f"=== {task_dir}/prd.md (Requirements) ===\n{prd_content}")
|
|
371
311
|
|
|
372
|
-
#
|
|
312
|
+
# 3. Technical design
|
|
373
313
|
info_content = read_file_content(repo_root, f"{task_dir}/info.md")
|
|
374
314
|
if info_content:
|
|
375
315
|
context_parts.append(
|
|
@@ -385,8 +325,7 @@ def get_check_context(repo_root: str, task_dir: str) -> str:
|
|
|
385
325
|
|
|
386
326
|
Read order:
|
|
387
327
|
1. All files in check.jsonl (check specs + dev specs)
|
|
388
|
-
2.
|
|
389
|
-
3. prd.md (for understanding task intent)
|
|
328
|
+
2. prd.md (for understanding task intent)
|
|
390
329
|
"""
|
|
391
330
|
context_parts = []
|
|
392
331
|
|
|
@@ -414,14 +353,7 @@ def get_check_context(repo_root: str, task_dir: str) -> str:
|
|
|
414
353
|
for file_path, content in spec_entries:
|
|
415
354
|
context_parts.append(f"=== {file_path} (Dev spec) ===\n{content}")
|
|
416
355
|
|
|
417
|
-
# 2.
|
|
418
|
-
upstream_entries = inject_upstream_context(repo_root, task_dir)
|
|
419
|
-
if upstream_entries:
|
|
420
|
-
context_parts.append("=== Upstream Outputs (三角色协作上游产出) ===")
|
|
421
|
-
for file_path, content in upstream_entries:
|
|
422
|
-
context_parts.append(f"=== {file_path} ===\n{content}")
|
|
423
|
-
|
|
424
|
-
# 3. Requirements document (for understanding task intent)
|
|
356
|
+
# 2. Requirements document (for understanding task intent)
|
|
425
357
|
prd_content = read_file_content(repo_root, f"{task_dir}/prd.md")
|
|
426
358
|
if prd_content:
|
|
427
359
|
context_parts.append(
|
|
@@ -438,9 +370,8 @@ def get_finish_context(repo_root: str, task_dir: str) -> str:
|
|
|
438
370
|
Read order:
|
|
439
371
|
1. All files in finish.jsonl (if exists)
|
|
440
372
|
2. Fallback to finish-work.md only (lightweight final check)
|
|
441
|
-
3.
|
|
442
|
-
4.
|
|
443
|
-
5. prd.md (for verifying requirements are met)
|
|
373
|
+
3. update-spec.md (for active spec sync)
|
|
374
|
+
4. prd.md (for verifying requirements are met)
|
|
444
375
|
"""
|
|
445
376
|
context_parts = []
|
|
446
377
|
|
|
@@ -460,14 +391,7 @@ def get_finish_context(repo_root: str, task_dir: str) -> str:
|
|
|
460
391
|
f"=== .claude/commands/trellis/finish-work.md (Finish checklist) ===\n{finish_work}"
|
|
461
392
|
)
|
|
462
393
|
|
|
463
|
-
# 2.
|
|
464
|
-
upstream_entries = inject_upstream_context(repo_root, task_dir)
|
|
465
|
-
if upstream_entries:
|
|
466
|
-
context_parts.append("=== Upstream Outputs (三角色协作上游产出) ===")
|
|
467
|
-
for file_path, content in upstream_entries:
|
|
468
|
-
context_parts.append(f"=== {file_path} ===\n{content}")
|
|
469
|
-
|
|
470
|
-
# 3. Spec update process (for active spec sync)
|
|
394
|
+
# 2. Spec update process (for active spec sync)
|
|
471
395
|
update_spec = read_file_content(
|
|
472
396
|
repo_root, ".claude/commands/trellis/update-spec.md"
|
|
473
397
|
)
|
|
@@ -476,7 +400,7 @@ def get_finish_context(repo_root: str, task_dir: str) -> str:
|
|
|
476
400
|
f"=== .claude/commands/trellis/update-spec.md (Spec update process) ===\n{update_spec}"
|
|
477
401
|
)
|
|
478
402
|
|
|
479
|
-
#
|
|
403
|
+
# 3. Requirements document (for verifying requirements are met)
|
|
480
404
|
prd_content = read_file_content(repo_root, f"{task_dir}/prd.md")
|
|
481
405
|
if prd_content:
|
|
482
406
|
context_parts.append(
|
|
@@ -492,8 +416,7 @@ def get_debug_context(repo_root: str, task_dir: str) -> str:
|
|
|
492
416
|
|
|
493
417
|
Read order:
|
|
494
418
|
1. All files in debug.jsonl (specs needed for fixing)
|
|
495
|
-
2.
|
|
496
|
-
3. codex-review-output.txt (Codex Review results)
|
|
419
|
+
2. codex-review-output.txt (Codex Review results)
|
|
497
420
|
"""
|
|
498
421
|
context_parts = []
|
|
499
422
|
|
|
@@ -519,14 +442,7 @@ def get_debug_context(repo_root: str, task_dir: str) -> str:
|
|
|
519
442
|
if content:
|
|
520
443
|
context_parts.append(f"=== {file_path} ({description}) ===\n{content}")
|
|
521
444
|
|
|
522
|
-
# 2.
|
|
523
|
-
upstream_entries = inject_upstream_context(repo_root, task_dir)
|
|
524
|
-
if upstream_entries:
|
|
525
|
-
context_parts.append("=== Upstream Outputs (三角色协作上游产出) ===")
|
|
526
|
-
for file_path, content in upstream_entries:
|
|
527
|
-
context_parts.append(f"=== {file_path} ===\n{content}")
|
|
528
|
-
|
|
529
|
-
# 3. Codex review output (if exists)
|
|
445
|
+
# 2. Codex review output (if exists)
|
|
530
446
|
codex_output = read_file_content(repo_root, f"{task_dir}/codex-review-output.txt")
|
|
531
447
|
if codex_output:
|
|
532
448
|
context_parts.append(
|
|
@@ -678,7 +594,7 @@ Dev specs and Codex Review results:
|
|
|
678
594
|
- Report which issues were fixed and which files were modified"""
|
|
679
595
|
|
|
680
596
|
|
|
681
|
-
def get_research_context(repo_root: str, task_dir:
|
|
597
|
+
def get_research_context(repo_root: str, task_dir: str | None) -> str:
|
|
682
598
|
"""
|
|
683
599
|
Context for Research Agent
|
|
684
600
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
"""
|
|
4
4
|
Session Start Hook - Inject structured context
|
|
5
5
|
"""
|
|
6
|
+
from __future__ import annotations
|
|
6
7
|
|
|
7
8
|
# IMPORTANT: Suppress all warnings FIRST
|
|
8
9
|
import warnings
|
|
@@ -65,30 +66,167 @@ def run_script(script_path: Path) -> str:
|
|
|
65
66
|
return "No context available"
|
|
66
67
|
|
|
67
68
|
|
|
68
|
-
def
|
|
69
|
-
"""
|
|
69
|
+
def detect_role(trellis_dir: Path) -> str | None:
|
|
70
|
+
"""Detect role from developer name ({role}-{name} convention)."""
|
|
71
|
+
dev_file = trellis_dir / ".developer"
|
|
72
|
+
if not dev_file.is_file():
|
|
73
|
+
return None
|
|
70
74
|
try:
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
content = dev_file.read_text(encoding="utf-8")
|
|
76
|
+
for line in content.splitlines():
|
|
77
|
+
if line.startswith("name="):
|
|
78
|
+
name = line.split("=", 1)[1].strip()
|
|
79
|
+
# Parse role from {role}-{name} pattern
|
|
80
|
+
if "-" in name:
|
|
81
|
+
role = name.split("-", 1)[0]
|
|
82
|
+
if role in ("pm", "designer", "frontend", "frontend-impl"):
|
|
83
|
+
return role
|
|
84
|
+
except (OSError, IOError):
|
|
85
|
+
pass
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def inject_upstream_context(trellis_dir: Path, project_dir: Path, output: StringIO) -> None:
|
|
90
|
+
"""Inject upstream context when source.json exists in current task."""
|
|
91
|
+
current_task_file = trellis_dir / ".current-task"
|
|
92
|
+
if not current_task_file.is_file():
|
|
93
|
+
return
|
|
78
94
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
95
|
+
try:
|
|
96
|
+
task_rel = current_task_file.read_text(encoding="utf-8").strip()
|
|
97
|
+
except (OSError, IOError):
|
|
98
|
+
return
|
|
99
|
+
|
|
100
|
+
if not task_rel:
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
task_dir = project_dir / task_rel
|
|
104
|
+
source_file = task_dir / "source.json"
|
|
105
|
+
if not source_file.is_file():
|
|
106
|
+
return
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
source = json.loads(source_file.read_text(encoding="utf-8"))
|
|
110
|
+
except (json.JSONDecodeError, OSError):
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
based_on = source.get("based_on")
|
|
114
|
+
if not based_on:
|
|
115
|
+
return
|
|
116
|
+
|
|
117
|
+
output.write("<upstream-context>\n")
|
|
118
|
+
output.write("## Based on Upstream Deliverable\n")
|
|
119
|
+
output.write(f"- Type: {based_on.get('type', '?')}\n")
|
|
120
|
+
output.write(f"- ID: {based_on.get('id', '?')}\n")
|
|
121
|
+
output.write(f"- Path: {based_on.get('path', '?')}\n\n")
|
|
122
|
+
|
|
123
|
+
# Read HANDOFF document
|
|
124
|
+
handoff_path = project_dir / based_on.get("handoff_doc", "")
|
|
125
|
+
if handoff_path.is_file():
|
|
126
|
+
output.write("### Handoff Document\n")
|
|
127
|
+
handoff_content = read_file(handoff_path)
|
|
128
|
+
if len(handoff_content) > 3000:
|
|
129
|
+
handoff_content = handoff_content[:3000] + "\n... (truncated)"
|
|
130
|
+
output.write(handoff_content)
|
|
131
|
+
output.write("\n\n")
|
|
132
|
+
|
|
133
|
+
# Read upstream deliverable files
|
|
134
|
+
source_path = project_dir / based_on.get("path", "")
|
|
135
|
+
if source_path.is_dir():
|
|
136
|
+
output.write("### Upstream Deliverable Files\n")
|
|
137
|
+
total_size = 0
|
|
138
|
+
text_exts = {".md", ".txt", ".tsx", ".ts", ".jsx", ".js", ".vue", ".css", ".json", ".yaml", ".yml", ".html"}
|
|
139
|
+
for fpath in sorted(source_path.rglob("*")):
|
|
140
|
+
if not fpath.is_file():
|
|
141
|
+
continue
|
|
142
|
+
if fpath.name == "HANDOFF.md":
|
|
143
|
+
continue
|
|
144
|
+
if fpath.suffix not in text_exts:
|
|
145
|
+
continue
|
|
146
|
+
if total_size >= 50000:
|
|
147
|
+
output.write("\n(total size cap reached, remaining files omitted)\n")
|
|
148
|
+
break
|
|
149
|
+
try:
|
|
150
|
+
content = fpath.read_text(encoding="utf-8")
|
|
151
|
+
except (OSError, UnicodeDecodeError):
|
|
152
|
+
continue
|
|
153
|
+
rel = fpath.relative_to(source_path)
|
|
154
|
+
output.write(f"\n#### {rel}\n")
|
|
155
|
+
ext = fpath.suffix[1:] if fpath.suffix else ""
|
|
156
|
+
if len(content) > 3000:
|
|
157
|
+
content = content[:3000] + "\n... (truncated)"
|
|
158
|
+
output.write(f"```{ext}\n{content}\n```\n")
|
|
159
|
+
total_size += len(content)
|
|
160
|
+
|
|
161
|
+
output.write("</upstream-context>\n\n")
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def inject_pool_summary(trellis_dir: Path, output: StringIO) -> None:
|
|
165
|
+
"""Inject available tasks summary from pool files."""
|
|
166
|
+
pool_dir = trellis_dir / "pool"
|
|
167
|
+
if not pool_dir.is_dir():
|
|
168
|
+
return
|
|
169
|
+
|
|
170
|
+
pool_files = sorted(pool_dir.glob("*.json"))
|
|
171
|
+
if not pool_files:
|
|
172
|
+
return
|
|
173
|
+
|
|
174
|
+
has_available = False
|
|
175
|
+
pool_output = StringIO()
|
|
176
|
+
|
|
177
|
+
for pool_file in pool_files:
|
|
178
|
+
try:
|
|
179
|
+
data = json.loads(pool_file.read_text(encoding="utf-8"))
|
|
180
|
+
except (json.JSONDecodeError, OSError):
|
|
181
|
+
continue
|
|
182
|
+
|
|
183
|
+
available = [
|
|
184
|
+
i for i in data.get("available", [])
|
|
185
|
+
if i.get("status") == "available"
|
|
186
|
+
]
|
|
187
|
+
if not available:
|
|
188
|
+
continue
|
|
189
|
+
|
|
190
|
+
has_available = True
|
|
191
|
+
pool_name = pool_file.stem
|
|
192
|
+
pool_output.write(f"## Available in {pool_name} ({len(available)})\n")
|
|
193
|
+
for item in available:
|
|
194
|
+
completed_by = item.get("completed_by", "?")
|
|
195
|
+
completed_at = item.get("completed_at", "?")
|
|
196
|
+
pool_output.write(
|
|
197
|
+
f"- {item.get('id', '?')}: {item.get('title', '?')} "
|
|
198
|
+
f"(by {completed_by}, {completed_at})\n"
|
|
199
|
+
)
|
|
200
|
+
pool_output.write("\n")
|
|
201
|
+
|
|
202
|
+
if has_available:
|
|
203
|
+
output.write("<available-tasks>\n")
|
|
204
|
+
output.write(pool_output.getvalue())
|
|
205
|
+
output.write("Use `/trellis:pick-task <pool> <id>` to start working on an available item.\n")
|
|
206
|
+
output.write("</available-tasks>\n\n")
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def inject_role_guidelines(trellis_dir: Path, role: str | None, output: StringIO) -> None:
|
|
210
|
+
"""Inject role-specific guidelines if role is detected."""
|
|
211
|
+
if not role:
|
|
212
|
+
return
|
|
213
|
+
|
|
214
|
+
role_dir_map = {
|
|
215
|
+
"pm": "pm",
|
|
216
|
+
"designer": "designer",
|
|
217
|
+
"frontend": "frontend-impl",
|
|
218
|
+
"frontend-impl": "frontend-impl",
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
role_dir = role_dir_map.get(role)
|
|
222
|
+
if not role_dir:
|
|
223
|
+
return
|
|
224
|
+
|
|
225
|
+
role_index = trellis_dir / "spec" / "roles" / role_dir / "index.md"
|
|
226
|
+
if role_index.is_file():
|
|
227
|
+
output.write(f"## Role: {role}\n")
|
|
228
|
+
output.write(read_file(role_index))
|
|
229
|
+
output.write("\n\n")
|
|
92
230
|
|
|
93
231
|
|
|
94
232
|
def main():
|
|
@@ -113,38 +251,6 @@ Read and follow all instructions below carefully.
|
|
|
113
251
|
output.write(run_script(context_script))
|
|
114
252
|
output.write("\n</current-state>\n\n")
|
|
115
253
|
|
|
116
|
-
output.write("<task-pools>\n")
|
|
117
|
-
pool_script = trellis_dir / "scripts" / "pool.py"
|
|
118
|
-
developer_script = trellis_dir / "scripts" / "get_developer.py"
|
|
119
|
-
|
|
120
|
-
# Get current developer role
|
|
121
|
-
developer_output = run_script(developer_script).strip()
|
|
122
|
-
if developer_output and developer_output != "No context available":
|
|
123
|
-
role = developer_output.split('-')[0] if '-' in developer_output else ""
|
|
124
|
-
output.write(f"## Current Role: {role}\n\n")
|
|
125
|
-
|
|
126
|
-
# Show available task pools based on role
|
|
127
|
-
if role == "designer":
|
|
128
|
-
output.write("### Available Requirements (from PM)\n")
|
|
129
|
-
requirements_output = run_script_with_args(
|
|
130
|
-
pool_script, ["list", "requirements", "--status", "available"]
|
|
131
|
-
)
|
|
132
|
-
output.write(requirements_output)
|
|
133
|
-
output.write("\n\nUse: `/trellis:pick-task requirements <task-id>` to start\n")
|
|
134
|
-
elif role == "frontend":
|
|
135
|
-
output.write("### Available Prototypes (from Designer)\n")
|
|
136
|
-
prototypes_output = run_script_with_args(
|
|
137
|
-
pool_script, ["list", "prototypes", "--status", "available"]
|
|
138
|
-
)
|
|
139
|
-
output.write(prototypes_output)
|
|
140
|
-
output.write("\n\nUse: `/trellis:pick-task prototypes <task-id>` to start\n")
|
|
141
|
-
elif role == "pm":
|
|
142
|
-
output.write("### PM Role (Pipeline Starting Point)\n")
|
|
143
|
-
output.write("PM creates tasks directly, not from pool.\n\n")
|
|
144
|
-
output.write("Use: `python3 .trellis/scripts/task.py create \"<title>\"` to start\n")
|
|
145
|
-
|
|
146
|
-
output.write("\n</task-pools>\n\n")
|
|
147
|
-
|
|
148
254
|
output.write("<workflow>\n")
|
|
149
255
|
workflow_content = read_file(trellis_dir / "workflow.md", "No workflow.md found")
|
|
150
256
|
output.write(workflow_content)
|
|
@@ -152,6 +258,10 @@ Read and follow all instructions below carefully.
|
|
|
152
258
|
|
|
153
259
|
output.write("<guidelines>\n")
|
|
154
260
|
|
|
261
|
+
# Detect role and inject role-specific guidelines
|
|
262
|
+
role = detect_role(trellis_dir)
|
|
263
|
+
inject_role_guidelines(trellis_dir, role, output)
|
|
264
|
+
|
|
155
265
|
output.write("## Frontend\n")
|
|
156
266
|
frontend_index = read_file(
|
|
157
267
|
trellis_dir / "spec" / "frontend" / "index.md", "Not configured"
|
|
@@ -174,6 +284,12 @@ Read and follow all instructions below carefully.
|
|
|
174
284
|
|
|
175
285
|
output.write("\n</guidelines>\n\n")
|
|
176
286
|
|
|
287
|
+
# Inject upstream context (from source.json in current task)
|
|
288
|
+
inject_upstream_context(trellis_dir, project_dir, output)
|
|
289
|
+
|
|
290
|
+
# Inject pool summary (available tasks)
|
|
291
|
+
inject_pool_summary(trellis_dir, output)
|
|
292
|
+
|
|
177
293
|
output.write("<instructions>\n")
|
|
178
294
|
start_md = read_file(
|
|
179
295
|
claude_dir / "commands" / "trellis" / "start.md", "No start.md found"
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Handoff - Complete Task and Add to Pool
|
|
2
|
+
|
|
3
|
+
Complete the current task, generate a HANDOFF.md document, and add the deliverable to the appropriate pool.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
|
|
9
|
+
- Must have a current task set (`.trellis/.current-task`)
|
|
10
|
+
- Task must have deliverable files in the output directory
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Process `[AI]`
|
|
15
|
+
|
|
16
|
+
### Step 1: Read Current Task
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
python3 ./.trellis/scripts/get_context.py
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Read the task's `task.json` to get:
|
|
23
|
+
- `output_dir`: where deliverables are stored
|
|
24
|
+
- `role`: which role completed this task
|
|
25
|
+
- `title`: task title
|
|
26
|
+
- `slug`: task slug / deliverable ID
|
|
27
|
+
|
|
28
|
+
If no current task is set, inform the user and stop.
|
|
29
|
+
|
|
30
|
+
### Step 2: Check Deliverables
|
|
31
|
+
|
|
32
|
+
Verify that the output directory exists and contains files:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
ls -la <output_dir>/
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
If empty or missing, warn the user:
|
|
39
|
+
> "The output directory `<output_dir>` appears empty. Would you like to continue anyway?"
|
|
40
|
+
|
|
41
|
+
### Step 3: Generate HANDOFF.md
|
|
42
|
+
|
|
43
|
+
Analyze all files in the output directory and generate a `HANDOFF.md` document.
|
|
44
|
+
|
|
45
|
+
**For PM role (requirements pool):**
|
|
46
|
+
|
|
47
|
+
```markdown
|
|
48
|
+
# {Title} - Requirements Handoff
|
|
49
|
+
|
|
50
|
+
## Task Info
|
|
51
|
+
- **Feature ID**: {slug}
|
|
52
|
+
- **Title**: {title}
|
|
53
|
+
- **Completed by**: {developer}
|
|
54
|
+
- **Completed at**: {timestamp}
|
|
55
|
+
|
|
56
|
+
## Core Requirements
|
|
57
|
+
(AI-generated summary of PRD key points, 2-3 paragraphs)
|
|
58
|
+
|
|
59
|
+
## Deliverable Files
|
|
60
|
+
- `prd.md` - Product requirements document
|
|
61
|
+
- (list all files)
|
|
62
|
+
|
|
63
|
+
## Key Design Points
|
|
64
|
+
1. (extracted from PRD)
|
|
65
|
+
2. ...
|
|
66
|
+
|
|
67
|
+
## Special Notes for Downstream
|
|
68
|
+
- (important details the Designer should know)
|
|
69
|
+
- (constraints, edge cases, specific UI requirements)
|
|
70
|
+
|
|
71
|
+
## Related Resources
|
|
72
|
+
- (links if any)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**For Designer role (prototypes pool):**
|
|
76
|
+
|
|
77
|
+
```markdown
|
|
78
|
+
# {Title} - Prototype Handoff
|
|
79
|
+
|
|
80
|
+
## Task Info
|
|
81
|
+
- **Feature ID**: {slug}
|
|
82
|
+
- **Based on**: {source requirement}
|
|
83
|
+
- **Completed by**: {developer}
|
|
84
|
+
- **Completed at**: {timestamp}
|
|
85
|
+
|
|
86
|
+
## Design Notes
|
|
87
|
+
(AI-generated summary of design approach, 2-3 paragraphs)
|
|
88
|
+
|
|
89
|
+
## Deliverable Files
|
|
90
|
+
- (list all component files)
|
|
91
|
+
|
|
92
|
+
## Component Structure
|
|
93
|
+
(component tree overview)
|
|
94
|
+
|
|
95
|
+
## Mock Data Notes
|
|
96
|
+
Current mock data used:
|
|
97
|
+
```typescript
|
|
98
|
+
// Document all mock data and their locations
|
|
99
|
+
// Include file name and line numbers
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Logic for Frontend to Implement
|
|
103
|
+
1. Replace mock login handler with real API call (file:line)
|
|
104
|
+
2. Add error handling for network failures
|
|
105
|
+
3. Implement loading state management
|
|
106
|
+
4. ...
|
|
107
|
+
|
|
108
|
+
## Special Notes for Downstream
|
|
109
|
+
- (interactions to preserve)
|
|
110
|
+
- (SDK dependencies)
|
|
111
|
+
- (form validation already implemented)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Write the generated HANDOFF.md to `<output_dir>/HANDOFF.md`.
|
|
115
|
+
|
|
116
|
+
### Step 4: Ask for Handoff Message (Optional)
|
|
117
|
+
|
|
118
|
+
Ask the user:
|
|
119
|
+
> "Any additional notes for the downstream role? (Press Enter to skip)"
|
|
120
|
+
|
|
121
|
+
If provided, append to the Special Notes section.
|
|
122
|
+
|
|
123
|
+
### Step 5: Add to Pool
|
|
124
|
+
|
|
125
|
+
Determine the target pool based on role:
|
|
126
|
+
- `pm` -> `requirements`
|
|
127
|
+
- `designer` -> `prototypes`
|
|
128
|
+
- `frontend-impl` or `frontend` -> `implementations`
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
python3 ./.trellis/scripts/pool.py add <pool> <slug> "<title>" <output_dir>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Step 6: Report
|
|
135
|
+
|
|
136
|
+
Output a summary:
|
|
137
|
+
```
|
|
138
|
+
Handoff complete!
|
|
139
|
+
- Deliverable: <slug>
|
|
140
|
+
- Pool: <pool>
|
|
141
|
+
- HANDOFF.md: <output_dir>/HANDOFF.md
|
|
142
|
+
- Status: available
|
|
143
|
+
|
|
144
|
+
Next: The downstream role can pick this up with:
|
|
145
|
+
/trellis:pick-task <pool> <slug>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Remind the user to commit and push so the downstream role can access the deliverables.
|