@mindfoldhq/trellis 0.5.0-beta.9 → 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.
Files changed (192) hide show
  1. package/README.md +60 -95
  2. package/dist/cli/index.js +7 -0
  3. package/dist/cli/index.js.map +1 -1
  4. package/dist/commands/init.d.ts +3 -0
  5. package/dist/commands/init.d.ts.map +1 -1
  6. package/dist/commands/init.js +117 -117
  7. package/dist/commands/init.js.map +1 -1
  8. package/dist/commands/update.d.ts.map +1 -1
  9. package/dist/commands/update.js +289 -33
  10. package/dist/commands/update.js.map +1 -1
  11. package/dist/configurators/antigravity.d.ts.map +1 -1
  12. package/dist/configurators/antigravity.js +2 -8
  13. package/dist/configurators/antigravity.js.map +1 -1
  14. package/dist/configurators/claude.d.ts.map +1 -1
  15. package/dist/configurators/claude.js +4 -10
  16. package/dist/configurators/claude.js.map +1 -1
  17. package/dist/configurators/codebuddy.d.ts.map +1 -1
  18. package/dist/configurators/codebuddy.js +3 -3
  19. package/dist/configurators/codebuddy.js.map +1 -1
  20. package/dist/configurators/codex.d.ts.map +1 -1
  21. package/dist/configurators/codex.js +5 -13
  22. package/dist/configurators/codex.js.map +1 -1
  23. package/dist/configurators/copilot.d.ts.map +1 -1
  24. package/dist/configurators/copilot.js +5 -19
  25. package/dist/configurators/copilot.js.map +1 -1
  26. package/dist/configurators/cursor.d.ts.map +1 -1
  27. package/dist/configurators/cursor.js +3 -3
  28. package/dist/configurators/cursor.js.map +1 -1
  29. package/dist/configurators/droid.d.ts.map +1 -1
  30. package/dist/configurators/droid.js +3 -3
  31. package/dist/configurators/droid.js.map +1 -1
  32. package/dist/configurators/gemini.d.ts.map +1 -1
  33. package/dist/configurators/gemini.js +3 -5
  34. package/dist/configurators/gemini.js.map +1 -1
  35. package/dist/configurators/index.d.ts.map +1 -1
  36. package/dist/configurators/index.js +37 -49
  37. package/dist/configurators/index.js.map +1 -1
  38. package/dist/configurators/kilo.d.ts.map +1 -1
  39. package/dist/configurators/kilo.js +2 -8
  40. package/dist/configurators/kilo.js.map +1 -1
  41. package/dist/configurators/kiro.d.ts.map +1 -1
  42. package/dist/configurators/kiro.js +3 -3
  43. package/dist/configurators/kiro.js.map +1 -1
  44. package/dist/configurators/opencode.d.ts.map +1 -1
  45. package/dist/configurators/opencode.js +7 -4
  46. package/dist/configurators/opencode.js.map +1 -1
  47. package/dist/configurators/pi.d.ts +3 -0
  48. package/dist/configurators/pi.d.ts.map +1 -0
  49. package/dist/configurators/pi.js +44 -0
  50. package/dist/configurators/pi.js.map +1 -0
  51. package/dist/configurators/qoder.d.ts.map +1 -1
  52. package/dist/configurators/qoder.js +3 -5
  53. package/dist/configurators/qoder.js.map +1 -1
  54. package/dist/configurators/shared.d.ts +28 -6
  55. package/dist/configurators/shared.d.ts.map +1 -1
  56. package/dist/configurators/shared.js +47 -15
  57. package/dist/configurators/shared.js.map +1 -1
  58. package/dist/configurators/windsurf.d.ts.map +1 -1
  59. package/dist/configurators/windsurf.js +2 -8
  60. package/dist/configurators/windsurf.js.map +1 -1
  61. package/dist/constants/paths.d.ts +2 -0
  62. package/dist/constants/paths.d.ts.map +1 -1
  63. package/dist/constants/paths.js +2 -0
  64. package/dist/constants/paths.js.map +1 -1
  65. package/dist/migrations/manifests/0.5.0-beta.0.json +2 -0
  66. package/dist/migrations/manifests/0.5.0-beta.10.json +9 -0
  67. package/dist/migrations/manifests/0.5.0-beta.11.json +9 -0
  68. package/dist/migrations/manifests/0.5.0-beta.12.json +9 -0
  69. package/dist/migrations/manifests/0.5.0-beta.13.json +9 -0
  70. package/dist/migrations/manifests/0.5.0-beta.14.json +9 -0
  71. package/dist/migrations/manifests/0.5.0-beta.15.json +116 -0
  72. package/dist/migrations/manifests/0.5.0-beta.16.json +9 -0
  73. package/dist/migrations/manifests/0.5.0-beta.17.json +9 -0
  74. package/dist/migrations/manifests/0.5.0-beta.18.json +9 -0
  75. package/dist/migrations/manifests/0.5.0-beta.19.json +9 -0
  76. package/dist/migrations/manifests/0.5.0-beta.5.json +2 -0
  77. package/dist/migrations/manifests/0.5.0-rc.0.json +9 -0
  78. package/dist/templates/claude/agents/trellis-research.md +1 -1
  79. package/dist/templates/claude/settings.json +0 -4
  80. package/dist/templates/codebuddy/agents/trellis-research.md +1 -1
  81. package/dist/templates/codex/agents/trellis-research.toml +3 -2
  82. package/dist/templates/codex/hooks/session-start.py +126 -26
  83. package/dist/templates/codex/skills/finish-work/SKILL.md +41 -109
  84. package/dist/templates/codex/skills/start/SKILL.md +12 -9
  85. package/dist/templates/common/bundled-skills/trellis-meta/SKILL.md +73 -0
  86. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/add-project-local-conventions.md +83 -0
  87. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-agents.md +54 -0
  88. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-context-loading.md +81 -0
  89. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-hooks.md +57 -0
  90. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-skills-or-commands.md +78 -0
  91. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-spec-structure.md +83 -0
  92. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-task-lifecycle.md +90 -0
  93. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-workflow.md +64 -0
  94. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/overview.md +55 -0
  95. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/context-injection.md +68 -0
  96. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/generated-files.md +80 -0
  97. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/overview.md +51 -0
  98. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/spec-system.md +102 -0
  99. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/task-system.md +101 -0
  100. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/workflow.md +75 -0
  101. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/workspace-memory.md +71 -0
  102. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/agents.md +79 -0
  103. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/hooks-and-settings.md +69 -0
  104. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/overview.md +59 -0
  105. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/platform-map.md +74 -0
  106. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/skills-and-commands.md +83 -0
  107. package/dist/templates/common/commands/continue.md +9 -5
  108. package/dist/templates/common/commands/finish-work.md +34 -10
  109. package/dist/templates/common/index.d.ts +22 -2
  110. package/dist/templates/common/index.d.ts.map +1 -1
  111. package/dist/templates/common/index.js +53 -4
  112. package/dist/templates/common/index.js.map +1 -1
  113. package/dist/templates/common/skills/brainstorm.md +3 -0
  114. package/dist/templates/copilot/hooks/session-start.py +127 -30
  115. package/dist/templates/copilot/prompts/finish-work.prompt.md +44 -112
  116. package/dist/templates/copilot/prompts/start.prompt.md +12 -9
  117. package/dist/templates/cursor/agents/trellis-check.md +1 -1
  118. package/dist/templates/cursor/agents/trellis-implement.md +1 -1
  119. package/dist/templates/cursor/agents/trellis-research.md +2 -2
  120. package/dist/templates/cursor/hooks.json +7 -1
  121. package/dist/templates/droid/droids/trellis-research.md +1 -1
  122. package/dist/templates/extract.d.ts +6 -0
  123. package/dist/templates/extract.d.ts.map +1 -1
  124. package/dist/templates/extract.js +14 -0
  125. package/dist/templates/extract.js.map +1 -1
  126. package/dist/templates/gemini/agents/trellis-research.md +1 -1
  127. package/dist/templates/kiro/agents/trellis-research.json +1 -1
  128. package/dist/templates/markdown/agents.md +19 -12
  129. package/dist/templates/markdown/gitignore.txt +3 -0
  130. package/dist/templates/markdown/spec/guides/cross-platform-thinking-guide.md.txt +24 -0
  131. package/dist/templates/opencode/agents/trellis-check.md +1 -1
  132. package/dist/templates/opencode/agents/trellis-implement.md +7 -4
  133. package/dist/templates/opencode/agents/trellis-research.md +2 -2
  134. package/dist/templates/opencode/lib/trellis-context.js +100 -13
  135. package/dist/templates/opencode/plugins/inject-subagent-context.js +70 -5
  136. package/dist/templates/opencode/plugins/inject-workflow-state.js +38 -58
  137. package/dist/templates/opencode/plugins/session-start.js +76 -31
  138. package/dist/templates/pi/agents/trellis-check.md +28 -0
  139. package/dist/templates/pi/agents/trellis-implement.md +33 -0
  140. package/dist/templates/pi/agents/trellis-research.md +25 -0
  141. package/dist/templates/pi/extensions/trellis/index.ts.txt +997 -0
  142. package/dist/templates/pi/index.d.ts +5 -0
  143. package/dist/templates/pi/index.d.ts.map +1 -0
  144. package/dist/templates/pi/index.js +12 -0
  145. package/dist/templates/pi/index.js.map +1 -0
  146. package/dist/templates/pi/settings.json +12 -0
  147. package/dist/templates/qoder/agents/trellis-research.md +1 -1
  148. package/dist/templates/shared-hooks/index.d.ts +31 -0
  149. package/dist/templates/shared-hooks/index.d.ts.map +1 -1
  150. package/dist/templates/shared-hooks/index.js +59 -0
  151. package/dist/templates/shared-hooks/index.js.map +1 -1
  152. package/dist/templates/shared-hooks/inject-shell-session-context.py +180 -0
  153. package/dist/templates/shared-hooks/inject-subagent-context.py +156 -27
  154. package/dist/templates/shared-hooks/inject-workflow-state.py +85 -105
  155. package/dist/templates/shared-hooks/session-start.py +222 -36
  156. package/dist/templates/trellis/gitignore.txt +3 -0
  157. package/dist/templates/trellis/index.d.ts +1 -0
  158. package/dist/templates/trellis/index.d.ts.map +1 -1
  159. package/dist/templates/trellis/index.js +2 -0
  160. package/dist/templates/trellis/index.js.map +1 -1
  161. package/dist/templates/trellis/scripts/common/__init__.py +8 -0
  162. package/dist/templates/trellis/scripts/common/active_task.py +593 -0
  163. package/dist/templates/trellis/scripts/common/cli_adapter.py +72 -14
  164. package/dist/templates/trellis/scripts/common/paths.py +61 -58
  165. package/dist/templates/trellis/scripts/common/session_context.py +12 -0
  166. package/dist/templates/trellis/scripts/common/task_context.py +27 -194
  167. package/dist/templates/trellis/scripts/common/task_store.py +102 -26
  168. package/dist/templates/trellis/scripts/common/tasks.py +4 -1
  169. package/dist/templates/trellis/scripts/common/workflow_phase.py +15 -3
  170. package/dist/templates/trellis/scripts/task.py +99 -34
  171. package/dist/templates/trellis/workflow.md +332 -69
  172. package/dist/types/ai-tools.d.ts +12 -3
  173. package/dist/types/ai-tools.d.ts.map +1 -1
  174. package/dist/types/ai-tools.js +29 -0
  175. package/dist/types/ai-tools.js.map +1 -1
  176. package/dist/utils/file-writer.d.ts.map +1 -1
  177. package/dist/utils/file-writer.js +7 -2
  178. package/dist/utils/file-writer.js.map +1 -1
  179. package/dist/utils/posix.d.ts +13 -0
  180. package/dist/utils/posix.d.ts.map +1 -0
  181. package/dist/utils/posix.js +15 -0
  182. package/dist/utils/posix.js.map +1 -0
  183. package/dist/utils/template-fetcher.d.ts +22 -6
  184. package/dist/utils/template-fetcher.d.ts.map +1 -1
  185. package/dist/utils/template-fetcher.js +405 -27
  186. package/dist/utils/template-fetcher.js.map +1 -1
  187. package/dist/utils/template-hash.d.ts +22 -3
  188. package/dist/utils/template-hash.d.ts.map +1 -1
  189. package/dist/utils/template-hash.js +99 -19
  190. package/dist/utils/template-hash.js.map +1 -1
  191. package/package.json +7 -7
  192. package/dist/templates/shared-hooks/statusline.py +0 -218
