@blxzer/cursor-trellis 0.3.5 → 0.4.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 (156) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/README.md +13 -0
  3. package/dist/cli/index.d.ts.map +1 -1
  4. package/dist/cli/index.js +3 -0
  5. package/dist/cli/index.js.map +1 -1
  6. package/dist/commands/goal/index.d.ts +3 -0
  7. package/dist/commands/goal/index.d.ts.map +1 -0
  8. package/dist/commands/goal/index.js +159 -0
  9. package/dist/commands/goal/index.js.map +1 -0
  10. package/dist/commands/goal/review.d.ts +6 -0
  11. package/dist/commands/goal/review.d.ts.map +1 -0
  12. package/dist/commands/goal/review.js +28 -0
  13. package/dist/commands/goal/review.js.map +1 -0
  14. package/dist/commands/sdk/run.d.ts.map +1 -1
  15. package/dist/commands/sdk/run.js +2 -27
  16. package/dist/commands/sdk/run.js.map +1 -1
  17. package/dist/commands/update.d.ts.map +1 -1
  18. package/dist/commands/update.js +5 -1
  19. package/dist/commands/update.js.map +1 -1
  20. package/dist/configurators/workflow.d.ts.map +1 -1
  21. package/dist/configurators/workflow.js +51 -2
  22. package/dist/configurators/workflow.js.map +1 -1
  23. package/dist/constants/paths.d.ts +4 -0
  24. package/dist/constants/paths.d.ts.map +1 -1
  25. package/dist/constants/paths.js +4 -0
  26. package/dist/constants/paths.js.map +1 -1
  27. package/dist/goal/action-packet.d.ts +89 -0
  28. package/dist/goal/action-packet.d.ts.map +1 -0
  29. package/dist/goal/action-packet.js +113 -0
  30. package/dist/goal/action-packet.js.map +1 -0
  31. package/dist/goal/audit.d.ts +14 -0
  32. package/dist/goal/audit.d.ts.map +1 -0
  33. package/dist/goal/audit.js +12 -0
  34. package/dist/goal/audit.js.map +1 -0
  35. package/dist/goal/constants.d.ts +26 -0
  36. package/dist/goal/constants.d.ts.map +1 -0
  37. package/dist/goal/constants.js +24 -0
  38. package/dist/goal/constants.js.map +1 -0
  39. package/dist/goal/paths.d.ts +8 -0
  40. package/dist/goal/paths.d.ts.map +1 -0
  41. package/dist/goal/paths.js +36 -0
  42. package/dist/goal/paths.js.map +1 -0
  43. package/dist/goal/preflight.d.ts +21 -0
  44. package/dist/goal/preflight.d.ts.map +1 -0
  45. package/dist/goal/preflight.js +97 -0
  46. package/dist/goal/preflight.js.map +1 -0
  47. package/dist/goal/review-seam.d.ts +44 -0
  48. package/dist/goal/review-seam.d.ts.map +1 -0
  49. package/dist/goal/review-seam.js +153 -0
  50. package/dist/goal/review-seam.js.map +1 -0
  51. package/dist/goal/reviewer-rules.d.ts +13 -0
  52. package/dist/goal/reviewer-rules.d.ts.map +1 -0
  53. package/dist/goal/reviewer-rules.js +153 -0
  54. package/dist/goal/reviewer-rules.js.map +1 -0
  55. package/dist/goal/reviewer.d.ts +9 -0
  56. package/dist/goal/reviewer.d.ts.map +1 -0
  57. package/dist/goal/reviewer.js +54 -0
  58. package/dist/goal/reviewer.js.map +1 -0
  59. package/dist/goal/runtime.d.ts +32 -0
  60. package/dist/goal/runtime.d.ts.map +1 -0
  61. package/dist/goal/runtime.js +242 -0
  62. package/dist/goal/runtime.js.map +1 -0
  63. package/dist/goal/sdk-client.d.ts +10 -0
  64. package/dist/goal/sdk-client.d.ts.map +1 -0
  65. package/dist/goal/sdk-client.js +34 -0
  66. package/dist/goal/sdk-client.js.map +1 -0
  67. package/dist/goal/state.d.ts +40 -0
  68. package/dist/goal/state.d.ts.map +1 -0
  69. package/dist/goal/state.js +37 -0
  70. package/dist/goal/state.js.map +1 -0
  71. package/dist/goal/task-triggers.d.ts +15 -0
  72. package/dist/goal/task-triggers.d.ts.map +1 -0
  73. package/dist/goal/task-triggers.js +23 -0
  74. package/dist/goal/task-triggers.js.map +1 -0
  75. package/dist/goal/walls.d.ts +12 -0
  76. package/dist/goal/walls.d.ts.map +1 -0
  77. package/dist/goal/walls.js +65 -0
  78. package/dist/goal/walls.js.map +1 -0
  79. package/dist/goal/worker-prompt.d.ts +13 -0
  80. package/dist/goal/worker-prompt.d.ts.map +1 -0
  81. package/dist/goal/worker-prompt.js +41 -0
  82. package/dist/goal/worker-prompt.js.map +1 -0
  83. package/dist/goal/worker.d.ts +42 -0
  84. package/dist/goal/worker.d.ts.map +1 -0
  85. package/dist/goal/worker.js +180 -0
  86. package/dist/goal/worker.js.map +1 -0
  87. package/dist/migrations/manifests/0.3.5.json +9 -0
  88. package/dist/migrations/manifests/0.3.6.json +9 -0
  89. package/dist/migrations/manifests/0.4.0.json +9 -0
  90. package/dist/templates/common/commands/continue.md +15 -3
  91. package/dist/templates/common/skills/check.md +7 -4
  92. package/dist/templates/cursor/agents/cstl-check.md +1 -0
  93. package/dist/templates/cursor/fixtures/expected-rules.d.ts.map +1 -1
  94. package/dist/templates/cursor/fixtures/expected-rules.js +6 -1
  95. package/dist/templates/cursor/fixtures/expected-rules.js.map +1 -1
  96. package/dist/templates/cursor/rules/cstl-cursor-modes.mdc +32 -0
  97. package/dist/templates/cursor/rules/cstl-triage.mdc +2 -4
  98. package/dist/templates/cursor/rules/retrieval-routing.mdc +26 -7
  99. package/dist/templates/markdown/index.d.ts +9 -0
  100. package/dist/templates/markdown/index.d.ts.map +1 -1
  101. package/dist/templates/markdown/index.js +9 -0
  102. package/dist/templates/markdown/index.js.map +1 -1
  103. package/dist/templates/markdown/spec/guides/artifact-locale-guide.md.txt +93 -0
  104. package/dist/templates/markdown/spec/guides/cross-platform-thinking-guide.md.txt +7 -7
  105. package/dist/templates/markdown/spec/guides/cursor-context-injection-guide.md.txt +38 -21
  106. package/dist/templates/markdown/spec/guides/cursor-native-modes-guide.md.txt +125 -0
  107. package/dist/templates/markdown/spec/guides/cursor-subagent-policy.md.txt +10 -8
  108. package/dist/templates/markdown/spec/guides/debug-loop-guide.md.txt +227 -0
  109. package/dist/templates/markdown/spec/guides/goal-release-regression-runbook.md.txt +132 -0
  110. package/dist/templates/markdown/spec/guides/index.md.txt +144 -104
  111. package/dist/templates/markdown/spec/guides/injection-budget-guide.md.txt +111 -0
  112. package/dist/templates/markdown/spec/guides/prototype-guide.md.txt +139 -0
  113. package/dist/templates/markdown/spec/guides/retrieval-daily-guide.md.txt +28 -6
  114. package/dist/templates/markdown/spec/guides/test-discipline-guide.md.txt +138 -0
  115. package/dist/templates/markdown/spec/guides/verification-strength-guide.md.txt +185 -0
  116. package/dist/templates/shared-hooks/session-start.py +10 -0
  117. package/dist/templates/trellis/config.yaml +18 -0
  118. package/dist/templates/trellis/index.d.ts +17 -0
  119. package/dist/templates/trellis/index.d.ts.map +1 -1
  120. package/dist/templates/trellis/index.js +40 -0
  121. package/dist/templates/trellis/index.js.map +1 -1
  122. package/dist/templates/trellis/pool/README.md +103 -0
  123. package/dist/templates/trellis/pool/items/.gitkeep +0 -0
  124. package/dist/templates/trellis/pool/plan.md +26 -0
  125. package/dist/templates/trellis/scripts/common/artifact_locale.py +278 -0
  126. package/dist/templates/trellis/scripts/common/codebase_retrieval_router.py +37 -2
  127. package/dist/templates/trellis/scripts/common/config.py +39 -0
  128. package/dist/templates/trellis/scripts/common/injection_budget.py +317 -0
  129. package/dist/templates/trellis/scripts/common/parent_orchestration.py +161 -5
  130. package/dist/templates/trellis/scripts/common/pool_store.py +702 -0
  131. package/dist/templates/trellis/scripts/common/retrieval_agent_instructions.py +9 -3
  132. package/dist/templates/trellis/scripts/common/session_context.py +32 -4
  133. package/dist/templates/trellis/scripts/common/session_memory.py +26 -0
  134. package/dist/templates/trellis/scripts/common/subagent_dispatch.py +74 -9
  135. package/dist/templates/trellis/scripts/common/task_context.py +18 -0
  136. package/dist/templates/trellis/scripts/common/task_dashboard.py +24 -1
  137. package/dist/templates/trellis/scripts/common/task_dependencies.py +673 -0
  138. package/dist/templates/trellis/scripts/common/task_gates.py +58 -5
  139. package/dist/templates/trellis/scripts/common/task_map.py +40 -8
  140. package/dist/templates/trellis/scripts/common/task_store.py +400 -39
  141. package/dist/templates/trellis/scripts/common/test_depends_mode_block.py +489 -0
  142. package/dist/templates/trellis/scripts/common/test_observable_defaults.py +135 -0
  143. package/dist/templates/trellis/scripts/common/test_pool_store.py +428 -0
  144. package/dist/templates/trellis/scripts/common/test_task_dependencies.py +345 -0
  145. package/dist/templates/trellis/scripts/cursor_retrieval_probe_prompt.md +14 -12
  146. package/dist/templates/trellis/scripts/generate_dispatch_prompt.py +182 -0
  147. package/dist/templates/trellis/scripts/injection_budget_probe.py +69 -0
  148. package/dist/templates/trellis/scripts/pool.py +192 -0
  149. package/dist/templates/trellis/scripts/retrieval_probe_matrix_template.json +3 -3
  150. package/dist/templates/trellis/scripts/spec_health_outcomes.py +160 -0
  151. package/dist/templates/trellis/scripts/task.py +85 -1
  152. package/dist/templates/trellis/scripts/verify_evidence_probe.py +138 -0
  153. package/dist/templates/trellis/tasks/locale/en/default-prd.md +19 -0
  154. package/dist/templates/trellis/tasks/locale/zh/default-prd.md +19 -0
  155. package/dist/templates/trellis/workflow.md +55 -5
  156. package/package.json +2 -2
