@blxzer/cursor-trellis 0.3.4 → 0.3.6

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 (210) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +13 -13
  3. package/README.zh-CN.md +1 -1
  4. package/bin/cstl.js +1 -1
  5. package/bin/smart-search.js +1 -1
  6. package/dist/cli/index.d.ts.map +1 -1
  7. package/dist/cli/index.js +17 -2
  8. package/dist/cli/index.js.map +1 -1
  9. package/dist/commands/campaign/canvas-render.d.ts +68 -0
  10. package/dist/commands/campaign/canvas-render.d.ts.map +1 -0
  11. package/dist/commands/campaign/canvas-render.js +414 -0
  12. package/dist/commands/campaign/canvas-render.js.map +1 -0
  13. package/dist/commands/campaign/compose.d.ts +15 -0
  14. package/dist/commands/campaign/compose.d.ts.map +1 -0
  15. package/dist/commands/campaign/compose.js +110 -0
  16. package/dist/commands/campaign/compose.js.map +1 -0
  17. package/dist/commands/campaign/index.d.ts +13 -0
  18. package/dist/commands/campaign/index.d.ts.map +1 -0
  19. package/dist/commands/campaign/index.js +134 -0
  20. package/dist/commands/campaign/index.js.map +1 -0
  21. package/dist/commands/campaign/kind-map.d.ts +6 -0
  22. package/dist/commands/campaign/kind-map.d.ts.map +1 -0
  23. package/dist/commands/campaign/kind-map.js +21 -0
  24. package/dist/commands/campaign/kind-map.js.map +1 -0
  25. package/dist/commands/campaign/mcp-server.d.ts +6 -0
  26. package/dist/commands/campaign/mcp-server.d.ts.map +1 -0
  27. package/dist/commands/campaign/mcp-server.js +167 -0
  28. package/dist/commands/campaign/mcp-server.js.map +1 -0
  29. package/dist/commands/campaign/render.d.ts +5 -0
  30. package/dist/commands/campaign/render.d.ts.map +1 -0
  31. package/dist/commands/campaign/render.js +103 -0
  32. package/dist/commands/campaign/render.js.map +1 -0
  33. package/dist/commands/campaign/trellis-load.d.ts +13 -0
  34. package/dist/commands/campaign/trellis-load.d.ts.map +1 -0
  35. package/dist/commands/campaign/trellis-load.js +122 -0
  36. package/dist/commands/campaign/trellis-load.js.map +1 -0
  37. package/dist/commands/campaign/types.d.ts +61 -0
  38. package/dist/commands/campaign/types.d.ts.map +1 -0
  39. package/dist/commands/campaign/types.js +3 -0
  40. package/dist/commands/campaign/types.js.map +1 -0
  41. package/dist/commands/goal/index.d.ts +3 -0
  42. package/dist/commands/goal/index.d.ts.map +1 -0
  43. package/dist/commands/goal/index.js +159 -0
  44. package/dist/commands/goal/index.js.map +1 -0
  45. package/dist/commands/goal/review.d.ts +6 -0
  46. package/dist/commands/goal/review.d.ts.map +1 -0
  47. package/dist/commands/goal/review.js +28 -0
  48. package/dist/commands/goal/review.js.map +1 -0
  49. package/dist/commands/init.d.ts.map +1 -1
  50. package/dist/commands/init.js +11 -1
  51. package/dist/commands/init.js.map +1 -1
  52. package/dist/commands/rpc/client.d.ts +51 -0
  53. package/dist/commands/rpc/client.d.ts.map +1 -0
  54. package/dist/commands/rpc/client.js +147 -0
  55. package/dist/commands/rpc/client.js.map +1 -0
  56. package/dist/commands/rpc/index.d.ts +10 -0
  57. package/dist/commands/rpc/index.d.ts.map +1 -0
  58. package/dist/commands/rpc/index.js +157 -0
  59. package/dist/commands/rpc/index.js.map +1 -0
  60. package/dist/commands/rpc/serve.d.ts +23 -0
  61. package/dist/commands/rpc/serve.d.ts.map +1 -0
  62. package/dist/commands/rpc/serve.js +60 -0
  63. package/dist/commands/rpc/serve.js.map +1 -0
  64. package/dist/commands/rpc/smoke.d.ts +16 -0
  65. package/dist/commands/rpc/smoke.d.ts.map +1 -0
  66. package/dist/commands/rpc/smoke.js +92 -0
  67. package/dist/commands/rpc/smoke.js.map +1 -0
  68. package/dist/commands/sdk/index.d.ts +9 -0
  69. package/dist/commands/sdk/index.d.ts.map +1 -0
  70. package/dist/commands/sdk/index.js +58 -0
  71. package/dist/commands/sdk/index.js.map +1 -0
  72. package/dist/commands/sdk/run.d.ts +42 -0
  73. package/dist/commands/sdk/run.d.ts.map +1 -0
  74. package/dist/commands/sdk/run.js +220 -0
  75. package/dist/commands/sdk/run.js.map +1 -0
  76. package/dist/commands/sdk/status.d.ts +14 -0
  77. package/dist/commands/sdk/status.d.ts.map +1 -0
  78. package/dist/commands/sdk/status.js +69 -0
  79. package/dist/commands/sdk/status.js.map +1 -0
  80. package/dist/configurators/workflow.d.ts.map +1 -1
  81. package/dist/configurators/workflow.js +13 -1
  82. package/dist/configurators/workflow.js.map +1 -1
  83. package/dist/goal/action-packet.d.ts +89 -0
  84. package/dist/goal/action-packet.d.ts.map +1 -0
  85. package/dist/goal/action-packet.js +113 -0
  86. package/dist/goal/action-packet.js.map +1 -0
  87. package/dist/goal/audit.d.ts +14 -0
  88. package/dist/goal/audit.d.ts.map +1 -0
  89. package/dist/goal/audit.js +12 -0
  90. package/dist/goal/audit.js.map +1 -0
  91. package/dist/goal/constants.d.ts +26 -0
  92. package/dist/goal/constants.d.ts.map +1 -0
  93. package/dist/goal/constants.js +24 -0
  94. package/dist/goal/constants.js.map +1 -0
  95. package/dist/goal/paths.d.ts +8 -0
  96. package/dist/goal/paths.d.ts.map +1 -0
  97. package/dist/goal/paths.js +36 -0
  98. package/dist/goal/paths.js.map +1 -0
  99. package/dist/goal/preflight.d.ts +21 -0
  100. package/dist/goal/preflight.d.ts.map +1 -0
  101. package/dist/goal/preflight.js +97 -0
  102. package/dist/goal/preflight.js.map +1 -0
  103. package/dist/goal/review-seam.d.ts +44 -0
  104. package/dist/goal/review-seam.d.ts.map +1 -0
  105. package/dist/goal/review-seam.js +153 -0
  106. package/dist/goal/review-seam.js.map +1 -0
  107. package/dist/goal/reviewer-rules.d.ts +13 -0
  108. package/dist/goal/reviewer-rules.d.ts.map +1 -0
  109. package/dist/goal/reviewer-rules.js +153 -0
  110. package/dist/goal/reviewer-rules.js.map +1 -0
  111. package/dist/goal/reviewer.d.ts +9 -0
  112. package/dist/goal/reviewer.d.ts.map +1 -0
  113. package/dist/goal/reviewer.js +54 -0
  114. package/dist/goal/reviewer.js.map +1 -0
  115. package/dist/goal/runtime.d.ts +32 -0
  116. package/dist/goal/runtime.d.ts.map +1 -0
  117. package/dist/goal/runtime.js +242 -0
  118. package/dist/goal/runtime.js.map +1 -0
  119. package/dist/goal/sdk-client.d.ts +10 -0
  120. package/dist/goal/sdk-client.d.ts.map +1 -0
  121. package/dist/goal/sdk-client.js +34 -0
  122. package/dist/goal/sdk-client.js.map +1 -0
  123. package/dist/goal/state.d.ts +40 -0
  124. package/dist/goal/state.d.ts.map +1 -0
  125. package/dist/goal/state.js +37 -0
  126. package/dist/goal/state.js.map +1 -0
  127. package/dist/goal/task-triggers.d.ts +15 -0
  128. package/dist/goal/task-triggers.d.ts.map +1 -0
  129. package/dist/goal/task-triggers.js +23 -0
  130. package/dist/goal/task-triggers.js.map +1 -0
  131. package/dist/goal/walls.d.ts +12 -0
  132. package/dist/goal/walls.d.ts.map +1 -0
  133. package/dist/goal/walls.js +65 -0
  134. package/dist/goal/walls.js.map +1 -0
  135. package/dist/goal/worker-prompt.d.ts +13 -0
  136. package/dist/goal/worker-prompt.d.ts.map +1 -0
  137. package/dist/goal/worker-prompt.js +41 -0
  138. package/dist/goal/worker-prompt.js.map +1 -0
  139. package/dist/goal/worker.d.ts +42 -0
  140. package/dist/goal/worker.d.ts.map +1 -0
  141. package/dist/goal/worker.js +180 -0
  142. package/dist/goal/worker.js.map +1 -0
  143. package/dist/migrations/manifests/0.3.5.json +9 -0
  144. package/dist/migrations/manifests/0.3.6.json +9 -0
  145. package/dist/templates/common/bundled-skills/smart-search-cli/SKILL.md +35 -29
  146. package/dist/templates/common/bundled-skills/smart-search-cli/references/cli-contract.md +11 -25
  147. package/dist/templates/common/commands/continue.md +15 -3
  148. package/dist/templates/common/skills/check.md +7 -4
  149. package/dist/templates/cursor/agents/cstl-check.md +1 -0
  150. package/dist/templates/cursor/fixtures/expected-rules.d.ts.map +1 -1
  151. package/dist/templates/cursor/fixtures/expected-rules.js +6 -1
  152. package/dist/templates/cursor/fixtures/expected-rules.js.map +1 -1
  153. package/dist/templates/cursor/rules/cstl-cursor-modes.mdc +32 -0
  154. package/dist/templates/cursor/rules/cstl-triage.mdc +2 -4
  155. package/dist/templates/cursor/rules/retrieval-routing.mdc +26 -7
  156. package/dist/templates/markdown/agents.md +2 -0
  157. package/dist/templates/markdown/index.d.ts +3 -0
  158. package/dist/templates/markdown/index.d.ts.map +1 -1
  159. package/dist/templates/markdown/index.js +3 -0
  160. package/dist/templates/markdown/index.js.map +1 -1
  161. package/dist/templates/markdown/spec/guides/cursor-context-injection-guide.md.txt +38 -21
  162. package/dist/templates/markdown/spec/guides/cursor-native-modes-guide.md.txt +125 -0
  163. package/dist/templates/markdown/spec/guides/index.md.txt +107 -104
  164. package/dist/templates/markdown/spec/guides/injection-budget-guide.md.txt +111 -0
  165. package/dist/templates/markdown/spec/guides/retrieval-daily-guide.md.txt +38 -6
  166. package/dist/templates/markdown/spec/guides/verification-strength-guide.md.txt +184 -0
  167. package/dist/templates/shared-hooks/session-start.py +10 -0
  168. package/dist/templates/trellis/config.yaml +18 -0
  169. package/dist/templates/trellis/index.d.ts +5 -0
  170. package/dist/templates/trellis/index.d.ts.map +1 -1
  171. package/dist/templates/trellis/index.js +14 -0
  172. package/dist/templates/trellis/index.js.map +1 -1
  173. package/dist/templates/trellis/scripts/common/artifact_locale.py +278 -0
  174. package/dist/templates/trellis/scripts/common/codebase_retrieval_router.py +51 -2
  175. package/dist/templates/trellis/scripts/common/config.py +39 -0
  176. package/dist/templates/trellis/scripts/common/injection_budget.py +317 -0
  177. package/dist/templates/trellis/scripts/common/parent_orchestration.py +500 -5
  178. package/dist/templates/trellis/scripts/common/retrieval_agent_instructions.py +9 -3
  179. package/dist/templates/trellis/scripts/common/session_context.py +32 -4
  180. package/dist/templates/trellis/scripts/common/session_memory.py +26 -0
  181. package/dist/templates/trellis/scripts/common/subagent_dispatch.py +74 -9
  182. package/dist/templates/trellis/scripts/common/task_context.py +18 -0
  183. package/dist/templates/trellis/scripts/common/task_dashboard.py +16 -1
  184. package/dist/templates/trellis/scripts/common/task_map.py +670 -614
  185. package/dist/templates/trellis/scripts/common/task_store.py +191 -40
  186. package/dist/templates/trellis/scripts/common/test_observable_defaults.py +135 -0
  187. package/dist/templates/trellis/scripts/cursor_retrieval_probe_prompt.md +14 -12
  188. package/dist/templates/trellis/scripts/generate_dispatch_prompt.py +182 -0
  189. package/dist/templates/trellis/scripts/injection_budget_probe.py +69 -0
  190. package/dist/templates/trellis/scripts/retrieval_probe_matrix_template.json +3 -3
  191. package/dist/templates/trellis/scripts/spec_health_outcomes.py +160 -0
  192. package/dist/templates/trellis/scripts/task.py +51 -0
  193. package/dist/templates/trellis/tasks/locale/en/default-prd.md +19 -0
  194. package/dist/templates/trellis/tasks/locale/zh/default-prd.md +19 -0
  195. package/dist/templates/trellis/workflow.md +23 -3
  196. package/dist/utils/codebase-retrieval-router.d.ts.map +1 -1
  197. package/dist/utils/codebase-retrieval-router.js +14 -0
  198. package/dist/utils/codebase-retrieval-router.js.map +1 -1
  199. package/dist/utils/cursor-sdk-gate.d.ts +10 -0
  200. package/dist/utils/cursor-sdk-gate.d.ts.map +1 -0
  201. package/dist/utils/cursor-sdk-gate.js +32 -0
  202. package/dist/utils/cursor-sdk-gate.js.map +1 -0
  203. package/dist/utils/project-capabilities.d.ts +30 -3
  204. package/dist/utils/project-capabilities.d.ts.map +1 -1
  205. package/dist/utils/project-capabilities.js +182 -11
  206. package/dist/utils/project-capabilities.js.map +1 -1
  207. package/dist/utils/readiness.d.ts.map +1 -1
  208. package/dist/utils/readiness.js +26 -0
  209. package/dist/utils/readiness.js.map +1 -1
  210. package/package.json +4 -3