@@ -1,12 +1,15 @@
1
1
  #!/usr/bin/env python3
2
2
  """Trellis UserPromptSubmit hook: inject per-turn workflow breadcrumb.
3
3
 
4
- Runs on every user prompt. Reads the active task (.trellis/.current-task)
5
- and emits a short <workflow-state> block reminding the main AI what task
6
- is active and its expected flow. Breadcrumb text is pulled from
7
- workflow.md [workflow-state:STATUS] tag blocks (single source of truth
8
- for users who fork the Trellis workflow), with hardcoded fallbacks so
9
- the hook never breaks when workflow.md is missing or malformed.
4
+ Runs on every user prompt. Resolves the active task through Trellis'
5
+ session-aware active task resolver and emits a short <workflow-state>
6
+ block reminding the main AI what task is active and its expected flow.
7
+ Breadcrumb text is pulled exclusively from workflow.md
8
+ [workflow-state:STATUS] tag blocks workflow.md is the single source of
9
+ truth. There are no fallback dicts in this script: when workflow.md is
10
+ missing or a tag is absent, the breadcrumb degrades to a generic
11
+ "Refer to workflow.md for current step." line so users see (and fix)
12
+ the broken state instead of the hook silently masking it.
10
13
 
11
14
  Shared across all hook-capable platforms (Claude, Cursor, Codex, Qoder,
12
15
  CodeBuddy, Droid, Gemini, Copilot). Kiro is not wired (no per-turn
@@ -15,12 +18,7 @@ writeSharedHooks() at init time.
15
18
 
16
19
  Silent exit 0 cases (no output):
17
20
  - No .trellis/ directory found (not a Trellis project)
18
- - No .current-task file, or it's empty
19
21
  - task.json malformed or missing status
20
-
21
- Unknown status (no tag + no hardcoded fallback) emits a generic
22
- breadcrumb rather than silent-exiting, so custom statuses surface in
23
- the UI instead of appearing as "randomly broken".
24
22
  """
