@blulotus/trellis 0.7.0 → 0.7.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 (343) hide show
  1. package/README.md +2 -2
  2. package/dist/cli/index.js +2 -29
  3. package/dist/cli/index.js.map +1 -1
  4. package/dist/commands/init.d.ts +0 -22
  5. package/dist/commands/init.d.ts.map +1 -1
  6. package/dist/commands/init.js +5 -12
  7. package/dist/commands/init.js.map +1 -1
  8. package/dist/commands/mem.d.ts +2 -2
  9. package/dist/commands/mem.d.ts.map +1 -1
  10. package/dist/commands/mem.js +7 -40
  11. package/dist/commands/mem.js.map +1 -1
  12. package/dist/commands/uninstall.d.ts +1 -1
  13. package/dist/commands/uninstall.d.ts.map +1 -1
  14. package/dist/commands/uninstall.js +4 -34
  15. package/dist/commands/uninstall.js.map +1 -1
  16. package/dist/commands/update.d.ts.map +1 -1
  17. package/dist/commands/update.js +2 -28
  18. package/dist/commands/update.js.map +1 -1
  19. package/dist/configurators/index.d.ts.map +1 -1
  20. package/dist/configurators/index.js +7 -73
  21. package/dist/configurators/index.js.map +1 -1
  22. package/dist/configurators/shared.d.ts +18 -70
  23. package/dist/configurators/shared.d.ts.map +1 -1
  24. package/dist/configurators/shared.js +20 -299
  25. package/dist/configurators/shared.js.map +1 -1
  26. package/dist/configurators/workflow.d.ts +0 -14
  27. package/dist/configurators/workflow.d.ts.map +1 -1
  28. package/dist/configurators/workflow.js +1 -37
  29. package/dist/configurators/workflow.js.map +1 -1
  30. package/dist/migrations/manifests/0.1.9.json +0 -12
  31. package/dist/migrations/manifests/0.2.14.json +0 -84
  32. package/dist/migrations/manifests/0.3.0-beta.0.json +0 -87
  33. package/dist/migrations/manifests/0.3.4.json +1 -13
  34. package/dist/migrations/manifests/0.4.0-beta.1.json +24 -176
  35. package/dist/migrations/manifests/0.5.0-beta.0.json +67 -1203
  36. package/dist/migrations/manifests/0.5.0-beta.15.json +0 -96
  37. package/dist/migrations/manifests/0.5.0-beta.5.json +0 -168
  38. package/dist/migrations/manifests/0.5.0-beta.9.json +0 -20
  39. package/dist/migrations/manifests/0.6.0-beta.23.json +0 -66
  40. package/dist/migrations/manifests/0.6.3.json +1 -16
  41. package/dist/migrations/manifests/0.6.6.json +1 -9
  42. package/dist/migrations/manifests/0.7.0.json +9 -0
  43. package/dist/migrations/manifests/0.7.1.json +9 -0
  44. package/dist/migrations/manifests/0.7.2.json +9 -0
  45. package/dist/migrations/manifests/0.8.0.json +9 -0
  46. package/dist/templates/codex/hooks/session-start.py +37 -13
  47. package/dist/templates/codex/hooks.json +5 -0
  48. package/dist/templates/common/bundled-skills/trellis-meta/SKILL.md +5 -5
  49. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-agents.md +0 -10
  50. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-hooks.md +1 -1
  51. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-skills-or-commands.md +1 -13
  52. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/overview.md +2 -2
  53. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/bundled-skills.md +9 -28
  54. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/generated-files.md +5 -5
  55. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/overview.md +1 -1
  56. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/agents.md +1 -17
  57. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/hooks-and-settings.md +4 -12
  58. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/overview.md +6 -6
  59. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/platform-map.md +5 -75
  60. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/skills-and-commands.md +0 -16
  61. package/dist/templates/common/bundled-skills/trellis-session-insight/SKILL.md +1 -1
  62. package/dist/templates/common/bundled-skills/trellis-session-insight/references/cli-quick-reference.md +2 -3
  63. package/dist/templates/common/commands/continue.md +24 -4
  64. package/dist/templates/common/commands/finish-work.md +19 -9
  65. package/dist/templates/common/commands/start.md +38 -29
  66. package/dist/templates/extract.d.ts +0 -10
  67. package/dist/templates/extract.d.ts.map +1 -1
  68. package/dist/templates/extract.js +0 -24
  69. package/dist/templates/extract.js.map +1 -1
  70. package/dist/templates/markdown/spec/guides/code-reuse-thinking-guide.md.txt +7 -9
  71. package/dist/templates/markdown/spec/guides/cross-layer-thinking-guide.md.txt +4 -8
  72. package/dist/templates/markdown/spec/guides/cross-platform-thinking-guide.md.txt +5 -5
  73. package/dist/templates/shared-hooks/index.d.ts +11 -43
  74. package/dist/templates/shared-hooks/index.d.ts.map +1 -1
  75. package/dist/templates/shared-hooks/index.js +9 -86
  76. package/dist/templates/shared-hooks/index.js.map +1 -1
  77. package/dist/templates/shared-hooks/inject-spec-context.py +1 -1
  78. package/dist/templates/shared-hooks/inject-subagent-context.py +23 -85
  79. package/dist/templates/shared-hooks/inject-workflow-state.py +56 -86
  80. package/dist/templates/shared-hooks/session-start.py +76 -98
  81. package/dist/templates/trellis/config.yaml +8 -8
  82. package/dist/templates/trellis/index.d.ts +1 -4
  83. package/dist/templates/trellis/index.d.ts.map +1 -1
  84. package/dist/templates/trellis/index.js +1 -5
  85. package/dist/templates/trellis/index.js.map +1 -1
  86. package/dist/templates/trellis/scripts/add_session.py +12 -11
  87. package/dist/templates/trellis/scripts/common/__init__.py +2 -0
  88. package/dist/templates/trellis/scripts/common/active_task.py +80 -152
  89. package/dist/templates/trellis/scripts/common/config.py +7 -7
  90. package/dist/templates/trellis/scripts/common/git_context.py +1 -1
  91. package/dist/templates/trellis/scripts/common/session_context.py +2 -2
  92. package/dist/templates/trellis/scripts/common/spec_inject.py +2 -2
  93. package/dist/templates/trellis/scripts/common/task_store.py +8 -28
  94. package/dist/templates/trellis/scripts/common/workflow_phase.py +4 -8
  95. package/dist/templates/trellis/scripts/get_context.py +2 -2
  96. package/dist/templates/trellis/scripts/init_developer.py +1 -1
  97. package/dist/templates/trellis/scripts/task.py +90 -57
  98. package/dist/templates/trellis/workflow.md +44 -66
  99. package/dist/types/ai-tools.d.ts +10 -10
  100. package/dist/types/ai-tools.d.ts.map +1 -1
  101. package/dist/types/ai-tools.js +3 -434
  102. package/dist/types/ai-tools.js.map +1 -1
  103. package/dist/utils/uninstall-scrubbers.d.ts +3 -18
  104. package/dist/utils/uninstall-scrubbers.d.ts.map +1 -1
  105. package/dist/utils/uninstall-scrubbers.js +24 -154
  106. package/dist/utils/uninstall-scrubbers.js.map +1 -1
  107. package/package.json +2 -2
  108. package/dist/configurators/antigravity.d.ts +0 -7
  109. package/dist/configurators/antigravity.d.ts.map +0 -1
  110. package/dist/configurators/antigravity.js +0 -11
  111. package/dist/configurators/antigravity.js.map +0 -1
  112. package/dist/configurators/codebuddy.d.ts +0 -10
  113. package/dist/configurators/codebuddy.d.ts.map +0 -1
  114. package/dist/configurators/codebuddy.js +0 -24
  115. package/dist/configurators/codebuddy.js.map +0 -1
  116. package/dist/configurators/copilot.d.ts +0 -14
  117. package/dist/configurators/copilot.d.ts.map +0 -1
  118. package/dist/configurators/copilot.js +0 -46
  119. package/dist/configurators/copilot.js.map +0 -1
  120. package/dist/configurators/cursor.d.ts +0 -10
  121. package/dist/configurators/cursor.d.ts.map +0 -1
  122. package/dist/configurators/cursor.js +0 -23
  123. package/dist/configurators/cursor.js.map +0 -1
  124. package/dist/configurators/devin.d.ts +0 -8
  125. package/dist/configurators/devin.d.ts.map +0 -1
  126. package/dist/configurators/devin.js +0 -12
  127. package/dist/configurators/devin.js.map +0 -1
  128. package/dist/configurators/droid.d.ts +0 -10
  129. package/dist/configurators/droid.d.ts.map +0 -1
  130. package/dist/configurators/droid.js +0 -24
  131. package/dist/configurators/droid.js.map +0 -1
  132. package/dist/configurators/dsh.d.ts +0 -26
  133. package/dist/configurators/dsh.d.ts.map +0 -1
  134. package/dist/configurators/dsh.js +0 -76
  135. package/dist/configurators/dsh.js.map +0 -1
  136. package/dist/configurators/gemini.d.ts +0 -18
  137. package/dist/configurators/gemini.d.ts.map +0 -1
  138. package/dist/configurators/gemini.js +0 -39
  139. package/dist/configurators/gemini.js.map +0 -1
  140. package/dist/configurators/grok.d.ts +0 -17
  141. package/dist/configurators/grok.d.ts.map +0 -1
  142. package/dist/configurators/grok.js +0 -38
  143. package/dist/configurators/grok.js.map +0 -1
  144. package/dist/configurators/kilo.d.ts +0 -7
  145. package/dist/configurators/kilo.d.ts.map +0 -1
  146. package/dist/configurators/kilo.js +0 -11
  147. package/dist/configurators/kilo.js.map +0 -1
  148. package/dist/configurators/kimi.d.ts +0 -26
  149. package/dist/configurators/kimi.d.ts.map +0 -1
  150. package/dist/configurators/kimi.js +0 -74
  151. package/dist/configurators/kimi.js.map +0 -1
  152. package/dist/configurators/kiro.d.ts +0 -11
  153. package/dist/configurators/kiro.d.ts.map +0 -1
  154. package/dist/configurators/kiro.js +0 -30
  155. package/dist/configurators/kiro.js.map +0 -1
  156. package/dist/configurators/omp.d.ts +0 -6
  157. package/dist/configurators/omp.d.ts.map +0 -1
  158. package/dist/configurators/omp.js +0 -28
  159. package/dist/configurators/omp.js.map +0 -1
  160. package/dist/configurators/opencode.d.ts +0 -5
  161. package/dist/configurators/opencode.d.ts.map +0 -1
  162. package/dist/configurators/opencode.js +0 -84
  163. package/dist/configurators/opencode.js.map +0 -1
  164. package/dist/configurators/pi.d.ts +0 -5
  165. package/dist/configurators/pi.d.ts.map +0 -1
  166. package/dist/configurators/pi.js +0 -39
  167. package/dist/configurators/pi.js.map +0 -1
  168. package/dist/configurators/qoder.d.ts +0 -12
  169. package/dist/configurators/qoder.d.ts.map +0 -1
  170. package/dist/configurators/qoder.js +0 -30
  171. package/dist/configurators/qoder.js.map +0 -1
  172. package/dist/configurators/reasonix.d.ts +0 -17
  173. package/dist/configurators/reasonix.d.ts.map +0 -1
  174. package/dist/configurators/reasonix.js +0 -37
  175. package/dist/configurators/reasonix.js.map +0 -1
  176. package/dist/configurators/snow.d.ts +0 -25
  177. package/dist/configurators/snow.d.ts.map +0 -1
  178. package/dist/configurators/snow.js +0 -69
  179. package/dist/configurators/snow.js.map +0 -1
  180. package/dist/configurators/trae.d.ts +0 -16
  181. package/dist/configurators/trae.d.ts.map +0 -1
  182. package/dist/configurators/trae.js +0 -34
  183. package/dist/configurators/trae.js.map +0 -1
  184. package/dist/configurators/zcode.d.ts +0 -23
  185. package/dist/configurators/zcode.d.ts.map +0 -1
  186. package/dist/configurators/zcode.js +0 -63
  187. package/dist/configurators/zcode.js.map +0 -1
  188. package/dist/templates/codebuddy/agents/trellis-check.md +0 -115
  189. package/dist/templates/codebuddy/agents/trellis-implement.md +0 -115
  190. package/dist/templates/codebuddy/agents/trellis-research.md +0 -137
  191. package/dist/templates/codebuddy/index.d.ts +0 -15
  192. package/dist/templates/codebuddy/index.d.ts.map +0 -1
  193. package/dist/templates/codebuddy/index.js +0 -15
  194. package/dist/templates/codebuddy/index.js.map +0 -1
  195. package/dist/templates/codebuddy/settings.json +0 -69
  196. package/dist/templates/copilot/copilot-instructions.md +0 -11
  197. package/dist/templates/copilot/hooks/session-start.py +0 -564
  198. package/dist/templates/copilot/hooks.json +0 -19
  199. package/dist/templates/copilot/index.d.ts +0 -28
  200. package/dist/templates/copilot/index.d.ts.map +0 -1
  201. package/dist/templates/copilot/index.js +0 -61
  202. package/dist/templates/copilot/index.js.map +0 -1
  203. package/dist/templates/copilot/prompts/before-dev.prompt.md +0 -39
  204. package/dist/templates/copilot/prompts/brainstorm.prompt.md +0 -173
  205. package/dist/templates/copilot/prompts/break-loop.prompt.md +0 -129
  206. package/dist/templates/copilot/prompts/check-cross-layer.prompt.md +0 -170
  207. package/dist/templates/copilot/prompts/check.prompt.md +0 -110
  208. package/dist/templates/copilot/prompts/create-command.prompt.md +0 -116
  209. package/dist/templates/copilot/prompts/finish-work.prompt.md +0 -99
  210. package/dist/templates/copilot/prompts/integrate-skill.prompt.md +0 -223
  211. package/dist/templates/copilot/prompts/onboard.prompt.md +0 -362
  212. package/dist/templates/copilot/prompts/parallel.prompt.md +0 -204
  213. package/dist/templates/copilot/prompts/record-session.prompt.md +0 -66
  214. package/dist/templates/copilot/prompts/start.prompt.md +0 -63
  215. package/dist/templates/copilot/prompts/update-spec.prompt.md +0 -358
  216. package/dist/templates/cursor/agents/trellis-check.md +0 -114
  217. package/dist/templates/cursor/agents/trellis-implement.md +0 -114
  218. package/dist/templates/cursor/agents/trellis-research.md +0 -136
  219. package/dist/templates/cursor/hooks.json +0 -24
  220. package/dist/templates/cursor/index.d.ts +0 -13
  221. package/dist/templates/cursor/index.d.ts.map +0 -1
  222. package/dist/templates/cursor/index.js +0 -13
  223. package/dist/templates/cursor/index.js.map +0 -1
  224. package/dist/templates/droid/droids/trellis-check.md +0 -107
  225. package/dist/templates/droid/droids/trellis-implement.md +0 -107
  226. package/dist/templates/droid/droids/trellis-research.md +0 -137
  227. package/dist/templates/droid/index.d.ts +0 -15
  228. package/dist/templates/droid/index.d.ts.map +0 -1
  229. package/dist/templates/droid/index.js +0 -15
  230. package/dist/templates/droid/index.js.map +0 -1
  231. package/dist/templates/droid/settings.json +0 -69
  232. package/dist/templates/dsh/DSH.md +0 -80
  233. package/dist/templates/dsh/agents/trellis-check.md +0 -99
  234. package/dist/templates/dsh/agents/trellis-implement.md +0 -106
  235. package/dist/templates/dsh/agents/trellis-research.md +0 -134
  236. package/dist/templates/dsh/index.d.ts +0 -22
  237. package/dist/templates/dsh/index.d.ts.map +0 -1
  238. package/dist/templates/dsh/index.js +0 -27
  239. package/dist/templates/dsh/index.js.map +0 -1
  240. package/dist/templates/gemini/agents/trellis-check.md +0 -107
  241. package/dist/templates/gemini/agents/trellis-implement.md +0 -107
  242. package/dist/templates/gemini/agents/trellis-research.md +0 -136
  243. package/dist/templates/gemini/index.d.ts +0 -13
  244. package/dist/templates/gemini/index.d.ts.map +0 -1
  245. package/dist/templates/gemini/index.js +0 -13
  246. package/dist/templates/gemini/index.js.map +0 -1
  247. package/dist/templates/gemini/settings.json +0 -40
  248. package/dist/templates/grok/agents/trellis-check.md +0 -106
  249. package/dist/templates/grok/agents/trellis-implement.md +0 -121
  250. package/dist/templates/grok/agents/trellis-research.md +0 -146
  251. package/dist/templates/grok/index.d.ts +0 -16
  252. package/dist/templates/grok/index.d.ts.map +0 -1
  253. package/dist/templates/grok/index.js +0 -19
  254. package/dist/templates/grok/index.js.map +0 -1
  255. package/dist/templates/kimi/agents/trellis-check.md +0 -104
  256. package/dist/templates/kimi/agents/trellis-implement.md +0 -120
  257. package/dist/templates/kimi/agents/trellis-research.md +0 -144
  258. package/dist/templates/kimi/index.d.ts +0 -20
  259. package/dist/templates/kimi/index.d.ts.map +0 -1
  260. package/dist/templates/kimi/index.js +0 -23
  261. package/dist/templates/kimi/index.js.map +0 -1
  262. package/dist/templates/kiro/agents/trellis-check.json +0 -26
  263. package/dist/templates/kiro/agents/trellis-implement.json +0 -26
  264. package/dist/templates/kiro/agents/trellis-research.json +0 -30
  265. package/dist/templates/kiro/agents/trellis.json +0 -34
  266. package/dist/templates/kiro/hooks/trellis-workflow-state.kiro.hook +0 -14
  267. package/dist/templates/kiro/index.d.ts +0 -32
  268. package/dist/templates/kiro/index.d.ts.map +0 -1
  269. package/dist/templates/kiro/index.js +0 -28
  270. package/dist/templates/kiro/index.js.map +0 -1
  271. package/dist/templates/omp/agents/trellis-check.md +0 -41
  272. package/dist/templates/omp/agents/trellis-implement.md +0 -55
  273. package/dist/templates/omp/agents/trellis-research.md +0 -30
  274. package/dist/templates/omp/extensions/trellis/index.ts.txt +0 -592
  275. package/dist/templates/omp/index.d.ts +0 -4
  276. package/dist/templates/omp/index.d.ts.map +0 -1
  277. package/dist/templates/omp/index.js +0 -9
  278. package/dist/templates/omp/index.js.map +0 -1
  279. package/dist/templates/opencode/agents/trellis-check.md +0 -122
  280. package/dist/templates/opencode/agents/trellis-implement.md +0 -123
  281. package/dist/templates/opencode/agents/trellis-research.md +0 -145
  282. package/dist/templates/opencode/lib/context-visibility.js +0 -86
  283. package/dist/templates/opencode/lib/session-utils.js +0 -526
  284. package/dist/templates/opencode/lib/trellis-context.js +0 -627
  285. package/dist/templates/opencode/package.json +0 -5
  286. package/dist/templates/opencode/plugins/inject-spec-context.js +0 -121
  287. package/dist/templates/opencode/plugins/inject-subagent-context.js +0 -569
  288. package/dist/templates/opencode/plugins/inject-workflow-state.js +0 -340
  289. package/dist/templates/opencode/plugins/session-start.js +0 -74
  290. package/dist/templates/pi/agents/trellis-check.md +0 -37
  291. package/dist/templates/pi/agents/trellis-implement.md +0 -52
  292. package/dist/templates/pi/agents/trellis-research.md +0 -25
  293. package/dist/templates/pi/extensions/trellis/index.ts.txt +0 -2033
  294. package/dist/templates/pi/index.d.ts +0 -5
  295. package/dist/templates/pi/index.d.ts.map +0 -1
  296. package/dist/templates/pi/index.js +0 -12
  297. package/dist/templates/pi/index.js.map +0 -1
  298. package/dist/templates/pi/settings.json +0 -9
  299. package/dist/templates/qoder/agents/trellis-check.md +0 -108
  300. package/dist/templates/qoder/agents/trellis-implement.md +0 -108
  301. package/dist/templates/qoder/agents/trellis-research.md +0 -137
  302. package/dist/templates/qoder/index.d.ts +0 -15
  303. package/dist/templates/qoder/index.d.ts.map +0 -1
  304. package/dist/templates/qoder/index.js +0 -15
  305. package/dist/templates/qoder/index.js.map +0 -1
  306. package/dist/templates/qoder/settings.json +0 -59
  307. package/dist/templates/reasonix/agents/trellis-check.md +0 -36
  308. package/dist/templates/reasonix/agents/trellis-implement.md +0 -51
  309. package/dist/templates/reasonix/index.d.ts +0 -13
  310. package/dist/templates/reasonix/index.d.ts.map +0 -1
  311. package/dist/templates/reasonix/index.js +0 -16
  312. package/dist/templates/reasonix/index.js.map +0 -1
  313. package/dist/templates/shared-hooks/inject-shell-session-context.py +0 -292
  314. package/dist/templates/snow/SNOW.md +0 -86
  315. package/dist/templates/snow/agents/trellis-check.md +0 -136
  316. package/dist/templates/snow/agents/trellis-implement.md +0 -158
  317. package/dist/templates/snow/agents/trellis-research.md +0 -114
  318. package/dist/templates/snow/hooks/beforeSubAgentStart.json +0 -15
  319. package/dist/templates/snow/hooks/onSessionStart.json +0 -15
  320. package/dist/templates/snow/hooks/onUserMessage.json +0 -15
  321. package/dist/templates/snow/hooks/write-trellis-context.py +0 -644
  322. package/dist/templates/snow/index.d.ts +0 -26
  323. package/dist/templates/snow/index.d.ts.map +0 -1
  324. package/dist/templates/snow/index.js +0 -36
  325. package/dist/templates/snow/index.js.map +0 -1
  326. package/dist/templates/trae/agents/trellis-check.md +0 -108
  327. package/dist/templates/trae/agents/trellis-implement.md +0 -108
  328. package/dist/templates/trae/agents/trellis-research.md +0 -137
  329. package/dist/templates/trae/hooks.json +0 -39
  330. package/dist/templates/trae/index.d.ts +0 -15
  331. package/dist/templates/trae/index.d.ts.map +0 -1
  332. package/dist/templates/trae/index.js +0 -15
  333. package/dist/templates/trae/index.js.map +0 -1
  334. package/dist/templates/trellis/gitattributes.txt +0 -9
  335. package/dist/templates/trellis/scripts/common/cli_adapter.py +0 -982
  336. package/dist/templates/zcode/agents/trellis-check.md +0 -110
  337. package/dist/templates/zcode/agents/trellis-implement.md +0 -116
  338. package/dist/templates/zcode/agents/trellis-research.md +0 -133
  339. package/dist/templates/zcode/config.json +0 -52
  340. package/dist/templates/zcode/index.d.ts +0 -25
  341. package/dist/templates/zcode/index.d.ts.map +0 -1
  342. package/dist/templates/zcode/index.js +0 -29
  343. package/dist/templates/zcode/index.js.map +0 -1
