@blxzer/cursor-trellis 0.1.0 → 0.1.2

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 (285) hide show
  1. package/README.md +124 -5
  2. package/README.zh-CN.md +170 -0
  3. package/dist/cli/index.d.ts.map +1 -1
  4. package/dist/cli/index.js +1 -33
  5. package/dist/cli/index.js.map +1 -1
  6. package/dist/commands/init.d.ts +0 -13
  7. package/dist/commands/init.d.ts.map +1 -1
  8. package/dist/commands/init.js +7 -14
  9. package/dist/commands/init.js.map +1 -1
  10. package/dist/commands/uninstall.d.ts.map +1 -1
  11. package/dist/commands/uninstall.js +3 -32
  12. package/dist/commands/uninstall.js.map +1 -1
  13. package/dist/commands/update.d.ts.map +1 -1
  14. package/dist/commands/update.js +7 -84
  15. package/dist/commands/update.js.map +1 -1
  16. package/dist/configurators/index.d.ts +7 -8
  17. package/dist/configurators/index.d.ts.map +1 -1
  18. package/dist/configurators/index.js +11 -236
  19. package/dist/configurators/index.js.map +1 -1
  20. package/dist/configurators/workflow.d.ts.map +1 -1
  21. package/dist/configurators/workflow.js +5 -1
  22. package/dist/configurators/workflow.js.map +1 -1
  23. package/dist/migrations/manifests/0.1.1.json +9 -0
  24. package/dist/migrations/manifests/0.1.2.json +9 -0
  25. package/dist/templates/common/bundled-skills/smart-search-cli/SKILL.md +3 -3
  26. package/dist/templates/common/bundled-skills/trellis-meta/SKILL.md +4 -2
  27. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-agents.md +8 -15
  28. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-hooks.md +7 -7
  29. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-skills-or-commands.md +18 -21
  30. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/overview.md +2 -2
  31. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/generated-files.md +10 -7
  32. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/overview.md +4 -4
  33. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/agents.md +20 -31
  34. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/hooks-and-settings.md +20 -31
  35. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/overview.md +24 -40
  36. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/platform-map.md +28 -59
  37. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/skills-and-commands.md +12 -28
  38. package/dist/templates/common/bundled-skills/trellis-skill-creator/references/trellis-skill-locations.md +2 -2
  39. package/dist/templates/cursor/hooks.json +6 -0
  40. package/dist/templates/cursor/rules/retrieval-routing.mdc +57 -0
  41. package/dist/templates/extract.d.ts +0 -14
  42. package/dist/templates/extract.d.ts.map +1 -1
  43. package/dist/templates/extract.js +0 -34
  44. package/dist/templates/extract.js.map +1 -1
  45. package/dist/templates/markdown/agents.md +2 -6
  46. package/dist/templates/markdown/index.d.ts +1 -0
  47. package/dist/templates/markdown/index.d.ts.map +1 -1
  48. package/dist/templates/markdown/index.js +1 -0
  49. package/dist/templates/markdown/index.js.map +1 -1
  50. package/dist/templates/markdown/prompts/run-semantic-slice-12.md.txt +41 -0
  51. package/dist/templates/markdown/spec/guides/code-reuse-thinking-guide.md.txt +14 -14
  52. package/dist/templates/markdown/spec/guides/cross-layer-thinking-guide.md.txt +8 -20
  53. package/dist/templates/markdown/spec/guides/cross-platform-thinking-guide.md.txt +15 -14
  54. package/dist/templates/markdown/spec/guides/cursor-context-injection-guide.md.txt +4 -2
  55. package/dist/templates/markdown/spec/guides/cursor-semantic-compliance.md.txt +49 -0
  56. package/dist/templates/markdown/spec/guides/cursor-subagent-policy.md.txt +18 -5
  57. package/dist/templates/markdown/spec/guides/retrieval-daily-guide.md.txt +68 -20
  58. package/dist/templates/shared-hooks/index.d.ts +1 -1
  59. package/dist/templates/shared-hooks/index.d.ts.map +1 -1
  60. package/dist/templates/shared-hooks/index.js +1 -0
  61. package/dist/templates/shared-hooks/index.js.map +1 -1
  62. package/dist/templates/shared-hooks/inject-retrieval-plan.py +163 -0
  63. package/dist/templates/shared-hooks/inject-subagent-context.py +31 -567
  64. package/dist/templates/shared-hooks/research-end-retrieval-pack.py +2 -11
  65. package/dist/templates/trellis/index.d.ts +12 -0
  66. package/dist/templates/trellis/index.d.ts.map +1 -1
  67. package/dist/templates/trellis/index.js +24 -0
  68. package/dist/templates/trellis/index.js.map +1 -1
  69. package/dist/templates/trellis/scripts/aggregate_retrieval_telemetry.py +342 -0
  70. package/dist/templates/trellis/scripts/batch_plan_envelope.py +105 -0
  71. package/dist/templates/trellis/scripts/codegraph_session_smoke.py +76 -0
  72. package/dist/templates/trellis/scripts/common/active_task.py +116 -147
  73. package/dist/templates/trellis/scripts/common/cli_adapter.py +34 -710
  74. package/dist/templates/trellis/scripts/common/codebase_retrieval_router.py +49 -106
  75. package/dist/templates/trellis/scripts/common/git_context.py +17 -2
  76. package/dist/templates/trellis/scripts/common/project_file_stats.py +91 -0
  77. package/dist/templates/trellis/scripts/common/retrieval_adapter_metadata.py +9 -11
  78. package/dist/templates/trellis/scripts/common/retrieval_agent_instructions.py +313 -0
  79. package/dist/templates/trellis/scripts/common/retrieval_pack.py +0 -6
  80. package/dist/templates/trellis/scripts/common/retrieval_pack_context.py +0 -4
  81. package/dist/templates/trellis/scripts/common/retrieval_plan_gate.py +76 -0
  82. package/dist/templates/trellis/scripts/common/retrieval_result_ranking.py +253 -0
  83. package/dist/templates/trellis/scripts/common/retrieval_tool_classification.py +157 -0
  84. package/dist/templates/trellis/scripts/common/semantic_plan_gate.py +52 -0
  85. package/dist/templates/trellis/scripts/common/subagent_dispatch.py +527 -0
  86. package/dist/templates/trellis/scripts/common/task_store.py +32 -0
  87. package/dist/templates/trellis/scripts/common/workflow_phase.py +3 -24
  88. package/dist/templates/trellis/scripts/rank_retrieval_candidates.py +80 -0
  89. package/dist/templates/trellis/scripts/route_codebase_retrieval.py +38 -11
  90. package/dist/templates/trellis/scripts/score_evidence.py +112 -0
  91. package/dist/templates/trellis/scripts/task.py +26 -0
  92. package/dist/templates/trellis/workflow.md +844 -960
  93. package/dist/types/ai-tools.d.ts +13 -16
  94. package/dist/types/ai-tools.d.ts.map +1 -1
  95. package/dist/types/ai-tools.js +5 -238
  96. package/dist/types/ai-tools.js.map +1 -1
  97. package/dist/utils/codebase-retrieval-router.d.ts +1 -8
  98. package/dist/utils/codebase-retrieval-router.d.ts.map +1 -1
  99. package/dist/utils/codebase-retrieval-router.js +39 -180
  100. package/dist/utils/codebase-retrieval-router.js.map +1 -1
  101. package/dist/utils/project-capabilities.d.ts.map +1 -1
  102. package/dist/utils/project-capabilities.js +10 -24
  103. package/dist/utils/project-capabilities.js.map +1 -1
  104. package/dist/utils/retrieval-agent-instructions.d.ts +14 -0
  105. package/dist/utils/retrieval-agent-instructions.d.ts.map +1 -0
  106. package/dist/utils/retrieval-agent-instructions.js +221 -0
  107. package/dist/utils/retrieval-agent-instructions.js.map +1 -0
  108. package/dist/utils/retrieval-execution-telemetry.d.ts +43 -85
  109. package/dist/utils/retrieval-execution-telemetry.d.ts.map +1 -1
  110. package/dist/utils/retrieval-execution-telemetry.js +289 -22
  111. package/dist/utils/retrieval-execution-telemetry.js.map +1 -1
  112. package/dist/utils/retrieval-result-ranking.js +1 -1
  113. package/dist/utils/retrieval-result-ranking.js.map +1 -1
  114. package/dist/utils/retrieval-tool-classification.d.ts +32 -0
  115. package/dist/utils/retrieval-tool-classification.d.ts.map +1 -0
  116. package/dist/utils/retrieval-tool-classification.js +113 -0
  117. package/dist/utils/retrieval-tool-classification.js.map +1 -0
  118. package/dist/utils/uninstall-scrubbers.d.ts +0 -36
  119. package/dist/utils/uninstall-scrubbers.d.ts.map +1 -1
  120. package/dist/utils/uninstall-scrubbers.js +0 -184
  121. package/dist/utils/uninstall-scrubbers.js.map +1 -1
  122. package/package.json +4 -2
  123. package/dist/commands/mem.d.ts +0 -30
  124. package/dist/commands/mem.d.ts.map +0 -1
  125. package/dist/commands/mem.js +0 -424
  126. package/dist/commands/mem.js.map +0 -1
  127. package/dist/configurators/antigravity.d.ts +0 -7
  128. package/dist/configurators/antigravity.d.ts.map +0 -1
  129. package/dist/configurators/antigravity.js +0 -19
  130. package/dist/configurators/antigravity.js.map +0 -1
  131. package/dist/configurators/claude.d.ts +0 -9
  132. package/dist/configurators/claude.d.ts.map +0 -1
  133. package/dist/configurators/claude.js +0 -72
  134. package/dist/configurators/claude.js.map +0 -1
  135. package/dist/configurators/codebuddy.d.ts +0 -10
  136. package/dist/configurators/codebuddy.d.ts.map +0 -1
  137. package/dist/configurators/codebuddy.js +0 -30
  138. package/dist/configurators/codebuddy.js.map +0 -1
  139. package/dist/configurators/codex.d.ts +0 -8
  140. package/dist/configurators/codex.d.ts.map +0 -1
  141. package/dist/configurators/codex.js +0 -87
  142. package/dist/configurators/codex.js.map +0 -1
  143. package/dist/configurators/copilot.d.ts +0 -10
  144. package/dist/configurators/copilot.d.ts.map +0 -1
  145. package/dist/configurators/copilot.js +0 -51
  146. package/dist/configurators/copilot.js.map +0 -1
  147. package/dist/configurators/droid.d.ts +0 -10
  148. package/dist/configurators/droid.d.ts.map +0 -1
  149. package/dist/configurators/droid.js +0 -30
  150. package/dist/configurators/droid.js.map +0 -1
  151. package/dist/configurators/gemini.d.ts +0 -16
  152. package/dist/configurators/gemini.d.ts.map +0 -1
  153. package/dist/configurators/gemini.js +0 -38
  154. package/dist/configurators/gemini.js.map +0 -1
  155. package/dist/configurators/kilo.d.ts +0 -7
  156. package/dist/configurators/kilo.d.ts.map +0 -1
  157. package/dist/configurators/kilo.js +0 -19
  158. package/dist/configurators/kilo.js.map +0 -1
  159. package/dist/configurators/kiro.d.ts +0 -8
  160. package/dist/configurators/kiro.d.ts.map +0 -1
  161. package/dist/configurators/kiro.js +0 -24
  162. package/dist/configurators/kiro.js.map +0 -1
  163. package/dist/configurators/opencode.d.ts +0 -14
  164. package/dist/configurators/opencode.d.ts.map +0 -1
  165. package/dist/configurators/opencode.js +0 -96
  166. package/dist/configurators/opencode.js.map +0 -1
  167. package/dist/configurators/pi.d.ts +0 -3
  168. package/dist/configurators/pi.d.ts.map +0 -1
  169. package/dist/configurators/pi.js +0 -45
  170. package/dist/configurators/pi.js.map +0 -1
  171. package/dist/configurators/qoder.d.ts +0 -11
  172. package/dist/configurators/qoder.d.ts.map +0 -1
  173. package/dist/configurators/qoder.js +0 -31
  174. package/dist/configurators/qoder.js.map +0 -1
  175. package/dist/configurators/windsurf.d.ts +0 -7
  176. package/dist/configurators/windsurf.d.ts.map +0 -1
  177. package/dist/configurators/windsurf.js +0 -19
  178. package/dist/configurators/windsurf.js.map +0 -1
  179. package/dist/templates/claude/agents/trellis-check.md +0 -124
  180. package/dist/templates/claude/agents/trellis-implement.md +0 -110
  181. package/dist/templates/claude/agents/trellis-research.md +0 -137
  182. package/dist/templates/claude/index.d.ts +0 -22
  183. package/dist/templates/claude/index.d.ts.map +0 -1
  184. package/dist/templates/claude/index.js +0 -46
  185. package/dist/templates/claude/index.js.map +0 -1
  186. package/dist/templates/claude/settings.json +0 -84
  187. package/dist/templates/codebuddy/agents/trellis-check.md +0 -115
  188. package/dist/templates/codebuddy/agents/trellis-implement.md +0 -110
  189. package/dist/templates/codebuddy/agents/trellis-research.md +0 -137
  190. package/dist/templates/codebuddy/index.d.ts +0 -15
  191. package/dist/templates/codebuddy/index.d.ts.map +0 -1
  192. package/dist/templates/codebuddy/index.js +0 -15
  193. package/dist/templates/codebuddy/index.js.map +0 -1
  194. package/dist/templates/codebuddy/settings.json +0 -70
  195. package/dist/templates/codex/agents/trellis-check.toml +0 -67
  196. package/dist/templates/codex/agents/trellis-implement.toml +0 -40
  197. package/dist/templates/codex/agents/trellis-research.toml +0 -73
  198. package/dist/templates/codex/config.toml +0 -28
  199. package/dist/templates/codex/hooks/session-start.py +0 -575
  200. package/dist/templates/codex/hooks.json +0 -15
  201. package/dist/templates/codex/index.d.ts +0 -39
  202. package/dist/templates/codex/index.d.ts.map +0 -1
  203. package/dist/templates/codex/index.js +0 -85
  204. package/dist/templates/codex/index.js.map +0 -1
  205. package/dist/templates/codex/skills/before-dev/SKILL.md +0 -40
  206. package/dist/templates/codex/skills/brainstorm/SKILL.md +0 -112
  207. package/dist/templates/codex/skills/break-loop/SKILL.md +0 -130
  208. package/dist/templates/codex/skills/check/SKILL.md +0 -98
  209. package/dist/templates/codex/skills/check-cross-layer/SKILL.md +0 -158
  210. package/dist/templates/codex/skills/create-command/SKILL.md +0 -101
  211. package/dist/templates/codex/skills/finish-work/SKILL.md +0 -90
  212. package/dist/templates/codex/skills/improve-ut/SKILL.md +0 -69
  213. package/dist/templates/codex/skills/integrate-skill/SKILL.md +0 -221
  214. package/dist/templates/codex/skills/onboard/SKILL.md +0 -363
  215. package/dist/templates/codex/skills/record-session/SKILL.md +0 -67
  216. package/dist/templates/codex/skills/start/SKILL.md +0 -66
  217. package/dist/templates/codex/skills/update-spec/SKILL.md +0 -335
  218. package/dist/templates/copilot/hooks/session-start.py +0 -557
  219. package/dist/templates/copilot/hooks.json +0 -19
  220. package/dist/templates/copilot/index.d.ts +0 -23
  221. package/dist/templates/copilot/index.d.ts.map +0 -1
  222. package/dist/templates/copilot/index.js +0 -54
  223. package/dist/templates/copilot/index.js.map +0 -1
  224. package/dist/templates/copilot/prompts/before-dev.prompt.md +0 -39
  225. package/dist/templates/copilot/prompts/brainstorm.prompt.md +0 -111
  226. package/dist/templates/copilot/prompts/break-loop.prompt.md +0 -129
  227. package/dist/templates/copilot/prompts/check-cross-layer.prompt.md +0 -157
  228. package/dist/templates/copilot/prompts/check.prompt.md +0 -97
  229. package/dist/templates/copilot/prompts/create-command.prompt.md +0 -116
  230. package/dist/templates/copilot/prompts/finish-work.prompt.md +0 -99
  231. package/dist/templates/copilot/prompts/integrate-skill.prompt.md +0 -223
  232. package/dist/templates/copilot/prompts/onboard.prompt.md +0 -362
  233. package/dist/templates/copilot/prompts/parallel.prompt.md +0 -204
  234. package/dist/templates/copilot/prompts/record-session.prompt.md +0 -66
  235. package/dist/templates/copilot/prompts/start.prompt.md +0 -63
  236. package/dist/templates/copilot/prompts/update-spec.prompt.md +0 -358
  237. package/dist/templates/droid/droids/trellis-check.md +0 -107
  238. package/dist/templates/droid/droids/trellis-implement.md +0 -102
  239. package/dist/templates/droid/droids/trellis-research.md +0 -137
  240. package/dist/templates/droid/index.d.ts +0 -15
  241. package/dist/templates/droid/index.d.ts.map +0 -1
  242. package/dist/templates/droid/index.js +0 -15
  243. package/dist/templates/droid/index.js.map +0 -1
  244. package/dist/templates/droid/settings.json +0 -70
  245. package/dist/templates/gemini/agents/trellis-check.md +0 -107
  246. package/dist/templates/gemini/agents/trellis-implement.md +0 -102
  247. package/dist/templates/gemini/agents/trellis-research.md +0 -136
  248. package/dist/templates/gemini/index.d.ts +0 -13
  249. package/dist/templates/gemini/index.d.ts.map +0 -1
  250. package/dist/templates/gemini/index.js +0 -13
  251. package/dist/templates/gemini/index.js.map +0 -1
  252. package/dist/templates/gemini/settings.json +0 -28
  253. package/dist/templates/kiro/agents/trellis-check.json +0 -26
  254. package/dist/templates/kiro/agents/trellis-implement.json +0 -26
  255. package/dist/templates/kiro/agents/trellis-research.json +0 -30
  256. package/dist/templates/kiro/index.d.ts +0 -18
  257. package/dist/templates/kiro/index.d.ts.map +0 -1
  258. package/dist/templates/kiro/index.js +0 -18
  259. package/dist/templates/kiro/index.js.map +0 -1
  260. package/dist/templates/opencode/agents/trellis-check.md +0 -122
  261. package/dist/templates/opencode/agents/trellis-implement.md +0 -118
  262. package/dist/templates/opencode/agents/trellis-research.md +0 -145
  263. package/dist/templates/opencode/lib/session-utils.js +0 -667
  264. package/dist/templates/opencode/lib/trellis-context.js +0 -336
  265. package/dist/templates/opencode/package.json +0 -5
  266. package/dist/templates/opencode/plugins/inject-subagent-context.js +0 -495
  267. package/dist/templates/opencode/plugins/inject-workflow-state.js +0 -159
  268. package/dist/templates/opencode/plugins/session-start.js +0 -101
  269. package/dist/templates/pi/agents/trellis-check.md +0 -37
  270. package/dist/templates/pi/agents/trellis-implement.md +0 -42
  271. package/dist/templates/pi/agents/trellis-research.md +0 -25
  272. package/dist/templates/pi/extensions/trellis/index.ts.txt +0 -1600
  273. package/dist/templates/pi/index.d.ts +0 -5
  274. package/dist/templates/pi/index.d.ts.map +0 -1
  275. package/dist/templates/pi/index.js +0 -12
  276. package/dist/templates/pi/index.js.map +0 -1
  277. package/dist/templates/pi/settings.json +0 -12
  278. package/dist/templates/qoder/agents/trellis-check.md +0 -108
  279. package/dist/templates/qoder/agents/trellis-implement.md +0 -103
  280. package/dist/templates/qoder/agents/trellis-research.md +0 -137
  281. package/dist/templates/qoder/index.d.ts +0 -15
  282. package/dist/templates/qoder/index.d.ts.map +0 -1
  283. package/dist/templates/qoder/index.js +0 -15
  284. package/dist/templates/qoder/index.js.map +0 -1
  285. package/dist/templates/qoder/settings.json +0 -47