25
23
  from __future__ import annotations
26
24
 
@@ -29,7 +27,7 @@ import os
29
27
  import re
30
28
  import sys
31
29
  from pathlib import Path
32
- from typing import Optional, Tuple
30
+ from typing import Optional
33
31
 
34
32
 
35
33
  # ---------------------------------------------------------------------------
@@ -54,48 +52,63 @@ def find_trellis_root(start: Path) -> Optional[Path]:
54
52
  # Active task discovery
55
53
  # ---------------------------------------------------------------------------
56
54
 
57
- def _normalize_task_ref(task_ref: str) -> str:
58
- """Normalize .current-task path ref.
55
+ def _detect_platform(input_data: dict) -> str | None:
56
+ if isinstance(input_data.get("cursor_version"), str):
57
+ return "cursor"
58
+ env_map = {
59
+ "CLAUDE_PROJECT_DIR": "claude",
60
+ "CURSOR_PROJECT_DIR": "cursor",
61
+ "CODEBUDDY_PROJECT_DIR": "codebuddy",
62
+ "FACTORY_PROJECT_DIR": "droid",
63
+ "GEMINI_PROJECT_DIR": "gemini",
64
+ "QODER_PROJECT_DIR": "qoder",
65
+ "KIRO_PROJECT_DIR": "kiro",
66
+ "COPILOT_PROJECT_DIR": "copilot",
67
+ }
68
+ for env_name, platform in env_map.items():
69
+ if os.environ.get(env_name):
70
+ return platform
71
+ script_parts = set(Path(sys.argv[0]).parts)
72
+ if ".claude" in script_parts:
73
+ return "claude"
74
+ if ".cursor" in script_parts:
75
+ return "cursor"
76
+ if ".codex" in script_parts:
77
+ return "codex"
78
+ if ".gemini" in script_parts:
79
+ return "gemini"
80
+ if ".qoder" in script_parts:
81
+ return "qoder"
82
+ if ".codebuddy" in script_parts:
83
+ return "codebuddy"
84
+ if ".factory" in script_parts:
85
+ return "droid"
86
+ if ".kiro" in script_parts:
87
+ return "kiro"
88
+ return None
59
89
 
60
- Accepts:
61
- - Absolute paths (left as-is)
62
- - Windows-style backslashes (converted to forward slash)
63
- - Legacy relative refs like "tasks/foo" (prefixed with .trellis/)
64
- """
65
- normalized = task_ref.strip()
66
- if not normalized:
67
- return ""
68
- path_obj = Path(normalized)
69
- if path_obj.is_absolute():
70
- return str(path_obj)
71
- normalized = normalized.replace("\\", "/")
72
- while normalized.startswith("./"):
73
- normalized = normalized[2:]
74
- if normalized.startswith("tasks/"):
75
- normalized = f".trellis/{normalized}"
76
- return normalized
77
-
78
-
79
- def get_active_task(root: Path) -> Optional[Tuple[str, str]]:
80
- """Return (task_id, status) from the current active task, else None.
81
-
82
- Reads .trellis/.current-task (a path relative to root, e.g.
83
- ".trellis/tasks/04-17-foo") then that task's task.json.
84
- Normalizes backslashes so Windows paths work on Unix and vice versa.
85
- """
86
- ref_file = root / ".trellis" / ".current-task"
87
- if not ref_file.is_file():
88
- return None
89
- try:
90
- raw = ref_file.read_text(encoding="utf-8").strip()
91
- except OSError:
92
- return None
93
- task_ref = _normalize_task_ref(raw)
94
- if not task_ref:
90
+
91
+ def _resolve_active_task(root: Path, input_data: dict):
92
+ scripts_dir = root / ".trellis" / "scripts"
93
+ if str(scripts_dir) not in sys.path:
94
+ sys.path.insert(0, str(scripts_dir))
95
+ from common.active_task import resolve_active_task # type: ignore[import-not-found]
96
+
97
+ return resolve_active_task(root, input_data, platform=_detect_platform(input_data))
98
+
99
+
100
+ def get_active_task(root: Path, input_data: dict) -> Optional[tuple[str, str, str]]:
101
+ """Return (task_id, status, source) from the current active task."""
102
+ active = _resolve_active_task(root, input_data)
103
+ if not active.task_path:
95
104
  return None