@@ -138,10 +138,15 @@ def _cursor_step_for_route(
138
138
  return (
139
139
  f"使用 **codegraph_search**,路径侧重 `extensions/`,符号 `{symbol}`。"
140
140
  )
141
+ if route_id == "definition-jump-native":
142
+ return (
143
+ f"**Prefer 原生**:用 **Grep** 定位 `{symbol}` 的定义行,再用 **Read** 验证文件与行号 "
144
+ "(Agent 无 GO_TO_DEFINITION)。仅当跨包 trap、重载歧义或需 blast 时再上 codegraph。"
145
+ )
141
146
  if route_id == "lsp-navigation":
142
147
  return (
143
- f"使用 **codegraph_node**(`includeCode=true`)或 **codegraph_search** 定位 `{symbol}` "
144
- "的定义/引用(Agent GO_TO_DEFINITION);再用 **Read** 验证行号与正文。"
148
+ f"**Own/codegraph**:用 **codegraph_explore** / **codegraph_search** 定位 `{symbol}` "
149
+ "的定义/引用(Grep+Read 不足或计划含 trap/blast 时);再用 **Read** 验证行号与正文。"
145
150
  )
146
151
  if role == "ast" or route_id in ("ast-codegraph",):
147
152
  return (
@@ -294,7 +299,8 @@ def render_agent_instructions(
294
299
 
295
300
  lines.append("")
296
301
  lines.append(
297
- "**codegraph 独用场景**:调用链、跨包 trap、extension 符号、影响面、定义/引用定位;"
302
+ "**Prefer / Adapt / Own**:定义跳转 Prefer **Grep → Read**;诊断 **ReadLints**;"
303
+ "codegraph 独用调用链、跨包 trap、extension 符号、影响面,以及 Grep+Read 歧义时的定义/引用;"
298
304
  "纯字面搜索用 Grep(勿用 GO_TO_DEFINITION,Agent 未暴露)。"
299
305
  )
300
306
 
@@ -20,7 +20,9 @@ import subprocess
20
20
  from pathlib import Path
21
21
 
22
22
  from .active_task import resolve_context_key
23
- from .config import get_git_packages
23
+ from .artifact_locale import artifact_locale_summary, resolve_artifact_locale
24
+ from .config import get_git_packages, get_journal_snippet_enabled
25
+ from .session_memory import get_latest_journal_summary
24
26
  from .git import run_git
25
27
  from .packages_context import get_packages_section
26
28
  from .tasks import iter_active_tasks, load_task, get_all_statuses, children_progress
@@ -46,7 +48,7 @@ from .paths import (
46
48
  # =============================================================================
47
49
 
48
50
  _PACKAGE_NAME = "@blxzer/cursor-trellis"
49
- _PYTHON_CMD = "python3"
51
+ _PYTHON_CMD = "python"
50
52
  _UPDATE_CHECK_TIMEOUT_SECONDS = 1.0
51
53
  _VERSION_RE = re.compile(
52
54
  r"^\s*(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:-([0-9A-Za-z.-]+))?\s*$"
@@ -797,6 +799,10 @@ def get_context_json(repo_root: Path | None = None) -> dict:
797
799
 
798
800
  result = {
799
801
  "developer": developer or "",
802
+ "artifactLocale": resolve_artifact_locale(
803
+ repo_root / selected_task if selected_task else None,
804
+ repo_root,
805
+ ),
800
806
  "git": {
801
807
  "isRepo": root_git_info["isRepo"],
802
808
  "branch": root_git_info["branch"],
@@ -819,9 +825,26 @@ def get_context_json(repo_root: Path | None = None) -> dict:
819
825
  if pkg_git_info:
820
826
  result["packageGit"] = pkg_git_info
821
827
 
828
+ if get_journal_snippet_enabled(repo_root):
829
+ result["journal"]["snippet"] = _resolve_journal_snippet(
830
+ repo_root,
831
+ journal_file,
832
+ developer,
833
+ )
834
+
822
835
  return result
823
836
 
824
837
 
838
+ def _resolve_journal_snippet(
839
+ repo_root: Path,
840
+ journal_file: Path | None,
841
+ developer: str | None,
842
+ ) -> str:
843
+ if not journal_file or not developer:
844
+ return ""
845
+ return get_latest_journal_summary(journal_file, repo_root, developer)
846
+
847
+
825
848
  def output_json(repo_root: Path | None = None) -> None:
826
849
  """Output context in JSON format.
827
850
 
@@ -860,11 +883,12 @@ def get_context_text(repo_root: Path | None = None) -> str:
860
883
  lines.append("## DEVELOPER")
861
884
  if not developer:
862
885
  lines.append(
863
- f"ERROR: Not initialized. Run: python3 ./{DIR_WORKFLOW}/{DIR_SCRIPTS}/init_developer.py <name>"
886
+ f"ERROR: Not initialized. Run: python ./{DIR_WORKFLOW}/{DIR_SCRIPTS}/init_developer.py <name>"
864
887
  )
865
888
  return "\n".join(lines)
866
889
 
867
890
  lines.append(f"Name: {developer}")
891
+ lines.append(artifact_locale_summary(repo_root))
868
892
  lines.append("")
869
893
 
870
894
  root_git_info = _collect_root_git_info(repo_root)
@@ -966,6 +990,10 @@ def get_context_text(repo_root: Path | None = None) -> str:
966
990
  lines.append(f"Line count: {journal_lines} / 2000")
967
991
  if journal_lines > 1800:
968
992
  lines.append("[!] WARNING: Approaching 2000 line limit!")
993
+ if get_journal_snippet_enabled(repo_root):
994
+ snippet = _resolve_journal_snippet(repo_root, journal_file, developer)
995
+ if snippet:
996
+ lines.append(f"Recent snippet: {snippet}")
969
997
  else:
970
998
  lines.append("No journal file found")
971
999
  lines.append("")
@@ -1085,7 +1113,7 @@ def get_context_text_record(repo_root: Path | None = None) -> str:
1085
1113
  developer = get_developer(repo_root)
1086
1114
  if not developer:
1087
1115
  lines.append(
1088
- f"ERROR: Not initialized. Run: python3 ./{DIR_WORKFLOW}/{DIR_SCRIPTS}/init_developer.py <name>"
1116
+ f"ERROR: Not initialized. Run: python ./{DIR_WORKFLOW}/{DIR_SCRIPTS}/init_developer.py <name>"
1089
1117
  )
1090
1118
  return "\n".join(lines)
1091
1119
 
@@ -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,8 @@ 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_dependencies import dashboard_deps_line
11
+ from .task_gates import verify_evidence_status
10
12
  from .task_map import get_child_state
11
13
  from .tasks import (
12
14
  children_progress,
@@ -23,6 +25,19 @@ def _task_path(dir_name: str) -> str:
23
25
  return f"{DIR_WORKFLOW}/{DIR_TASKS}/{dir_name}"
24
26
 
25
27
 
28
+ def format_verify_summary(task_dir: Path, task_data: dict) -> str:
29
+ """Return a compact verify evidence label for dashboard task lines."""
30
+ verify_path = task_dir / "verify.md"
31
+ if not verify_path.is_file():
32
+ return "[verify: missing]"
33
+ status = verify_evidence_status(task_dir, task_data)
34
+ if all(status.values()):
35
+ return "[verify: ok]"
36
+ if any(status.values()):
37
+ return "[verify: partial]"
38
+ return "[verify: missing]"
39
+
40
+
26
41
  def _selected_line(
27
42
  repo_root: Path,
28
43
  platform_input: dict | None = None,
@@ -123,10 +138,18 @@ def _append_task(
123
138
  integration_state = get_child_state(parent_dir, name)
124
139
  status_display = format_child_task_display(task.status, integration_state)
125
140
  assignee = task.assignee or "-"
141
+ verify_summary = format_verify_summary(task.directory, task.raw or {})
126
142
  prefix = " " * indent + " - "
127
143
  lines.append(
128
- f"{prefix}{_task_path(name)} ({status_display}){progress} [{assignee}]"
144
+ f"{prefix}{_task_path(name)} ({status_display}){progress} [{assignee}] {verify_summary}"
145
+ )
146
+ deps_line = dashboard_deps_line(
147
+ task.directory,
148
+ task.raw or {},
149
+ parent_dir=parent_dir,
129
150
  )
151
+ if deps_line:
152
+ lines.append(f"{prefix}{deps_line}")
130
153
  for child_name in task.children:
131
154
  if child_name in all_tasks:
132
155
  _append_task(