@@ -0,0 +1,527 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Subagent dispatch prompt builder — single source for hook + CLI Layer 2.
4
+
5
+ Agent-facing only; not documented in user README.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import os
11
+ import sys
12
+ from pathlib import Path
13
+ from typing import Literal
14
+
15
+ from .io import read_json
16
+ from .paths import FILE_TASK_JSON
17
+
18
+ DIR_WORKFLOW = ".trellis"
19
+ DIR_SPEC = "spec"
20
+
21
+ AGENT_IMPLEMENT = "trellis-implement"
22
+ AGENT_CHECK = "trellis-check"
23
+ AGENT_RESEARCH = "trellis-research"
24
+
25
+ AGENTS_ALL = (AGENT_IMPLEMENT, AGENT_CHECK, AGENT_RESEARCH)
26
+ AGENTS_REQUIRE_TASK = (AGENT_IMPLEMENT, AGENT_CHECK)
27
+
28
+ INJECTION_MARKER = "<!-- trellis-hook-injected -->"
29
+
30
+ DispatchRole = Literal["implement", "check", "research"]
31
+
32
+ DEFAULT_SCOPES: dict[str, str] = {
33
+ "implement": (
34
+ "Implement per prd.md and implement.md; run project lint/typecheck/tests."
35
+ ),
36
+ "check": (
37
+ "Review against prd/spec; fix in-contract defects; record evidence in verify.md."
38
+ ),
39
+ "research": "Research and persist to {TASK}/research/.",
40
+ }
41
+
42
+
43
+ def prompt_has_injection_marker(text: str) -> bool:
44
+ return INJECTION_MARKER in text
45
+
46
+
47
+ def read_file_content(base_path: str, file_path: str) -> str | None:
48
+ full_path = os.path.join(base_path, file_path)
49
+ if os.path.exists(full_path) and os.path.isfile(full_path):
50
+ try:
51
+ with open(full_path, "r", encoding="utf-8") as f:
52
+ return f.read()
53
+ except OSError:
54
+ return None
55
+ return None
56
+
57
+
58
+ def read_directory_contents(
59
+ base_path: str, dir_path: str, max_files: int = 20
60
+ ) -> list[tuple[str, str]]:
61
+ full_path = os.path.join(base_path, dir_path)
62
+ if not os.path.exists(full_path) or not os.path.isdir(full_path):
63
+ return []
64
+
65
+ results: list[tuple[str, str]] = []
66
+ try:
67
+ md_files = sorted(
68
+ f
69
+ for f in os.listdir(full_path)
70
+ if f.endswith(".md") and os.path.isfile(os.path.join(full_path, f))
71
+ )
72
+ for filename in md_files[:max_files]:
73
+ file_full_path = os.path.join(full_path, filename)
74
+ relative_path = os.path.join(dir_path, filename)
75
+ try:
76
+ with open(file_full_path, "r", encoding="utf-8") as f:
77
+ results.append((relative_path, f.read()))
78
+ except OSError:
79
+ continue
80
+ except OSError:
81
+ pass
82
+ return results
83
+
84
+
85
+ def read_jsonl_entries(
86
+ base_path: str,
87
+ jsonl_path: str,
88
+ *,
89
+ warn_prefix: str = "subagent-dispatch",
90
+ ) -> list[tuple[str, str]]:
91
+ full_path = os.path.join(base_path, jsonl_path)
92
+ if not os.path.exists(full_path):
93
+ print(
94
+ f"[{warn_prefix}] WARN: {jsonl_path} not found — "
95
+ "sub-agent will receive only task artifacts",
96
+ file=sys.stderr,
97
+ )
98
+ return []
99
+
100
+ results: list[tuple[str, str]] = []
101
+ saw_real_entry = False
102
+ try:
103
+ with open(full_path, "r", encoding="utf-8") as f:
104
+ for line in f:
105
+ line = line.strip()
106
+ if not line:
107
+ continue
108
+ try:
109
+ item = json.loads(line)
110
+ file_path = item.get("file") or item.get("path")
111
+ entry_type = item.get("type", "file")
112
+ if not file_path:
113
+ continue
114
+ saw_real_entry = True
115
+ if entry_type == "directory":
116
+ results.extend(read_directory_contents(base_path, file_path))
117
+ else:
118
+ content = read_file_content(base_path, file_path)
119
+ if content:
120
+ results.append((file_path, content))
121
+ except json.JSONDecodeError:
122
+ continue
123
+ except OSError:
124
+ pass
125
+
126
+ if not saw_real_entry:
127
+ print(
128
+ f"[{warn_prefix}] WARN: {jsonl_path} has no curated entries "
129
+ "(only seed / empty) — sub-agent will receive only task artifacts.",
130
+ file=sys.stderr,
131
+ )
132
+ return results
133
+
134
+
135
+ def get_agent_context(repo_root: str, task_dir: str, agent_type: str) -> str:
136
+ context_parts: list[str] = []
137
+ agent_jsonl = f"{task_dir}/{agent_type}.jsonl"
138
+ for file_path, content in read_jsonl_entries(repo_root, agent_jsonl):
139
+ context_parts.append(f"=== {file_path} ===\n{content}")
140
+ return "\n\n".join(context_parts)
141
+
142
+
143
+ def get_implement_context(repo_root: str, task_dir: str) -> str:
144
+ context_parts: list[str] = []
145
+ base_context = get_agent_context(repo_root, task_dir, "implement")
146
+ if base_context:
147
+ context_parts.append(base_context)
148
+
149
+ prd_content = read_file_content(repo_root, f"{task_dir}/prd.md")
150
+ if prd_content:
151
+ context_parts.append(f"=== {task_dir}/prd.md (Requirements) ===\n{prd_content}")
152
+
153
+ design_content = read_file_content(repo_root, f"{task_dir}/design.md")
154
+ if design_content:
155
+ context_parts.append(
156
+ f"=== {task_dir}/design.md (Technical Design) ===\n{design_content}"
157
+ )
158
+
159
+ implement_plan_content = read_file_content(repo_root, f"{task_dir}/implement.md")
160
+ if implement_plan_content:
161
+ context_parts.append(
162
+ f"=== {task_dir}/implement.md (Execution Plan) ===\n{implement_plan_content}"
163
+ )
164
+ return "\n\n".join(context_parts)
165
+
166
+
167
+ def get_check_context(repo_root: str, task_dir: str) -> str:
168
+ context_parts: list[str] = []
169
+ for file_path, content in read_jsonl_entries(repo_root, f"{task_dir}/check.jsonl"):
170
+ context_parts.append(f"=== {file_path} ===\n{content}")
171
+
172
+ prd_content = read_file_content(repo_root, f"{task_dir}/prd.md")
173
+ if prd_content:
174
+ context_parts.append(f"=== {task_dir}/prd.md (Requirements) ===\n{prd_content}")
175
+
176
+ design_content = read_file_content(repo_root, f"{task_dir}/design.md")
177
+ if design_content:
178
+ context_parts.append(
179
+ f"=== {task_dir}/design.md (Technical Design) ===\n{design_content}"
180
+ )
181
+
182
+ implement_plan_content = read_file_content(repo_root, f"{task_dir}/implement.md")
183
+ if implement_plan_content:
184
+ context_parts.append(
185
+ f"=== {task_dir}/implement.md (Execution Plan) ===\n{implement_plan_content}"
186
+ )
187
+ return "\n\n".join(context_parts)
188
+
189
+
190
+ def get_finish_context(repo_root: str, task_dir: str) -> str:
191
+ return get_check_context(repo_root, task_dir)
192
+
193
+
194
+ def get_research_context(repo_root: str, task_dir: str | None) -> str:
195
+ _ = task_dir
196
+ spec_path = f"{DIR_WORKFLOW}/{DIR_SPEC}"
197
+ spec_root = Path(repo_root) / DIR_WORKFLOW / DIR_SPEC
198
+
199
+ tree_lines = [f"{spec_path}/"]
200
+ if spec_root.is_dir():
201
+ pkg_dirs = sorted(d for d in spec_root.iterdir() if d.is_dir())
202
+ for i, pkg_dir in enumerate(pkg_dirs):
203
+ is_last = i == len(pkg_dirs) - 1
204
+ prefix = "└── " if is_last else "├── "
205
+ layers = sorted(d.name for d in pkg_dir.iterdir() if d.is_dir())
206
+ layer_info = f" ({', '.join(layers)})" if layers else ""
207
+ tree_lines.append(f"{prefix}{pkg_dir.name}/{layer_info}")
208
+
209
+ project_structure = f"""## Project Spec Directory Structure
210
+
211
+ ```
212
+ {chr(10).join(tree_lines)}
213
+ ```
214
+
215
+ To get structured package info, run: `python ./{DIR_WORKFLOW}/scripts/get_context.py --mode packages`
216
+
217
+ ## Search Tips
218
+
219
+ - Spec files: `{spec_path}/**/*.md`
220
+ - Code search: Use Glob and Grep tools
221
+ - External facts / docs: load `smart-search-cli` skill and use Bash (`smart-search` CLI), not Cursor WebSearch/WebFetch by default"""
222
+ return project_structure
223
+
224
+
225
+ def build_implement_prompt(original_prompt: str, context: str) -> str:
226
+ return f"""{INJECTION_MARKER}
227
+ # Implement Agent Task
228
+
229
+ You are the Implement Agent in the Multi-Agent Pipeline.
230
+
231
+ ## Your Context
232
+
233
+ All the information you need has been prepared for you:
234
+
235
+ {context}
236
+
237
+ ---
238
+
239
+ ## Your Task
240
+
241
+ {original_prompt}
242
+
243
+ ---
244
+
245
+ ## Workflow
246
+
247
+ 1. **Understand specs** - All dev specs are injected above, understand them
248
+ 2. **Understand task artifacts** - Read requirements, technical design if present, and execution plan if present
249
+ 3. **Implement feature** - Implement following specs and task artifacts
250
+ 4. **Self-check** - Ensure code quality against check specs
251
+
252
+ ## Important Constraints
253
+
254
+ - Do NOT execute git commit, only code modifications
255
+ - Follow all dev specs injected above
256
+ - Report list of modified/created files when done"""
257
+
258
+
259
+ def build_check_prompt(original_prompt: str, context: str) -> str:
260
+ return f"""{INJECTION_MARKER}
261
+ # Check Agent Task
262
+
263
+ You are the Check Agent in the Multi-Agent Pipeline (code and cross-layer checker).
264
+
265
+ ## Your Context
266
+
267
+ All check specs and dev specs you need:
268
+
269
+ {context}
270
+
271
+ ---
272
+
273
+ ## Your Task
274
+
275
+ {original_prompt}
276
+
277
+ ---
278
+
279
+ ## Workflow
280
+
281
+ 1. **Get changes** - Run `git diff --name-only` and `git diff` to get code changes
282
+ 2. **Check against specs** - Check item by item against specs above
283
+ 3. **Self-fix** - Fix issues directly, don't just report
284
+ 4. **Run verification** - Run project's lint and typecheck commands
285
+
286
+ ## Important Constraints
287
+
288
+ - Fix issues yourself, don't just report
289
+ - Must execute complete checklist in check specs
290
+ - Pay special attention to impact radius analysis (L1-L5)"""
291
+
292
+
293
+ def build_finish_prompt(original_prompt: str, context: str) -> str:
294
+ return f"""{INJECTION_MARKER}
295
+ # Finish Agent Task
296
+
297
+ You are performing the final check before creating a PR.
298
+
299
+ ## Your Context
300
+
301
+ Finish checklist and requirements:
302
+
303
+ {context}
304
+
305
+ ---
306
+
307
+ ## Your Task
308
+
309
+ {original_prompt}
310
+
311
+ ---
312
+
313
+ ## Workflow
314
+
315
+ 1. **Review changes** - Run `git diff --name-only` to see all changed files
316
+ 2. **Verify task artifacts** - Check requirements in prd.md and, when present, design.md / implement.md
317
+ 3. **Spec sync** - Analyze whether changes introduce new patterns, contracts, or conventions
318
+ 4. **Run final checks** - Execute lint and typecheck
319
+ 5. **Confirm ready** - Ensure code is ready for PR
320
+
321
+ ## Important Constraints
322
+
323
+ - You MAY update spec files when gaps are detected (use update-spec.md as guide)
324
+ - MUST read the target spec file BEFORE editing (avoid duplicating existing content)
325
+ - Do NOT update specs for trivial changes (typos, formatting, obvious fixes)
326
+ - Verify all acceptance criteria in prd.md are met"""
327
+
328
+
329
+ def build_research_prompt(original_prompt: str, context: str) -> str:
330
+ return f"""{INJECTION_MARKER}
331
+ # Research Agent Task
332
+
333
+ You are the Trellis Research Agent.
334
+
335
+ ## Core Principle
336
+
337
+ **You do one thing: find, explain, and PERSIST information.**
338
+
339
+ Conversations get compacted; files do not. Every research topic MUST be written under `{{TASK_DIR}}/research/`. Chat-only findings are a failure.
340
+
341
+ ## Dispatch contract
342
+
343
+ - External facts: load `smart-search-cli` skill + Bash — default **not** Cursor `WebSearch`/`WebFetch`.
344
+ - Do NOT spawn nested `trellis-implement` / `trellis-check` / `trellis-research` sub-agents.
345
+
346
+ ## Project Info
347
+
348
+ {context}
349
+
350
+ ---
351
+
352
+ ## Your Task
353
+
354
+ {original_prompt}
355
+
356
+ ---
357
+
358
+ ## Workflow
359
+
360
+ 1. **Resolve task** — ensure `{{TASK_DIR}}/research/` exists
361
+ 2. **Classify** — internal / external / mixed
362
+ 3. **Search** — Glob/Grep/Read for repo; `smart-search-cli` + CLI for external
363
+ 4. **Persist** — Write each topic to `{{TASK_DIR}}/research/<topic-slug>.md`
364
+ 5. **Report** — Reply with file paths + one-line summaries only
365
+
366
+ ## Write ALLOWED
367
+
368
+ - `{{TASK_DIR}}/research/*.md` only
369
+
370
+ ## Write FORBIDDEN
371
+
372
+ - Code, `.trellis/spec/`, platform config, git operations"""
373
+
374
+
375
+ def _repo_relative(task_dir: Path, repo_root: Path) -> str:
376
+ try:
377
+ return task_dir.relative_to(repo_root).as_posix()
378
+ except ValueError:
379
+ return task_dir.as_posix()
380
+
381
+
382
+ def _truncate_context(context: str, max_chars: int | None, warnings: list[str]) -> str:
383
+ if max_chars is None or len(context) <= max_chars:
384
+ return context
385
+ warnings.append(f"context truncated at {max_chars} chars")
386
+ return context[:max_chars] + "\n...[truncated]..."
387
+
388
+
389
+ def _resolve_scope(role: DispatchRole, scope: str | None, task_rel: str) -> str:
390
+ if scope and scope.strip():
391
+ return scope.strip()
392
+ default = DEFAULT_SCOPES[role]
393
+ if role == "research":
394
+ return default.replace("{TASK}", task_rel)
395
+ return default
396
+
397
+
398
+ def _compose_task_prompt(task_rel: str, scope: str) -> str:
399
+ return f"Selected task: {task_rel}\n\n{scope}"
400
+
401
+
402
+ def build_dispatch_prompt(
403
+ repo_root: Path,
404
+ task_dir: Path,
405
+ role: str,
406
+ *,
407
+ scope: str | None = None,
408
+ finish: bool = False,
409
+ max_chars: int | None = None,
410
+ original_prompt: str | None = None,
411
+ require_in_progress: bool = True,
412
+ ) -> tuple[str | None, list[str], list[str]]:
413
+ """
414
+ Build a full subagent dispatch prompt.
415
+
416
+ Returns (prompt, warnings, errors). prompt is None on hard failure.
417
+ When original_prompt is set (hook path), it is used as the task section as-is.
418
+ """
419
+ warnings: list[str] = []
420
+ errors: list[str] = []
421
+
422
+ if role not in ("implement", "check", "research"):
423
+ errors.append(f"invalid role: {role!r}")
424
+ return None, warnings, errors
425
+
426
+ repo_root_str = str(repo_root)
427
+ if not task_dir.is_dir():
428
+ errors.append(f"task directory not found: {task_dir}")
429
+ return None, warnings, errors
430
+
431
+ task_rel = _repo_relative(task_dir, repo_root)
432
+ prd_path = task_dir / "prd.md"
433
+ if role in ("implement", "check"):
434
+ if not prd_path.is_file():
435
+ errors.append(f"prd.md missing under {task_rel}")
436
+ return None, warnings, errors
437
+ elif not prd_path.is_file():
438
+ warnings.append(f"prd.md missing under {task_rel}")
439
+
440
+ if role in ("implement", "check") and require_in_progress:
441
+ task_json_path = task_dir / FILE_TASK_JSON
442
+ task_data = read_json(task_json_path) if task_json_path.is_file() else None
443
+ status = (task_data or {}).get("status")
444
+ if status != "in_progress":
445
+ errors.append(f"task status must be in_progress, got {status!r}")
446
+
447
+ if original_prompt is not None:
448
+ task_prompt = original_prompt
449
+ else:
450
+ resolved_scope = _resolve_scope(role, scope, task_rel) # type: ignore[arg-type]
451
+ task_prompt = _compose_task_prompt(task_rel, resolved_scope)
452
+
453
+ if role == "implement":
454
+ context = get_implement_context(repo_root_str, task_rel)
455
+ context = _truncate_context(context, max_chars, warnings)
456
+ if not context:
457
+ errors.append("no implement context available")
458
+ return None, warnings, errors
459
+ prompt = build_implement_prompt(task_prompt, context)
460
+ elif role == "check":
461
+ context = get_finish_context(repo_root_str, task_rel) if finish else get_check_context(
462
+ repo_root_str, task_rel
463
+ )
464
+ context = _truncate_context(context, max_chars, warnings)
465
+ if not context:
466
+ errors.append("no check context available")
467
+ return None, warnings, errors
468
+ prompt = (
469
+ build_finish_prompt(task_prompt, context)
470
+ if finish
471
+ else build_check_prompt(task_prompt, context)
472
+ )
473
+ else:
474
+ context = get_research_context(repo_root_str, task_rel)
475
+ context = _truncate_context(context, max_chars, warnings)
476
+ if not context:
477
+ errors.append("no research context available")
478
+ return None, warnings, errors
479
+ prompt = build_research_prompt(task_prompt, context)
480
+
481
+ return prompt, warnings, errors
482
+
483
+
484
+ def build_dispatch_prompt_for_agent(
485
+ repo_root: str,
486
+ task_dir: str,
487
+ subagent_type: str,
488
+ original_prompt: str,
489
+ *,
490
+ finish: bool = False,
491
+ max_chars: int | None = None,
492
+ ) -> str | None:
493
+ """Hook helper: map subagent type to role and build prompt."""
494
+ if subagent_type == AGENT_RESEARCH and not task_dir:
495
+ context = get_research_context(repo_root, None)
496
+ if max_chars is not None and len(context) > max_chars:
497
+ context = context[:max_chars] + "\n...[truncated]..."
498
+ if not context:
499
+ return None
500
+ return build_research_prompt(original_prompt, context)
501
+
502
+ role_map = {
503
+ AGENT_IMPLEMENT: "implement",
504
+ AGENT_CHECK: "check",
505
+ AGENT_RESEARCH: "research",
506
+ }
507
+ role = role_map.get(subagent_type)
508
+ if not role:
509
+ return None
510
+
511
+ repo_path = Path(repo_root)
512
+ task_path = repo_path / task_dir.replace("\\", "/")
513
+ is_finish = finish or "[finish]" in original_prompt.lower()
514
+ prompt, _, errors = build_dispatch_prompt(
515
+ repo_path,
516
+ task_path,
517
+ role,
518
+ finish=is_finish if role == "check" else False,
519
+ max_chars=max_chars,
520
+ original_prompt=original_prompt,
521
+ require_in_progress=False,
522
+ )
523
+ if errors:
524
+ for item in errors:
525
+ print(f"[inject-subagent-context] {item}", file=sys.stderr)
526
+ return None
527
+ return prompt
@@ -1272,6 +1272,38 @@ def cmd_integrate_child(args: argparse.Namespace) -> int:
1272
1272
  # Command: generate-child-prompt / parent-status / review-child