@@ -90,26 +90,14 @@ def find_repo_root(start_path: str) -> str | None:
90
90
  def _detect_platform(input_data: dict) -> str | None:
91
91
  if _hook_event_name(input_data) == "SubagentStart":
92
92
  return "codex"
93
- if isinstance(input_data.get("cursor_version"), str):
94
- return "cursor"
95
- # CLAUDE_PROJECT_DIR is a compatibility alias that several hosts set
96
- # alongside their own variable CodeBuddy, ZCode and Trae all do. It must
97
- # therefore be checked LAST, or every one of them is detected as claude and
98
- # the context key becomes `claude_<their-session-id>`. That key does not
99
- # match the session file `task.py start` wrote under the host's real name,
100
- # so the sub-agent starts with no task context while the pointer exists on
101
- # disk. Same fix as inject-workflow-state.py and session-start.py; this
102
- # third copy was missed when those two were corrected.
93
+ # CLAUDE_PROJECT_DIR is a compatibility alias other hosts may also set, so
94
+ # it is checked last — a vendor-specific key always wins. Detecting the
95
+ # wrong host would build the context key `claude_<their-session-id>`, which
96
+ # never matches the session file `task.py start` wrote under the host's
97
+ # real name, so the sub-agent would start with no task context while the
98
+ # pointer exists on disk.
103
99
  env_map = {
104
- "ZCODE_PROJECT_DIR": "zcode",
105
- "CURSOR_PROJECT_DIR": "cursor",
106
- "CODEBUDDY_PROJECT_DIR": "codebuddy",
107
- "FACTORY_PROJECT_DIR": "droid",
108
- "GEMINI_PROJECT_DIR": "gemini",
109
- "QODER_PROJECT_DIR": "qoder",
110
- "KIRO_PROJECT_DIR": "kiro",
111
- "COPILOT_PROJECT_DIR": "copilot",
112
- "TRAE_PROJECT_DIR": "trae",
100
+ "CODEX_PROJECT_DIR": "codex",
113
101
  # Last: the shared alias, only meaningful once no vendor key matched.
114
102
  "CLAUDE_PROJECT_DIR": "claude",
115
103
  }
