@mudssky/trellis 0.6.16-mudssky.5

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 (846) hide show
  1. package/AGENT_INSTALL.md +100 -0
  2. package/COPYRIGHT +15 -0
  3. package/INTEGRATION.md +140 -0
  4. package/LICENSE +235 -0
  5. package/README.md +125 -0
  6. package/bin/trellis.js +3 -0
  7. package/corresponding-source.tar.gz +0 -0
  8. package/dist/cli/index.d.ts +3 -0
  9. package/dist/cli/index.d.ts.map +1 -0
  10. package/dist/cli/index.js +304 -0
  11. package/dist/cli/index.js.map +1 -0
  12. package/dist/commands/ablate.d.ts +14 -0
  13. package/dist/commands/ablate.d.ts.map +1 -0
  14. package/dist/commands/ablate.js +308 -0
  15. package/dist/commands/ablate.js.map +1 -0
  16. package/dist/commands/channel/adapters/claude.d.ts +53 -0
  17. package/dist/commands/channel/adapters/claude.d.ts.map +1 -0
  18. package/dist/commands/channel/adapters/claude.js +225 -0
  19. package/dist/commands/channel/adapters/claude.js.map +1 -0
  20. package/dist/commands/channel/adapters/codex.d.ts +93 -0
  21. package/dist/commands/channel/adapters/codex.d.ts.map +1 -0
  22. package/dist/commands/channel/adapters/codex.js +573 -0
  23. package/dist/commands/channel/adapters/codex.js.map +1 -0
  24. package/dist/commands/channel/adapters/index.d.ts +94 -0
  25. package/dist/commands/channel/adapters/index.d.ts.map +1 -0
  26. package/dist/commands/channel/adapters/index.js +116 -0
  27. package/dist/commands/channel/adapters/index.js.map +1 -0
  28. package/dist/commands/channel/adapters/types.d.ts +33 -0
  29. package/dist/commands/channel/adapters/types.d.ts.map +1 -0
  30. package/dist/commands/channel/adapters/types.js +2 -0
  31. package/dist/commands/channel/adapters/types.js.map +1 -0
  32. package/dist/commands/channel/agent-loader.d.ts +32 -0
  33. package/dist/commands/channel/agent-loader.d.ts.map +1 -0
  34. package/dist/commands/channel/agent-loader.js +158 -0
  35. package/dist/commands/channel/agent-loader.js.map +1 -0
  36. package/dist/commands/channel/context-loader.d.ts +26 -0
  37. package/dist/commands/channel/context-loader.d.ts.map +1 -0
  38. package/dist/commands/channel/context-loader.js +311 -0
  39. package/dist/commands/channel/context-loader.js.map +1 -0
  40. package/dist/commands/channel/context-trust.d.ts +31 -0
  41. package/dist/commands/channel/context-trust.d.ts.map +1 -0
  42. package/dist/commands/channel/context-trust.js +139 -0
  43. package/dist/commands/channel/context-trust.js.map +1 -0
  44. package/dist/commands/channel/context.d.ts +16 -0
  45. package/dist/commands/channel/context.d.ts.map +1 -0
  46. package/dist/commands/channel/context.js +83 -0
  47. package/dist/commands/channel/context.js.map +1 -0
  48. package/dist/commands/channel/create.d.ts +27 -0
  49. package/dist/commands/channel/create.d.ts.map +1 -0
  50. package/dist/commands/channel/create.js +39 -0
  51. package/dist/commands/channel/create.js.map +1 -0
  52. package/dist/commands/channel/dev-parse-trace.d.ts +14 -0
  53. package/dist/commands/channel/dev-parse-trace.d.ts.map +1 -0
  54. package/dist/commands/channel/dev-parse-trace.js +70 -0
  55. package/dist/commands/channel/dev-parse-trace.js.map +1 -0
  56. package/dist/commands/channel/guard.d.ts +150 -0
  57. package/dist/commands/channel/guard.d.ts.map +1 -0
  58. package/dist/commands/channel/guard.js +480 -0
  59. package/dist/commands/channel/guard.js.map +1 -0
  60. package/dist/commands/channel/index.d.ts +3 -0
  61. package/dist/commands/channel/index.d.ts.map +1 -0
  62. package/dist/commands/channel/index.js +535 -0
  63. package/dist/commands/channel/index.js.map +1 -0
  64. package/dist/commands/channel/interrupt.d.ts +10 -0
  65. package/dist/commands/channel/interrupt.d.ts.map +1 -0
  66. package/dist/commands/channel/interrupt.js +22 -0
  67. package/dist/commands/channel/interrupt.js.map +1 -0
  68. package/dist/commands/channel/kill.d.ts +7 -0
  69. package/dist/commands/channel/kill.d.ts.map +1 -0
  70. package/dist/commands/channel/kill.js +130 -0
  71. package/dist/commands/channel/kill.js.map +1 -0
  72. package/dist/commands/channel/list.d.ts +17 -0
  73. package/dist/commands/channel/list.d.ts.map +1 -0
  74. package/dist/commands/channel/list.js +233 -0
  75. package/dist/commands/channel/list.js.map +1 -0
  76. package/dist/commands/channel/messages.d.ts +15 -0
  77. package/dist/commands/channel/messages.d.ts.map +1 -0
  78. package/dist/commands/channel/messages.js +245 -0
  79. package/dist/commands/channel/messages.js.map +1 -0
  80. package/dist/commands/channel/rm.d.ts +27 -0
  81. package/dist/commands/channel/rm.d.ts.map +1 -0
  82. package/dist/commands/channel/rm.js +216 -0
  83. package/dist/commands/channel/rm.js.map +1 -0
  84. package/dist/commands/channel/run.d.ts +30 -0
  85. package/dist/commands/channel/run.d.ts.map +1 -0
  86. package/dist/commands/channel/run.js +130 -0
  87. package/dist/commands/channel/run.js.map +1 -0
  88. package/dist/commands/channel/send.d.ts +11 -0
  89. package/dist/commands/channel/send.d.ts.map +1 -0
  90. package/dist/commands/channel/send.js +24 -0
  91. package/dist/commands/channel/send.js.map +1 -0
  92. package/dist/commands/channel/spawn.d.ts +43 -0
  93. package/dist/commands/channel/spawn.d.ts.map +1 -0
  94. package/dist/commands/channel/spawn.js +247 -0
  95. package/dist/commands/channel/spawn.js.map +1 -0
  96. package/dist/commands/channel/store/events.d.ts +22 -0
  97. package/dist/commands/channel/store/events.d.ts.map +1 -0
  98. package/dist/commands/channel/store/events.js +43 -0
  99. package/dist/commands/channel/store/events.js.map +1 -0
  100. package/dist/commands/channel/store/filter.d.ts +3 -0
  101. package/dist/commands/channel/store/filter.d.ts.map +1 -0
  102. package/dist/commands/channel/store/filter.js +2 -0
  103. package/dist/commands/channel/store/filter.js.map +1 -0
  104. package/dist/commands/channel/store/lock.d.ts +23 -0
  105. package/dist/commands/channel/store/lock.d.ts.map +1 -0
  106. package/dist/commands/channel/store/lock.js +99 -0
  107. package/dist/commands/channel/store/lock.js.map +1 -0
  108. package/dist/commands/channel/store/paths.d.ts +77 -0
  109. package/dist/commands/channel/store/paths.d.ts.map +1 -0
  110. package/dist/commands/channel/store/paths.js +272 -0
  111. package/dist/commands/channel/store/paths.js.map +1 -0
  112. package/dist/commands/channel/store/schema.d.ts +27 -0
  113. package/dist/commands/channel/store/schema.d.ts.map +1 -0
  114. package/dist/commands/channel/store/schema.js +34 -0
  115. package/dist/commands/channel/store/schema.js.map +1 -0
  116. package/dist/commands/channel/store/thread-state.d.ts +5 -0
  117. package/dist/commands/channel/store/thread-state.d.ts.map +1 -0
  118. package/dist/commands/channel/store/thread-state.js +16 -0
  119. package/dist/commands/channel/store/thread-state.js.map +1 -0
  120. package/dist/commands/channel/store/watch.d.ts +19 -0
  121. package/dist/commands/channel/store/watch.d.ts.map +1 -0
  122. package/dist/commands/channel/store/watch.js +153 -0
  123. package/dist/commands/channel/store/watch.js.map +1 -0
  124. package/dist/commands/channel/supervisor/idle.d.ts +45 -0
  125. package/dist/commands/channel/supervisor/idle.d.ts.map +1 -0
  126. package/dist/commands/channel/supervisor/idle.js +70 -0
  127. package/dist/commands/channel/supervisor/idle.js.map +1 -0
  128. package/dist/commands/channel/supervisor/inbox.d.ts +30 -0
  129. package/dist/commands/channel/supervisor/inbox.d.ts.map +1 -0
  130. package/dist/commands/channel/supervisor/inbox.js +160 -0
  131. package/dist/commands/channel/supervisor/inbox.js.map +1 -0
  132. package/dist/commands/channel/supervisor/shutdown.d.ts +68 -0
  133. package/dist/commands/channel/supervisor/shutdown.d.ts.map +1 -0
  134. package/dist/commands/channel/supervisor/shutdown.js +146 -0
  135. package/dist/commands/channel/supervisor/shutdown.js.map +1 -0
  136. package/dist/commands/channel/supervisor/stdout.d.ts +71 -0
  137. package/dist/commands/channel/supervisor/stdout.d.ts.map +1 -0
  138. package/dist/commands/channel/supervisor/stdout.js +229 -0
  139. package/dist/commands/channel/supervisor/stdout.js.map +1 -0
  140. package/dist/commands/channel/supervisor/turns.d.ts +31 -0
  141. package/dist/commands/channel/supervisor/turns.d.ts.map +1 -0
  142. package/dist/commands/channel/supervisor/turns.js +45 -0
  143. package/dist/commands/channel/supervisor/turns.js.map +1 -0
  144. package/dist/commands/channel/supervisor/warning.d.ts +48 -0
  145. package/dist/commands/channel/supervisor/warning.d.ts.map +1 -0
  146. package/dist/commands/channel/supervisor/warning.js +77 -0
  147. package/dist/commands/channel/supervisor/warning.js.map +1 -0
  148. package/dist/commands/channel/supervisor.d.ts +91 -0
  149. package/dist/commands/channel/supervisor.d.ts.map +1 -0
  150. package/dist/commands/channel/supervisor.js +463 -0
  151. package/dist/commands/channel/supervisor.js.map +1 -0
  152. package/dist/commands/channel/text-body.d.ts +13 -0
  153. package/dist/commands/channel/text-body.d.ts.map +1 -0
  154. package/dist/commands/channel/text-body.js +47 -0
  155. package/dist/commands/channel/text-body.js.map +1 -0
  156. package/dist/commands/channel/threads.d.ts +39 -0
  157. package/dist/commands/channel/threads.d.ts.map +1 -0
  158. package/dist/commands/channel/threads.js +107 -0
  159. package/dist/commands/channel/threads.js.map +1 -0
  160. package/dist/commands/channel/title.d.ts +12 -0
  161. package/dist/commands/channel/title.d.ts.map +1 -0
  162. package/dist/commands/channel/title.js +24 -0
  163. package/dist/commands/channel/title.js.map +1 -0
  164. package/dist/commands/channel/wait.d.ts +17 -0
  165. package/dist/commands/channel/wait.d.ts.map +1 -0
  166. package/dist/commands/channel/wait.js +75 -0
  167. package/dist/commands/channel/wait.js.map +1 -0
  168. package/dist/commands/init.d.ts +71 -0
  169. package/dist/commands/init.d.ts.map +1 -0
  170. package/dist/commands/init.js +1631 -0
  171. package/dist/commands/init.js.map +1 -0
  172. package/dist/commands/integration.d.ts +23 -0
  173. package/dist/commands/integration.d.ts.map +1 -0
  174. package/dist/commands/integration.js +373 -0
  175. package/dist/commands/integration.js.map +1 -0
  176. package/dist/commands/mem.d.ts +30 -0
  177. package/dist/commands/mem.d.ts.map +1 -0
  178. package/dist/commands/mem.js +424 -0
  179. package/dist/commands/mem.js.map +1 -0
  180. package/dist/commands/uninstall.d.ts +36 -0
  181. package/dist/commands/uninstall.d.ts.map +1 -0
  182. package/dist/commands/uninstall.js +205 -0
  183. package/dist/commands/uninstall.js.map +1 -0
  184. package/dist/commands/update.d.ts +104 -0
  185. package/dist/commands/update.d.ts.map +1 -0
  186. package/dist/commands/update.js +2184 -0
  187. package/dist/commands/update.js.map +1 -0
  188. package/dist/commands/upgrade.d.ts +28 -0
  189. package/dist/commands/upgrade.d.ts.map +1 -0
  190. package/dist/commands/upgrade.js +86 -0
  191. package/dist/commands/upgrade.js.map +1 -0
  192. package/dist/commands/workflow.d.ts +35 -0
  193. package/dist/commands/workflow.d.ts.map +1 -0
  194. package/dist/commands/workflow.js +233 -0
  195. package/dist/commands/workflow.js.map +1 -0
  196. package/dist/configurators/antigravity.d.ts +7 -0
  197. package/dist/configurators/antigravity.d.ts.map +1 -0
  198. package/dist/configurators/antigravity.js +11 -0
  199. package/dist/configurators/antigravity.js.map +1 -0
  200. package/dist/configurators/claude.d.ts +23 -0
  201. package/dist/configurators/claude.d.ts.map +1 -0
  202. package/dist/configurators/claude.js +124 -0
  203. package/dist/configurators/claude.js.map +1 -0
  204. package/dist/configurators/codebuddy.d.ts +10 -0
  205. package/dist/configurators/codebuddy.d.ts.map +1 -0
  206. package/dist/configurators/codebuddy.js +24 -0
  207. package/dist/configurators/codebuddy.js.map +1 -0
  208. package/dist/configurators/codex.d.ts +57 -0
  209. package/dist/configurators/codex.d.ts.map +1 -0
  210. package/dist/configurators/codex.js +170 -0
  211. package/dist/configurators/codex.js.map +1 -0
  212. package/dist/configurators/copilot.d.ts +14 -0
  213. package/dist/configurators/copilot.d.ts.map +1 -0
  214. package/dist/configurators/copilot.js +46 -0
  215. package/dist/configurators/copilot.js.map +1 -0
  216. package/dist/configurators/cursor.d.ts +10 -0
  217. package/dist/configurators/cursor.d.ts.map +1 -0
  218. package/dist/configurators/cursor.js +23 -0
  219. package/dist/configurators/cursor.js.map +1 -0
  220. package/dist/configurators/devin.d.ts +8 -0
  221. package/dist/configurators/devin.d.ts.map +1 -0
  222. package/dist/configurators/devin.js +12 -0
  223. package/dist/configurators/devin.js.map +1 -0
  224. package/dist/configurators/droid.d.ts +10 -0
  225. package/dist/configurators/droid.d.ts.map +1 -0
  226. package/dist/configurators/droid.js +24 -0
  227. package/dist/configurators/droid.js.map +1 -0
  228. package/dist/configurators/dsh.d.ts +29 -0
  229. package/dist/configurators/dsh.d.ts.map +1 -0
  230. package/dist/configurators/dsh.js +64 -0
  231. package/dist/configurators/dsh.js.map +1 -0
  232. package/dist/configurators/gemini.d.ts +18 -0
  233. package/dist/configurators/gemini.d.ts.map +1 -0
  234. package/dist/configurators/gemini.js +39 -0
  235. package/dist/configurators/gemini.js.map +1 -0
  236. package/dist/configurators/grok.d.ts +17 -0
  237. package/dist/configurators/grok.d.ts.map +1 -0
  238. package/dist/configurators/grok.js +38 -0
  239. package/dist/configurators/grok.js.map +1 -0
  240. package/dist/configurators/index.d.ts +68 -0
  241. package/dist/configurators/index.d.ts.map +1 -0
  242. package/dist/configurators/index.js +191 -0
  243. package/dist/configurators/index.js.map +1 -0
  244. package/dist/configurators/kilo.d.ts +7 -0
  245. package/dist/configurators/kilo.d.ts.map +1 -0
  246. package/dist/configurators/kilo.js +11 -0
  247. package/dist/configurators/kilo.js.map +1 -0
  248. package/dist/configurators/kimi.d.ts +26 -0
  249. package/dist/configurators/kimi.d.ts.map +1 -0
  250. package/dist/configurators/kimi.js +74 -0
  251. package/dist/configurators/kimi.js.map +1 -0
  252. package/dist/configurators/kiro.d.ts +11 -0
  253. package/dist/configurators/kiro.d.ts.map +1 -0
  254. package/dist/configurators/kiro.js +30 -0
  255. package/dist/configurators/kiro.js.map +1 -0
  256. package/dist/configurators/omp.d.ts +6 -0
  257. package/dist/configurators/omp.d.ts.map +1 -0
  258. package/dist/configurators/omp.js +28 -0
  259. package/dist/configurators/omp.js.map +1 -0
  260. package/dist/configurators/opencode.d.ts +5 -0
  261. package/dist/configurators/opencode.d.ts.map +1 -0
  262. package/dist/configurators/opencode.js +81 -0
  263. package/dist/configurators/opencode.js.map +1 -0
  264. package/dist/configurators/pi.d.ts +5 -0
  265. package/dist/configurators/pi.d.ts.map +1 -0
  266. package/dist/configurators/pi.js +39 -0
  267. package/dist/configurators/pi.js.map +1 -0
  268. package/dist/configurators/qoder.d.ts +12 -0
  269. package/dist/configurators/qoder.d.ts.map +1 -0
  270. package/dist/configurators/qoder.js +30 -0
  271. package/dist/configurators/qoder.js.map +1 -0
  272. package/dist/configurators/reasonix.d.ts +17 -0
  273. package/dist/configurators/reasonix.d.ts.map +1 -0
  274. package/dist/configurators/reasonix.js +37 -0
  275. package/dist/configurators/reasonix.js.map +1 -0
  276. package/dist/configurators/shared.d.ts +187 -0
  277. package/dist/configurators/shared.d.ts.map +1 -0
  278. package/dist/configurators/shared.js +589 -0
  279. package/dist/configurators/shared.js.map +1 -0
  280. package/dist/configurators/snow.d.ts +25 -0
  281. package/dist/configurators/snow.d.ts.map +1 -0
  282. package/dist/configurators/snow.js +69 -0
  283. package/dist/configurators/snow.js.map +1 -0
  284. package/dist/configurators/trae.d.ts +16 -0
  285. package/dist/configurators/trae.d.ts.map +1 -0
  286. package/dist/configurators/trae.js +34 -0
  287. package/dist/configurators/trae.js.map +1 -0
  288. package/dist/configurators/workflow.d.ts +53 -0
  289. package/dist/configurators/workflow.d.ts.map +1 -0
  290. package/dist/configurators/workflow.js +212 -0
  291. package/dist/configurators/workflow.js.map +1 -0
  292. package/dist/configurators/zcode.d.ts +25 -0
  293. package/dist/configurators/zcode.d.ts.map +1 -0
  294. package/dist/configurators/zcode.js +67 -0
  295. package/dist/configurators/zcode.js.map +1 -0
  296. package/dist/constants/paths.d.ts +74 -0
  297. package/dist/constants/paths.d.ts.map +1 -0
  298. package/dist/constants/paths.js +83 -0
  299. package/dist/constants/paths.js.map +1 -0
  300. package/dist/constants/version.d.ts +12 -0
  301. package/dist/constants/version.d.ts.map +1 -0
  302. package/dist/constants/version.js +18 -0
  303. package/dist/constants/version.js.map +1 -0
  304. package/dist/index.d.ts +9 -0
  305. package/dist/index.d.ts.map +1 -0
  306. package/dist/index.js +9 -0
  307. package/dist/index.js.map +1 -0
  308. package/dist/migrations/index.d.ts +62 -0
  309. package/dist/migrations/index.d.ts.map +1 -0
  310. package/dist/migrations/index.js +190 -0
  311. package/dist/migrations/index.js.map +1 -0
  312. package/dist/migrations/manifests/0.1.9.json +30 -0
  313. package/dist/migrations/manifests/0.2.0.json +49 -0
  314. package/dist/migrations/manifests/0.2.12.json +9 -0
  315. package/dist/migrations/manifests/0.2.13.json +9 -0
  316. package/dist/migrations/manifests/0.2.14.json +175 -0
  317. package/dist/migrations/manifests/0.2.15.json +33 -0
  318. package/dist/migrations/manifests/0.3.0-beta.0.json +297 -0
  319. package/dist/migrations/manifests/0.3.0-beta.1.json +9 -0
  320. package/dist/migrations/manifests/0.3.0-beta.10.json +9 -0
  321. package/dist/migrations/manifests/0.3.0-beta.11.json +9 -0
  322. package/dist/migrations/manifests/0.3.0-beta.12.json +9 -0
  323. package/dist/migrations/manifests/0.3.0-beta.13.json +9 -0
  324. package/dist/migrations/manifests/0.3.0-beta.14.json +9 -0
  325. package/dist/migrations/manifests/0.3.0-beta.15.json +9 -0
  326. package/dist/migrations/manifests/0.3.0-beta.16.json +9 -0
  327. package/dist/migrations/manifests/0.3.0-beta.2.json +9 -0
  328. package/dist/migrations/manifests/0.3.0-beta.3.json +9 -0
  329. package/dist/migrations/manifests/0.3.0-beta.4.json +9 -0
  330. package/dist/migrations/manifests/0.3.0-beta.5.json +9 -0
  331. package/dist/migrations/manifests/0.3.0-beta.6.json +9 -0
  332. package/dist/migrations/manifests/0.3.0-beta.7.json +11 -0
  333. package/dist/migrations/manifests/0.3.0-beta.8.json +9 -0
  334. package/dist/migrations/manifests/0.3.0-beta.9.json +9 -0
  335. package/dist/migrations/manifests/0.3.0-rc.0.json +9 -0
  336. package/dist/migrations/manifests/0.3.0-rc.1.json +9 -0
  337. package/dist/migrations/manifests/0.3.0-rc.2.json +9 -0
  338. package/dist/migrations/manifests/0.3.0-rc.3.json +9 -0
  339. package/dist/migrations/manifests/0.3.0-rc.4.json +9 -0
  340. package/dist/migrations/manifests/0.3.0-rc.5.json +9 -0
  341. package/dist/migrations/manifests/0.3.0-rc.6.json +9 -0
  342. package/dist/migrations/manifests/0.3.0.json +11 -0
  343. package/dist/migrations/manifests/0.3.1.json +9 -0
  344. package/dist/migrations/manifests/0.3.10.json +9 -0
  345. package/dist/migrations/manifests/0.3.2.json +9 -0
  346. package/dist/migrations/manifests/0.3.3.json +9 -0
  347. package/dist/migrations/manifests/0.3.4.json +21 -0
  348. package/dist/migrations/manifests/0.3.5.json +9 -0
  349. package/dist/migrations/manifests/0.3.6.json +9 -0
  350. package/dist/migrations/manifests/0.3.7.json +9 -0
  351. package/dist/migrations/manifests/0.3.8.json +9 -0
  352. package/dist/migrations/manifests/0.3.9.json +9 -0
  353. package/dist/migrations/manifests/0.4.0-beta.1.json +228 -0
  354. package/dist/migrations/manifests/0.4.0-beta.10.json +9 -0
  355. package/dist/migrations/manifests/0.4.0-beta.2.json +9 -0
  356. package/dist/migrations/manifests/0.4.0-beta.3.json +9 -0
  357. package/dist/migrations/manifests/0.4.0-beta.4.json +9 -0
  358. package/dist/migrations/manifests/0.4.0-beta.5.json +9 -0
  359. package/dist/migrations/manifests/0.4.0-beta.6.json +9 -0
  360. package/dist/migrations/manifests/0.4.0-beta.7.json +9 -0
  361. package/dist/migrations/manifests/0.4.0-beta.8.json +34 -0
  362. package/dist/migrations/manifests/0.4.0-beta.9.json +9 -0
  363. package/dist/migrations/manifests/0.4.0-rc.0.json +9 -0
  364. package/dist/migrations/manifests/0.4.0-rc.1.json +9 -0
  365. package/dist/migrations/manifests/0.4.0.json +9 -0
  366. package/dist/migrations/manifests/0.5.0-beta.0.json +1646 -0
  367. package/dist/migrations/manifests/0.5.0-beta.1.json +9 -0
  368. package/dist/migrations/manifests/0.5.0-beta.10.json +9 -0
  369. package/dist/migrations/manifests/0.5.0-beta.11.json +9 -0
  370. package/dist/migrations/manifests/0.5.0-beta.12.json +9 -0
  371. package/dist/migrations/manifests/0.5.0-beta.13.json +9 -0
  372. package/dist/migrations/manifests/0.5.0-beta.14.json +9 -0
  373. package/dist/migrations/manifests/0.5.0-beta.15.json +116 -0
  374. package/dist/migrations/manifests/0.5.0-beta.16.json +9 -0
  375. package/dist/migrations/manifests/0.5.0-beta.17.json +9 -0
  376. package/dist/migrations/manifests/0.5.0-beta.18.json +9 -0
  377. package/dist/migrations/manifests/0.5.0-beta.19.json +9 -0
  378. package/dist/migrations/manifests/0.5.0-beta.2.json +9 -0
  379. package/dist/migrations/manifests/0.5.0-beta.3.json +9 -0
  380. package/dist/migrations/manifests/0.5.0-beta.4.json +9 -0
  381. package/dist/migrations/manifests/0.5.0-beta.5.json +222 -0
  382. package/dist/migrations/manifests/0.5.0-beta.6.json +9 -0
  383. package/dist/migrations/manifests/0.5.0-beta.7.json +9 -0
  384. package/dist/migrations/manifests/0.5.0-beta.8.json +9 -0
  385. package/dist/migrations/manifests/0.5.0-beta.9.json +48 -0
  386. package/dist/migrations/manifests/0.5.0-rc.0.json +9 -0
  387. package/dist/migrations/manifests/0.5.0-rc.1.json +9 -0
  388. package/dist/migrations/manifests/0.5.0-rc.2.json +9 -0
  389. package/dist/migrations/manifests/0.5.0-rc.3.json +9 -0
  390. package/dist/migrations/manifests/0.5.0-rc.4.json +9 -0
  391. package/dist/migrations/manifests/0.5.0-rc.5.json +9 -0
  392. package/dist/migrations/manifests/0.5.0-rc.6.json +9 -0
  393. package/dist/migrations/manifests/0.5.0-rc.7.json +9 -0
  394. package/dist/migrations/manifests/0.5.0.json +9 -0
  395. package/dist/migrations/manifests/0.5.1.json +9 -0
  396. package/dist/migrations/manifests/0.5.10.json +9 -0
  397. package/dist/migrations/manifests/0.5.11.json +16 -0
  398. package/dist/migrations/manifests/0.5.12.json +9 -0
  399. package/dist/migrations/manifests/0.5.13.json +9 -0
  400. package/dist/migrations/manifests/0.5.14.json +9 -0
  401. package/dist/migrations/manifests/0.5.15.json +9 -0
  402. package/dist/migrations/manifests/0.5.16.json +9 -0
  403. package/dist/migrations/manifests/0.5.17.json +9 -0
  404. package/dist/migrations/manifests/0.5.18.json +9 -0
  405. package/dist/migrations/manifests/0.5.19.json +9 -0
  406. package/dist/migrations/manifests/0.5.2.json +9 -0
  407. package/dist/migrations/manifests/0.5.3.json +9 -0
  408. package/dist/migrations/manifests/0.5.4.json +9 -0
  409. package/dist/migrations/manifests/0.5.5.json +9 -0
  410. package/dist/migrations/manifests/0.5.6.json +9 -0
  411. package/dist/migrations/manifests/0.5.7.json +16 -0
  412. package/dist/migrations/manifests/0.5.8.json +9 -0
  413. package/dist/migrations/manifests/0.5.9.json +9 -0
  414. package/dist/migrations/manifests/0.6.0-beta.0.json +16 -0
  415. package/dist/migrations/manifests/0.6.0-beta.1.json +9 -0
  416. package/dist/migrations/manifests/0.6.0-beta.10.json +9 -0
  417. package/dist/migrations/manifests/0.6.0-beta.11.json +9 -0
  418. package/dist/migrations/manifests/0.6.0-beta.12.json +9 -0
  419. package/dist/migrations/manifests/0.6.0-beta.13.json +9 -0
  420. package/dist/migrations/manifests/0.6.0-beta.14.json +9 -0
  421. package/dist/migrations/manifests/0.6.0-beta.15.json +9 -0
  422. package/dist/migrations/manifests/0.6.0-beta.16.json +9 -0
  423. package/dist/migrations/manifests/0.6.0-beta.17.json +9 -0
  424. package/dist/migrations/manifests/0.6.0-beta.18.json +16 -0
  425. package/dist/migrations/manifests/0.6.0-beta.19.json +9 -0
  426. package/dist/migrations/manifests/0.6.0-beta.2.json +9 -0
  427. package/dist/migrations/manifests/0.6.0-beta.20.json +9 -0
  428. package/dist/migrations/manifests/0.6.0-beta.21.json +9 -0
  429. package/dist/migrations/manifests/0.6.0-beta.22.json +9 -0
  430. package/dist/migrations/manifests/0.6.0-beta.23.json +88 -0
  431. package/dist/migrations/manifests/0.6.0-beta.3.json +9 -0
  432. package/dist/migrations/manifests/0.6.0-beta.4.json +9 -0
  433. package/dist/migrations/manifests/0.6.0-beta.5.json +9 -0
  434. package/dist/migrations/manifests/0.6.0-beta.6.json +16 -0
  435. package/dist/migrations/manifests/0.6.0-beta.7.json +9 -0
  436. package/dist/migrations/manifests/0.6.0-beta.8.json +9 -0
  437. package/dist/migrations/manifests/0.6.0-beta.9.json +9 -0
  438. package/dist/migrations/manifests/0.6.0-rc.0.json +9 -0
  439. package/dist/migrations/manifests/0.6.0.json +1 -0
  440. package/dist/migrations/manifests/0.6.1.json +9 -0
  441. package/dist/migrations/manifests/0.6.10.json +9 -0
  442. package/dist/migrations/manifests/0.6.11.json +9 -0
  443. package/dist/migrations/manifests/0.6.12.json +9 -0
  444. package/dist/migrations/manifests/0.6.13.json +9 -0
  445. package/dist/migrations/manifests/0.6.14.json +9 -0
  446. package/dist/migrations/manifests/0.6.15.json +9 -0
  447. package/dist/migrations/manifests/0.6.16.json +9 -0
  448. package/dist/migrations/manifests/0.6.2.json +9 -0
  449. package/dist/migrations/manifests/0.6.3.json +24 -0
  450. package/dist/migrations/manifests/0.6.4.json +9 -0
  451. package/dist/migrations/manifests/0.6.5.json +9 -0
  452. package/dist/migrations/manifests/0.6.6.json +17 -0
  453. package/dist/migrations/manifests/0.6.7.json +9 -0
  454. package/dist/migrations/manifests/0.6.8.json +17 -0
  455. package/dist/migrations/manifests/0.6.9.json +9 -0
  456. package/dist/migrations/manifests/0.7.0-beta.0.json +9 -0
  457. package/dist/migrations/manifests/0.7.0-beta.1.json +9 -0
  458. package/dist/migrations/manifests/0.7.0-beta.2.json +9 -0
  459. package/dist/migrations/manifests/0.7.0-beta.3.json +9 -0
  460. package/dist/templates/claude/agents/trellis-check.md +115 -0
  461. package/dist/templates/claude/agents/trellis-implement.md +110 -0
  462. package/dist/templates/claude/agents/trellis-research.md +137 -0
  463. package/dist/templates/claude/hooks/statusline.py +332 -0
  464. package/dist/templates/claude/index.d.ts +31 -0
  465. package/dist/templates/claude/index.d.ts.map +1 -0
  466. package/dist/templates/claude/index.js +58 -0
  467. package/dist/templates/claude/index.js.map +1 -0
  468. package/dist/templates/claude/settings.json +73 -0
  469. package/dist/templates/codebuddy/agents/trellis-check.md +115 -0
  470. package/dist/templates/codebuddy/agents/trellis-implement.md +110 -0
  471. package/dist/templates/codebuddy/agents/trellis-research.md +137 -0
  472. package/dist/templates/codebuddy/index.d.ts +15 -0
  473. package/dist/templates/codebuddy/index.d.ts.map +1 -0
  474. package/dist/templates/codebuddy/index.js +15 -0
  475. package/dist/templates/codebuddy/index.js.map +1 -0
  476. package/dist/templates/codebuddy/settings.json +69 -0
  477. package/dist/templates/codex/agents/trellis-check.toml +56 -0
  478. package/dist/templates/codex/agents/trellis-implement.toml +37 -0
  479. package/dist/templates/codex/agents/trellis-research.toml +78 -0
  480. package/dist/templates/codex/config.toml +39 -0
  481. package/dist/templates/codex/hooks/session-start.py +552 -0
  482. package/dist/templates/codex/hooks.json +27 -0
  483. package/dist/templates/codex/index.d.ts +35 -0
  484. package/dist/templates/codex/index.d.ts.map +1 -0
  485. package/dist/templates/codex/index.js +68 -0
  486. package/dist/templates/codex/index.js.map +1 -0
  487. package/dist/templates/command-renderer.d.ts +2 -0
  488. package/dist/templates/command-renderer.d.ts.map +1 -0
  489. package/dist/templates/command-renderer.js +13 -0
  490. package/dist/templates/command-renderer.js.map +1 -0
  491. package/dist/templates/common/bundled-skills/trellis-channel/SKILL.md +67 -0
  492. package/dist/templates/common/bundled-skills/trellis-channel/references/command-reference.md +480 -0
  493. package/dist/templates/common/bundled-skills/trellis-channel/references/forum.md +233 -0
  494. package/dist/templates/common/bundled-skills/trellis-channel/references/progress-debugging.md +226 -0
  495. package/dist/templates/common/bundled-skills/trellis-channel/references/workers.md +276 -0
  496. package/dist/templates/common/bundled-skills/trellis-channel/references/workflows.md +128 -0
  497. package/dist/templates/common/bundled-skills/trellis-meta/SKILL.md +85 -0
  498. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/add-project-local-conventions.md +83 -0
  499. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-agents.md +56 -0
  500. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-context-loading.md +84 -0
  501. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-hooks.md +57 -0
  502. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-skills-or-commands.md +123 -0
  503. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-spec-structure.md +83 -0
  504. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-task-lifecycle.md +90 -0
  505. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-workflow.md +65 -0
  506. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/overview.md +55 -0
  507. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/bundled-skills.md +159 -0
  508. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/context-injection.md +68 -0
  509. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/generated-files.md +80 -0
  510. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/multi-agent-channel.md +69 -0
  511. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/overview.md +51 -0
  512. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/spec-system.md +102 -0
  513. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/task-system.md +130 -0
  514. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/workflow.md +75 -0
  515. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/workspace-memory.md +71 -0
  516. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/agents.md +83 -0
  517. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/hooks-and-settings.md +72 -0
  518. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/overview.md +59 -0
  519. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/platform-map.md +112 -0
  520. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/skills-and-commands.md +86 -0
  521. package/dist/templates/common/bundled-skills/trellis-session-insight/SKILL.md +81 -0
  522. package/dist/templates/common/bundled-skills/trellis-session-insight/references/cli-quick-reference.md +65 -0
  523. package/dist/templates/common/bundled-skills/trellis-session-insight/references/triggering-patterns.md +93 -0
  524. package/dist/templates/common/bundled-skills/trellis-spec-bootstrap/SKILL.md +41 -0
  525. package/dist/templates/common/bundled-skills/trellis-spec-bootstrap/references/mcp-setup.md +90 -0
  526. package/dist/templates/common/bundled-skills/trellis-spec-bootstrap/references/repository-analysis.md +59 -0
  527. package/dist/templates/common/bundled-skills/trellis-spec-bootstrap/references/spec-task-planning.md +61 -0
  528. package/dist/templates/common/bundled-skills/trellis-spec-bootstrap/references/spec-writing.md +70 -0
  529. package/dist/templates/common/commands/continue.md +56 -0
  530. package/dist/templates/common/commands/finish-work.md +66 -0
  531. package/dist/templates/common/commands/start.md +59 -0
  532. package/dist/templates/common/index.d.ts +48 -0
  533. package/dist/templates/common/index.d.ts.map +1 -0
  534. package/dist/templates/common/index.js +105 -0
  535. package/dist/templates/common/index.js.map +1 -0
  536. package/dist/templates/common/skills/before-dev.md +46 -0
  537. package/dist/templates/common/skills/brainstorm.md +195 -0
  538. package/dist/templates/common/skills/break-loop.md +184 -0
  539. package/dist/templates/common/skills/check.md +106 -0
  540. package/dist/templates/common/skills/update-spec.md +351 -0
  541. package/dist/templates/copilot/copilot-instructions.md +11 -0
  542. package/dist/templates/copilot/hooks/session-start.py +548 -0
  543. package/dist/templates/copilot/hooks.json +19 -0
  544. package/dist/templates/copilot/index.d.ts +28 -0
  545. package/dist/templates/copilot/index.d.ts.map +1 -0
  546. package/dist/templates/copilot/index.js +62 -0
  547. package/dist/templates/copilot/index.js.map +1 -0
  548. package/dist/templates/copilot/prompts/before-dev.prompt.md +39 -0
  549. package/dist/templates/copilot/prompts/brainstorm.prompt.md +156 -0
  550. package/dist/templates/copilot/prompts/break-loop.prompt.md +129 -0
  551. package/dist/templates/copilot/prompts/check-cross-layer.prompt.md +170 -0
  552. package/dist/templates/copilot/prompts/check.prompt.md +110 -0
  553. package/dist/templates/copilot/prompts/create-command.prompt.md +116 -0
  554. package/dist/templates/copilot/prompts/finish-work.prompt.md +99 -0
  555. package/dist/templates/copilot/prompts/integrate-skill.prompt.md +223 -0
  556. package/dist/templates/copilot/prompts/onboard.prompt.md +362 -0
  557. package/dist/templates/copilot/prompts/parallel.prompt.md +204 -0
  558. package/dist/templates/copilot/prompts/record-session.prompt.md +66 -0
  559. package/dist/templates/copilot/prompts/start.prompt.md +63 -0
  560. package/dist/templates/copilot/prompts/update-spec.prompt.md +358 -0
  561. package/dist/templates/cursor/agents/trellis-check.md +114 -0
  562. package/dist/templates/cursor/agents/trellis-implement.md +109 -0
  563. package/dist/templates/cursor/agents/trellis-research.md +136 -0
  564. package/dist/templates/cursor/hooks.json +24 -0
  565. package/dist/templates/cursor/index.d.ts +13 -0
  566. package/dist/templates/cursor/index.d.ts.map +1 -0
  567. package/dist/templates/cursor/index.js +13 -0
  568. package/dist/templates/cursor/index.js.map +1 -0
  569. package/dist/templates/droid/droids/trellis-check.md +107 -0
  570. package/dist/templates/droid/droids/trellis-implement.md +102 -0
  571. package/dist/templates/droid/droids/trellis-research.md +137 -0
  572. package/dist/templates/droid/index.d.ts +15 -0
  573. package/dist/templates/droid/index.d.ts.map +1 -0
  574. package/dist/templates/droid/index.js +15 -0
  575. package/dist/templates/droid/index.js.map +1 -0
  576. package/dist/templates/droid/settings.json +69 -0
  577. package/dist/templates/dsh/DSH.md +56 -0
  578. package/dist/templates/dsh/index.d.ts +21 -0
  579. package/dist/templates/dsh/index.d.ts.map +1 -0
  580. package/dist/templates/dsh/index.js +25 -0
  581. package/dist/templates/dsh/index.js.map +1 -0
  582. package/dist/templates/extract.d.ts +40 -0
  583. package/dist/templates/extract.d.ts.map +1 -0
  584. package/dist/templates/extract.js +113 -0
  585. package/dist/templates/extract.js.map +1 -0
  586. package/dist/templates/gemini/agents/trellis-check.md +107 -0
  587. package/dist/templates/gemini/agents/trellis-implement.md +102 -0
  588. package/dist/templates/gemini/agents/trellis-research.md +136 -0
  589. package/dist/templates/gemini/index.d.ts +13 -0
  590. package/dist/templates/gemini/index.d.ts.map +1 -0
  591. package/dist/templates/gemini/index.js +13 -0
  592. package/dist/templates/gemini/index.js.map +1 -0
  593. package/dist/templates/gemini/settings.json +40 -0
  594. package/dist/templates/grok/agents/trellis-check.md +105 -0
  595. package/dist/templates/grok/agents/trellis-implement.md +116 -0
  596. package/dist/templates/grok/agents/trellis-research.md +146 -0
  597. package/dist/templates/grok/index.d.ts +16 -0
  598. package/dist/templates/grok/index.d.ts.map +1 -0
  599. package/dist/templates/grok/index.js +19 -0
  600. package/dist/templates/grok/index.js.map +1 -0
  601. package/dist/templates/kimi/agents/trellis-check.md +103 -0
  602. package/dist/templates/kimi/agents/trellis-implement.md +115 -0
  603. package/dist/templates/kimi/agents/trellis-research.md +144 -0
  604. package/dist/templates/kimi/index.d.ts +20 -0
  605. package/dist/templates/kimi/index.d.ts.map +1 -0
  606. package/dist/templates/kimi/index.js +23 -0
  607. package/dist/templates/kimi/index.js.map +1 -0
  608. package/dist/templates/kiro/agents/trellis-check.json +26 -0
  609. package/dist/templates/kiro/agents/trellis-implement.json +26 -0
  610. package/dist/templates/kiro/agents/trellis-research.json +30 -0
  611. package/dist/templates/kiro/agents/trellis.json +34 -0
  612. package/dist/templates/kiro/hooks/trellis-workflow-state.kiro.hook +14 -0
  613. package/dist/templates/kiro/index.d.ts +32 -0
  614. package/dist/templates/kiro/index.d.ts.map +1 -0
  615. package/dist/templates/kiro/index.js +28 -0
  616. package/dist/templates/kiro/index.js.map +1 -0
  617. package/dist/templates/markdown/agents.md +21 -0
  618. package/dist/templates/markdown/gitignore.txt +15 -0
  619. package/dist/templates/markdown/index.d.ts +27 -0
  620. package/dist/templates/markdown/index.d.ts.map +1 -0
  621. package/dist/templates/markdown/index.js +53 -0
  622. package/dist/templates/markdown/index.js.map +1 -0
  623. package/dist/templates/markdown/spec/backend/database-guidelines.md.txt +51 -0
  624. package/dist/templates/markdown/spec/backend/directory-structure.md.txt +54 -0
  625. package/dist/templates/markdown/spec/backend/error-handling.md.txt +51 -0
  626. package/dist/templates/markdown/spec/backend/index.md.txt +38 -0
  627. package/dist/templates/markdown/spec/backend/logging-guidelines.md.txt +51 -0
  628. package/dist/templates/markdown/spec/backend/quality-guidelines.md.txt +51 -0
  629. package/dist/templates/markdown/spec/frontend/component-guidelines.md.txt +59 -0
  630. package/dist/templates/markdown/spec/frontend/directory-structure.md.txt +54 -0
  631. package/dist/templates/markdown/spec/frontend/hook-guidelines.md.txt +51 -0
  632. package/dist/templates/markdown/spec/frontend/index.md.txt +39 -0
  633. package/dist/templates/markdown/spec/frontend/quality-guidelines.md.txt +51 -0
  634. package/dist/templates/markdown/spec/frontend/state-management.md.txt +51 -0
  635. package/dist/templates/markdown/spec/frontend/type-safety.md.txt +51 -0
  636. package/dist/templates/markdown/spec/guides/code-reuse-thinking-guide.md.txt +223 -0
  637. package/dist/templates/markdown/spec/guides/cross-layer-thinking-guide.md.txt +327 -0
  638. package/dist/templates/markdown/spec/guides/cross-platform-thinking-guide.md.txt +633 -0
  639. package/dist/templates/markdown/spec/guides/index.md.txt +97 -0
  640. package/dist/templates/markdown/workspace-index.md +125 -0
  641. package/dist/templates/markdown/worktree.yaml.txt +58 -0
  642. package/dist/templates/omp/agents/trellis-check.md +41 -0
  643. package/dist/templates/omp/agents/trellis-implement.md +45 -0
  644. package/dist/templates/omp/agents/trellis-research.md +30 -0
  645. package/dist/templates/omp/extensions/trellis/index.ts.txt +1783 -0
  646. package/dist/templates/omp/index.d.ts +4 -0
  647. package/dist/templates/omp/index.d.ts.map +1 -0
  648. package/dist/templates/omp/index.js +9 -0
  649. package/dist/templates/omp/index.js.map +1 -0
  650. package/dist/templates/opencode/agents/trellis-check.md +122 -0
  651. package/dist/templates/opencode/agents/trellis-implement.md +118 -0
  652. package/dist/templates/opencode/agents/trellis-research.md +145 -0
  653. package/dist/templates/opencode/lib/context-visibility.js +148 -0
  654. package/dist/templates/opencode/lib/session-utils.js +527 -0
  655. package/dist/templates/opencode/lib/trellis-context.js +627 -0
  656. package/dist/templates/opencode/package.json +5 -0
  657. package/dist/templates/opencode/plugins/inject-subagent-context.js +569 -0
  658. package/dist/templates/opencode/plugins/inject-workflow-state.js +240 -0
  659. package/dist/templates/opencode/plugins/session-start.js +65 -0
  660. package/dist/templates/pi/agents/trellis-check.md +37 -0
  661. package/dist/templates/pi/agents/trellis-implement.md +42 -0
  662. package/dist/templates/pi/agents/trellis-research.md +25 -0
  663. package/dist/templates/pi/extensions/trellis/index.ts.txt +2149 -0
  664. package/dist/templates/pi/index.d.ts +5 -0
  665. package/dist/templates/pi/index.d.ts.map +1 -0
  666. package/dist/templates/pi/index.js +12 -0
  667. package/dist/templates/pi/index.js.map +1 -0
  668. package/dist/templates/pi/settings.json +9 -0
  669. package/dist/templates/qoder/agents/trellis-check.md +108 -0
  670. package/dist/templates/qoder/agents/trellis-implement.md +103 -0
  671. package/dist/templates/qoder/agents/trellis-research.md +137 -0
  672. package/dist/templates/qoder/index.d.ts +15 -0
  673. package/dist/templates/qoder/index.d.ts.map +1 -0
  674. package/dist/templates/qoder/index.js +15 -0
  675. package/dist/templates/qoder/index.js.map +1 -0
  676. package/dist/templates/qoder/settings.json +59 -0
  677. package/dist/templates/reasonix/agents/trellis-check.md +36 -0
  678. package/dist/templates/reasonix/agents/trellis-implement.md +41 -0
  679. package/dist/templates/reasonix/index.d.ts +13 -0
  680. package/dist/templates/reasonix/index.d.ts.map +1 -0
  681. package/dist/templates/reasonix/index.js +16 -0
  682. package/dist/templates/reasonix/index.js.map +1 -0
  683. package/dist/templates/shared-hooks/index.d.ts +77 -0
  684. package/dist/templates/shared-hooks/index.d.ts.map +1 -0
  685. package/dist/templates/shared-hooks/index.js +141 -0
  686. package/dist/templates/shared-hooks/index.js.map +1 -0
  687. package/dist/templates/shared-hooks/inject-shell-session-context.py +292 -0
  688. package/dist/templates/shared-hooks/inject-subagent-context.py +1212 -0
  689. package/dist/templates/shared-hooks/inject-workflow-state.py +488 -0
  690. package/dist/templates/shared-hooks/session-start.py +949 -0
  691. package/dist/templates/snow/SNOW.md +86 -0
  692. package/dist/templates/snow/agents/trellis-check.md +135 -0
  693. package/dist/templates/snow/agents/trellis-implement.md +153 -0
  694. package/dist/templates/snow/agents/trellis-research.md +114 -0
  695. package/dist/templates/snow/hooks/beforeSubAgentStart.json +15 -0
  696. package/dist/templates/snow/hooks/onSessionStart.json +15 -0
  697. package/dist/templates/snow/hooks/onUserMessage.json +15 -0
  698. package/dist/templates/snow/hooks/write-trellis-context.py +644 -0
  699. package/dist/templates/snow/index.d.ts +26 -0
  700. package/dist/templates/snow/index.d.ts.map +1 -0
  701. package/dist/templates/snow/index.js +36 -0
  702. package/dist/templates/snow/index.js.map +1 -0
  703. package/dist/templates/template-utils.d.ts +26 -0
  704. package/dist/templates/template-utils.d.ts.map +1 -0
  705. package/dist/templates/template-utils.js +65 -0
  706. package/dist/templates/template-utils.js.map +1 -0
  707. package/dist/templates/trae/agents/trellis-check.md +108 -0
  708. package/dist/templates/trae/agents/trellis-implement.md +103 -0
  709. package/dist/templates/trae/agents/trellis-research.md +137 -0
  710. package/dist/templates/trae/hooks.json +39 -0
  711. package/dist/templates/trae/index.d.ts +15 -0
  712. package/dist/templates/trae/index.d.ts.map +1 -0
  713. package/dist/templates/trae/index.js +15 -0
  714. package/dist/templates/trae/index.js.map +1 -0
  715. package/dist/templates/trellis/agents/check.md +70 -0
  716. package/dist/templates/trellis/agents/implement.md +71 -0
  717. package/dist/templates/trellis/config.yaml +177 -0
  718. package/dist/templates/trellis/gitattributes.txt +9 -0
  719. package/dist/templates/trellis/gitignore.txt +32 -0
  720. package/dist/templates/trellis/index.d.ts +67 -0
  721. package/dist/templates/trellis/index.d.ts.map +1 -0
  722. package/dist/templates/trellis/index.js +134 -0
  723. package/dist/templates/trellis/index.js.map +1 -0
  724. package/dist/templates/trellis/scripts/__init__.py +5 -0
  725. package/dist/templates/trellis/scripts/add_session.py +1545 -0
  726. package/dist/templates/trellis/scripts/common/__init__.py +94 -0
  727. package/dist/templates/trellis/scripts/common/active_task.py +857 -0
  728. package/dist/templates/trellis/scripts/common/cli_adapter.py +950 -0
  729. package/dist/templates/trellis/scripts/common/config.py +482 -0
  730. package/dist/templates/trellis/scripts/common/developer.py +192 -0
  731. package/dist/templates/trellis/scripts/common/git.py +203 -0
  732. package/dist/templates/trellis/scripts/common/git_context.py +106 -0
  733. package/dist/templates/trellis/scripts/common/io.py +160 -0
  734. package/dist/templates/trellis/scripts/common/log.py +45 -0
  735. package/dist/templates/trellis/scripts/common/packages_context.py +238 -0
  736. package/dist/templates/trellis/scripts/common/paths.py +543 -0
  737. package/dist/templates/trellis/scripts/common/safe_commit.py +322 -0
  738. package/dist/templates/trellis/scripts/common/session_context.py +892 -0
  739. package/dist/templates/trellis/scripts/common/task_context.py +470 -0
  740. package/dist/templates/trellis/scripts/common/task_queue.py +188 -0
  741. package/dist/templates/trellis/scripts/common/task_store.py +1883 -0
  742. package/dist/templates/trellis/scripts/common/task_utils.py +498 -0
  743. package/dist/templates/trellis/scripts/common/tasks.py +122 -0
  744. package/dist/templates/trellis/scripts/common/trellis_config.py +257 -0
  745. package/dist/templates/trellis/scripts/common/types.py +110 -0
  746. package/dist/templates/trellis/scripts/common/workflow_phase.py +242 -0
  747. package/dist/templates/trellis/scripts/get_context.py +16 -0
  748. package/dist/templates/trellis/scripts/get_developer.py +26 -0
  749. package/dist/templates/trellis/scripts/hooks/linear_sync.py +243 -0
  750. package/dist/templates/trellis/scripts/init_developer.py +51 -0
  751. package/dist/templates/trellis/scripts/task.py +811 -0
  752. package/dist/templates/trellis/tasks/.gitkeep +0 -0
  753. package/dist/templates/trellis/workflow.md +721 -0
  754. package/dist/templates/zcode/agents/trellis-check.md +109 -0
  755. package/dist/templates/zcode/agents/trellis-implement.md +111 -0
  756. package/dist/templates/zcode/agents/trellis-research.md +133 -0
  757. package/dist/templates/zcode/config.json +52 -0
  758. package/dist/templates/zcode/index.d.ts +25 -0
  759. package/dist/templates/zcode/index.d.ts.map +1 -0
  760. package/dist/templates/zcode/index.js +29 -0
  761. package/dist/templates/zcode/index.js.map +1 -0
  762. package/dist/types/ai-tools.d.ts +105 -0
  763. package/dist/types/ai-tools.d.ts.map +1 -0
  764. package/dist/types/ai-tools.js +502 -0
  765. package/dist/types/ai-tools.js.map +1 -0
  766. package/dist/types/migration.d.ts +125 -0
  767. package/dist/types/migration.d.ts.map +1 -0
  768. package/dist/types/migration.js +8 -0
  769. package/dist/types/migration.js.map +1 -0
  770. package/dist/utils/ablation-store.d.ts +123 -0
  771. package/dist/utils/ablation-store.d.ts.map +1 -0
  772. package/dist/utils/ablation-store.js +749 -0
  773. package/dist/utils/ablation-store.js.map +1 -0
  774. package/dist/utils/agent-refs.d.ts +31 -0
  775. package/dist/utils/agent-refs.d.ts.map +1 -0
  776. package/dist/utils/agent-refs.js +63 -0
  777. package/dist/utils/agent-refs.js.map +1 -0
  778. package/dist/utils/atomic-write.d.ts +12 -0
  779. package/dist/utils/atomic-write.d.ts.map +1 -0
  780. package/dist/utils/atomic-write.js +30 -0
  781. package/dist/utils/atomic-write.js.map +1 -0
  782. package/dist/utils/compare-versions.d.ts +12 -0
  783. package/dist/utils/compare-versions.d.ts.map +1 -0
  784. package/dist/utils/compare-versions.js +86 -0
  785. package/dist/utils/compare-versions.js.map +1 -0
  786. package/dist/utils/cwd-guard.d.ts +38 -0
  787. package/dist/utils/cwd-guard.d.ts.map +1 -0
  788. package/dist/utils/cwd-guard.js +63 -0
  789. package/dist/utils/cwd-guard.js.map +1 -0
  790. package/dist/utils/file-writer.d.ts +36 -0
  791. package/dist/utils/file-writer.d.ts.map +1 -0
  792. package/dist/utils/file-writer.js +204 -0
  793. package/dist/utils/file-writer.js.map +1 -0
  794. package/dist/utils/integration.d.ts +96 -0
  795. package/dist/utils/integration.d.ts.map +1 -0
  796. package/dist/utils/integration.js +1549 -0
  797. package/dist/utils/integration.js.map +1 -0
  798. package/dist/utils/managed-paths.d.ts +5 -0
  799. package/dist/utils/managed-paths.d.ts.map +1 -0
  800. package/dist/utils/managed-paths.js +51 -0
  801. package/dist/utils/managed-paths.js.map +1 -0
  802. package/dist/utils/managed-removal.d.ts +74 -0
  803. package/dist/utils/managed-removal.d.ts.map +1 -0
  804. package/dist/utils/managed-removal.js +270 -0
  805. package/dist/utils/managed-removal.js.map +1 -0
  806. package/dist/utils/manifest-prune.d.ts +61 -0
  807. package/dist/utils/manifest-prune.d.ts.map +1 -0
  808. package/dist/utils/manifest-prune.js +135 -0
  809. package/dist/utils/manifest-prune.js.map +1 -0
  810. package/dist/utils/posix.d.ts +13 -0
  811. package/dist/utils/posix.d.ts.map +1 -0
  812. package/dist/utils/posix.js +15 -0
  813. package/dist/utils/posix.js.map +1 -0
  814. package/dist/utils/project-detector.d.ts +46 -0
  815. package/dist/utils/project-detector.d.ts.map +1 -0
  816. package/dist/utils/project-detector.js +666 -0
  817. package/dist/utils/project-detector.js.map +1 -0
  818. package/dist/utils/proxy.d.ts +25 -0
  819. package/dist/utils/proxy.d.ts.map +1 -0
  820. package/dist/utils/proxy.js +60 -0
  821. package/dist/utils/proxy.js.map +1 -0
  822. package/dist/utils/registry-config.d.ts +7 -0
  823. package/dist/utils/registry-config.d.ts.map +1 -0
  824. package/dist/utils/registry-config.js +173 -0
  825. package/dist/utils/registry-config.js.map +1 -0
  826. package/dist/utils/task-json.d.ts +13 -0
  827. package/dist/utils/task-json.d.ts.map +1 -0
  828. package/dist/utils/task-json.js +12 -0
  829. package/dist/utils/task-json.js.map +1 -0
  830. package/dist/utils/template-fetcher.d.ts +161 -0
  831. package/dist/utils/template-fetcher.d.ts.map +1 -0
  832. package/dist/utils/template-fetcher.js +1020 -0
  833. package/dist/utils/template-fetcher.js.map +1 -0
  834. package/dist/utils/template-hash.d.ts +127 -0
  835. package/dist/utils/template-hash.d.ts.map +1 -0
  836. package/dist/utils/template-hash.js +335 -0
  837. package/dist/utils/template-hash.js.map +1 -0
  838. package/dist/utils/uninstall-scrubbers.d.ts +66 -0
  839. package/dist/utils/uninstall-scrubbers.d.ts.map +1 -0
  840. package/dist/utils/uninstall-scrubbers.js +417 -0
  841. package/dist/utils/uninstall-scrubbers.js.map +1 -0
  842. package/dist/utils/workflow-resolver.d.ts +86 -0
  843. package/dist/utils/workflow-resolver.d.ts.map +1 -0
  844. package/dist/utils/workflow-resolver.js +266 -0
  845. package/dist/utils/workflow-resolver.js.map +1 -0
  846. package/package.json +58 -0