1273
1273
  # =============================================================================
1274
1274
 
1275
+ def cmd_generate_dispatch_prompt(args: argparse.Namespace) -> int:
1276
+ """Build a full Task dispatch prompt (Agent-facing CLI Layer 2)."""
1277
+ from .subagent_dispatch import build_dispatch_prompt
1278
+
1279
+ repo_root = get_repo_root()
1280
+ task_dir = resolve_task_dir(args.task_dir, repo_root)
1281
+ role = args.role
1282
+ scope = getattr(args, "scope", None)
1283
+ finish = bool(getattr(args, "finish", False))
1284
+ max_chars = getattr(args, "max_chars", None)
1285
+
1286
+ prompt, warnings, errors = build_dispatch_prompt(
1287
+ repo_root,
1288
+ task_dir,
1289
+ role,
1290
+ scope=scope,
1291
+ finish=finish,
1292
+ max_chars=max_chars,
1293
+ )
1294
+ for item in warnings:
1295
+ print(f"[generate-dispatch-prompt] WARN: {item}", file=sys.stderr)
1296
+ if errors:
1297
+ for item in errors:
1298
+ print(f"[generate-dispatch-prompt] Error: {item}", file=sys.stderr)
1299
+ return 1
1300
+ if prompt is None:
1301
+ print(colored("Error: could not build dispatch prompt", Colors.RED), file=sys.stderr)
1302
+ return 1
1303
+ print(prompt)
1304
+ return 0
1305
+
1306
+
1275
1307
  def cmd_generate_child_prompt(args: argparse.Namespace) -> int:
1276
1308
  """Generate a child implementation prompt for parent orchestration."""
1277
1309
  from .parent_orchestration import build_child_prompt
@@ -132,7 +132,7 @@ def get_step(step_id: str) -> str:
132
132
 
133
133
 
134
134
  def _platform_matches(platform: str, block_names: list[str]) -> bool:
135
- """Case-insensitive fuzzy match: accept 'cursor', 'Cursor', 'claude-code', 'Claude Code'."""
135
+ """Case-insensitive fuzzy match: accept 'cursor' or 'Cursor'."""
136
136
  needle = platform.lower().replace("-", "").replace("_", "").replace(" ", "")
137
137
  for name in block_names:
138
138
  hay = name.lower().replace("-", "").replace("_", "").replace(" ", "")
@@ -142,29 +142,8 @@ def _platform_matches(platform: str, block_names: list[str]) -> bool:
142
142
 
143
143
 
144
144
  def resolve_effective_platform(platform: str, config: dict) -> str:
145
- """Map ``codex`` to a dispatch-mode-namespaced virtual platform name.
146
-
147
- When ``--platform codex`` is passed, return ``"codex-inline"`` (default)
148
- or ``"codex-sub-agent"`` based on ``.trellis/config.yaml`` ``codex.dispatch_mode``.
149
- ``filter_platform`` then surfaces blocks whose marker lists include the
150
- namespaced name (e.g. ``[codex-sub-agent, ...]`` or ``[codex-inline, Kilo,
151
- Antigravity, Windsurf]``).
152
-
153
- Default is ``inline`` because Codex sub-agents run with ``fork_turns="none"``
154
- isolation and can't inherit the parent session's task context — inline
155
- keeps the main agent in charge so context isn't lost. Invalid / missing
156
- values also fall back to inline.
157
-
158
- Other platforms are returned unchanged.
159
- """
160
- if platform == "codex":
161
- mode = "inline"
162
- codex_cfg = config.get("codex") if isinstance(config, dict) else None
163
- if isinstance(codex_cfg, dict):
164
- cfg_mode = codex_cfg.get("dispatch_mode")
165
- if cfg_mode in ("inline", "sub-agent"):
166
- mode = cfg_mode
167
- return f"codex-{mode}"
145
+ """Return the platform name unchanged (Cursor-only fork)."""
146
+ _ = config
168
147
  return platform