@@ -119,20 +107,8 @@ def _detect_platform(input_data: dict) -> str | None:
119
107
  script_parts = set(Path(sys.argv[0]).parts)
120
108
  if ".claude" in script_parts:
121
109
  return "claude"
122
- if ".cursor" in script_parts:
123
- return "cursor"
124
- if ".gemini" in script_parts:
125
- return "gemini"
126
- if ".qoder" in script_parts:
127
- return "qoder"
128
- if ".codebuddy" in script_parts:
129
- return "codebuddy"
130
- if ".factory" in script_parts:
131
- return "droid"
132
- if ".kiro" in script_parts:
133
- return "kiro"
134
- if ".zcode" in script_parts:
135
- return "zcode"
110
+ if ".codex" in script_parts:
111
+ return "codex"
136
112
  return None
137
113
 
138
114
 
@@ -888,8 +864,8 @@ def _handle_codex_subagent_start(input_data: dict) -> None:
888
864
  if not subagent_type or not parent_session_id:
889
865
  return
890
866
 
891
- # Payload cwd first, then our own — some hosts (CodeBuddy IDE 4.10.4)
892
- # report "/" for every hook event. See inject-workflow-state.py.
867
+ # Payload cwd first, then our own — a host may report "/" for every hook
868
+ # event. See inject-workflow-state.py.
893
869
  repo_root = None