96
105
 
97
- path_obj = Path(task_ref)
98
- task_dir = path_obj if path_obj.is_absolute() else root / path_obj
106
+ task_dir = Path(active.task_path)
107
+ if not task_dir.is_absolute():
108
+ task_dir = root / task_dir
109
+ if active.stale:
110
+ return task_dir.name, f"stale_{active.source_type}", active.source
111
+
99
112
  task_json = task_dir / "task.json"
100
113
  if not task_json.is_file():
101
114
  return None
@@ -108,7 +121,7 @@ def get_active_task(root: Path) -> Optional[Tuple[str, str]]:
108
121
  status = data.get("status", "")
109
122
  if not isinstance(status, str) or not status:
110
123
  return None
111
- return task_id, status
124
+ return task_id, status, active.source
112
125
 
113
126
 
114
127
  # ---------------------------------------------------------------------------
@@ -122,64 +135,24 @@ _TAG_RE = re.compile(
122
135
  re.DOTALL,
123
136
  )
124
137
 
125
- # Hardcoded defaults for built-in Trellis statuses. Used when workflow.md is
126
- # missing, malformed, or lacks the tag for this status.
127
- #
128
- # `no_task` is a pseudo-status emitted when .current-task is missing — it keeps
129
- # the Next-Action reminder flowing per-turn even without an active task.
130
- _FALLBACK_BREADCRUMBS = {
131
- "no_task": (
132
- "No active task.\n"
133
- "Trigger words in the user message that REQUIRE creating a task "
134
- "(non-negotiable, do NOT self-exempt): 重构 / 抽成 / 独立 / 分发 / "
135
- "拆出来 / 搞一个 / 做成 / 接入 / 集成 / refactor / rewrite / extract / "
136
- "productize / publish / build X / design Y.\n"
137
- "Task is NOT required ONLY if ALL three hold: (a) zero file writes "
138
- "this turn, (b) answer fits in one reply with no multi-round plan, "
139
- "(c) no research beyond reading 1-2 repo files.\n"
140
- "When in doubt: create task. Over-tasking is cheap; under-tasking "
141
- "leaks plans and research into main context.\n"
142
- "Flow: load `trellis-brainstorm` skill → it creates the task via "
143
- "`python3 ./.trellis/scripts/task.py create` and drives requirements Q&A. "
144
- "For research-heavy work (tool comparison, docs, cross-platform survey), "
145
- "spawn `trellis-research` sub-agents via Task tool — NEVER do 3+ inline "
146
- "WebFetch/WebSearch/`gh api` calls in the main conversation."
147
- ),
148
- "planning": (
149
- "Complete prd.md via trellis-brainstorm skill; then run task.py start.\n"
150
- "Research belongs in `{task_dir}/research/*.md`, written by "
151
- "`trellis-research` sub-agents. Do NOT inline WebFetch/WebSearch in "
152
- "main session — PRD only links to research files."
153
- ),
154
- "in_progress": (
155
- "Flow: trellis-implement → trellis-check → trellis-update-spec → finish\n"
156
- "Check conversation history + git status to determine current step; "
157
- "do NOT skip trellis-check."
158
- ),
159
- "completed": (
160
- "User commits changes; then run task.py archive."
161
- ),
162
- }
163
-
164
-
165
138
  def load_breadcrumbs(root: Path) -> dict[str, str]:
166
139
  """Parse workflow.md for [workflow-state:STATUS] blocks.
167
140
 
168
- Returns {status: body_text}. Missing tags fall back to hardcoded
169
- defaults so the hook always has something to say for built-in
170
- statuses. Custom statuses without tags fall to generic breadcrumb
171
- downstream (see build_breadcrumb).
141
+ Returns {status: body_text}. workflow.md is the single source of
142
+ truth there are no fallback dicts in this script. Missing tags
143
+ (or a missing/unreadable workflow.md) fall back to a generic line
144
+ in build_breadcrumb so users see the broken state and fix
145
+ workflow.md, rather than the hook silently masking the issue.
172
146
  """
173
- result = dict(_FALLBACK_BREADCRUMBS)
174
-
175
147
  workflow = root / ".trellis" / "workflow.md"
176
148
  if not workflow.is_file():
177
- return result
149
+ return {}
178
150
  try:
179
151
  content = workflow.read_text(encoding="utf-8")
180
152
  except OSError:
181
- return result
153
+ return {}
182
154
 
155
+ result: dict[str, str] = {}
183
156
  for match in _TAG_RE.finditer(content):
184
157
  status = match.group(1)
185
158
  body = match.group(2).strip()
@@ -189,18 +162,24 @@ def load_breadcrumbs(root: Path) -> dict[str, str]:
189
162
 
190
163
 
191
164
  def build_breadcrumb(
192
- task_id: Optional[str], status: str, templates: dict[str, str]
165
+ task_id: Optional[str],
166
+ status: str,
167
+ templates: dict[str, str],
168
+ source: str | None = None,
193
169
  ) -> str:
194
170
  """Build the <workflow-state>...</workflow-state> block.
195
171
 
196
- - Known status (in templates or fallback) → detailed template body
197
- - Unknown status (no tag + no fallback) → generic "refer to workflow.md"
172
+ - Known status (tag present in workflow.md) → detailed template body
173
+ - Unknown status (no tag, or workflow.md missing) → generic
174
+ "Refer to workflow.md for current step." line
198
175
  - `no_task` pseudo-status (task_id is None) → header omits task info
199
176
  """
200
177
  body = templates.get(status)
201
178
  if body is None:
202
179
  body = "Refer to workflow.md for current step."
203
180
  header = f"Status: {status}" if task_id is None else f"Task: {task_id} ({status})"
181
+ if source:
182
+ header = f"{header}\nSource: {source}"
204
183
  return f"<workflow-state>\n{header}\n{body}\n</workflow-state>"
205
184
 
206
185
 
@@ -222,13 +201,14 @@ def main() -> int:
222
201
  return 0 # not a Trellis project
223
202
 
224
203
  templates = load_breadcrumbs(root)
225
- task = get_active_task(root)
204
+ task = get_active_task(root, data)
226
205
  if task is None:
227
206
  # No active task — still emit a breadcrumb nudging AI toward
228
207
  # trellis-brainstorm + task.py create when user describes real work.
229
208
  breadcrumb = build_breadcrumb(None, "no_task", templates)
230
209
  else:
231
- breadcrumb = build_breadcrumb(*task, templates=templates)
210
+ task_id, status, source = task
211
+ breadcrumb = build_breadcrumb(task_id, status, templates, source)
232
212
 
233
213
  output = {
234
214
  "hookSpecificOutput": {