@@ -0,0 +1,2184 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import chalk from "chalk";
5
+ import inquirer from "inquirer";
6
+ import { DIR_NAMES, FILE_NAMES, PATHS } from "../constants/paths.js";
7
+ import { COMMAND_NAME, CLI_VERSION, VERSION, PACKAGE_NAME, UPGRADE_CHANNEL, } from "../constants/version.js";
8
+ import { getMigrationsForVersion, getAllMigrations, getMigrationMetadata, getConfigSectionsAddedBetween, } from "../migrations/index.js";
9
+ import { loadHashes, saveHashes, updateHashes, isTemplateModified, removeHash, renameHash, computeHash, shouldExcludeFromHash, } from "../utils/template-hash.js";
10
+ import { compareVersions } from "../utils/compare-versions.js";
11
+ import { toPosix } from "../utils/posix.js";
12
+ import { setupProxy } from "../utils/proxy.js";
13
+ import { emptyTaskJson } from "../utils/task-json.js";
14
+ // Import templates for comparison
15
+ import { getAllScripts, getAllAgents,
16
+ // Configuration
17
+ configYamlTemplate, gitignoreTemplate, workflowMdTemplate, } from "../templates/trellis/index.js";
18
+ import { agentsMdContent } from "../templates/markdown/index.js";
19
+ import { COPILOT_INSTRUCTIONS_BLOCK_END, COPILOT_INSTRUCTIONS_BLOCK_START, COPILOT_INSTRUCTIONS_PATH, getCopilotInstructions, } from "../templates/copilot/index.js";
20
+ import { ALL_MANAGED_DIRS, getConfiguredPlatforms, collectPlatformTemplates, } from "../configurators/index.js";
21
+ import { replacePythonCommandLiterals } from "../configurators/shared.js";
22
+ import { preserveCodexAgentModelKeys } from "../configurators/codex.js";
23
+ import { printZcodeSetupHint } from "../configurators/zcode.js";
24
+ import { ensureGitattributes } from "../configurators/workflow.js";
25
+ import { pruneOrphanManifestKeys } from "../utils/manifest-prune.js";
26
+ import { fetchRegistrySpecTemplates, collectDirectoryFiles, removeDirectory, parseRegistrySource, probeRegistryIndex, downloadTemplateById, } from "../utils/template-fetcher.js";
27
+ import { loadSpecRegistryConfig } from "../utils/registry-config.js";
28
+ import { cleanupEmptyDirs, TRELLIS_BLOCK_END, TRELLIS_BLOCK_START, } from "../utils/managed-paths.js";
29
+ // downstream: 030-integration-controls
30
+ import { applyIntegration, planIntegration, readIntegrationStatus, } from "../utils/integration.js";
31
+ export { cleanupEmptyDirs, TRELLIS_BLOCK_END, TRELLIS_BLOCK_START, } from "../utils/managed-paths.js";
32
+ const CLAUDE_SETTINGS_PATH = ".claude/settings.json";
33
+ const LEGACY_UNTRACKED_AGENTS_MD_BLOCK_HASHES = new Set([
34
+ // v0.5.0-beta.17 and earlier wrote AGENTS.md but did not hash-track it.
35
+ // This hash is the pristine Trellis-managed block before the Subagents
36
+ // section was added, so old untouched projects can be updated without a
37
+ // false "modified by you" conflict.
38
+ "c1f511b1cfc1902f2147da159f09cc51f380b0c9e341cdb3ac5dea5233f3e307",
39
+ ]);
40
+ // Paths that should never be touched (true user data)
41
+ // spec/ is user-customized content created during init; update should never modify it
42
+ const PROTECTED_PATHS = [
43
+ `${DIR_NAMES.WORKFLOW}/${DIR_NAMES.WORKSPACE}`, // workspace/
44
+ `${DIR_NAMES.WORKFLOW}/${DIR_NAMES.TASKS}`, // tasks/
45
+ `${DIR_NAMES.WORKFLOW}/${DIR_NAMES.SPEC}`, // spec/
46
+ `${DIR_NAMES.WORKFLOW}/.developer`,
47
+ `${DIR_NAMES.WORKFLOW}/.current-task`,
48
+ ];
49
+ function getManagedBlock(content, startMarker, endMarker) {
50
+ const start = content.indexOf(startMarker);
51
+ if (start === -1) {
52
+ return null;
53
+ }
54
+ const end = content.indexOf(endMarker, start);
55
+ if (end === -1) {
56
+ return null;
57
+ }
58
+ return content.slice(start, end + endMarker.length);
59
+ }
60
+ function getTrellisManagedBlock(content) {
61
+ return getManagedBlock(content, TRELLIS_BLOCK_START, TRELLIS_BLOCK_END);
62
+ }
63
+ function replaceManagedBlock(existingContent, templateContent, startMarker, endMarker) {
64
+ const existingStart = existingContent.indexOf(startMarker);
65
+ if (existingStart === -1) {
66
+ return null;
67
+ }
68
+ const existingEnd = existingContent.indexOf(endMarker, existingStart);
69
+ if (existingEnd === -1) {
70
+ return null;
71
+ }
72
+ const templateBlock = getManagedBlock(templateContent, startMarker, endMarker);
73
+ if (!templateBlock) {
74
+ return null;
75
+ }
76
+ return (existingContent.slice(0, existingStart) +
77
+ templateBlock +
78
+ existingContent.slice(existingEnd + endMarker.length));
79
+ }
80
+ function mergeManagedBlockContent(existingContent, templateContent, startMarker, endMarker) {
81
+ const replaced = replaceManagedBlock(existingContent, templateContent, startMarker, endMarker);
82
+ if (replaced !== null) {
83
+ return replaced;
84
+ }
85
+ const templateBlock = getManagedBlock(templateContent, startMarker, endMarker);
86
+ if (!templateBlock) {
87
+ return templateContent;
88
+ }
89
+ const trimmed = existingContent.replace(/\s+$/, "");
90
+ return `${trimmed}\n\n${templateBlock}\n`;
91
+ }
92
+ function buildManagedBlockTemplate(cwd, relativePath, templateContent, startMarker, endMarker) {
93
+ const fullPath = path.join(cwd, ...relativePath.split("/"));
94
+ if (!fs.existsSync(fullPath)) {
95
+ return templateContent;
96
+ }
97
+ const existingContent = fs.readFileSync(fullPath, "utf-8");
98
+ return mergeManagedBlockContent(existingContent, templateContent, startMarker, endMarker);
99
+ }
100
+ function buildAgentsMdTemplate(cwd) {
101
+ return buildManagedBlockTemplate(cwd, FILE_NAMES.AGENTS, agentsMdContent, TRELLIS_BLOCK_START, TRELLIS_BLOCK_END);
102
+ }
103
+ function buildCopilotInstructionsTemplate(cwd) {
104
+ return buildManagedBlockTemplate(cwd, COPILOT_INSTRUCTIONS_PATH, getCopilotInstructions(), COPILOT_INSTRUCTIONS_BLOCK_START, COPILOT_INSTRUCTIONS_BLOCK_END);
105
+ }
106
+ function isKnownUntrackedTemplate(relativePath, existingContent) {
107
+ if (relativePath !== FILE_NAMES.AGENTS) {
108
+ return false;
109
+ }
110
+ const managedBlock = getTrellisManagedBlock(existingContent);
111
+ if (!managedBlock) {
112
+ return false;
113
+ }
114
+ return LEGACY_UNTRACKED_AGENTS_MD_BLOCK_HASHES.has(computeHash(managedBlock));
115
+ }
116
+ function isSafeUntrackedCopilotInstructionsMerge(relativePath, existingContent, newContent) {
117
+ if (relativePath !== COPILOT_INSTRUCTIONS_PATH) {
118
+ return false;
119
+ }
120
+ if (getManagedBlock(existingContent, COPILOT_INSTRUCTIONS_BLOCK_START, COPILOT_INSTRUCTIONS_BLOCK_END)) {
121
+ return false;
122
+ }
123
+ return (mergeManagedBlockContent(existingContent, getCopilotInstructions(), COPILOT_INSTRUCTIONS_BLOCK_START, COPILOT_INSTRUCTIONS_BLOCK_END) === newContent);
124
+ }
125
+ /**
126
+ * Check if a path is blocked by PROTECTED_PATHS
127
+ */
128
+ function isProtectedPath(filePath) {
129
+ return PROTECTED_PATHS.some((pp) => filePath === pp || filePath.startsWith(pp.endsWith("/") ? pp : pp + "/"));
130
+ }
131
+ /**
132
+ * Collect and classify safe-file-delete migrations
133
+ *
134
+ * safe-file-delete auto-executes (no --migrate needed) when:
135
+ * - File exists
136
+ * - Content hash matches allowed_hashes
137
+ * - Path is not protected or in update.skip
138
+ * - Path is not owned by the current template set
139
+ */
140
+ function collectSafeFileDeletes(migrations, cwd, skipPaths, currentTemplatePaths,
141
+ /**
142
+ * Bypass `update.skip` for safe-file-delete. Enable this for breaking releases
143
+ * where honoring skip would leave the project half-migrated (old files at
144
+ * protected paths sitting next to the new architecture forever). The hash
145
+ * check in `allowed_hashes` is still the ultimate safety net — user-modified
146
+ * files still stay put with a "skip-modified" warning.
147
+ */
148
+ bypassUpdateSkip = false) {
149
+ // Historical migrations are loaded forever, so current template ownership
150
+ // must win when a later release intentionally restores a retired path.
151
+ const safeDeletes = migrations.filter((m) => m.type === "safe-file-delete" && !currentTemplatePaths.has(m.from));
152
+ const results = [];
153
+ for (const item of safeDeletes) {
154
+ const fullPath = path.join(cwd, item.from);
155
+ // Check: file exists?
156
+ if (!fs.existsSync(fullPath)) {
157
+ results.push({ item, action: "skip-missing" });
158
+ continue;
159
+ }
160
+ // Check: protected path? (user data dirs — always protected, never bypassed)
161
+ if (isProtectedPath(item.from)) {
162
+ results.push({ item, action: "skip-protected" });
163
+ continue;
164
+ }
165
+ // Check: update.skip? (can be bypassed for breaking releases)
166
+ if (!bypassUpdateSkip &&
167
+ skipPaths.some((skip) => item.from === skip ||
168
+ item.from.startsWith(skip.endsWith("/") ? skip : skip + "/"))) {
169
+ results.push({ item, action: "skip-update-skip" });
170
+ continue;
171
+ }
172
+ // Check: hash matches allowed_hashes?
173
+ if (!item.allowed_hashes || item.allowed_hashes.length === 0) {
174
+ // No allowed hashes defined — skip for safety
175
+ results.push({ item, action: "skip-modified" });
176
+ continue;
177
+ }
178
+ try {
179
+ const content = fs.readFileSync(fullPath, "utf-8");
180
+ const fileHash = computeHash(content);
181
+ if (item.allowed_hashes.includes(fileHash)) {
182
+ results.push({ item, action: "delete" });
183
+ }
184
+ else {
185
+ results.push({ item, action: "skip-modified" });
186
+ }
187
+ }
188
+ catch {
189
+ results.push({ item, action: "skip-missing" });
190
+ }
191
+ }
192
+ return results;
193
+ }
194
+ /**
195
+ * Print safe-file-delete summary
196
+ */
197
+ function printSafeFileDeleteSummary(classified) {
198
+ const toDelete = classified.filter((c) => c.action === "delete");
199
+ const modified = classified.filter((c) => c.action === "skip-modified");
200
+ const updateSkip = classified.filter((c) => c.action === "skip-update-skip");
201
+ if (toDelete.length === 0 &&
202
+ modified.length === 0 &&
203
+ updateSkip.length === 0) {
204
+ return;
205
+ }
206
+ console.log(chalk.cyan(" Deprecated commands cleanup:"));
207
+ if (toDelete.length > 0) {
208
+ for (const c of toDelete) {
209
+ console.log(chalk.green(` āœ• ${c.item.from}${c.item.description ? ` (${c.item.description})` : ""}`));
210
+ }
211
+ }
212
+ if (modified.length > 0) {
213
+ for (const c of modified) {
214
+ console.log(chalk.yellow(` ? ${c.item.from} (modified, skipped)`));
215
+ }
216
+ }
217
+ if (updateSkip.length > 0) {
218
+ for (const c of updateSkip) {
219
+ console.log(chalk.gray(` ā—‹ ${c.item.from} (skipped, update.skip)`));
220
+ }
221
+ }
222
+ console.log("");
223
+ }
224
+ /**
225
+ * Execute safe-file-delete items (delete files + clean up empty dirs)
226
+ */
227
+ function executeSafeFileDeletes(classified, cwd) {
228
+ const toDelete = classified.filter((c) => c.action === "delete");
229
+ let deleted = 0;
230
+ for (const c of toDelete) {
231
+ const fullPath = path.join(cwd, c.item.from);
232
+ try {
233
+ fs.unlinkSync(fullPath);
234
+ removeHash(cwd, c.item.from);
235
+ cleanupEmptyDirs(cwd, path.dirname(c.item.from));
236
+ deleted++;
237
+ }
238
+ catch {
239
+ // File may have been removed between classify and execute
240
+ }
241
+ }
242
+ return deleted;
243
+ }
244
+ /**
245
+ * Load update.skip paths from .trellis/config.yaml
246
+ *
247
+ * Parses simple YAML structure:
248
+ * update:
249
+ * skip:
250
+ * - path1
251
+ * - path2
252
+ *
253
+ * @internal Exported for testing only
254
+ */
255
+ export function loadUpdateSkipPaths(cwd) {
256
+ const configPath = path.join(cwd, DIR_NAMES.WORKFLOW, "config.yaml");
257
+ if (!fs.existsSync(configPath))
258
+ return [];
259
+ try {
260
+ const content = fs.readFileSync(configPath, "utf-8");
261
+ const lines = content.split("\n");
262
+ const paths = [];
263
+ let inUpdate = false;
264
+ let inSkip = false;
265
+ for (const line of lines) {
266
+ const trimmed = line.trimEnd();
267
+ // Check for "update:" section (no indentation or at root level)
268
+ if (/^update:\s*$/.test(trimmed)) {
269
+ inUpdate = true;
270
+ inSkip = false;
271
+ continue;
272
+ }
273
+ // Check for "skip:" under update (indented)
274
+ if (inUpdate && /^\s+skip:\s*$/.test(trimmed)) {
275
+ inSkip = true;
276
+ continue;
277
+ }
278
+ // Collect list items under skip
279
+ if (inSkip) {
280
+ const match = trimmed.match(/^\s+-\s+(.+)$/);
281
+ if (match) {
282
+ paths.push(match[1].trim().replace(/^['"]|['"]$/g, ""));
283
+ continue;
284
+ }
285
+ // If line is non-empty and not a list item, we've left the skip section
286
+ if (trimmed !== "" && !trimmed.startsWith("#")) {
287
+ inSkip = false;
288
+ inUpdate = false;
289
+ }
290
+ }
291
+ // If we're in update but hit a non-indented line, we've left the update section
292
+ if (inUpdate &&
293
+ trimmed !== "" &&
294
+ !trimmed.startsWith(" ") &&
295
+ !trimmed.startsWith("#")) {
296
+ inUpdate = false;
297
+ inSkip = false;
298
+ }
299
+ }
300
+ return paths;
301
+ }
302
+ catch {
303
+ // Config exists but failed to parse — warn user that skip rules won't apply
304
+ console.warn(`Warning: failed to parse ${configPath}, update.skip rules will not be applied`);
305
+ return [];
306
+ }
307
+ }
308
+ /**
309
+ * Extract a "section" from a config.yaml-style template by sectionHeading.
310
+ *
311
+ * A section is delimited by `#---...---` separator lines (the same pattern
312
+ * used in the bundled `config.yaml` template). The first line inside the
313
+ * separator block whose `# ` content matches `sectionHeading` identifies the
314
+ * section; the section spans from that opening separator block through the
315
+ * line preceding the next `#---` separator block (or EOF).
316
+ *
317
+ * Returns the extracted text including its leading separator block, or `null`
318
+ * when no matching section is found.
319
+ *
320
+ * @internal Exported for testing only.
321
+ */
322
+ export function extractConfigSection(template, sectionHeading) {
323
+ const lines = template.split("\n");
324
+ const isSeparator = (line) => /^#-{3,}\s*$/.test(line.trimEnd());
325
+ for (let i = 0; i < lines.length; i++) {
326
+ if (!isSeparator(lines[i]))
327
+ continue;
328
+ // Look ahead for `# <heading>` then another separator that closes the
329
+ // heading block.
330
+ const headingLine = lines[i + 1];
331
+ const closingSeparator = lines[i + 2];
332
+ if (headingLine === undefined || closingSeparator === undefined)
333
+ continue;
334
+ if (!headingLine.startsWith("# "))
335
+ continue;
336
+ if (!isSeparator(closingSeparator))
337
+ continue;
338
+ if (headingLine.slice(2).trim() !== sectionHeading)
339
+ continue;
340
+ // Section starts at i; find the next separator block to bound it.
341
+ let end = lines.length;
342
+ for (let j = i + 3; j < lines.length; j++) {
343
+ if (isSeparator(lines[j])) {
344
+ end = j;
345
+ break;
346
+ }
347
+ }
348
+ return lines.slice(i, end).join("\n").replace(/\n+$/, "");
349
+ }
350
+ return null;
351
+ }
352
+ /**
353
+ * Apply additive config.yaml sections introduced between two versions.
354
+ *
355
+ * Walks the supplied entries, dedupes by `file+sentinel`, and for each unique
356
+ * entry: if the user file exists and lacks the sentinel, extracts the named
357
+ * section from `templateContent` and appends it. Idempotent — re-running the
358
+ * step on a file that already contains the sentinel is a no-op.
359
+ *
360
+ * @internal Exported for testing only.
361
+ */
362
+ export function applyConfigSectionsAdded(entries, cwd, bundledTemplates) {
363
+ const seen = new Set();
364
+ let appended = 0;
365
+ for (const entry of entries) {
366
+ const dedupeKey = `${entry.file}::${entry.sentinel}`;
367
+ if (seen.has(dedupeKey))
368
+ continue;
369
+ seen.add(dedupeKey);
370
+ const targetPath = path.join(cwd, entry.file);
371
+ if (!fs.existsSync(targetPath))
372
+ continue;
373
+ let userContent;
374
+ try {
375
+ userContent = fs.readFileSync(targetPath, "utf-8");
376
+ }
377
+ catch {
378
+ continue;
379
+ }
380
+ if (userContent.includes(entry.sentinel))
381
+ continue;
382
+ const template = bundledTemplates.get(entry.file);
383
+ if (!template)
384
+ continue;
385
+ const section = extractConfigSection(template, entry.sectionHeading);
386
+ if (!section)
387
+ continue;
388
+ const separator = userContent.endsWith("\n") ? "\n" : "\n\n";
389
+ const newContent = userContent + separator + section + "\n";
390
+ try {
391
+ fs.writeFileSync(targetPath, newContent);
392
+ }
393
+ catch {
394
+ continue;
395
+ }
396
+ console.log(chalk.green(` + Added config section "${entry.sectionHeading}" to ${entry.file}`));
397
+ appended++;
398
+ }
399
+ return { appended };
400
+ }
401
+ /**
402
+ * Collect all template files that should be managed by update
403
+ * Only collects templates for platforms that are already configured (have directories)
404
+ */
405
+ /**
406
+ * Detect if legacy Codex upgrade is needed.
407
+ *
408
+ * Old Trellis versions used `.agents/skills/` as codex's configDir.
409
+ * New versions use `.codex/` for Codex-specific config and `.agents/skills/`
410
+ * as a shared layer.
411
+ *
412
+ * Detection: Trellis-tracked hashes contain `.agents/skills/` entries
413
+ * but `.codex/` does not exist. This avoids misclassifying repos that
414
+ * have `.agents/skills/` from other tools (Kimi CLI, Amp, etc.).
415
+ *
416
+ * Returns true if upgrade is needed. Does NOT perform the upgrade —
417
+ * caller should run configurePlatform("codex") after backup/confirm.
418
+ */
419
+ function needsCodexUpgrade(cwd) {
420
+ if (fs.existsSync(path.join(cwd, ".codex"))) {
421
+ return false;
422
+ }
423
+ // Legacy Codex marker: old Codex installs tracked command-as-skill files
424
+ // under `.agents/skills/` before `.codex/` existed as a separate config dir.
425
+ // A current or future non-Codex platform may own those paths too, so do not
426
+ // trigger the Codex backfill when a configured non-Codex platform declares
427
+ // the marker paths in its templates.
428
+ const hashes = loadHashes(cwd);
429
+ const legacyMarkers = [
430
+ ".agents/skills/trellis-continue/SKILL.md",
431
+ ".agents/skills/trellis-finish-work/SKILL.md",
432
+ ];
433
+ const hasLegacyMarker = legacyMarkers.some((key) => hashes[key] !== undefined);
434
+ if (!hasLegacyMarker) {
435
+ return false;
436
+ }
437
+ for (const platformId of getConfiguredPlatforms(cwd)) {
438
+ if (platformId === "codex") {
439
+ continue;
440
+ }
441
+ const platformFiles = collectPlatformTemplates(platformId);
442
+ if (platformFiles && legacyMarkers.some((key) => platformFiles.has(key))) {
443
+ return false;
444
+ }
445
+ }
446
+ return true;
447
+ }
448
+ function preserveExistingClaudeStatusLine(cwd, templates) {
449
+ const newSettingsContent = templates.get(CLAUDE_SETTINGS_PATH);
450
+ if (!newSettingsContent)
451
+ return;
452
+ const settingsPath = path.join(cwd, CLAUDE_SETTINGS_PATH);
453
+ if (!fs.existsSync(settingsPath))
454
+ return;
455
+ try {
456
+ const existingSettings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
457
+ if (!Object.prototype.hasOwnProperty.call(existingSettings, "statusLine")) {
458
+ return;
459
+ }
460
+ const newSettings = JSON.parse(newSettingsContent);
461
+ if (Object.prototype.hasOwnProperty.call(newSettings, "statusLine")) {
462
+ return;
463
+ }
464
+ newSettings.statusLine = existingSettings.statusLine;
465
+ templates.set(CLAUDE_SETTINGS_PATH, `${JSON.stringify(newSettings, null, 2)}\n`);
466
+ }
467
+ catch {
468
+ // Invalid local JSON is handled by the normal conflict path.
469
+ }
470
+ }
471
+ function preserveExistingRegistryConfig(cwd, template) {
472
+ const registry = loadSpecRegistryConfig(cwd);
473
+ if (!registry)
474
+ return template;
475
+ return (template.trimEnd() +
476
+ "\n\n" +
477
+ "#-------------------------------------------------------------------------------\n" +
478
+ "# Registry\n" +
479
+ "#-------------------------------------------------------------------------------\n\n" +
480
+ `# Source used to install .trellis/spec. ${COMMAND_NAME} update refreshes this\n` +
481
+ "# hash-tracked spec template while preserving local edits through the\n" +
482
+ "# normal update conflict flow.\n" +
483
+ "registry:\n" +
484
+ " spec:\n" +
485
+ ` source: ${registry.source}\n` +
486
+ (registry.template ? ` template: ${registry.template}\n` : ""));
487
+ }
488
+ async function collectRegistrySpecTemplates(cwd) {
489
+ const config = loadSpecRegistryConfig(cwd);
490
+ if (!config)
491
+ return new Map();
492
+ let registry;
493
+ try {
494
+ registry = parseRegistrySource(config.source);
495
+ }
496
+ catch (error) {
497
+ console.log(chalk.yellow(`Warning: invalid registry.spec.source in .trellis/config.yaml: ${error instanceof Error ? error.message : String(error)}`));
498
+ return new Map();
499
+ }
500
+ const probe = await probeRegistryIndex(`${registry.rawBaseUrl}/index.json`, registry);
501
+ if (probe.templates.length > 0) {
502
+ if (!config.template) {
503
+ console.log(chalk.gray("Registry spec update skipped: marketplace registries require registry.spec.template."));
504
+ return new Map();
505
+ }
506
+ const template = probe.templates.find((candidate) => candidate.id === config.template);
507
+ if (!template) {
508
+ console.log(chalk.yellow(`Warning: registry spec update skipped: template "${config.template}" was not found in registry index.`));
509
+ return new Map();
510
+ }
511
+ const tempRoot = await fs.promises.mkdtemp(path.join(os.tmpdir(), "trellis-registry-template-"));
512
+ try {
513
+ const result = await downloadTemplateById(tempRoot, config.template, "overwrite", template, registry, undefined, probe.backend);
514
+ if (!result.success) {
515
+ console.log(chalk.yellow(`Warning: registry spec update skipped: ${result.message}`));
516
+ return new Map();
517
+ }
518
+ return collectDirectoryFiles(path.join(tempRoot, PATHS.SPEC), PATHS.SPEC);
519
+ }
520
+ finally {
521
+ await removeDirectory(tempRoot);
522
+ }
523
+ }
524
+ if (!probe.isNotFound) {
525
+ console.log(chalk.yellow(`Warning: registry spec update skipped: ${probe.error?.message ?? "could not reach registry"}`));
526
+ return new Map();
527
+ }
528
+ const result = await fetchRegistrySpecTemplates(registry, probe.backend);
529
+ if (!result.success) {
530
+ console.log(chalk.yellow(`Warning: registry spec update skipped: ${result.message ?? "download failed"}`));
531
+ return new Map();
532
+ }
533
+ return result.files;
534
+ }
535
+ async function collectTemplateFiles(cwd, extraPlatforms,
536
+ /**
537
+ * Bypass `update.skip` when collecting templates. Enable this for breaking
538
+ * releases so new files (e.g. `continue.md` added in 0.5.0) and template
539
+ * updates can land even under skip-protected paths. Without this, users with
540
+ * `.claude/commands/` in their skip list would silently miss new commands.
541
+ * Existing user customizations are still guarded at WRITE time via the
542
+ * "Modified by you" conflict prompt — they can skip per-file there.
543
+ */
544
+ bypassUpdateSkip = false) {
545
+ const files = new Map();
546
+ const platforms = getConfiguredPlatforms(cwd);
547
+ if (extraPlatforms) {
548
+ for (const p of extraPlatforms) {
549
+ platforms.add(p);
550
+ }
551
+ }
552
+ // Python scripts (single source of truth: getAllScripts())
553
+ for (const [scriptPath, content] of getAllScripts()) {
554
+ files.set(`${PATHS.SCRIPTS}/${scriptPath}`, content);
555
+ }
556
+ // Channel runtime agent definitions (single source of truth: getAllAgents()).
557
+ // Backfilled by `trellis update` if missing so users who installed before the
558
+ // bundled agents existed pick them up. Edited files take the standard
559
+ // modified-file prompt path.
560
+ for (const [agentFile, content] of getAllAgents()) {
561
+ files.set(`${PATHS.AGENTS}/${agentFile}`, content);
562
+ }
563
+ // Configuration
564
+ const configRelativePath = `${DIR_NAMES.WORKFLOW}/config.yaml`;
565
+ const configPath = path.join(cwd, configRelativePath);
566
+ const existingConfig = fs.existsSync(configPath)
567
+ ? fs.readFileSync(configPath, "utf-8")
568
+ : "";
569
+ files.set(configRelativePath, /^\s*integration:\s*(?:#.*)?$/m.test(existingConfig)
570
+ ? existingConfig
571
+ : preserveExistingRegistryConfig(cwd, configYamlTemplate));
572
+ files.set(`${DIR_NAMES.WORKFLOW}/.gitignore`, gitignoreTemplate);
573
+ // workflow.md is included here because it is runtime-parsed by
574
+ // get_context.py and shared hooks. Keep it on the normal template update
575
+ // path: if the installed file still matches the tracked hash, update the
576
+ // whole file. If the user edited it, the standard modified-file prompt /
577
+ // --force behavior applies. Partial tag-block merging is unsafe because
578
+ // platform routing markers outside [workflow-state:*] blocks are also
579
+ // script-consumed.
580
+ files.set(`${DIR_NAMES.WORKFLOW}/workflow.md`, workflowMdTemplate);
581
+ // workspace/index.md stays excluded — it's runtime-appended by add_session.py
582
+ // (journal index) and has no script-parsed structure.
583
+ files.set(FILE_NAMES.AGENTS, buildAgentsMdTemplate(cwd));
584
+ // Platform-specific templates (only for configured platforms)
585
+ for (const platformId of platforms) {
586
+ const platformFiles = collectPlatformTemplates(platformId);
587
+ if (platformFiles) {
588
+ for (const [filePath, content] of platformFiles) {
589
+ files.set(filePath, content);
590
+ }
591
+ if (platformId === "copilot") {
592
+ files.set(COPILOT_INSTRUCTIONS_PATH, buildCopilotInstructionsTemplate(cwd));
593
+ }
594
+ }
595
+ }
596
+ // Users configure sub-agent models by editing `model` /
597
+ // `model_reasoning_effort` directly on the generated agent tomls. Preserve
598
+ // those two keys from the on-disk files into the freshly rendered desired
599
+ // content so a project whose only local edit is these keys is not flagged
600
+ // as a modified-file conflict by the hash comparison below.
601
+ if (platforms.has("codex")) {
602
+ preserveCodexAgentModelKeys(cwd, files);
603
+ }
604
+ preserveExistingClaudeStatusLine(cwd, files);
605
+ for (const [filePath, content] of await collectRegistrySpecTemplates(cwd)) {
606
+ files.set(filePath, content);
607
+ }
608
+ // Apply update.skip from config.yaml (unless bypassed for breaking release)
609
+ if (!bypassUpdateSkip) {
610
+ const skipPaths = loadUpdateSkipPaths(cwd);
611
+ if (skipPaths.length > 0) {
612
+ for (const [filePath] of [...files]) {
613
+ if (skipPaths.some((skip) => filePath === skip ||
614
+ filePath.startsWith(skip.endsWith("/") ? skip : skip + "/"))) {
615
+ files.delete(filePath);
616
+ }
617
+ }
618
+ }
619
+ }
620
+ // Apply python3→python replacement for Windows consistency with init-time writes
621
+ for (const [filePath, content] of files) {
622
+ files.set(filePath, replacePythonCommandLiterals(content));
623
+ }
624
+ return files;
625
+ }
626
+ /**
627
+ * Analyze changes between current files and templates
628
+ *
629
+ * Uses hash tracking to distinguish between:
630
+ * - User didn't modify + template same = skip (unchangedFiles)
631
+ * - User didn't modify + template updated = auto-update (autoUpdateFiles)
632
+ * - User modified = needs confirmation (changedFiles)
633
+ */
634
+ function analyzeChanges(cwd, hashes, templates) {
635
+ const result = {
636
+ newFiles: [],
637
+ unchangedFiles: [],
638
+ autoUpdateFiles: [],
639
+ changedFiles: [],
640
+ userDeletedFiles: [],
641
+ protectedPaths: PROTECTED_PATHS,
642
+ };
643
+ for (const [relativePath, newContent] of templates) {
644
+ const fullPath = path.join(cwd, relativePath);
645
+ const exists = fs.existsSync(fullPath);
646
+ const change = {
647
+ path: fullPath,
648
+ relativePath,
649
+ newContent,
650
+ status: "new",
651
+ };
652
+ if (!exists) {
653
+ const storedHash = hashes[relativePath];
654
+ if (storedHash) {
655
+ // Previously installed but user deleted — respect deletion
656
+ result.userDeletedFiles.push(change);
657
+ }
658
+ else {
659
+ change.status = "new";
660
+ result.newFiles.push(change);
661
+ }
662
+ }
663
+ else {
664
+ const existingContent = fs.readFileSync(fullPath, "utf-8");
665
+ if (existingContent === newContent) {
666
+ // Content same as template - already up to date
667
+ change.status = "unchanged";
668
+ result.unchangedFiles.push(change);
669
+ }
670
+ else {
671
+ // Content differs - check if user modified or template updated
672
+ const storedHash = hashes[relativePath];
673
+ const currentHash = computeHash(existingContent);
674
+ if ((storedHash && storedHash === currentHash) ||
675
+ (!storedHash &&
676
+ isKnownUntrackedTemplate(relativePath, existingContent)) ||
677
+ (!storedHash &&
678
+ isSafeUntrackedCopilotInstructionsMerge(relativePath, existingContent, newContent))) {
679
+ // Either the tracked hash matches, or this is a known pristine template
680
+ // from before the path was hash-tracked. Safe to auto-update.
681
+ change.status = "changed";
682
+ result.autoUpdateFiles.push(change);
683
+ }
684
+ else {
685
+ // Hash differs (or no stored hash) - user modified the file
686
+ // Needs confirmation
687
+ change.status = "changed";
688
+ result.changedFiles.push(change);
689
+ }
690
+ }
691
+ }
692
+ }
693
+ return result;
694
+ }
695
+ /**
696
+ * Receipt entries that are wrong or missing for a file that is already
697
+ * byte-identical to its template.
698
+ *
699
+ * Nothing else repairs these. `analyzeChanges` classifies such a file
700
+ * `unchanged`, and the write-back draws only from `newFiles`,
701
+ * `autoUpdateFiles` and overwritten `changedFiles` — so a poisoned or absent
702
+ * entry beside a pristine file survives every subsequent `trellis update`,
703
+ * however many times it is run. Identical template content across versions is
704
+ * not what saves such an entry from going stale; it is precisely what freezes
705
+ * it, because the file never leaves the `unchanged` bucket.
706
+ *
707
+ * Recording the template's hash here cannot bless a local edit. Membership in
708
+ * `unchangedFiles` means the file on disk already *is* the template, byte for
709
+ * byte, so the value written is the one a correct receipt would already hold.
710
+ * A genuinely customized file differs from its template, lands in
711
+ * `changedFiles`, and is never seen by this function.
712
+ *
713
+ * That also leaves the mixed-ownership paths — `AGENTS.md`,
714
+ * `.github/copilot-instructions.md`, `.trellis/config.yaml` — free to differ
715
+ * from their recorded hash, which for them is the correct state: once the
716
+ * repository has appended its own content they are no longer `unchanged`.
717
+ */
718
+ function collectUnchangedFileHashRepairs(changes, hashes) {
719
+ const files = new Map();
720
+ for (const file of changes.unchangedFiles) {
721
+ const key = toPosix(file.relativePath);
722
+ const recorded = hashes[key];
723
+ if (recorded === undefined) {
724
+ // A missing entry is only an omission for a path the receipt is meant
725
+ // to carry. `EXCLUDE_FROM_HASH` holds paths deliberately left out —
726
+ // `.trellis/.gitignore` among them — and adding those here would put
727
+ // this path in disagreement with `initializeHashes` about what the
728
+ // receipt tracks at all.
729
+ if (!shouldExcludeFromHash(key)) {
730
+ files.set(key, file.newContent);
731
+ }
732
+ continue;
733
+ }
734
+ // An entry that already exists and disagrees with the file is repaired
735
+ // whatever the path: a wrong value is strictly worse than an absent one,
736
+ // because it reads as a real local modification.
737
+ if (recorded !== computeHash(file.newContent)) {
738
+ files.set(key, file.newContent);
739
+ }
740
+ }
741
+ return files;
742
+ }
743
+ /**
744
+ * Print change summary
745
+ */
746
+ function printChangeSummary(changes) {
747
+ console.log("\nScanning for changes...\n");
748
+ if (changes.newFiles.length > 0) {
749
+ console.log(chalk.green(" New files (will add):"));
750
+ for (const file of changes.newFiles) {
751
+ console.log(chalk.green(` + ${file.relativePath}`));
752
+ }
753
+ console.log("");
754
+ }
755
+ if (changes.autoUpdateFiles.length > 0) {
756
+ console.log(chalk.cyan(" Template updated (will auto-update):"));
757
+ for (const file of changes.autoUpdateFiles) {
758
+ console.log(chalk.cyan(` ↑ ${file.relativePath}`));
759
+ }
760
+ console.log("");
761
+ }
762
+ if (changes.unchangedFiles.length > 0) {
763
+ console.log(chalk.gray(" Unchanged files (will skip):"));
764
+ for (const file of changes.unchangedFiles.slice(0, 5)) {
765
+ console.log(chalk.gray(` ā—‹ ${file.relativePath}`));
766
+ }
767
+ if (changes.unchangedFiles.length > 5) {
768
+ console.log(chalk.gray(` ... and ${changes.unchangedFiles.length - 5} more`));
769
+ }
770
+ console.log("");
771
+ }
772
+ if (changes.changedFiles.length > 0) {
773
+ console.log(chalk.yellow(" Modified by you (need your decision):"));
774
+ for (const file of changes.changedFiles) {
775
+ console.log(chalk.yellow(` ? ${file.relativePath}`));
776
+ }
777
+ console.log("");
778
+ }
779
+ if (changes.userDeletedFiles.length > 0) {
780
+ console.log(chalk.gray(" Deleted by you (preserved):"));
781
+ for (const file of changes.userDeletedFiles) {
782
+ console.log(chalk.gray(` \u2715 ${file.relativePath}`));
783
+ }
784
+ console.log("");
785
+ }
786
+ // Only show protected paths that actually exist
787
+ const existingProtectedPaths = changes.protectedPaths.filter((p) => {
788
+ const fullPath = path.join(process.cwd(), p);
789
+ return fs.existsSync(fullPath);
790
+ });
791
+ if (existingProtectedPaths.length > 0) {
792
+ console.log(chalk.gray(" User data (preserved):"));
793
+ for (const protectedPath of existingProtectedPaths) {
794
+ console.log(chalk.gray(` ā—‹ ${protectedPath}/`));
795
+ }
796
+ console.log("");
797
+ }
798
+ }
799
+ /**
800
+ * Prompt user for conflict resolution
801
+ */
802
+ async function promptConflictResolution(file, options, applyToAll) {
803
+ // If we have a batch action, use it
804
+ if (applyToAll.action) {
805
+ return applyToAll.action;
806
+ }
807
+ // Check command-line options
808
+ if (options.force) {
809
+ return "overwrite";
810
+ }
811
+ if (options.skipAll) {
812
+ return "skip";
813
+ }
814
+ if (options.createNew) {
815
+ return "create-new";
816
+ }
817
+ // Interactive prompt
818
+ const { action } = await inquirer.prompt([
819
+ {
820
+ type: "list",
821
+ name: "action",
822
+ message: `${file.relativePath} has changes.`,
823
+ choices: [
824
+ {
825
+ name: "[1] Overwrite - Replace with new version",
826
+ value: "overwrite",
827
+ },
828
+ { name: "[2] Skip - Keep your current version", value: "skip" },
829
+ {
830
+ name: "[3] Create copy - Save new version as .new",
831
+ value: "create-new",
832
+ },
833
+ { name: "[a] Apply Overwrite to all", value: "overwrite-all" },
834
+ { name: "[s] Apply Skip to all", value: "skip-all" },
835
+ { name: "[n] Apply Create copy to all", value: "create-new-all" },
836
+ ],
837
+ default: "skip",
838
+ },
839
+ ]);
840
+ if (action === "overwrite-all") {
841
+ applyToAll.action = "overwrite";
842
+ return "overwrite";
843
+ }
844
+ if (action === "skip-all") {
845
+ applyToAll.action = "skip";
846
+ return "skip";
847
+ }
848
+ if (action === "create-new-all") {
849
+ applyToAll.action = "create-new";
850
+ return "create-new";
851
+ }
852
+ return action;
853
+ }
854
+ /**
855
+ * Create a timestamped backup directory path
856
+ */
857
+ function createBackupDirPath(cwd) {
858
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
859
+ return path.join(cwd, DIR_NAMES.WORKFLOW, `.backup-${timestamp}`);
860
+ }
861
+ /**
862
+ * Backup a single file to the backup directory
863
+ */
864
+ function backupFile(cwd, backupDir, relativePath) {
865
+ const srcPath = path.join(cwd, relativePath);
866
+ if (!fs.existsSync(srcPath))
867
+ return;
868
+ const backupPath = path.join(backupDir, relativePath);
869
+ fs.mkdirSync(path.dirname(backupPath), { recursive: true });
870
+ fs.copyFileSync(srcPath, backupPath);
871
+ }
872
+ /**
873
+ * Directories to backup as complete snapshot (derived from platform registry)
874
+ */
875
+ const BACKUP_DIRS = ALL_MANAGED_DIRS;
876
+ /** Root-level managed files to include in update backups. */
877
+ const BACKUP_FILES = [FILE_NAMES.AGENTS];
878
+ /**
879
+ * Patterns to exclude from backup (user data that shouldn't be backed up)
880
+ */
881
+ const BACKUP_EXCLUDE_PATTERNS = [
882
+ ".backup-", // Previous backups
883
+ "/node_modules", // Installed dependencies; restore via package manager
884
+ "/workspace/", // Developer workspace (user data)
885
+ "/tasks/", // Task data (user data)
886
+ "/spec/", // Spec files (user-customized content)
887
+ "/backlog/", // Backlog data (user data)
888
+ "/agent-traces/", // Agent traces (user data, legacy name)
889
+ // Platform-native worktree dirs — these are full sub-repos the CLI
890
+ // spawns for parallel sessions. Backing them up on every update would
891
+ // snapshot the entire nested working tree. Confirmed conventions:
892
+ // Claude Code: .claude/worktrees/
893
+ // Cursor CLI: .cursor/worktrees/
894
+ // Gemini CLI: .gemini/worktrees/
895
+ // Matches any platform using the same convention (future-proof).
896
+ "/worktrees/",
897
+ "/worktree/",
898
+ ];
899
+ /**
900
+ * Check if a path should be excluded from backup
901
+ * @internal Exported for testing only
902
+ */
903
+ export function shouldExcludeFromBackup(relativePath) {
904
+ // Normalize Windows backslashes to forward slashes so patterns like
905
+ // "/worktrees/" / "/tasks/" match regardless of host OS. Without this,
906
+ // Windows `path.relative` returns `.claude\worktrees\...` and none of
907
+ // the slash-prefixed exclude patterns trigger — which causes
908
+ // `collectAllFiles` to descend into platform worktrees (full nested
909
+ // project copies) and explode the scan. Same normalization pattern
910
+ // used by `isManagedPath` in configurators/index.ts.
911
+ const normalized = relativePath.replace(/\\/g, "/");
912
+ for (const pattern of BACKUP_EXCLUDE_PATTERNS) {
913
+ if (normalized.includes(pattern)) {
914
+ return true;
915
+ }
916
+ }
917
+ return false;
918
+ }
919
+ /**
920
+ * Create complete snapshot backup of all managed directories
921
+ * Backs up all managed platform/workflow directories entirely
922
+ * (excluding user data like workspace/, tasks/, backlog/)
923
+ */
924
+ function createFullBackup(cwd) {
925
+ const backupDir = createBackupDirPath(cwd);
926
+ let hasFiles = false;
927
+ for (const dir of BACKUP_DIRS) {
928
+ const dirPath = path.join(cwd, dir);
929
+ if (!fs.existsSync(dirPath))
930
+ continue;
931
+ const files = collectAllFiles(dirPath, cwd);
932
+ for (const fullPath of files) {
933
+ const relativePath = path.relative(cwd, fullPath);
934
+ // Skip excluded paths
935
+ if (shouldExcludeFromBackup(relativePath))
936
+ continue;
937
+ // Create backup
938
+ if (!hasFiles) {
939
+ fs.mkdirSync(backupDir, { recursive: true });
940
+ hasFiles = true;
941
+ }
942
+ backupFile(cwd, backupDir, relativePath);
943
+ }
944
+ }
945
+ for (const relativePath of BACKUP_FILES) {
946
+ const fullPath = path.join(cwd, relativePath);
947
+ if (!fs.existsSync(fullPath))
948
+ continue;
949
+ if (shouldExcludeFromBackup(relativePath))
950
+ continue;
951
+ if (!hasFiles) {
952
+ fs.mkdirSync(backupDir, { recursive: true });
953
+ hasFiles = true;
954
+ }
955
+ backupFile(cwd, backupDir, relativePath);
956
+ }
957
+ return hasFiles ? backupDir : null;
958
+ }
959
+ /**
960
+ * Update version file
961
+ */
962
+ function updateVersionFile(cwd) {
963
+ const versionPath = path.join(cwd, DIR_NAMES.WORKFLOW, ".version");
964
+ fs.writeFileSync(versionPath, VERSION);
965
+ }
966
+ /**
967
+ * Get current installed version
968
+ */
969
+ function getInstalledVersion(cwd) {
970
+ const versionPath = path.join(cwd, DIR_NAMES.WORKFLOW, ".version");
971
+ if (fs.existsSync(versionPath)) {
972
+ return fs.readFileSync(versionPath, "utf-8").trim();
973
+ }
974
+ return "unknown";
975
+ }
976
+ /**
977
+ * Fetch latest version from npm registry
978
+ */
979
+ async function getLatestNpmVersion() {
980
+ try {
981
+ const response = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}/${UPGRADE_CHANNEL ?? "latest"}`);
982
+ if (!response.ok) {
983
+ return null;
984
+ }
985
+ const data = (await response.json());
986
+ return data.version ?? null;
987
+ }
988
+ catch {
989
+ return null;
990
+ }
991
+ }
992
+ /**
993
+ * Recursively collect all files in a directory
994
+ */
995
+ function collectAllFiles(dirPath, cwd = process.cwd()) {
996
+ if (!fs.existsSync(dirPath))
997
+ return [];
998
+ const rootStat = fs.statSync(dirPath);
999
+ if (rootStat.isFile()) {
1000
+ return [dirPath];
1001
+ }
1002
+ if (!rootStat.isDirectory()) {
1003
+ return [];
1004
+ }
1005
+ const files = [];
1006
+ const stack = [dirPath];
1007
+ while (stack.length > 0) {
1008
+ const currentDir = stack.pop();
1009
+ if (!currentDir)
1010
+ continue;
1011
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true });
1012
+ for (const entry of entries) {
1013
+ const fullPath = path.join(currentDir, entry.name);
1014
+ const relativePath = path.relative(cwd, fullPath);
1015
+ // Never follow symlinks / Windows directory junctions — a junction
1016
+ // pointing at an ancestor would loop the scan forever. Node's
1017
+ // `isSymbolicLink()` returns true for NTFS junctions since v12.
1018
+ if (entry.isSymbolicLink())
1019
+ continue;
1020
+ if (entry.isDirectory()) {
1021
+ if (!shouldExcludeFromBackup(relativePath)) {
1022
+ stack.push(fullPath);
1023
+ }
1024
+ }
1025
+ else if (entry.isFile()) {
1026
+ files.push(fullPath);
1027
+ }
1028
+ }
1029
+ }
1030
+ return files;
1031
+ }
1032
+ /**
1033
+ * Whether every file under `dirRelativePath` byte-matches the CURRENT
1034
+ * template content for its path. Stricter than {@link isDirectorySafeToReplace},
1035
+ * which also accepts files that are merely unmodified relative to an old
1036
+ * stored hash (i.e. stale-but-untouched). Used to decide the safe *direction*
1037
+ * of a rename-dir merge when both source and target exist: if the target
1038
+ * already holds canonical current-version bytes, the source (however it got
1039
+ * there) must not be allowed to overwrite it with older/differently-flavored
1040
+ * content (#447 — a legacy `.pi/skills/` copy rendered with the Pi-specific
1041
+ * resolver must not clobber the shared, neutral `.agents/skills/` content
1042
+ * Codex/Gemini already wrote).
1043
+ */
1044
+ function dirMatchesCurrentTemplates(cwd, dirRelativePath, templates) {
1045
+ const dirFullPath = path.join(cwd, dirRelativePath);
1046
+ if (!fs.existsSync(dirFullPath))
1047
+ return false;
1048
+ const files = collectAllFiles(dirFullPath, cwd);
1049
+ if (files.length === 0)
1050
+ return false;
1051
+ for (const fullPath of files) {
1052
+ const relativePath = toPosix(path.relative(cwd, fullPath));
1053
+ const templateContent = templates.get(relativePath);
1054
+ if (templateContent === undefined)
1055
+ return false;
1056
+ if (fs.readFileSync(fullPath, "utf-8") !== templateContent)
1057
+ return false;
1058
+ }
1059
+ return true;
1060
+ }
1061
+ /**
1062
+ * Check if a directory only contains unmodified template files
1063
+ * Returns true if safe to delete:
1064
+ * - All files are tracked and unmodified, OR
1065
+ * - All files match current template content (even if not tracked)
1066
+ */
1067
+ function isDirectorySafeToReplace(cwd, dirRelativePath, hashes, templates) {
1068
+ const dirFullPath = path.join(cwd, dirRelativePath);
1069
+ if (!fs.existsSync(dirFullPath))
1070
+ return true;
1071
+ const files = collectAllFiles(dirFullPath, cwd);
1072
+ if (files.length === 0)
1073
+ return true; // Empty directory is safe
1074
+ for (const fullPath of files) {
1075
+ // POSIX-normalize: hashes/templates keys are persisted as POSIX, but
1076
+ // `path.relative` returns OS-native separators (backslash on Windows).
1077
+ const relativePath = toPosix(path.relative(cwd, fullPath));
1078
+ const storedHash = hashes[relativePath];
1079
+ const templateContent = templates.get(relativePath);
1080
+ // Check if file matches template content (handles untracked files)
1081
+ if (templateContent) {
1082
+ const currentContent = fs.readFileSync(fullPath, "utf-8");
1083
+ if (currentContent === templateContent) {
1084
+ // File matches template - safe
1085
+ continue;
1086
+ }
1087
+ }
1088
+ // Check if file is tracked and unmodified
1089
+ if (storedHash && !isTemplateModified(cwd, relativePath, hashes)) {
1090
+ // Tracked and unmodified - safe
1091
+ continue;
1092
+ }
1093
+ // File is either user-created or user-modified - not safe
1094
+ return false;
1095
+ }
1096
+ return true;
1097
+ }
1098
+ /**
1099
+ * Recursively delete a directory
1100
+ */
1101
+ function removeDirectoryRecursive(dirPath) {
1102
+ if (!fs.existsSync(dirPath))
1103
+ return;
1104
+ fs.rmSync(dirPath, { recursive: true, force: true });
1105
+ }
1106
+ /**
1107
+ * Check if a file is safe to overwrite (matches template content)
1108
+ */
1109
+ function isFileSafeToReplace(cwd, relativePath, templates) {
1110
+ const fullPath = path.join(cwd, relativePath);
1111
+ if (!fs.existsSync(fullPath))
1112
+ return true;
1113
+ const templateContent = templates.get(relativePath);
1114
+ if (!templateContent)
1115
+ return false; // Not a template file
1116
+ const currentContent = fs.readFileSync(fullPath, "utf-8");
1117
+ return currentContent === templateContent;
1118
+ }
1119
+ /**
1120
+ * Classify migrations based on file state and user modifications
1121
+ */
1122
+ /**
1123
+ * Whether the manifest records any file under `dirRelativePath` — i.e. whether
1124
+ * Trellis actually created this directory. Used to gate rename-dir migrations:
1125
+ * a directory Trellis never wrote (e.g. a user's own `.windsurf/` editor
1126
+ * config that merely shares a path with a retired Trellis platform dir) must
1127
+ * not be auto-moved.
1128
+ */
1129
+ export function dirHasManifestEntries(dirRelativePath, hashes) {
1130
+ const prefix = dirRelativePath.endsWith("/")
1131
+ ? dirRelativePath
1132
+ : dirRelativePath + "/";
1133
+ return Object.keys(hashes).some((key) => key === dirRelativePath || key.startsWith(prefix));
1134
+ }
1135
+ export function classifyMigrations(migrations, cwd, hashes, templates) {
1136
+ const result = {
1137
+ auto: [],
1138
+ confirm: [],
1139
+ conflict: [],
1140
+ skip: [],
1141
+ };
1142
+ for (const item of migrations) {
1143
+ // safe-file-delete handled separately (not via --migrate)
1144
+ if (item.type === "safe-file-delete")
1145
+ continue;
1146
+ // Enforce PROTECTED_PATHS — never migrate FROM protected paths (prevents moving/deleting user data)
1147
+ if (isProtectedPath(item.from)) {
1148
+ result.skip.push(item);
1149
+ continue;
1150
+ }
1151
+ // For non-rename types, also block writing TO protected paths
1152
+ // rename/rename-dir are allowed to target protected paths (e.g., 0.2.0 renames into .trellis/workspace)
1153
+ if (item.to &&
1154
+ isProtectedPath(item.to) &&
1155
+ item.type !== "rename" &&
1156
+ item.type !== "rename-dir") {
1157
+ result.skip.push(item);
1158
+ continue;
1159
+ }
1160
+ const oldPath = path.join(cwd, item.from);
1161
+ const oldExists = fs.existsSync(oldPath);
1162
+ if (!oldExists) {
1163
+ // Old file doesn't exist, nothing to migrate
1164
+ result.skip.push(item);
1165
+ continue;
1166
+ }
1167
+ if (item.type === "rename" && item.to) {
1168
+ const newPath = path.join(cwd, item.to);
1169
+ const newExists = fs.existsSync(newPath);
1170
+ if (newExists) {
1171
+ // Both exist - check if new file matches template (safe to overwrite)
1172
+ if (isFileSafeToReplace(cwd, item.to, templates)) {
1173
+ // New file is just template content - safe to delete and rename
1174
+ result.auto.push(item);
1175
+ }
1176
+ else {
1177
+ // New file has user content - conflict
1178
+ result.conflict.push(item);
1179
+ }
1180
+ }
1181
+ else if (isTemplateModified(cwd, item.from, hashes)) {
1182
+ // User has modified the file - needs confirmation
1183
+ result.confirm.push(item);
1184
+ }
1185
+ else {
1186
+ // Unmodified template - safe to auto-migrate
1187
+ result.auto.push(item);
1188
+ }
1189
+ }
1190
+ else if (item.type === "rename-dir" && item.to) {
1191
+ const newPath = path.join(cwd, item.to);
1192
+ const newExists = fs.existsSync(newPath);
1193
+ if (newExists) {
1194
+ // Target exists - check if it only contains unmodified template files
1195
+ if (isDirectorySafeToReplace(cwd, item.to, hashes, templates)) {
1196
+ // Safe to delete target and rename source
1197
+ result.auto.push(item);
1198
+ }
1199
+ else {
1200
+ // Target has user modifications - conflict
1201
+ result.conflict.push(item);
1202
+ }
1203
+ }
1204
+ else if (dirHasManifestEntries(item.from, hashes)) {
1205
+ // Trellis created this directory (the manifest tracks files under it),
1206
+ // so the rename is ours to make.
1207
+ result.auto.push(item);
1208
+ }
1209
+ else {
1210
+ // Target absent and the source has no manifest record: this is very
1211
+ // likely a user-owned directory that merely shares a path with a
1212
+ // retired Trellis platform dir (e.g. a real `.windsurf/` editor
1213
+ // config). Skipping avoids silently moving the user's data out from
1214
+ // under their editor — even under --force, since skip never executes.
1215
+ result.skip.push(item);
1216
+ }
1217
+ }
1218
+ else if (item.type === "delete") {
1219
+ if (isTemplateModified(cwd, item.from, hashes)) {
1220
+ // User has modified - needs confirmation before delete
1221
+ result.confirm.push(item);
1222
+ }
1223
+ else {
1224
+ // Unmodified - safe to auto-delete
1225
+ result.auto.push(item);
1226
+ }
1227
+ }
1228
+ }
1229
+ return result;
1230
+ }
1231
+ /**
1232
+ * Print migration summary
1233
+ */
1234
+ function printMigrationSummary(classified) {
1235
+ const total = classified.auto.length +
1236
+ classified.confirm.length +
1237
+ classified.conflict.length +
1238
+ classified.skip.length;
1239
+ if (total === 0) {
1240
+ console.log(chalk.gray(" No migrations to apply.\n"));
1241
+ return;
1242
+ }
1243
+ if (classified.auto.length > 0) {
1244
+ console.log(chalk.green(" āœ“ Auto-migrate (unmodified):"));
1245
+ for (const item of classified.auto) {
1246
+ if (item.type === "rename") {
1247
+ console.log(chalk.green(` ${item.from} → ${item.to}`));
1248
+ }
1249
+ else if (item.type === "rename-dir") {
1250
+ console.log(chalk.green(` [dir] ${item.from}/ → ${item.to}/`));
1251
+ }
1252
+ else {
1253
+ console.log(chalk.green(` āœ• ${item.from}`));
1254
+ }
1255
+ }
1256
+ console.log("");
1257
+ }
1258
+ if (classified.confirm.length > 0) {
1259
+ console.log(chalk.yellow(" ⚠ Requires confirmation (modified by user):"));
1260
+ for (const item of classified.confirm) {
1261
+ if (item.type === "rename") {
1262
+ console.log(chalk.yellow(` ${item.from} → ${item.to}`));
1263
+ }
1264
+ else {
1265
+ console.log(chalk.yellow(` āœ• ${item.from}`));
1266
+ }
1267
+ }
1268
+ console.log("");
1269
+ }
1270
+ if (classified.conflict.length > 0) {
1271
+ console.log(chalk.red(" ⊘ Conflict (both old and new exist):"));
1272
+ for (const item of classified.conflict) {
1273
+ if (item.type === "rename-dir") {
1274
+ console.log(chalk.red(` [dir] ${item.from}/ ↔ ${item.to}/`));
1275
+ }
1276
+ else {
1277
+ console.log(chalk.red(` ${item.from} ↔ ${item.to}`));
1278
+ }
1279
+ }
1280
+ console.log(chalk.gray(" → Resolve manually: merge or delete one, then re-run update"));
1281
+ console.log("");
1282
+ }
1283
+ if (classified.skip.length > 0) {
1284
+ console.log(chalk.gray(" ā—‹ Skipping (not found, protected, or not Trellis-owned):"));
1285
+ for (const item of classified.skip.slice(0, 3)) {
1286
+ console.log(chalk.gray(` ${item.from}`));
1287
+ }
1288
+ if (classified.skip.length > 3) {
1289
+ console.log(chalk.gray(` ... and ${classified.skip.length - 3} more`));
1290
+ }
1291
+ console.log("");
1292
+ }
1293
+ }
1294
+ /**
1295
+ * Prompt user for migration action on a single item.
1296
+ *
1297
+ * Design notes:
1298
+ * - Default is `backup-rename`: safest — preserves user's content as a .backup
1299
+ * alongside the rename, so Enter-to-continue never destroys work or leaves
1300
+ * stale paths behind.
1301
+ * - "Skip" leaves a stale old path that won't be cleaned by later updates —
1302
+ * warn explicitly so users understand the consequence.
1303
+ * - Show manifest description + why-flagged so users can make an informed
1304
+ * choice without needing to dig through the diff.
1305
+ */
1306
+ async function promptMigrationAction(item) {
1307
+ const headline = item.type === "rename"
1308
+ ? `${chalk.cyan(item.from)} → ${chalk.green(item.to)}`
1309
+ : `${chalk.red("Delete")} ${chalk.cyan(item.from)}`;
1310
+ const description = item.description ?? "No description provided in manifest.";
1311
+ // Actions with inline guidance so users see the trade-off per choice.
1312
+ const renameLabel = item.type === "rename"
1313
+ ? "[r] Rename anyway — use if the file is unchanged, or any edits are fine to move as-is"
1314
+ : "[d] Delete anyway — use if you don't need this file (already migrated to replacement)";
1315
+ const backupLabel = item.type === "rename"
1316
+ ? "[b] Backup original, then proceed — SAFEST: writes <new-path>.backup with your current content, then renames"
1317
+ : "[b] Backup original, then proceed — SAFEST: writes <path>.backup with your current content, then deletes";
1318
+ const skipLabel = item.type === "rename"
1319
+ ? "[s] Skip — leaves the old path in place (you'll see it flagged on future updates until cleaned up manually)"
1320
+ : "[s] Skip — keeps the deprecated file (you'll see it flagged on future updates until cleaned up manually)";
1321
+ // Prefer the per-migration `reason` (version-specific context authored in the
1322
+ // manifest) over a generic fallback. Hardcoding version-specific hints here
1323
+ // rots fast — every release gets a new set of edge cases.
1324
+ const whyFlagged = item.reason
1325
+ ? chalk.gray(item.reason
1326
+ .split("\n")
1327
+ .map((line) => ` ${line}`)
1328
+ .join("\n"))
1329
+ : chalk.gray(` Why prompted: file content doesn't match the Trellis template hash\n` +
1330
+ ` for this path — usually local customization. If unsure, pick [b].`);
1331
+ const message = [
1332
+ headline,
1333
+ "",
1334
+ chalk.bold(" What:") + " " + description,
1335
+ whyFlagged,
1336
+ "",
1337
+ chalk.bold(" Choose:"),
1338
+ ].join("\n");
1339
+ const { choice } = await inquirer.prompt([
1340
+ {
1341
+ type: "list",
1342
+ name: "choice",
1343
+ message,
1344
+ choices: [
1345
+ { name: backupLabel, value: "backup-rename" },
1346
+ { name: renameLabel, value: "rename" },
1347
+ { name: skipLabel, value: "skip" },
1348
+ ],
1349
+ default: "backup-rename",
1350
+ },
1351
+ ]);
1352
+ return choice;
1353
+ }
1354
+ /**
1355
+ * Sort migrations for safe execution order
1356
+ * - rename-dir with deeper paths first (to handle nested directories)
1357
+ * - rename-dir before rename/delete
1358
+ */
1359
+ /** @internal Exported for testing only */
1360
+ export function sortMigrationsForExecution(migrations) {
1361
+ return [...migrations].sort((a, b) => {
1362
+ // rename-dir should be sorted by path depth (deeper first)
1363
+ if (a.type === "rename-dir" && b.type === "rename-dir") {
1364
+ const aDepth = a.from.split("/").length;
1365
+ const bDepth = b.from.split("/").length;
1366
+ return bDepth - aDepth; // Deeper paths first
1367
+ }
1368
+ // rename-dir before rename/delete (directories first)
1369
+ if (a.type === "rename-dir" && b.type !== "rename-dir")
1370
+ return -1;
1371
+ if (a.type !== "rename-dir" && b.type === "rename-dir")
1372
+ return 1;
1373
+ return 0;
1374
+ });
1375
+ }
1376
+ /**
1377
+ * Execute classified migrations
1378
+ *
1379
+ * @param options.force - Force migrate modified files without asking
1380
+ * @param options.skipAll - Skip all modified files without asking
1381
+ * If neither is set, prompts interactively for modified files
1382
+ */
1383
+ export async function executeMigrations(classified, cwd, options, templates) {
1384
+ const result = {
1385
+ renamed: 0,
1386
+ deleted: 0,
1387
+ skipped: 0,
1388
+ conflicts: classified.conflict.length,
1389
+ };
1390
+ // Sort migrations for safe execution order
1391
+ const sortedAuto = sortMigrationsForExecution(classified.auto);
1392
+ // 1. Execute auto migrations (unmodified files and directories)
1393
+ for (const item of sortedAuto) {
1394
+ if (item.type === "rename" && item.to) {
1395
+ const oldPath = path.join(cwd, item.from);
1396
+ const newPath = path.join(cwd, item.to);
1397
+ // Ensure target directory exists
1398
+ fs.mkdirSync(path.dirname(newPath), { recursive: true });
1399
+ fs.renameSync(oldPath, newPath);
1400
+ // Update hash tracking
1401
+ renameHash(cwd, item.from, item.to);
1402
+ // Make executable if it's a script
1403
+ if (item.to.endsWith(".sh") || item.to.endsWith(".py")) {
1404
+ fs.chmodSync(newPath, "755");
1405
+ }
1406
+ // Clean up empty source directory
1407
+ cleanupEmptyDirs(cwd, path.dirname(item.from));
1408
+ result.renamed++;
1409
+ }
1410
+ else if (item.type === "rename-dir" && item.to) {
1411
+ const oldPath = path.join(cwd, item.from);
1412
+ const newPath = path.join(cwd, item.to);
1413
+ const oldPrefix = item.from.endsWith("/") ? item.from : item.from + "/";
1414
+ const newPrefix = item.to.endsWith("/") ? item.to : item.to + "/";
1415
+ // Target already exists and already holds canonical, current-version
1416
+ // content (e.g. Codex/Gemini already wrote the shared `.agents/skills/`
1417
+ // root before Pi's legacy `.pi/skills/` copy gets retired). Renaming
1418
+ // the source in would clobber good content with older/differently-
1419
+ // flavored bytes, so just drop the now-redundant source instead (#447).
1420
+ if (fs.existsSync(newPath) &&
1421
+ dirMatchesCurrentTemplates(cwd, item.to, templates)) {
1422
+ removeDirectoryRecursive(oldPath);
1423
+ const hashes = loadHashes(cwd);
1424
+ const updatedHashes = {};
1425
+ for (const [hashPath, hashValue] of Object.entries(hashes)) {
1426
+ if (hashPath.startsWith(oldPrefix))
1427
+ continue; // source retired
1428
+ updatedHashes[hashPath] = hashValue;
1429
+ }
1430
+ saveHashes(cwd, updatedHashes);
1431
+ result.deleted++;
1432
+ continue;
1433
+ }
1434
+ // If target exists (safe to replace, already checked in classification)
1435
+ // delete it first before renaming
1436
+ if (fs.existsSync(newPath)) {
1437
+ removeDirectoryRecursive(newPath);
1438
+ }
1439
+ // Ensure parent directory exists
1440
+ fs.mkdirSync(path.dirname(newPath), { recursive: true });
1441
+ // Rename the entire directory (includes all user files)
1442
+ fs.renameSync(oldPath, newPath);
1443
+ // Batch update hash tracking for all files in the directory
1444
+ const hashes = loadHashes(cwd);
1445
+ const updatedHashes = {};
1446
+ for (const [hashPath, hashValue] of Object.entries(hashes)) {
1447
+ if (hashPath.startsWith(oldPrefix)) {
1448
+ // Rename path: old prefix -> new prefix
1449
+ const newHashPath = newPrefix + hashPath.slice(oldPrefix.length);
1450
+ updatedHashes[newHashPath] = hashValue;
1451
+ }
1452
+ else if (hashPath.startsWith(newPrefix)) {
1453
+ // Skip old hashes from deleted target directory
1454
+ // (they will be replaced by renamed source files)
1455
+ continue;
1456
+ }
1457
+ else {
1458
+ // Keep unchanged
1459
+ updatedHashes[hashPath] = hashValue;
1460
+ }
1461
+ }
1462
+ saveHashes(cwd, updatedHashes);
1463
+ result.renamed++;
1464
+ }
1465
+ else if (item.type === "delete") {
1466
+ const filePath = path.join(cwd, item.from);
1467
+ fs.unlinkSync(filePath);
1468
+ // Remove from hash tracking
1469
+ removeHash(cwd, item.from);
1470
+ // Clean up empty directory
1471
+ cleanupEmptyDirs(cwd, path.dirname(item.from));
1472
+ result.deleted++;
1473
+ }
1474
+ }
1475
+ // 2. Handle confirm items (modified files)
1476
+ // Note: All files are already backed up by createMigrationBackup before execution
1477
+ for (const item of classified.confirm) {
1478
+ let action;
1479
+ if (options.force) {
1480
+ // Force mode: proceed (already backed up)
1481
+ action = "rename";
1482
+ }
1483
+ else if (options.skipAll) {
1484
+ // Skip mode: skip all modified files
1485
+ action = "skip";
1486
+ }
1487
+ else {
1488
+ // Default: interactive prompt
1489
+ action = await promptMigrationAction(item);
1490
+ }
1491
+ if (action === "skip") {
1492
+ result.skipped++;
1493
+ continue;
1494
+ }
1495
+ // For `backup-rename`, leave an inline .backup copy of the user's modified
1496
+ // original next to the new location (for rename) or in place (for delete).
1497
+ // This is in addition to the full project snapshot at .trellis/.backup-*/;
1498
+ // the inline copy is more discoverable when the user wants to diff or merge
1499
+ // their customizations against the new template.
1500
+ if (item.type === "rename" && item.to) {
1501
+ const oldPath = path.join(cwd, item.from);
1502
+ const newPath = path.join(cwd, item.to);
1503
+ fs.mkdirSync(path.dirname(newPath), { recursive: true });
1504
+ if (action === "backup-rename") {
1505
+ // Copy original alongside the new path before the rename overwrites nothing
1506
+ // (target dir is guaranteed fresh since `conflict` is handled elsewhere).
1507
+ fs.copyFileSync(oldPath, newPath + ".backup");
1508
+ }
1509
+ fs.renameSync(oldPath, newPath);
1510
+ renameHash(cwd, item.from, item.to);
1511
+ if (item.to.endsWith(".sh") || item.to.endsWith(".py")) {
1512
+ fs.chmodSync(newPath, "755");
1513
+ }
1514
+ // Clean up empty source directory
1515
+ cleanupEmptyDirs(cwd, path.dirname(item.from));
1516
+ result.renamed++;
1517
+ }
1518
+ else if (item.type === "delete") {
1519
+ const filePath = path.join(cwd, item.from);
1520
+ if (action === "backup-rename") {
1521
+ // Keep a .backup copy in place before deletion so the user can recover
1522
+ // inline without digging through .trellis/.backup-*/.
1523
+ fs.copyFileSync(filePath, filePath + ".backup");
1524
+ }
1525
+ fs.unlinkSync(filePath);
1526
+ removeHash(cwd, item.from);
1527
+ // Clean up empty directory
1528
+ cleanupEmptyDirs(cwd, path.dirname(item.from));
1529
+ result.deleted++;
1530
+ }
1531
+ }
1532
+ // 3. Skip count already tracked (old files not found)
1533
+ result.skipped += classified.skip.length;
1534
+ return result;
1535
+ }
1536
+ /**
1537
+ * Print migration result summary
1538
+ */
1539
+ function printMigrationResult(result) {
1540
+ const parts = [];
1541
+ if (result.renamed > 0) {
1542
+ parts.push(`${result.renamed} renamed`);
1543
+ }
1544
+ if (result.deleted > 0) {
1545
+ parts.push(`${result.deleted} deleted`);
1546
+ }
1547
+ if (result.skipped > 0) {
1548
+ parts.push(`${result.skipped} skipped`);
1549
+ }
1550
+ if (result.conflicts > 0) {
1551
+ parts.push(`${result.conflicts} conflict${result.conflicts > 1 ? "s" : ""}`);
1552
+ }
1553
+ if (parts.length > 0) {
1554
+ console.log(chalk.cyan(`Migration complete: ${parts.join(", ")}`));
1555
+ }
1556
+ }
1557
+ /**
1558
+ * One-time 0.2.0 migration: rename `traces-*.md` → `journal-*.md` in every
1559
+ * developer workspace directory.
1560
+ *
1561
+ * Never overwrites an existing `journal-N.md`: a newer session may already
1562
+ * have created it, and `.trellis/workspace/` is excluded from the update
1563
+ * backup (see `BACKUP_EXCLUDE_PATTERNS`), so clobbering it would be
1564
+ * unrecoverable data loss. Conflicting `traces-N.md` files are left in place
1565
+ * and reported instead.
1566
+ */
1567
+ export function renameTracesToJournal(workspaceDir) {
1568
+ const skipped = [];
1569
+ let renamed = 0;
1570
+ if (!fs.existsSync(workspaceDir))
1571
+ return { renamed, skipped };
1572
+ for (const dev of fs.readdirSync(workspaceDir)) {
1573
+ const devPath = path.join(workspaceDir, dev);
1574
+ if (!fs.statSync(devPath).isDirectory())
1575
+ continue;
1576
+ for (const file of fs.readdirSync(devPath)) {
1577
+ if (!(file.startsWith("traces-") && file.endsWith(".md")))
1578
+ continue;
1579
+ const oldPath = path.join(devPath, file);
1580
+ const newPath = path.join(devPath, file.replace("traces-", "journal-"));
1581
+ if (fs.existsSync(newPath)) {
1582
+ skipped.push(oldPath);
1583
+ continue;
1584
+ }
1585
+ fs.renameSync(oldPath, newPath);
1586
+ renamed++;
1587
+ }
1588
+ }
1589
+ return { renamed, skipped };
1590
+ }
1591
+ /**
1592
+ * Main update command
1593
+ */
1594
+ export async function update(options) {
1595
+ const cwd = process.cwd();
1596
+ // Check if Trellis is initialized
1597
+ if (!fs.existsSync(path.join(cwd, DIR_NAMES.WORKFLOW))) {
1598
+ console.log(chalk.red("Error: Trellis not initialized in this directory."));
1599
+ console.log(chalk.gray(`Run '${COMMAND_NAME} init' first.`));
1600
+ return;
1601
+ }
1602
+ // downstream: 030-integration-controls
1603
+ // Preflight the recovery receipt before update mutates templates, and retain
1604
+ // the desired config even if config.yaml itself is refreshed later.
1605
+ const desiredIntegration = readIntegrationStatus(cwd).config;
1606
+ console.log(chalk.cyan("\nTrellis Update"));
1607
+ console.log(chalk.cyan("══════════════\n"));
1608
+ // Set up proxy before any network calls (npm version check)
1609
+ setupProxy();
1610
+ // Get versions
1611
+ const projectVersion = getInstalledVersion(cwd);
1612
+ const cliVersion = VERSION;
1613
+ const latestNpmVersion = await getLatestNpmVersion();
1614
+ // Version comparison
1615
+ const cliVsProject = compareVersions(cliVersion, projectVersion);
1616
+ const cliVsNpm = latestNpmVersion
1617
+ ? compareVersions(CLI_VERSION, latestNpmVersion)
1618
+ : 0;
1619
+ // Display versions with context
1620
+ console.log(`Project version: ${chalk.white(projectVersion)}`);
1621
+ console.log(`CLI version: ${chalk.white(CLI_VERSION)}`);
1622
+ if (latestNpmVersion) {
1623
+ console.log(`Latest on npm: ${chalk.white(latestNpmVersion)}`);
1624
+ }
1625
+ else {
1626
+ console.log(chalk.gray("Latest on npm: (unable to fetch)"));
1627
+ }
1628
+ console.log("");
1629
+ // Check if CLI is outdated compared to npm
1630
+ if (cliVsNpm < 0 && latestNpmVersion) {
1631
+ console.log(chalk.yellow(`āš ļø Your CLI (${CLI_VERSION}) is behind npm (${latestNpmVersion}).`));
1632
+ console.log(chalk.yellow(` Run: ${COMMAND_NAME} upgrade\n`));
1633
+ }
1634
+ // Check for downgrade situation
1635
+ if (cliVsProject < 0) {
1636
+ console.log(chalk.red(`āŒ Cannot update: CLI version (${cliVersion}) < project version (${projectVersion})`));
1637
+ console.log(chalk.red(` This would DOWNGRADE your project!\n`));
1638
+ if (!options.allowDowngrade) {
1639
+ console.log(chalk.gray("Solutions:"));
1640
+ console.log(chalk.gray(` 1. Update your CLI: ${COMMAND_NAME} upgrade`));
1641
+ console.log(chalk.gray(` 2. Force downgrade: ${COMMAND_NAME} update --allow-downgrade\n`));
1642
+ return;
1643
+ }
1644
+ console.log(chalk.yellow("āš ļø --allow-downgrade flag set. Proceeding with downgrade...\n"));
1645
+ }
1646
+ // Migration metadata is displayed at the end to prevent scrolling off screen
1647
+ // Load template hashes for modification detection
1648
+ let hashes = loadHashes(cwd);
1649
+ const zcodeConfigured = getConfiguredPlatforms(cwd).has("zcode");
1650
+ const isFirstHashTracking = Object.keys(hashes).length === 0;
1651
+ // Handle unknown version - skip regular migrations but safe-file-delete still runs
1652
+ const isUnknownVersion = projectVersion === "unknown";
1653
+ if (isUnknownVersion) {
1654
+ console.log(chalk.yellow(`āš ļø No version file found. Skipping migrations — run ${COMMAND_NAME} init to fix.`));
1655
+ console.log(chalk.gray(" Template updates will still be applied."));
1656
+ console.log(chalk.gray(" Safe file cleanup will still run (hash-verified).\n"));
1657
+ }
1658
+ // Detect legacy Codex (has .agents/skills/ tracked by Trellis but no .codex/)
1659
+ // NOTE: this MUST happen before pruneOrphanManifestKeys below, since the
1660
+ // detector reads the raw manifest looking for .agents/skills/ markers that
1661
+ // the prune step would otherwise consider orphans (codex hasn't been added
1662
+ // to configuredPlatforms yet at this point).
1663
+ const codexUpgradeNeeded = needsCodexUpgrade(cwd);
1664
+ if (codexUpgradeNeeded) {
1665
+ console.log(chalk.yellow(" Legacy Codex detected: .agents/skills/ tracked without .codex/ — will create .codex/ directory"));
1666
+ }
1667
+ // Self-heal poisoned manifests: prune entries that no current platform
1668
+ // configurator owns. This silently removes user-owned paths that early
1669
+ // buggy versions of `trellis init` over-hashed (e.g. .codex/sessions/*).
1670
+ // Include codex in known-platforms when codexUpgradeNeeded so legacy Codex
1671
+ // markers under .agents/skills/ survive into the upgrade flow.
1672
+ {
1673
+ const configuredPlatforms = new Set(getConfiguredPlatforms(cwd));
1674
+ if (codexUpgradeNeeded)
1675
+ configuredPlatforms.add("codex");
1676
+ const prune = pruneOrphanManifestKeys(cwd, [...configuredPlatforms], hashes);
1677
+ if (prune.pruned.length > 0) {
1678
+ console.log(chalk.gray(` Pruned ${prune.pruned.length} orphan manifest entries from .template-hashes.json`));
1679
+ hashes = prune.hashes;
1680
+ }
1681
+ }
1682
+ // For breaking releases with recommendMigrate + --migrate, bypass update.skip
1683
+ // across the board (safe-file-delete, new file writes, template updates).
1684
+ // Why: honoring skip here leaves users forever half-migrated — old deprecated
1685
+ // files persist under skip-protected paths, new commands like `continue.md`
1686
+ // never land, and every future update re-flags the same mess. Rename
1687
+ // migrations already ignore update.skip; this makes the rest consistent
1688
+ // during a breaking upgrade. User customizations are still guarded by the
1689
+ // per-file conflict prompt ("Modified by you") at write time.
1690
+ const breakingBypass = options.migrate === true &&
1691
+ cliVsProject > 0 &&
1692
+ projectVersion !== "unknown" &&
1693
+ (() => {
1694
+ const md = getMigrationMetadata(projectVersion, cliVersion);
1695
+ return md.breaking && md.recommendMigrate;
1696
+ })();
1697
+ // Collect templates (used for both migration classification and change analysis)
1698
+ const templates = await collectTemplateFiles(cwd, codexUpgradeNeeded ? new Set(["codex"]) : undefined, breakingBypass);
1699
+ // Load update.skip paths (used for both safe-file-delete and template collection)
1700
+ const skipPaths = loadUpdateSkipPaths(cwd);
1701
+ // Collect safe-file-delete items from ALL manifests (hash match is the safety net)
1702
+ // This runs regardless of version — unknown version still gets safe cleanup
1703
+ const allMigrations = getAllMigrations();
1704
+ const safeFileDeletes = collectSafeFileDeletes(allMigrations, cwd, skipPaths, new Set(templates.keys()), breakingBypass);
1705
+ const hasSafeDeletes = safeFileDeletes.filter((c) => c.action === "delete").length > 0;
1706
+ // Check for pending regular migrations (skip if unknown version)
1707
+ let pendingMigrations = isUnknownVersion
1708
+ ? []
1709
+ : getMigrationsForVersion(projectVersion, cliVersion);
1710
+ // Also check for "orphaned" migrations - where source still exists but version says we shouldn't migrate
1711
+ // This handles cases where version was updated but migrations weren't applied
1712
+ const orphanedMigrations = allMigrations.filter((item) => {
1713
+ // Only check rename and rename-dir migrations
1714
+ if (item.type !== "rename" && item.type !== "rename-dir")
1715
+ return false;
1716
+ if (!item.from || !item.to)
1717
+ return false;
1718
+ const oldPath = path.join(cwd, item.from);
1719
+ const newPath = path.join(cwd, item.to);
1720
+ // Orphaned if: source exists AND target doesn't exist
1721
+ // AND this migration isn't already in pendingMigrations
1722
+ const sourceExists = fs.existsSync(oldPath);
1723
+ const targetExists = fs.existsSync(newPath);
1724
+ const alreadyPending = pendingMigrations.some((m) => m.from === item.from && m.to === item.to);
1725
+ return sourceExists && !targetExists && !alreadyPending;
1726
+ });
1727
+ // Add orphaned migrations to pending (they need to be applied)
1728
+ if (orphanedMigrations.length > 0) {
1729
+ console.log(chalk.yellow("āš ļø Detected incomplete migrations from previous updates:"));
1730
+ for (const item of orphanedMigrations) {
1731
+ console.log(chalk.yellow(` ${item.from} → ${item.to}`));
1732
+ }
1733
+ console.log("");
1734
+ pendingMigrations = [...pendingMigrations, ...orphanedMigrations];
1735
+ }
1736
+ const hasMigrations = pendingMigrations.length > 0;
1737
+ // Classify migrations (stored for later backup creation)
1738
+ let classifiedMigrations = null;
1739
+ if (hasMigrations) {
1740
+ console.log(chalk.cyan("Analyzing migrations...\n"));
1741
+ classifiedMigrations = classifyMigrations(pendingMigrations, cwd, hashes, templates);
1742
+ printMigrationSummary(classifiedMigrations);
1743
+ // Hard-stop: pending rename/delete work from a breaking release requires --migrate.
1744
+ // Why: without --migrate, those entries are skipped and update()'s later path silently
1745
+ // bumps the version stamp, leaving old paths orphaned next to new templates. Force
1746
+ // explicit opt-in so the user can't half-migrate by accident.
1747
+ const pendingMigrationCount = classifiedMigrations.auto.length +
1748
+ classifiedMigrations.confirm.length +
1749
+ classifiedMigrations.conflict.length;
1750
+ if (pendingMigrationCount > 0 &&
1751
+ !options.migrate &&
1752
+ !options.dryRun &&
1753
+ cliVsProject > 0 &&
1754
+ projectVersion !== "unknown") {
1755
+ const gateMetadata = getMigrationMetadata(projectVersion, cliVersion);
1756
+ if (gateMetadata.breaking && gateMetadata.recommendMigrate) {
1757
+ console.log(chalk.bgRed.white.bold(" āœ– MIGRATION REQUIRED ") +
1758
+ chalk.red(` Breaking changes between ${projectVersion} → ${cliVersion} require --migrate.`));
1759
+ console.log("");
1760
+ console.log(chalk.yellow(` Run: ${COMMAND_NAME} update --migrate`));
1761
+ console.log("");
1762
+ console.log(chalk.gray(" Without --migrate, renamed/relocated files from breaking releases aren't moved,\n" +
1763
+ " leaving your project with stale paths alongside new templates.\n" +
1764
+ " Use --dry-run to preview what --migrate will do."));
1765
+ process.exit(1);
1766
+ }
1767
+ }
1768
+ // Soft hint: non-breaking migrations or projects that chose not to set recommendMigrate
1769
+ if (!options.migrate) {
1770
+ const autoCount = classifiedMigrations.auto.length;
1771
+ const confirmCount = classifiedMigrations.confirm.length;
1772
+ if (autoCount > 0 || confirmCount > 0) {
1773
+ console.log(chalk.gray(`Tip: Use --migrate to apply migrations (prompts for modified files).`));
1774
+ if (confirmCount > 0) {
1775
+ console.log(chalk.gray(` Use --migrate -f to force all, or --migrate -s to skip modified.\n`));
1776
+ }
1777
+ else {
1778
+ console.log("");
1779
+ }
1780
+ }
1781
+ }
1782
+ }
1783
+ // Print safe-file-delete summary (always shown, runs without --migrate)
1784
+ if (safeFileDeletes.length > 0) {
1785
+ printSafeFileDeleteSummary(safeFileDeletes);
1786
+ }
1787
+ // Analyze changes (pass hashes for modification detection)
1788
+ const changes = analyzeChanges(cwd, hashes, templates);
1789
+ const unchangedFileHashRepairs = collectUnchangedFileHashRepairs(changes, hashes);
1790
+ // Print summary
1791
+ printChangeSummary(changes);
1792
+ // First-time hash tracking hint
1793
+ if (isFirstHashTracking && changes.changedFiles.length > 0) {
1794
+ console.log(chalk.cyan("ā„¹ļø First update with hash tracking enabled."));
1795
+ console.log(chalk.gray(" Changed files shown above may not be actual user modifications."));
1796
+ console.log(chalk.gray(" After this update, hash tracking will accurately detect changes.\n"));
1797
+ }
1798
+ // Ensure project-root .gitattributes carries the journal merge=union rule.
1799
+ // Additive-only (see ensureGitattributes) — runs regardless of whether
1800
+ // other template files changed, so it must sit before the "nothing to do"
1801
+ // early-return below. Never touches disk in --dry-run.
1802
+ if (!options.dryRun) {
1803
+ ensureGitattributes(cwd);
1804
+ }
1805
+ // Check if there's anything to do
1806
+ const isUpgrade = cliVsProject > 0;
1807
+ const isDowngrade = cliVsProject < 0;
1808
+ const isSameVersion = cliVsProject === 0;
1809
+ // Check if we have pending migrations that need to be applied
1810
+ const hasPendingMigrations = options.migrate &&
1811
+ classifiedMigrations &&
1812
+ (classifiedMigrations.auto.length > 0 ||
1813
+ classifiedMigrations.confirm.length > 0);
1814
+ if (changes.newFiles.length === 0 &&
1815
+ changes.autoUpdateFiles.length === 0 &&
1816
+ changes.changedFiles.length === 0 &&
1817
+ !hasPendingMigrations &&
1818
+ !hasSafeDeletes) {
1819
+ // The "already up to date" exit still has to repair the receipt: this is
1820
+ // exactly the clean tree where every file is `unchanged`, so it is the run
1821
+ // where a wrong entry would otherwise be skipped again.
1822
+ if (!options.dryRun && unchangedFileHashRepairs.size > 0) {
1823
+ updateHashes(cwd, unchangedFileHashRepairs);
1824
+ }
1825
+ if (isSameVersion) {
1826
+ console.log(chalk.green("āœ“ Already up to date!"));
1827
+ }
1828
+ else {
1829
+ // Version changed but no file changes needed — still update the version stamp
1830
+ updateVersionFile(cwd);
1831
+ if (isUpgrade) {
1832
+ console.log(chalk.green(`āœ“ No file changes needed for ${projectVersion} → ${cliVersion}`));
1833
+ }
1834
+ else if (isDowngrade) {
1835
+ console.log(chalk.green(`āœ“ No file changes needed for ${projectVersion} → ${cliVersion} (downgrade)`));
1836
+ }
1837
+ }
1838
+ if (zcodeConfigured)
1839
+ printZcodeSetupHint();
1840
+ if (!options.dryRun) {
1841
+ // downstream: 030-integration-controls
1842
+ applyIntegration(planIntegration(cwd, desiredIntegration));
1843
+ }
1844
+ return;
1845
+ }
1846
+ // Show what this operation will do
1847
+ if (isUpgrade) {
1848
+ console.log(chalk.green(`This will UPGRADE: ${projectVersion} → ${cliVersion}\n`));
1849
+ }
1850
+ else if (isDowngrade) {
1851
+ console.log(chalk.red(`āš ļø This will DOWNGRADE: ${projectVersion} → ${cliVersion}\n`));
1852
+ }
1853
+ // Show breaking change warning before confirm
1854
+ if (cliVsProject > 0 && projectVersion !== "unknown") {
1855
+ const preConfirmMetadata = getMigrationMetadata(projectVersion, cliVersion);
1856
+ if (preConfirmMetadata.breaking) {
1857
+ console.log(chalk.cyan("═".repeat(60)));
1858
+ console.log(chalk.bgRed.white.bold(" āš ļø BREAKING CHANGES ") +
1859
+ chalk.red.bold(" Review the changes above carefully!"));
1860
+ if (preConfirmMetadata.changelog.length > 0) {
1861
+ console.log("");
1862
+ console.log(chalk.white(preConfirmMetadata.changelog[0]));
1863
+ }
1864
+ if (preConfirmMetadata.recommendMigrate && !options.migrate) {
1865
+ console.log("");
1866
+ console.log(chalk.bgGreen.black.bold(" šŸ’” RECOMMENDED ") +
1867
+ chalk.green.bold(" Run with --migrate to complete the migration"));
1868
+ }
1869
+ // Notice when update.skip is bypassed so user isn't surprised when
1870
+ // skipPaths-protected files get cleaned up during this breaking upgrade.
1871
+ if (breakingBypass && skipPaths.length > 0) {
1872
+ const willBypass = safeFileDeletes.filter((c) => c.action === "delete" &&
1873
+ skipPaths.some((skip) => c.item.from === skip ||
1874
+ c.item.from.startsWith(skip.endsWith("/") ? skip : skip + "/")));
1875
+ if (willBypass.length > 0) {
1876
+ console.log("");
1877
+ console.log(chalk.bgYellow.black.bold(" ⚠ update.skip BYPASSED ") +
1878
+ chalk.yellow.bold(` Breaking release — ${willBypass.length.toString()} file(s) under your update.skip paths will be cleaned up.`));
1879
+ console.log(chalk.gray(" Hash-verified: only files matching known Trellis templates are deleted. Your local customizations (hash mismatch) are still preserved."));
1880
+ }
1881
+ }
1882
+ console.log(chalk.cyan("═".repeat(60)));
1883
+ console.log("");
1884
+ }
1885
+ }
1886
+ // Dry run mode
1887
+ if (options.dryRun) {
1888
+ console.log(chalk.gray("[Dry run] No changes made."));
1889
+ return;
1890
+ }
1891
+ // Batch-resolution flags are explicit consent for non-interactive runs.
1892
+ // Prompting here breaks CI and `node ... update --force --migrate` smoke tests.
1893
+ if (!options.force && !options.skipAll && !options.createNew) {
1894
+ const { proceed } = await inquirer.prompt([
1895
+ {
1896
+ type: "confirm",
1897
+ name: "proceed",
1898
+ message: "Proceed?",
1899
+ default: true,
1900
+ },
1901
+ ]);
1902
+ if (!proceed) {
1903
+ console.log(chalk.yellow("Update cancelled."));
1904
+ return;
1905
+ }
1906
+ }
1907
+ // Create complete backup of all managed platform/workflow directories
1908
+ const backupDir = createFullBackup(cwd);
1909
+ if (backupDir) {
1910
+ console.log(chalk.gray(`\nBackup created: ${path.relative(cwd, backupDir)}/`));
1911
+ }
1912
+ // Execute migrations if --migrate flag is set
1913
+ if (options.migrate && classifiedMigrations) {
1914
+ const migrationResult = await executeMigrations(classifiedMigrations, cwd, {
1915
+ force: options.force,
1916
+ skipAll: options.skipAll,
1917
+ }, templates);
1918
+ printMigrationResult(migrationResult);
1919
+ // Hardcoded: Rename traces-*.md to journal-*.md in workspace directories
1920
+ // Why hardcoded: The migration system only supports fixed path renames, not pattern-based.
1921
+ // traces-*.md files are in .trellis/workspace/{developer}/ with variable developer names
1922
+ // and variable file numbers (traces-1.md, traces-2.md, etc.), so we can't enumerate them
1923
+ // in the migration manifest. This is a one-time migration for the 0.2.0 naming redesign.
1924
+ const workspaceDir = path.join(cwd, PATHS.WORKSPACE);
1925
+ const { renamed: journalRenamed, skipped: journalSkipped } = renameTracesToJournal(workspaceDir);
1926
+ if (journalRenamed > 0) {
1927
+ console.log(chalk.cyan(`Renamed ${journalRenamed} traces file(s) to journal`));
1928
+ }
1929
+ for (const oldPath of journalSkipped) {
1930
+ console.warn(chalk.yellow(`Kept ${path.relative(cwd, oldPath)}: its journal target already exists`));
1931
+ }
1932
+ }
1933
+ // Execute safe-file-delete (after backup, before template writes)
1934
+ let safeDeleted = 0;
1935
+ if (hasSafeDeletes) {
1936
+ safeDeleted = executeSafeFileDeletes(safeFileDeletes, cwd);
1937
+ if (safeDeleted > 0) {
1938
+ console.log(chalk.cyan(`\nCleaned up ${safeDeleted} deprecated command file(s)`));
1939
+ }
1940
+ }
1941
+ // Track results
1942
+ let added = 0;
1943
+ let autoUpdated = 0;
1944
+ let updated = 0;
1945
+ let skipped = 0;
1946
+ let createdNew = 0;
1947
+ // Add new files
1948
+ if (changes.newFiles.length > 0) {
1949
+ console.log(chalk.blue("\nAdding new files..."));
1950
+ for (const file of changes.newFiles) {
1951
+ const dir = path.dirname(file.path);
1952
+ fs.mkdirSync(dir, { recursive: true });
1953
+ fs.writeFileSync(file.path, file.newContent);
1954
+ // Make scripts executable
1955
+ if (file.relativePath.endsWith(".sh") ||
1956
+ file.relativePath.endsWith(".py")) {
1957
+ fs.chmodSync(file.path, "755");
1958
+ }
1959
+ console.log(chalk.green(` + ${file.relativePath}`));
1960
+ added++;
1961
+ }
1962
+ }
1963
+ // Auto-update files (template updated, user didn't modify)
1964
+ if (changes.autoUpdateFiles.length > 0) {
1965
+ console.log(chalk.blue("\nAuto-updating template files..."));
1966
+ for (const file of changes.autoUpdateFiles) {
1967
+ fs.writeFileSync(file.path, file.newContent);
1968
+ // Make scripts executable
1969
+ if (file.relativePath.endsWith(".sh") ||
1970
+ file.relativePath.endsWith(".py")) {
1971
+ fs.chmodSync(file.path, "755");
1972
+ }
1973
+ console.log(chalk.cyan(` ↑ ${file.relativePath}`));
1974
+ autoUpdated++;
1975
+ }
1976
+ }
1977
+ // Handle changed files
1978
+ if (changes.changedFiles.length > 0) {
1979
+ console.log(chalk.blue("\n--- Resolving conflicts ---\n"));
1980
+ const applyToAll = { action: null };
1981
+ for (const file of changes.changedFiles) {
1982
+ const action = await promptConflictResolution(file, options, applyToAll);
1983
+ if (action === "overwrite") {
1984
+ fs.writeFileSync(file.path, file.newContent);
1985
+ if (file.relativePath.endsWith(".sh") ||
1986
+ file.relativePath.endsWith(".py")) {
1987
+ fs.chmodSync(file.path, "755");
1988
+ }
1989
+ console.log(chalk.yellow(` āœ“ Overwritten: ${file.relativePath}`));
1990
+ updated++;
1991
+ }
1992
+ else if (action === "create-new") {
1993
+ const newPath = file.path + ".new";
1994
+ fs.writeFileSync(newPath, file.newContent);
1995
+ console.log(chalk.blue(` āœ“ Created: ${file.relativePath}.new`));
1996
+ createdNew++;
1997
+ }
1998
+ else {
1999
+ console.log(chalk.gray(` ā—‹ Skipped: ${file.relativePath}`));
2000
+ skipped++;
2001
+ }
2002
+ }
2003
+ }
2004
+ // Append additive config.yaml sections introduced between versions.
2005
+ // Sentinel-gated, so users keep their customizations and re-running update
2006
+ // on already-migrated files is a no-op. Skipped on unknown / downgrade.
2007
+ let configSectionsAppended = 0;
2008
+ if (cliVsProject > 0 && projectVersion !== "unknown") {
2009
+ const sectionEntries = getConfigSectionsAddedBetween(projectVersion, cliVersion);
2010
+ if (sectionEntries.length > 0) {
2011
+ const { appended } = applyConfigSectionsAdded(sectionEntries, cwd, templates);
2012
+ configSectionsAppended = appended;
2013
+ }
2014
+ }
2015
+ // Update version file
2016
+ updateVersionFile(cwd);
2017
+ // Update template hashes for new, auto-updated, and overwritten files
2018
+ const filesToHash = new Map(unchangedFileHashRepairs);
2019
+ for (const file of changes.newFiles) {
2020
+ filesToHash.set(file.relativePath, file.newContent);
2021
+ }
2022
+ // Auto-updated files always get new hash
2023
+ for (const file of changes.autoUpdateFiles) {
2024
+ filesToHash.set(file.relativePath, file.newContent);
2025
+ }
2026
+ // Only hash overwritten files (not skipped or .new copies)
2027
+ for (const file of changes.changedFiles) {
2028
+ const fullPath = path.join(cwd, file.relativePath);
2029
+ if (fs.existsSync(fullPath)) {
2030
+ const content = fs.readFileSync(fullPath, "utf-8");
2031
+ if (content === file.newContent) {
2032
+ filesToHash.set(file.relativePath, file.newContent);
2033
+ }
2034
+ }
2035
+ }
2036
+ if (filesToHash.size > 0) {
2037
+ updateHashes(cwd, filesToHash);
2038
+ }
2039
+ // downstream: 030-integration-controls
2040
+ // Park newly introduced disabled resources and refresh pristine parked
2041
+ // copies from current templates without exposing stale restore content.
2042
+ applyIntegration(planIntegration(cwd, desiredIntegration));
2043
+ // Print summary
2044
+ console.log(chalk.cyan("\n--- Summary ---\n"));
2045
+ if (added > 0) {
2046
+ console.log(` Added: ${added} file(s)`);
2047
+ }
2048
+ if (autoUpdated > 0) {
2049
+ console.log(` Auto-updated: ${autoUpdated} file(s)`);
2050
+ }
2051
+ if (updated > 0) {
2052
+ console.log(` Updated: ${updated} file(s)`);
2053
+ }
2054
+ if (skipped > 0) {
2055
+ console.log(` Skipped: ${skipped} file(s)`);
2056
+ }
2057
+ if (createdNew > 0) {
2058
+ console.log(` Created .new copies: ${createdNew} file(s)`);
2059
+ }
2060
+ if (safeDeleted > 0) {
2061
+ console.log(` Cleaned up: ${safeDeleted} deprecated file(s)`);
2062
+ }
2063
+ if (configSectionsAppended > 0) {
2064
+ console.log(` Config sections added: ${configSectionsAppended}`);
2065
+ }
2066
+ if (backupDir) {
2067
+ console.log(` Backup: ${path.relative(cwd, backupDir)}/`);
2068
+ }
2069
+ const actionWord = isDowngrade ? "Downgrade" : "Update";
2070
+ console.log(chalk.green(`\nāœ… ${actionWord} complete! (${projectVersion} → ${cliVersion})`));
2071
+ if (createdNew > 0) {
2072
+ console.log(chalk.gray("\nTip: Review .new files and merge changes manually if needed."));
2073
+ }
2074
+ // Create migration task if there are breaking changes with migration guides
2075
+ if (cliVsProject > 0 && projectVersion !== "unknown") {
2076
+ const metadata = getMigrationMetadata(projectVersion, cliVersion);
2077
+ if (metadata.breaking && metadata.migrationGuides.length > 0) {
2078
+ // Create task directory
2079
+ const today = new Date();
2080
+ const monthDay = `${String(today.getMonth() + 1).padStart(2, "0")}-${String(today.getDate()).padStart(2, "0")}`;
2081
+ const taskSlug = `migrate-to-${cliVersion}`;
2082
+ const taskDirName = `${monthDay}-${taskSlug}`;
2083
+ const tasksDir = path.join(cwd, DIR_NAMES.WORKFLOW, DIR_NAMES.TASKS);
2084
+ const taskDir = path.join(tasksDir, taskDirName);
2085
+ // Check if task already exists
2086
+ if (!fs.existsSync(taskDir)) {
2087
+ fs.mkdirSync(taskDir, { recursive: true });
2088
+ // Get current developer for assignee.
2089
+ // `.developer` is a key=value file (written by init_developer.py):
2090
+ // name=<developer-name>
2091
+ // initialized_at=<iso8601>
2092
+ // Reading it raw and .trim()-ing embeds the entire file contents
2093
+ // (including the `name=` prefix and the `initialized_at` line) into
2094
+ // the assignee field, producing bogus assignees like
2095
+ // "name=suyuan\ninitialized_at=2026-04-07T23:41:21.978312" that
2096
+ // later break session-start task rendering.
2097
+ const developerFile = path.join(cwd, DIR_NAMES.WORKFLOW, ".developer");
2098
+ let currentDeveloper = "unknown";
2099
+ if (fs.existsSync(developerFile)) {
2100
+ const raw = fs.readFileSync(developerFile, "utf-8");
2101
+ const nameMatch = raw.match(/^\s*name\s*=\s*(.+?)\s*$/m);
2102
+ if (nameMatch) {
2103
+ currentDeveloper = nameMatch[1];
2104
+ }
2105
+ }
2106
+ // Build task.json — canonical 24-field shape via shared factory.
2107
+ const taskTitle = `Migrate to v${cliVersion}`;
2108
+ const todayStr = today.toISOString().split("T")[0];
2109
+ const taskJson = emptyTaskJson({
2110
+ id: taskSlug,
2111
+ name: taskSlug,
2112
+ title: taskTitle,
2113
+ description: `Breaking change migration from v${projectVersion} to v${cliVersion}`,
2114
+ status: "planning",
2115
+ scope: "migration",
2116
+ priority: "P1",
2117
+ creator: "trellis-update",
2118
+ assignee: currentDeveloper,
2119
+ createdAt: todayStr,
2120
+ });
2121
+ // Write task.json
2122
+ const taskJsonPath = path.join(taskDir, "task.json");
2123
+ fs.writeFileSync(taskJsonPath, JSON.stringify(taskJson, null, 2));
2124
+ // Build PRD content
2125
+ let prdContent = `# Migration Task: Upgrade to v${cliVersion}\n\n`;
2126
+ prdContent += `**Created**: ${todayStr}\n`;
2127
+ prdContent += `**From Version**: ${projectVersion}\n`;
2128
+ prdContent += `**To Version**: ${cliVersion}\n`;
2129
+ prdContent += `**Assignee**: ${currentDeveloper}\n\n`;
2130
+ prdContent += `## Status\n\n- [ ] Review migration guide\n- [ ] Update custom files\n- [ ] Run \`${COMMAND_NAME} update --migrate\`\n- [ ] Test workflows\n\n`;
2131
+ for (const { version, guide, aiInstructions, } of metadata.migrationGuides) {
2132
+ prdContent += `---\n\n## v${version} Migration Guide\n\n`;
2133
+ prdContent += guide;
2134
+ prdContent += "\n\n";
2135
+ if (aiInstructions) {
2136
+ prdContent += `### AI Assistant Instructions\n\n`;
2137
+ prdContent += `When helping with this migration:\n\n`;
2138
+ prdContent += aiInstructions;
2139
+ prdContent += "\n\n";
2140
+ }
2141
+ }
2142
+ // Write PRD
2143
+ const prdPath = path.join(taskDir, "prd.md");
2144
+ fs.writeFileSync(prdPath, prdContent);
2145
+ console.log("");
2146
+ console.log(chalk.bgCyan.black.bold(" šŸ“‹ MIGRATION TASK CREATED "));
2147
+ console.log(chalk.cyan(`A task has been created to help you complete the migration:`));
2148
+ console.log(chalk.white(` ${DIR_NAMES.WORKFLOW}/${DIR_NAMES.TASKS}/${taskDirName}/`));
2149
+ console.log("");
2150
+ console.log(chalk.gray("Use AI to help: Ask Claude/Cursor to read the task and fix your custom files."));
2151
+ }
2152
+ }
2153
+ }
2154
+ if (zcodeConfigured)
2155
+ printZcodeSetupHint();
2156
+ // Display breaking change warnings at the very end (so they don't scroll off screen)
2157
+ if (cliVsProject > 0 && projectVersion !== "unknown") {
2158
+ const finalMetadata = getMigrationMetadata(projectVersion, cliVersion);
2159
+ if (finalMetadata.breaking || finalMetadata.changelog.length > 0) {
2160
+ console.log("");
2161
+ console.log(chalk.cyan("═".repeat(60)));
2162
+ if (finalMetadata.breaking) {
2163
+ console.log(chalk.bgRed.white.bold(" āš ļø BREAKING CHANGES ") +
2164
+ chalk.red.bold(" This update contains breaking changes!"));
2165
+ console.log("");
2166
+ }
2167
+ if (finalMetadata.changelog.length > 0) {
2168
+ console.log(chalk.cyan.bold("šŸ“‹ What's Changed:"));
2169
+ for (const entry of finalMetadata.changelog) {
2170
+ console.log(chalk.white(` ${entry}`));
2171
+ }
2172
+ console.log("");
2173
+ }
2174
+ if (finalMetadata.recommendMigrate && !options.migrate) {
2175
+ console.log(chalk.bgGreen.black.bold(" šŸ’” RECOMMENDED ") +
2176
+ chalk.green.bold(" Run with --migrate to complete the migration"));
2177
+ console.log(chalk.gray(" This will remove legacy files and apply all changes."));
2178
+ console.log("");
2179
+ }
2180
+ console.log(chalk.cyan("═".repeat(60)));
2181
+ }
2182
+ }
2183
+ }
2184
+ //# sourceMappingURL=update.js.map