169
148
 
170
149
 
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env python3
2
+ """CLI: rank codebase path candidates for B/E/D intents (eval / dogfood)."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ import sys
9
+ from pathlib import Path
10
+
11
+ from common.retrieval_result_ranking import rank_retrieval_result_candidates
12
+
13
+
14
+ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
15
+ parser = argparse.ArgumentParser(
16
+ description="Rank retrieval path candidates (caller / trap / env intents).",
17
+ )
18
+ parser.add_argument(
19
+ "--candidates",
20
+ help="Path to JSON array of candidate objects, or '-' for stdin.",
21
+ )
22
+ parser.add_argument(
23
+ "--intents",
24
+ required=True,
25
+ help="Comma-separated intent ids (e.g. caller-chain,env-config-literal).",
26
+ )
27
+ parser.add_argument("--top-k", type=int, default=5)
28
+ parser.add_argument("--expanded-pool-size", type=int, default=None)
29
+ parser.add_argument(
30
+ "--caller-pool-expansion",
31
+ action="store_true",
32
+ help="Enable caller pool expansion (caller-chain intent).",
33
+ )
34
+ parser.add_argument("--min-concrete-callers", type=int, default=3)
35
+ parser.add_argument("--json", action="store_true", help="Emit JSON (default).")
36
+ parser.add_argument("--pretty", action="store_true")
37
+ return parser.parse_args(argv)
38
+
39
+
40
+ def load_candidates(source: str | None) -> list[dict]:
41
+ if source is None or source == "-":
42
+ raw = sys.stdin.read()
43
+ else:
44
+ raw = Path(source).read_text(encoding="utf-8")
45
+ parsed = json.loads(raw)
46
+ if not isinstance(parsed, list):
47
+ raise ValueError("candidates must be a JSON array")
48
+ return [item for item in parsed if isinstance(item, dict)]
49
+
50
+
51
+ def main(argv: list[str] | None = None) -> int:
52
+ args = parse_args(argv)
53
+ try:
54
+ candidates = load_candidates(args.candidates)
55
+ except (OSError, json.JSONDecodeError, ValueError) as error:
56
+ print(f"rank_retrieval_candidates: {error}", file=sys.stderr)
57
+ return 2
58
+
59
+ intents = [part.strip() for part in args.intents.split(",") if part.strip()]
60
+ expansion = None
61
+ if args.caller_pool_expansion:
62
+ expansion = {
63
+ "enabled": True,
64
+ "minConcreteCallers": args.min_concrete_callers,
65
+ }
66
+
67
+ result = rank_retrieval_result_candidates(
68
+ candidates,
69
+ intents=intents,
70
+ top_k=args.top_k,
71
+ expanded_pool_size=args.expanded_pool_size,
72
+ caller_pool_expansion=expansion,
73
+ )
74
+ indent = 2 if args.pretty else None
75
+ print(json.dumps(result, indent=indent, ensure_ascii=False))
76
+ return 0
77
+
78
+
79
+ if __name__ == "__main__":
80
+ raise SystemExit(main())