894
870
  for candidate in (_string_value(input_data.get("cwd")), os.getcwd()):
895
871
  if not candidate:
@@ -938,11 +914,11 @@ def _handle_codex_subagent_start(input_data: dict) -> None:
938
914
 
939
915
 
940
916
  def _extract_subagent_name(value: Any) -> str:
941
- """Extract a sub-agent name from common platform encodings.
917
+ """Extract a sub-agent name from the shapes hook JSON can carry.
942
918
 
943
- Cursor's native Task args encode custom sub-agents as a protobuf oneof,
944
- which can appear in hook JSON as either ``{"custom": {"name": "..."}}``
945
- or ``{"type": {"case": "custom", "value": {"name": "..."}}}``.
919
+ A plain string is the common case; a host may instead nest the name as
920
+ ``{"custom": {"name": "..."}}`` or
921
+ ``{"type": {"case": "custom", "value": {"name": "..."}}}``.
946
922
  """
947
923
  direct = _string_value(value)
948
924
  if direct:
@@ -1013,14 +989,8 @@ def _parse_hook_input(input_data: dict) -> tuple[str, str, dict]:
1013
989
  """Parse hook input across different platform formats.
1014
990
 
1015
991
  Returns (subagent_type, original_prompt, tool_input).
1016
- Handles:
1017
- - Claude Code / Qoder / Droid: tool_name=Task|Agent, tool_input.subagent_type
1018
- - CodeBuddy: tool_name=task (IDE) or Task (CLI), tool_input.subagent_name
1019
- - Cursor: tool_name=Task|Subagent, tool_input.subagent_type
1020
- - Copilot CLI: toolName=task (camelCase key, lowercase value)
1021
- - ZCode: toolName=Agent, toolInput/tool_input.subagent_type
1022
- - Gemini CLI: tool_name IS the agent name (BeforeTool matcher already filtered)
1023
- - Kiro: agentSpawn hook, agent_name field at top level
992
+ Handles the Claude Code shape: tool_name=Task|Agent with
993
+ tool_input.subagent_type (camelCase key spellings accepted too).
1024
994
  """
1025
995
  tool_input = input_data.get("tool_input", {})
1026
996
  if not isinstance(tool_input, dict):
@@ -1037,21 +1007,6 @@ def _parse_hook_input(input_data: dict) -> tuple[str, str, dict]:
1037
1007
  tool_input,
1038
1008
  )
1039
1009
 
1040
- # Kiro: agentSpawn hook passes agent_name at top level
1041
- agent_name = input_data.get("agent_name", "")
1042
- if agent_name:
1043
- return agent_name, tool_input.get("prompt", input_data.get("prompt", "")), tool_input
1044
-
1045
- # Gemini CLI: BeforeTool where tool_name IS the agent name
1046
- # (matcher already ensured it's one of our agents)
1047
- if tool_name in AGENTS_ALL:
1048
- return tool_name, tool_input.get("prompt", ""), tool_input
1049
-
1050
- # Copilot CLI: toolName field (camelCase), value might be the agent name
1051
- tool_name_camel = input_data.get("toolName", "")
1052
- if tool_name_camel in AGENTS_ALL:
1053
- return tool_name_camel, input_data.get("toolArgs", ""), tool_input
1054
-
1055
1010
  return "", "", tool_input
1056
1011
 
1057
1012
 
@@ -1140,32 +1095,15 @@ def main():
1140
1095
  if not context:
1141
1096
  sys.exit(0)
1142
1097
 
1143
- # Return updated input. Most platforms ignore unrecognized fields, so we
1144
- # include multiple formats. ZCode is stricter; live probing confirmed the
1145
- # nested Claude-compatible shape below reaches the sub-agent prompt.
1098
+ # Return updated input in the Claude Code PreToolUse shape.
1146
1099
  updated = {**tool_input, "prompt": new_prompt}
1147
- if _detect_platform(input_data) == "zcode":
1148
- output = {
1149
- "hookSpecificOutput": {
1150
- "hookEventName": "PreToolUse",
1151
- "permissionDecision": "allow",
1152
- "updatedInput": updated,
1153
- }
1154
- }
1155
- else:
1156
- output = {
1157
- # Claude Code / Qoder / CodeBuddy / Droid format
1158
- "hookSpecificOutput": {
1159
- "hookEventName": "PreToolUse",
1160
- "permissionDecision": "allow",
1161
- "updatedInput": updated,
1162
- },
1163
- # Cursor format
1164
- "permission": "allow",
1165
- "updated_input": updated,
1166
- # Gemini format
1100
+ output = {
1101
+ "hookSpecificOutput": {
1102
+ "hookEventName": "PreToolUse",
1103
+ "permissionDecision": "allow",
1167
1104
  "updatedInput": updated,
1168
1105
  }
1106
+ }
1169
1107
 
1170
1108
  print(json.dumps(output, ensure_ascii=False))
1171
1109
  sys.exit(0)
@@ -1,15 +1,16 @@
1
1
  #!/usr/bin/env python3
2
2
  """Trellis per-turn breadcrumb hook (UserPromptSubmit / BeforeAgent equivalent).
