@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.
Files changed (65) hide show
  1. package/dist/configurators/workflow.d.ts.map +1 -1
  2. package/dist/configurators/workflow.js +58 -1
  3. package/dist/configurators/workflow.js.map +1 -1
  4. package/dist/constants/paths.d.ts +17 -0
  5. package/dist/constants/paths.d.ts.map +1 -1
  6. package/dist/constants/paths.js +19 -0
  7. package/dist/constants/paths.js.map +1 -1
  8. package/dist/templates/claude/commands/trellis/handoff.md +90 -387
  9. package/dist/templates/claude/commands/trellis/pick-task.md +74 -444
  10. package/dist/templates/claude/hooks/inject-subagent-context.py +17 -101
  11. package/dist/templates/claude/hooks/ralph-loop.py +1 -0
  12. package/dist/templates/claude/hooks/session-start.py +170 -54
  13. package/dist/templates/iflow/commands/trellis/handoff.md +148 -0
  14. package/dist/templates/iflow/commands/trellis/pick-task.md +145 -0
  15. package/dist/templates/iflow/hooks/inject-subagent-context.py +1 -0
  16. package/dist/templates/iflow/hooks/ralph-loop.py +1 -0
  17. package/dist/templates/iflow/hooks/session-start.py +171 -0
  18. package/dist/templates/markdown/index.d.ts +9 -0
  19. package/dist/templates/markdown/index.d.ts.map +1 -1
  20. package/dist/templates/markdown/index.js +10 -0
  21. package/dist/templates/markdown/index.js.map +1 -1
  22. package/dist/templates/markdown/spec/roles/designer/index.md.txt +57 -0
  23. package/dist/templates/markdown/spec/roles/designer/mock-data-standards.md.txt +63 -0
  24. package/dist/templates/markdown/spec/roles/designer/prototype-guidelines.md.txt +49 -0
  25. package/dist/templates/markdown/spec/roles/frontend-impl/api-integration.md.txt +63 -0
  26. package/dist/templates/markdown/spec/roles/frontend-impl/index.md.txt +57 -0
  27. package/dist/templates/markdown/spec/roles/frontend-impl/prototype-to-production.md.txt +57 -0
  28. package/dist/templates/markdown/spec/roles/pm/index.md.txt +45 -0
  29. package/dist/templates/markdown/spec/roles/pm/prd-template.md.txt +64 -0
  30. package/dist/templates/markdown/spec/roles/pm/requirement-checklist.md.txt +43 -0
  31. package/dist/templates/trellis/index.d.ts +1 -0
  32. package/dist/templates/trellis/index.d.ts.map +1 -1
  33. package/dist/templates/trellis/index.js +2 -0
  34. package/dist/templates/trellis/index.js.map +1 -1
  35. package/dist/templates/trellis/scripts/add_session.py +3 -2
  36. package/dist/templates/trellis/scripts/common/cli_adapter.py +4 -4
  37. package/dist/templates/trellis/scripts/common/developer.py +4 -4
  38. package/dist/templates/trellis/scripts/common/git_context.py +7 -7
  39. package/dist/templates/trellis/scripts/common/paths.py +64 -14
  40. package/dist/templates/trellis/scripts/common/phase.py +2 -2
  41. package/dist/templates/trellis/scripts/common/registry.py +16 -16
  42. package/dist/templates/trellis/scripts/common/task_queue.py +10 -10
  43. package/dist/templates/trellis/scripts/common/task_utils.py +5 -5
  44. package/dist/templates/trellis/scripts/common/worktree.py +8 -8
  45. package/dist/templates/trellis/scripts/pool.py +214 -265
  46. package/dist/templates/trellis/scripts/task.py +3 -116
  47. package/package.json +3 -3
  48. package/dist/templates/claude/commands/trellis/before-role-work.md +0 -364
  49. package/dist/templates/trellis/VERSION +0 -1
  50. package/dist/templates/trellis/deliverables/README.md +0 -51
  51. package/dist/templates/trellis/paths.README.md +0 -277
  52. package/dist/templates/trellis/paths.yaml +0 -41
  53. package/dist/templates/trellis/pool/implementations.json +0 -5
  54. package/dist/templates/trellis/pool/prototypes.json +0 -5
  55. package/dist/templates/trellis/pool/requirements.json +0 -5
  56. package/dist/templates/trellis/scripts/common/project_paths.py +0 -189
  57. package/dist/templates/trellis/scripts/handoff_generator.py +0 -380
  58. package/dist/templates/trellis/spec/roles/designer/index.md +0 -243
  59. package/dist/templates/trellis/spec/roles/designer/mock-data-standards.md +0 -481
  60. package/dist/templates/trellis/spec/roles/designer/prototype-guidelines.md +0 -429
  61. package/dist/templates/trellis/spec/roles/frontend-impl/api-integration.md +0 -565
  62. package/dist/templates/trellis/spec/roles/frontend-impl/index.md +0 -321
  63. package/dist/templates/trellis/spec/roles/frontend-impl/state-management.md +0 -599
  64. package/dist/templates/trellis/spec/roles/pm/index.md +0 -112
  65. 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) -> Optional[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) -> Optional[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) -> Optional[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. Upstream outputs (HANDOFF + deliverables, if three-role collaboration)
350
- 3. prd.md (requirements)
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. Inject upstream outputs (三角色协作)
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
- # 4. Technical design
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. Upstream outputs (HANDOFF + deliverables, if three-role collaboration)
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. Inject upstream outputs (三角色协作)
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. Upstream outputs (HANDOFF + deliverables, if three-role collaboration)
442
- 4. update-spec.md (for active spec sync)
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. Inject upstream outputs (三角色协作)
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
- # 4. Requirements document (for verifying requirements are met)
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. Upstream outputs (HANDOFF + deliverables, if three-role collaboration)
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. Inject upstream outputs (三角色协作)
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: Optional[str]) -> str:
597
+ def get_research_context(repo_root: str, task_dir: str | None) -> str:
682
598
  """
683
599
  Context for Research Agent
684
600
 
@@ -17,6 +17,7 @@ State file: .trellis/.ralph-state.json
17
17
  - Tracks current iteration count per session
18
18
  - Resets when task changes
19
19
  """
20
+ from __future__ import annotations
20
21
 
21
22
  # IMPORTANT: Suppress all warnings FIRST
22
23
  import warnings
@@ -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 run_script_with_args(script_path: Path, args: list[str]) -> str:
69
- """Run script with additional arguments"""
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
- if script_path.suffix == ".py":
72
- env = os.environ.copy()
73
- env["PYTHONIOENCODING"] = "utf-8"
74
- cmd = [sys.executable, "-W", "ignore", str(script_path)] + args
75
- else:
76
- env = os.environ
77
- cmd = [str(script_path)] + args
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
- result = subprocess.run(
80
- cmd,
81
- capture_output=True,
82
- text=True,
83
- encoding="utf-8",
84
- errors="replace",
85
- timeout=5,
86
- cwd=script_path.parent.parent.parent,
87
- env=env,
88
- )
89
- return result.stdout if result.returncode == 0 else "No available tasks"
90
- except (subprocess.TimeoutExpired, FileNotFoundError, PermissionError):
91
- return "No available tasks"
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.