@@ -20,6 +20,7 @@ from .paths import DIR_WORKFLOW, DIR_WORKSPACE, FILE_JOURNAL_PREFIX, get_develop
20
20
 
21
21
 
22
22
  SECTION_NAMES = {"summary", "main changes", "git commits", "testing", "status", "next steps"}
23
+ JOURNAL_SNIPPET_MAX_TOKENS = 150
23
24
  FIELD_WEIGHTS = {
24
25
  "title": 8,
25
26
  "task": 8,
@@ -337,6 +338,31 @@ def normalize_space(value: str) -> str:
337
338
  return re.sub(r"\s+", " ", value).strip()
338
339
 
339
340
 
341
+ def truncate_to_token_budget(text: str, max_tokens: int = JOURNAL_SNIPPET_MAX_TOKENS) -> str:
342
+ """Collapse whitespace and hard-cap by whitespace-split token count."""
343
+ normalized = normalize_space(text)
344
+ if not normalized:
345
+ return ""
346
+ tokens = normalized.split()
347
+ if len(tokens) <= max_tokens:
348
+ return normalized
349
+ return " ".join(tokens[:max_tokens])
350
+
351
+
352
+ def get_latest_journal_summary(
353
+ journal_path: Path,
354
+ repo_root: Path,
355
+ developer: str,
356
+ max_tokens: int = JOURNAL_SNIPPET_MAX_TOKENS,
357
+ ) -> str:
358
+ """Return the latest session summary from one journal file (≤ max_tokens)."""
359
+ entries = parse_journal(journal_path, repo_root, developer)
360
+ if not entries:
361
+ return ""
362
+ summary = entries[-1].sections.get("summary", "")
363
+ return truncate_to_token_budget(summary, max_tokens)
364
+
365
+
340
366
  def to_repo_path(path: Path, repo_root: Path) -> str:
341
367
  try:
342
368
  return path.resolve().relative_to(repo_root.resolve()).as_posix()
@@ -12,6 +12,11 @@ import sys
12
12
  from pathlib import Path
13
13
  from typing import Literal
14
14
 
15
+ from .injection_budget import (
16
+ JSONL_MAX_ENTRIES,
17
+ JSONL_MAX_FILE_CHARS,
18
+ JSONL_MAX_TOTAL_CHARS,
19
+ )
15
20
  from .io import read_json
16
21
  from .paths import FILE_TASK_JSON
17
22
 
@@ -87,7 +92,8 @@ def read_jsonl_entries(
87
92
  jsonl_path: str,
88
93
  *,
89
94
  warn_prefix: str = "subagent-dispatch",
90
- ) -> list[tuple[str, str]]:
95
+ apply_budget: bool = True,
96
+ ) -> tuple[list[tuple[str, str]], list[str]]:
91
97
  full_path = os.path.join(base_path, jsonl_path)