3
3
 
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
-
8
- The emitted ``hookEventName`` field is platform-aware: most hosts expect
9
- ``UserPromptSubmit`` (Claude Code naming, also accepted by Cursor / Qoder /
10
- CodeBuddy / Droid / Codex / Copilot wiring), but Gemini CLI 0.40.x renamed
11
- its per-turn event to ``BeforeAgent`` and its schema validator rejects the
12
- legacy name. ``_detect_platform`` picks the right value at runtime.
4
+ Runs on every user prompt, but emits nothing until the session opts into the
5
+ Trellis workflow by invoking `trellis-start`, `trellis-continue` or
6
+ `trellis-finish-work` (each runs `task.py engage`). Once engaged, it resolves the
7
+ active task through Trellis' session-aware active task resolver and emits a short
8
+ <workflow-state> block reminding the main AI what task is active and its expected
9
+ flow.
10
+
11
+ The emitted ``hookEventName`` field is ``UserPromptSubmit`` (Claude Code
12
+ naming, also accepted by Codex wiring). ``_detect_platform`` reports which
13
+ host is running so the payload shape can stay host-aware.
13
14
  Breadcrumb text is pulled exclusively from the resolved workflow file's
14
15
  [workflow-state:STATUS] tag blocks — the active task may select a
15
16
  per-task variant (`.trellis/workflows/<id>.md` via task.json `workflow`),
@@ -20,14 +21,10 @@ missing or a tag is absent, the breadcrumb degrades to a generic
20
21
  the broken state instead of the hook silently masking it.
21
22
 
22
23
  Which platforms register this hook is decided by SHARED_HOOKS_BY_PLATFORM
23
- in templates/shared-hooks/index.ts — currently Claude, Codex, Gemini,
24
- Qoder, Copilot, CodeBuddy, Droid, Kiro, Trae and ZCode. That table is the
25
- source of truth; each listed platform's collect<Platform>Templates() pulls
26
- this file into its template map through collectSharedHooks(), and a single
27
- writer puts that map on disk at init time. Kiro wires this via the CLI
28
- custom agent's ``hooks.userPromptSubmit`` and the IDE ``.kiro.hook``
29
- ``promptSubmit`` event; its output branch emits a plain-text breadcrumb
30
- (Kiro adds hook stdout directly to the conversation context).
24
+ in templates/shared-hooks/index.ts — currently Claude Code and Codex. That
25
+ table is the source of truth; each listed platform's
26
+ collect<Platform>Templates() pulls this file into its template map through
27
+ collectSharedHooks(), and a single writer puts that map on disk at init time.
31
28
 
32
29
  Silent exit 0 cases (no output):
33
30
  - No .trellis/ directory found (not a Trellis project)
@@ -67,14 +64,6 @@ if sys.platform.startswith("win"):
67
64
  from typing import Optional
68
65
 
69
66
 
70
- # Bootstrap notice for Codex while the session has no active task. Codex does not
71
- # get the full SessionStart overview; this short reminder points the main session
72
- # at the start skill once and leaves the per-turn state block compact.
73
- CODEX_NO_TASK_BOOTSTRAP_NOTICE = """<trellis-bootstrap>
74
- If you have not already loaded Trellis context this session, read the `trellis-start` skill once.
75
- </trellis-bootstrap>"""
76
-
77
-
78
67
  # ---------------------------------------------------------------------------
79
68
  # CWD-robust Trellis root discovery (fixes hook-path-robustness for this hook)
80
69
  # ---------------------------------------------------------------------------
@@ -98,26 +87,13 @@ def find_trellis_root(start: Path) -> Optional[Path]:
98
87
  # ---------------------------------------------------------------------------
99
88
 
100
89
  def _detect_platform(input_data: dict) -> str | None:
101
- if isinstance(input_data.get("cursor_version"), str):
102
- return "cursor"
103
- # CLAUDE_PROJECT_DIR is a compatibility alias that several hosts set
104
- # alongside their own variable CodeBuddy, ZCode and Trae all do. It must
105
- # therefore be checked LAST, or every one of them is detected as claude and
106
- # the context key becomes `claude_<their-session-id>`. That key does not
107
- # match the session file `task.py start` wrote under the host's real name,
108
- # so every turn reports no_task while the pointer exists on disk.
109
- # Observed on CodeBuddy IDE 4.10.4: session file `codebuddy_ae54840e….json`
110
- # alongside marker `update-check-claude_ae54840e….marker`, same id.
90
+ # CLAUDE_PROJECT_DIR is a compatibility alias other hosts may also set, so
91
+ # it is checked last — a vendor-specific key always wins. Detecting the
92
+ # wrong host would build the context key `claude_<their-session-id>`, which
93
+ # never matches the session file `task.py start` wrote under the host's
94
+ # real name, and every turn would report no_task while the pointer exists.
111
95
  env_map = {
112
- "ZCODE_PROJECT_DIR": "zcode",
113
- "CURSOR_PROJECT_DIR": "cursor",
114
- "CODEBUDDY_PROJECT_DIR": "codebuddy",
115
- "FACTORY_PROJECT_DIR": "droid",
116
- "GEMINI_PROJECT_DIR": "gemini",
117
- "QODER_PROJECT_DIR": "qoder",
118
- "KIRO_PROJECT_DIR": "kiro",
119
- "COPILOT_PROJECT_DIR": "copilot",
120
- "TRAE_PROJECT_DIR": "trae",
96
+ "CODEX_PROJECT_DIR": "codex",
121
97
  # Last: the shared alias, only meaningful once no vendor key matched.
122
98
  "CLAUDE_PROJECT_DIR": "claude",
123
99
  }
@@ -127,24 +103,8 @@ def _detect_platform(input_data: dict) -> str | None:
127
103
  script_parts = set(Path(sys.argv[0]).parts)
128
104
  if ".claude" in script_parts:
129
105
  return "claude"
130
- if ".cursor" in script_parts:
131
- return "cursor"
132
106
  if ".codex" in script_parts:
133
107
  return "codex"
134
- if ".gemini" in script_parts:
135
- return "gemini"
136
- if ".qoder" in script_parts:
137
- return "qoder"
138
- if ".codebuddy" in script_parts:
139
- return "codebuddy"
140
- if ".factory" in script_parts:
141
- return "droid"
142
- if ".kiro" in script_parts:
143
- return "kiro"
144
- if ".trae" in script_parts:
145
- return "trae"
146
- if ".zcode" in script_parts:
147
- return "zcode"
148
108
  return None
149
109
 
150
110
 
@@ -157,6 +117,30 @@ def _resolve_active_task(root: Path, input_data: dict):
157
117
  return resolve_active_task(root, input_data, platform=_detect_platform(input_data))
158
118
 
159
119
 
120
+ def _is_session_engaged(root: Path, input_data: dict) -> bool:
121
+ """Whether this session opted into the Trellis workflow.
122
+
123
+ A project too old to have `is_session_engaged` (hooks updated, `.trellis/scripts`
124
+ not) raises ImportError. Reporting False then would silently stop the per-turn
125
+ breadcrumb — the one channel that enforces the planning and commit gates — so
126
+ the stale-scripts case keeps the old always-inject behavior and says why.
127
+ """
128
+ scripts_dir = root / ".trellis" / "scripts"
129
+ if str(scripts_dir) not in sys.path:
130
+ sys.path.insert(0, str(scripts_dir))
131
+ try:
132
+ from common.active_task import is_session_engaged # type: ignore[import-not-found]
133
+ except ImportError:
134
+ print(
135
+ "trellis inject-workflow-state: .trellis/scripts predates opt-in "
136
+ "engagement; injecting unconditionally. Run `trellis update`.",
137
+ file=sys.stderr,
138
+ )
139
+ return True
140
+
141
+ return is_session_engaged(root, input_data, platform=_detect_platform(input_data))
142
+
143
+
160
144
  def get_active_task(root: Path, input_data: dict) -> Optional[tuple[str, str, str]]:
161
145
  """Return (task_id, status, source) from the current active task."""
162
146
  active = _resolve_active_task(root, input_data)
@@ -400,8 +384,8 @@ def build_breadcrumb(
400
384
  def _load_hook_input() -> dict:
401
385
  """Read hook JSON without trusting host runners to close stdin.
402
386
 
403
- Kiro IDE `runCommand` and similar hook runners can leave stdin open while
404
- sending no payload. A plain `json.load(sys.stdin)` then blocks forever.
387
+ A hook runner can leave stdin open while sending no payload. A plain
388
+ `json.load(sys.stdin)` then blocks forever.
405
389
  Normal hook runners write the complete JSON payload and close stdin, so the
406
390
  short daemon read preserves that path while failing closed to `{}` for
407
391
  non-piping hosts.
@@ -443,6 +427,12 @@ def main() -> int:
443
427
  if root is None:
444
428
  return 0 # not a Trellis project
445
429
 
430
+ # Opt-in gate, ahead of every read below: a session that never invoked an
431
+ # entry point gets no breadcrumb, and does not pay the task-resolution and
432
+ # workflow.md reads either.
433
+ if not _is_session_engaged(root, data):
434
+ return 0
435
+
446
436
  config = _read_trellis_config(root)
447
437
  if prompt_has_skip_keyword(data.get("prompt", ""), _resolve_skip_keyword(config)):
448
438
  return 0 # user opted out of the per-turn breadcrumb for this turn
@@ -451,8 +441,8 @@ def main() -> int:
451
441
  platform = _detect_platform(data)
452
442
  task = get_active_task(root, data)
453
443
  if task is None:
454
- # No active task still emit a breadcrumb nudging AI toward
455
- # trellis-brainstorm + task.py create when user describes real work.
444
+ # Engaged but no active task: either the entry point has not created one
445
+ # yet, or `task.py finish` / archive cleared the pointer mid-session.
456
446
  no_task_key = resolve_breadcrumb_key("no_task", platform, config)
457
447
  breadcrumb = build_breadcrumb(
458
448
  None, "no_task", templates, breadcrumb_key=no_task_key
@@ -465,31 +455,11 @@ def main() -> int:
465
455
  task_id, status, templates, source_for_breadcrumb, breadcrumb_key=status_key
466
456
  )
467
457
  if platform == "codex":
468
- parts: list[str] = []
469
- if task is None:
470
- parts.append(CODEX_NO_TASK_BOOTSTRAP_NOTICE)
471
- parts.append(_codex_mode_banner(config))
472
- parts.append(breadcrumb)
473
- breadcrumb = "\n\n".join(parts)
474
-
475
- # Kiro (CLI userPromptSubmit / IDE promptSubmit) adds a hook's stdout
476
- # directly to the conversation context — no JSON envelope. Emit the bare
477
- # breadcrumb text. Conditionally isolated: all other platforms keep the
478
- # hookSpecificOutput JSON path below unchanged.
479
- if platform == "kiro":
480
- print(breadcrumb)
481
- return 0
482
-
483
- # Gemini CLI 0.40.x rejects "UserPromptSubmit" — its per-turn event is
484
- # named "BeforeAgent". Other platforms (Claude/Cursor/Qoder/CodeBuddy/
485
- # Droid/Codex/Copilot) accept the original Claude-style name.
486
- hook_event_name = (
487
- "BeforeAgent" if platform == "gemini" else "UserPromptSubmit"
488
- )
458
+ breadcrumb = f"{_codex_mode_banner(config)}\n\n{breadcrumb}"
489
459
 
490
460
  output = {
491
461
  "hookSpecificOutput": {
492
- "hookEventName": hook_event_name,
462
+ "hookEventName": "UserPromptSubmit",
493
463
  "additionalContext": breadcrumb,
494
464
  }
495
465
  }
@@ -2,6 +2,11 @@
2
2
  # -*- coding: utf-8 -*-
3
3
  """
4
4
  Session Start Hook - Inject structured context
5
+
6
+ Emits nothing until the session opts into the Trellis workflow by invoking
7
+ `trellis-start`, `trellis-continue` or `trellis-finish-work` (each runs
8
+ `task.py engage`). The session-identity env bridge still runs unconditionally —
9
+ it is plumbing, not context, and `task.py engage` depends on it.
5
10
  """
6
11
  from __future__ import annotations
7
12
 
@@ -67,38 +72,27 @@ def _normalize_windows_shell_path(path_str: str) -> str:
67
72
  return path_str
68
73
 
69
74
 
70
- _FIRST_REPLY_NOTICE_HEAD = """<first-reply-notice>
71
- On the first visible assistant reply in this session, briefly acknowledge that Trellis SessionStart context loaded."""
72
-
73
- _FIRST_REPLY_NOTICE_TAIL = """Choose the acknowledgment language in this order:
74
- 1. Use the language of the user's current request (the user message that triggered this reply).
75
- 2. If that request has no clear natural language, use an explicitly established project communication language.
76
- 3. If neither provides a language, output the language-neutral fallback exactly: `Trellis SessionStart ✓`.
77
- Continue directly with the user's request after the acknowledgment.
78
- The acknowledgment must not alter the language used for the remainder of the response.
79
- This notice is one-shot: do not repeat it after the first visible assistant reply in this session.
80
- </first-reply-notice>"""
81
-
82
- FIRST_REPLY_NOTICE = f"{_FIRST_REPLY_NOTICE_HEAD}\n{_FIRST_REPLY_NOTICE_TAIL}"
83
-
84
-
85
- def _build_first_reply_notice(update_hint: str | None) -> str:
86
- """First-reply notice, carrying the Trellis update reminder when there is one.
75
+ def _build_update_notice(update_hint: str | None) -> str:
76
+ """Escalate a pending Trellis update into the visible reply.
87
77
 
88
78
  The reminder has to reach the *user*, not just the model's context — a line
89
79
  buried in SessionStart context is exactly how the update step kept getting
90
- skipped. This block is already the payload's one "say it out loud" channel,
91
- so the hint rides along instead of growing a second mechanism.
92
-
93
- With no hint the notice is byte-identical to the plain constant: no empty
94
- block, no placeholder line.
80
+ skipped. So it gets an explicit "relay this out loud" instruction rather
81
+ than a plain state line.
82
+
83
+ Emits nothing when no update is pending, which is the normal case: the block
84
+ exists only on the sessions that have something to report. Replaces the old
85
+ <first-reply-notice>, whose other job — making the AI announce that Trellis
86
+ context loaded — died with opt-in engagement, where the user invoked the
87
+ entry point and its output is the acknowledgment.
95
88
  """
96
89
  if not update_hint:
97
- return FIRST_REPLY_NOTICE
90
+ return ""
98
91
  return (
99
- f"{_FIRST_REPLY_NOTICE_HEAD}\n"
100
- f"Also relay this Trellis maintenance notice on its own line in that same reply: {update_hint}\n"
101
- f"{_FIRST_REPLY_NOTICE_TAIL}"
92
+ "<trellis-maintenance-notice>\n"
93
+ "Relay this Trellis maintenance notice on its own line in your next "
94
+ f"visible reply, verbatim: {update_hint}\n"
95
+ "</trellis-maintenance-notice>\n\n"
102
96
  )
103
97
 
104
98
 
@@ -160,15 +154,7 @@ def should_skip_injection() -> bool:
160
154
  return True
161
155
  non_interactive_vars = [
162
156
  "CLAUDE_NON_INTERACTIVE",
163
- "QODER_NON_INTERACTIVE",
164
- "CODEBUDDY_NON_INTERACTIVE",
165
- "FACTORY_NON_INTERACTIVE",
166
- "CURSOR_NON_INTERACTIVE",
167
- "GEMINI_NON_INTERACTIVE",
168
- "KIRO_NON_INTERACTIVE",
169
- "COPILOT_NON_INTERACTIVE",
170
- "TRAE_NON_INTERACTIVE",
171
- "ZCODE_NON_INTERACTIVE",
157
+ "CODEX_NON_INTERACTIVE",
172
158
  ]
173
159
  return any(os.environ.get(var) == "1" for var in non_interactive_vars)
174
160
 
@@ -216,26 +202,13 @@ def _format_git_state(repo_root: Path) -> str:
216
202
 
217
203
 
218
204
  def _detect_platform(input_data: dict) -> str | None:
219
- if isinstance(input_data.get("cursor_version"), str):
220
- return "cursor"
221
- # CLAUDE_PROJECT_DIR is a compatibility alias that several hosts set
222
- # alongside their own variable CodeBuddy, ZCode and Trae all do. It must
223
- # therefore be checked LAST, or every one of them is detected as claude and
224
- # the context key becomes `claude_<their-session-id>`. That key does not
225
- # match the session file `task.py start` wrote under the host's real name,
226
- # so every turn reports no_task while the pointer exists on disk.
227
- # Observed on CodeBuddy IDE 4.10.4: session file `codebuddy_ae54840e….json`
228
- # alongside marker `update-check-claude_ae54840e….marker`, same id.
205
+ # CLAUDE_PROJECT_DIR is a compatibility alias other hosts may also set, so
206
+ # it is checked last — a vendor-specific key always wins. Detecting the
207
+ # wrong host would build the context key `claude_<their-session-id>`, which
208
+ # never matches the session file `task.py start` wrote under the host's
209
+ # real name, and every turn would report no_task while the pointer exists.
229
210
  env_map = {
230
- "ZCODE_PROJECT_DIR": "zcode",
231
- "CURSOR_PROJECT_DIR": "cursor",
232
- "CODEBUDDY_PROJECT_DIR": "codebuddy",
233
- "FACTORY_PROJECT_DIR": "droid",
234
- "GEMINI_PROJECT_DIR": "gemini",
235
- "QODER_PROJECT_DIR": "qoder",
236
- "KIRO_PROJECT_DIR": "kiro",
237
- "COPILOT_PROJECT_DIR": "copilot",
238
- "TRAE_PROJECT_DIR": "trae",
211
+ "CODEX_PROJECT_DIR": "codex",
239
212
  # Last: the shared alias, only meaningful once no vendor key matched.
240
213
  "CLAUDE_PROJECT_DIR": "claude",
241
214
  }
@@ -245,24 +218,8 @@ def _detect_platform(input_data: dict) -> str | None:
245
218
  script_parts = set(Path(sys.argv[0]).parts)
246
219
  if ".claude" in script_parts:
247
220
  return "claude"
248
- if ".cursor" in script_parts:
249
- return "cursor"
250
221
  if ".codex" in script_parts:
251
222
  return "codex"
252
- if ".gemini" in script_parts:
253
- return "gemini"
254
- if ".qoder" in script_parts:
255
- return "qoder"
256
- if ".codebuddy" in script_parts:
257
- return "codebuddy"
258
- if ".factory" in script_parts:
259
- return "droid"
260
- if ".kiro" in script_parts:
261
- return "kiro"
262
- if ".trae" in script_parts:
263
- return "trae"
264
- if ".zcode" in script_parts:
265
- return "zcode"
266
223
  return None
267
224
 
268
225
 
@@ -275,6 +232,35 @@ def _resolve_context_key(trellis_dir: Path, input_data: dict) -> str | None:
275
232
  return resolve_context_key(input_data, platform=_detect_platform(input_data))
276
233
 
277
234
 
235
+ def _is_session_engaged(trellis_dir: Path, input_data: dict) -> bool:
236
+ """Whether this session opted into the Trellis workflow.
237
+
238
+ A project too old to have `is_session_engaged` (pre-opt-in `.trellis/scripts`,
239
+ e.g. an install that upgraded the hooks but not the scripts) raises ImportError
240
+ here. Reporting False then would make Trellis look uninstalled on every
241
+ session with no way to diagnose it, so the stale-scripts case keeps the old
242
+ always-inject behavior and says why on stderr.
243
+ """
244
+ scripts_dir = trellis_dir / "scripts"
245
+ if str(scripts_dir) not in sys.path:
246
+ sys.path.insert(0, str(scripts_dir))
247
+ try:
248
+ from common.active_task import is_session_engaged # type: ignore[import-not-found]
249
+ except ImportError:
250
+ print(
251
+ "trellis session-start: .trellis/scripts predates opt-in engagement; "
252
+ "injecting context unconditionally. Run `trellis update`.",
253
+ file=sys.stderr,
254
+ )
255
+ return True
256
+
257
+ return is_session_engaged(
258
+ trellis_dir.parent,
259
+ input_data,
260
+ platform=_detect_platform(input_data),
261
+ )
262
+
263
+
278
264
  def _persist_context_key_for_bash(context_key: str | None) -> None:
279
265
  """Expose Trellis session identity to later Claude Code Bash commands.
280
266
 
@@ -853,15 +839,7 @@ def main():
853
839
  # Try platform-specific env vars, hook cwd, fallback to cwd
854
840
  project_dir_env_vars = [
855
841
  "CLAUDE_PROJECT_DIR",
856
- "QODER_PROJECT_DIR",
857
- "CODEBUDDY_PROJECT_DIR",
858
- "FACTORY_PROJECT_DIR",
859
- "CURSOR_PROJECT_DIR",
860
- "GEMINI_PROJECT_DIR",
861
- "KIRO_PROJECT_DIR",
862
- "COPILOT_PROJECT_DIR",
863
- "TRAE_PROJECT_DIR",
864
- "ZCODE_PROJECT_DIR",
842
+ "CODEX_PROJECT_DIR",
865
843
  ]
866
844
  project_dir = None
867
845
  for var in project_dir_env_vars:
@@ -876,6 +854,21 @@ def main():
876
854
  context_key = _resolve_context_key(trellis_dir, hook_input)
877
855
  _persist_context_key_for_bash(context_key)
878
856
 
857
+ # Context injection is opt-in. Everything below this point costs tokens, and
858
+ # a session that never invoked an entry point (`trellis-start` /
859
+ # `trellis-continue` / `trellis-finish-work`) is not doing Trellis work — an
860
+ # in-flight task belonging to some other window is not consent.
861
+ #
862
+ # The env bridge above runs FIRST and unconditionally: it is the only channel
863
+ # by which session identity reaches `task.py` in a Bash child on Claude Code,
864
+ # so gating it would leave `task.py engage` unable to write the very flag
865
+ # this check reads.
866
+ #
867
+ # An engaged session still re-enters here on /clear and /compact, which is
868
+ # the point: compaction drops the workflow context a long task depends on.
869
+ if not _is_session_engaged(trellis_dir, hook_input):
870
+ sys.exit(0)
871
+
879
872
  # Load config for scope filtering and legacy detection
880
873
  is_mono, packages, scope_config, task_pkg, default_pkg = _load_trellis_config(
881
874
  trellis_dir,
@@ -892,8 +885,7 @@ Trellis compact SessionStart context. Use it to orient the session; load details
892
885
  </session-context>
893
886
 
894
887
  """)
895
- output.write(_build_first_reply_notice(_resolve_update_hint(trellis_dir, context_key)))
896
- output.write("\n\n")
888
+ output.write(_build_update_notice(_resolve_update_hint(trellis_dir, context_key)))
897
889
 
898
890
  # Legacy migration warning
899
891
  legacy_warning = _check_legacy_spec(trellis_dir, is_mono, packages)
@@ -937,29 +929,15 @@ Context loaded. Follow <task-status>. Load workflow/spec/task details only when
937
929
 
938
930
  context_text = output.getvalue()
939
931
 
940
- # Kiro (CLI trellis agent agentSpawn) adds a hook's stdout directly to the
941
- # conversation context — no JSON envelope. Emit the bare overview text.
942
- # Conditionally isolated: all other platforms keep the JSON path below.
943
- if _detect_platform(hook_input) == "kiro":
944
- print(context_text, flush=True)
945
- return
946
-
947
- platform = _detect_platform(hook_input)
948
932
  result: dict[str, object] = {
949
- # Claude Code / Qoder / CodeBuddy / Droid / Gemini / Copilot / Trae /
950
- # ZCode format.
933
+ # Claude Code / Codex format.
951
934
  "hookSpecificOutput": {
952
935
  "hookEventName": "SessionStart",
953
936
  "additionalContext": context_text,
954
937
  },
938
+ # Snake_case alias for hosts that read the top-level key instead.
939
+ "additional_context": context_text,
955
940
  }
956
- # Cursor sessionStart format (top-level snake_case per Cursor docs).
957
- # ZCode reads BOTH `hookSpecificOutput.additionalContext` and top-level
958
- # `additional_context` without deduplication, so emitting both keys would
959
- # duplicate the context in the conversation. Keep the previous shared output
960
- # shape for every other platform.
961
- if platform != "zcode":
962
- result["additional_context"] = context_text
963
941
 
964
942
  # Output JSON - stdout is already configured for UTF-8
965
943
  print(json.dumps(result, ensure_ascii=False), flush=True)