92
98
  if not os.path.exists(full_path):
93
99
  print(
@@ -95,10 +101,13 @@ def read_jsonl_entries(
95
101
  "sub-agent will receive only task artifacts",
96
102
  file=sys.stderr,
97
103
  )
98
- return []
104
+ return [], []
99
105
 
100
106
  results: list[tuple[str, str]] = []
107
+ skipped: list[str] = []
101
108
  saw_real_entry = False
109
+ loaded_entries = 0
110
+ total_chars = 0
102
111
  try:
103
112
  with open(full_path, "r", encoding="utf-8") as f:
104
113
  for line in f:
@@ -112,12 +121,38 @@ def read_jsonl_entries(
112
121
  if not file_path:
113
122
  continue
114
123
  saw_real_entry = True
124
+ if apply_budget and loaded_entries >= JSONL_MAX_ENTRIES:
125
+ skipped.append(f"{file_path}: entry cap ({JSONL_MAX_ENTRIES})")
126
+ continue
127
+
128
+ expanded: list[tuple[str, str]] = []
115
129
  if entry_type == "directory":
116
- results.extend(read_directory_contents(base_path, file_path))
130
+ expanded = read_directory_contents(base_path, file_path)
117
131
  else:
118
132
  content = read_file_content(base_path, file_path)
119
133
  if content:
120
- results.append((file_path, content))
134
+ expanded = [(file_path, content)]
135
+
136
+ if not expanded:
137
+ skipped.append(f"{file_path}: missing or empty")
138
+ continue
139
+
140
+ block_chars = sum(len(content) for _, content in expanded)
141
+ if apply_budget and block_chars > JSONL_MAX_FILE_CHARS:
142
+ skipped.append(
143
+ f"{file_path}: file cap ({block_chars}>{JSONL_MAX_FILE_CHARS} chars)"
144
+ )
145
+ continue
146
+ if apply_budget and total_chars + block_chars > JSONL_MAX_TOTAL_CHARS:
147
+ skipped.append(
148
+ f"{file_path}: total cap "
149
+ f"({total_chars + block_chars}>{JSONL_MAX_TOTAL_CHARS})"
150
+ )
151
+ continue
152
+
153
+ results.extend(expanded)
154
+ total_chars += block_chars
155
+ loaded_entries += 1
121
156
  except json.JSONDecodeError:
122
157
  continue
123
158
  except OSError:
@@ -129,13 +164,23 @@ def read_jsonl_entries(
129
164
  "(only seed / empty) — sub-agent will receive only task artifacts.",
130
165
  file=sys.stderr,
131
166
  )
132
- return results
167
+ elif apply_budget and (results or skipped):
168
+ loaded_paths = ", ".join(path for path, _ in results) or "(none)"
169
+ print(
170
+ f"[{warn_prefix}] injection-budget: {jsonl_path} "
171
+ f"loaded {loaded_entries} entries ({total_chars} chars): {loaded_paths}",
172
+ file=sys.stderr,
173
+ )
174
+ for reason in skipped:
175
+ print(f"[{warn_prefix}] injection-budget: skipped {reason}", file=sys.stderr)
176
+ return results, skipped
133
177
 
134
178
 
135
179
  def get_agent_context(repo_root: str, task_dir: str, agent_type: str) -> str:
136
180
  context_parts: list[str] = []
137
181
  agent_jsonl = f"{task_dir}/{agent_type}.jsonl"
138
- for file_path, content in read_jsonl_entries(repo_root, agent_jsonl):
182
+ entries, _ = read_jsonl_entries(repo_root, agent_jsonl)
183
+ for file_path, content in entries:
139
184
  context_parts.append(f"=== {file_path} ===\n{content}")
140
185
  return "\n\n".join(context_parts)
141
186
 
@@ -166,7 +211,8 @@ def get_implement_context(repo_root: str, task_dir: str) -> str:
166
211
 
167
212
  def get_check_context(repo_root: str, task_dir: str) -> str:
168
213
  context_parts: list[str] = []
169
- for file_path, content in read_jsonl_entries(repo_root, f"{task_dir}/check.jsonl"):
214
+ entries, _ = read_jsonl_entries(repo_root, f"{task_dir}/check.jsonl")
215
+ for file_path, content in entries:
170
216
  context_parts.append(f"=== {file_path} ===\n{content}")
171
217
 
172
218
  prd_content = read_file_content(repo_root, f"{task_dir}/prd.md")
@@ -192,7 +238,25 @@ def get_finish_context(repo_root: str, task_dir: str) -> str:
192
238
 
193
239
 
194
240
  def get_research_context(repo_root: str, task_dir: str | None) -> str:
195
- _ = task_dir
241
+ context_parts: list[str] = []
242
+
243
+ if task_dir:
244
+ entries, _ = read_jsonl_entries(repo_root, f"{task_dir}/research.jsonl")
245
+ for file_path, content in entries:
246
+ context_parts.append(f"=== {file_path} ===\n{content}")
247
+
248
+ prd_content = read_file_content(repo_root, f"{task_dir}/prd.md")
249
+ if prd_content:
250
+ context_parts.append(
251
+ f"=== {task_dir}/prd.md (Requirements) ===\n{prd_content}"
252
+ )
253
+
254
+ design_content = read_file_content(repo_root, f"{task_dir}/design.md")
255
+ if design_content:
256
+ context_parts.append(
257
+ f"=== {task_dir}/design.md (Technical Design) ===\n{design_content}"
258
+ )
259
+
196
260
  spec_path = f"{DIR_WORKFLOW}/{DIR_SPEC}"
197
261
  spec_root = Path(repo_root) / DIR_WORKFLOW / DIR_SPEC
198
262
 
@@ -219,7 +283,8 @@ To get structured package info, run: `python ./{DIR_WORKFLOW}/scripts/get_contex
219
283
  - Spec files: `{spec_path}/**/*.md`
220
284
  - Code search: Use Glob and Grep tools
221
285
  - External facts / docs: load `smart-search-cli` skill and use Bash (`smart-search` CLI), not Cursor WebSearch/WebFetch by default"""
222
- return project_structure
286
+ context_parts.append(project_structure)
287
+ return "\n\n".join(context_parts)
223
288
 
224
289
 
225
290
  def build_implement_prompt(original_prompt: str, context: str) -> str:
@@ -24,6 +24,11 @@ from pathlib import Path
24
24
  from .log import Colors, colored
25
25
  from .paths import get_repo_root
26
26
  from .task_utils import resolve_task_dir
27
+ from .injection_budget import (
28
+ JSONL_MAX_ENTRIES,
29
+ JSONL_MAX_TOTAL_CHARS,
30
+ measure_jsonl_manifest,
31
+ )
27
32
 
28
33
 
29
34
  # =============================================================================
@@ -159,6 +164,19 @@ def _validate_jsonl(jsonl_file: Path, repo_root: Path) -> int:
159
164
 
160
165
  if errors == 0:
161
166
  print(f" {colored(f'{file_name}: ✓ ({real_entries} entries)', Colors.GREEN)}")
167
+ budget = measure_jsonl_manifest(repo_root, jsonl_file.parent, file_name)
168
+ if real_entries > JSONL_MAX_ENTRIES:
169
+ print(
170
+ f" {colored(f'{file_name}: budget WARN — {real_entries} entries (max {JSONL_MAX_ENTRIES})', Colors.YELLOW)}"
171
+ )
172
+ if budget.expanded_chars > JSONL_MAX_TOTAL_CHARS:
173
+ print(
174
+ f" {colored(f'{file_name}: budget WARN — expanded {budget.expanded_chars} chars (max {JSONL_MAX_TOTAL_CHARS})', Colors.YELLOW)}"
175
+ )
176
+ elif budget.skipped:
177
+ print(
178
+ f" {colored(f'{file_name}: budget WARN — {len(budget.skipped)} entries would be skipped at dispatch', Colors.YELLOW)}"
179
+ )
162
180
  else:
163
181
  print(f" {colored(f'{file_name}: ✗ ({errors} errors)', Colors.RED)}")
164
182
 
@@ -7,6 +7,7 @@ from pathlib import Path
7
7
 
8
8
  from .active_task import resolve_selected_task
9
9
  from .paths import DIR_TASKS, DIR_WORKFLOW, get_developer, get_repo_root, get_tasks_dir
10
+ from .task_gates import verify_evidence_status
10
11
  from .task_map import get_child_state
11
12
  from .tasks import (
12
13
  children_progress,
@@ -23,6 +24,19 @@ def _task_path(dir_name: str) -> str:
23
24
  return f"{DIR_WORKFLOW}/{DIR_TASKS}/{dir_name}"
24
25
 
25
26
 
27
+ def format_verify_summary(task_dir: Path, task_data: dict) -> str:
28
+ """Return a compact verify evidence label for dashboard task lines."""
29
+ verify_path = task_dir / "verify.md"
30
+ if not verify_path.is_file():
31
+ return "[verify: missing]"
32
+ status = verify_evidence_status(task_dir, task_data)
33
+ if all(status.values()):
34
+ return "[verify: ok]"
35
+ if any(status.values()):
36
+ return "[verify: partial]"
37
+ return "[verify: missing]"
38
+
39
+
26
40
  def _selected_line(
27
41
  repo_root: Path,
28
42
  platform_input: dict | None = None,
@@ -123,9 +137,10 @@ def _append_task(
123
137
  integration_state = get_child_state(parent_dir, name)
124
138
  status_display = format_child_task_display(task.status, integration_state)
125
139
  assignee = task.assignee or "-"
140
+ verify_summary = format_verify_summary(task.directory, task.raw or {})
126
141
  prefix = " " * indent + " - "
127
142
  lines.append(
128
- f"{prefix}{_task_path(name)} ({status_display}){progress} [{assignee}]"
143
+ f"{prefix}{_task_path(name)} ({status_display}){progress} [{assignee}] {verify_summary}"
129
144
  )
130
145
  for child_name in task.children:
131
146
  if child_name in all_tasks: