@mindfoldhq/trellis 0.6.0-beta.2 → 0.6.0-beta.21

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 (331) hide show
  1. package/README.md +1 -1
  2. package/dist/cli/index.d.ts +1 -1
  3. package/dist/cli/index.d.ts.map +1 -1
  4. package/dist/cli/index.js +58 -2
  5. package/dist/cli/index.js.map +1 -1
  6. package/dist/commands/channel/adapters/claude.d.ts +29 -0
  7. package/dist/commands/channel/adapters/claude.d.ts.map +1 -0
  8. package/dist/commands/channel/adapters/claude.js +203 -0
  9. package/dist/commands/channel/adapters/claude.js.map +1 -0
  10. package/dist/commands/channel/adapters/codex.d.ts +85 -0
  11. package/dist/commands/channel/adapters/codex.d.ts.map +1 -0
  12. package/dist/commands/channel/adapters/codex.js +505 -0
  13. package/dist/commands/channel/adapters/codex.js.map +1 -0
  14. package/dist/commands/channel/adapters/index.d.ts +84 -0
  15. package/dist/commands/channel/adapters/index.d.ts.map +1 -0
  16. package/dist/commands/channel/adapters/index.js +115 -0
  17. package/dist/commands/channel/adapters/index.js.map +1 -0
  18. package/dist/commands/channel/adapters/types.d.ts +33 -0
  19. package/dist/commands/channel/adapters/types.d.ts.map +1 -0
  20. package/dist/commands/channel/adapters/types.js +2 -0
  21. package/dist/commands/channel/adapters/types.js.map +1 -0
  22. package/dist/commands/channel/agent-loader.d.ts +32 -0
  23. package/dist/commands/channel/agent-loader.d.ts.map +1 -0
  24. package/dist/commands/channel/agent-loader.js +154 -0
  25. package/dist/commands/channel/agent-loader.js.map +1 -0
  26. package/dist/commands/channel/context-loader.d.ts +26 -0
  27. package/dist/commands/channel/context-loader.d.ts.map +1 -0
  28. package/dist/commands/channel/context-loader.js +290 -0
  29. package/dist/commands/channel/context-loader.js.map +1 -0
  30. package/dist/commands/channel/context.d.ts +16 -0
  31. package/dist/commands/channel/context.d.ts.map +1 -0
  32. package/dist/commands/channel/context.js +83 -0
  33. package/dist/commands/channel/context.js.map +1 -0
  34. package/dist/commands/channel/create.d.ts +27 -0
  35. package/dist/commands/channel/create.d.ts.map +1 -0
  36. package/dist/commands/channel/create.js +39 -0
  37. package/dist/commands/channel/create.js.map +1 -0
  38. package/dist/commands/channel/dev-parse-trace.d.ts +14 -0
  39. package/dist/commands/channel/dev-parse-trace.d.ts.map +1 -0
  40. package/dist/commands/channel/dev-parse-trace.js +70 -0
  41. package/dist/commands/channel/dev-parse-trace.js.map +1 -0
  42. package/dist/commands/channel/guard.d.ts +150 -0
  43. package/dist/commands/channel/guard.d.ts.map +1 -0
  44. package/dist/commands/channel/guard.js +474 -0
  45. package/dist/commands/channel/guard.js.map +1 -0
  46. package/dist/commands/channel/index.d.ts +3 -0
  47. package/dist/commands/channel/index.d.ts.map +1 -0
  48. package/dist/commands/channel/index.js +531 -0
  49. package/dist/commands/channel/index.js.map +1 -0
  50. package/dist/commands/channel/interrupt.d.ts +10 -0
  51. package/dist/commands/channel/interrupt.d.ts.map +1 -0
  52. package/dist/commands/channel/interrupt.js +22 -0
  53. package/dist/commands/channel/interrupt.js.map +1 -0
  54. package/dist/commands/channel/kill.d.ts +7 -0
  55. package/dist/commands/channel/kill.d.ts.map +1 -0
  56. package/dist/commands/channel/kill.js +121 -0
  57. package/dist/commands/channel/kill.js.map +1 -0
  58. package/dist/commands/channel/list.d.ts +17 -0
  59. package/dist/commands/channel/list.d.ts.map +1 -0
  60. package/dist/commands/channel/list.js +233 -0
  61. package/dist/commands/channel/list.js.map +1 -0
  62. package/dist/commands/channel/messages.d.ts +15 -0
  63. package/dist/commands/channel/messages.d.ts.map +1 -0
  64. package/dist/commands/channel/messages.js +245 -0
  65. package/dist/commands/channel/messages.js.map +1 -0
  66. package/dist/commands/channel/rm.d.ts +27 -0
  67. package/dist/commands/channel/rm.d.ts.map +1 -0
  68. package/dist/commands/channel/rm.js +216 -0
  69. package/dist/commands/channel/rm.js.map +1 -0
  70. package/dist/commands/channel/run.d.ts +30 -0
  71. package/dist/commands/channel/run.d.ts.map +1 -0
  72. package/dist/commands/channel/run.js +130 -0
  73. package/dist/commands/channel/run.js.map +1 -0
  74. package/dist/commands/channel/send.d.ts +11 -0
  75. package/dist/commands/channel/send.d.ts.map +1 -0
  76. package/dist/commands/channel/send.js +24 -0
  77. package/dist/commands/channel/send.js.map +1 -0
  78. package/dist/commands/channel/spawn.d.ts +40 -0
  79. package/dist/commands/channel/spawn.d.ts.map +1 -0
  80. package/dist/commands/channel/spawn.js +244 -0
  81. package/dist/commands/channel/spawn.js.map +1 -0
  82. package/dist/commands/channel/store/events.d.ts +39 -0
  83. package/dist/commands/channel/store/events.d.ts.map +1 -0
  84. package/dist/commands/channel/store/events.js +87 -0
  85. package/dist/commands/channel/store/events.js.map +1 -0
  86. package/dist/commands/channel/store/filter.d.ts +3 -0
  87. package/dist/commands/channel/store/filter.d.ts.map +1 -0
  88. package/dist/commands/channel/store/filter.js +2 -0
  89. package/dist/commands/channel/store/filter.js.map +1 -0
  90. package/dist/commands/channel/store/lock.d.ts +23 -0
  91. package/dist/commands/channel/store/lock.d.ts.map +1 -0
  92. package/dist/commands/channel/store/lock.js +99 -0
  93. package/dist/commands/channel/store/lock.js.map +1 -0
  94. package/dist/commands/channel/store/paths.d.ts +63 -0
  95. package/dist/commands/channel/store/paths.d.ts.map +1 -0
  96. package/dist/commands/channel/store/paths.js +246 -0
  97. package/dist/commands/channel/store/paths.js.map +1 -0
  98. package/dist/commands/channel/store/schema.d.ts +27 -0
  99. package/dist/commands/channel/store/schema.d.ts.map +1 -0
  100. package/dist/commands/channel/store/schema.js +34 -0
  101. package/dist/commands/channel/store/schema.js.map +1 -0
  102. package/dist/commands/channel/store/thread-state.d.ts +5 -0
  103. package/dist/commands/channel/store/thread-state.d.ts.map +1 -0
  104. package/dist/commands/channel/store/thread-state.js +16 -0
  105. package/dist/commands/channel/store/thread-state.js.map +1 -0
  106. package/dist/commands/channel/store/watch.d.ts +19 -0
  107. package/dist/commands/channel/store/watch.d.ts.map +1 -0
  108. package/dist/commands/channel/store/watch.js +146 -0
  109. package/dist/commands/channel/store/watch.js.map +1 -0
  110. package/dist/commands/channel/supervisor/idle.d.ts +46 -0
  111. package/dist/commands/channel/supervisor/idle.d.ts.map +1 -0
  112. package/dist/commands/channel/supervisor/idle.js +72 -0
  113. package/dist/commands/channel/supervisor/idle.js.map +1 -0
  114. package/dist/commands/channel/supervisor/inbox.d.ts +30 -0
  115. package/dist/commands/channel/supervisor/inbox.d.ts.map +1 -0
  116. package/dist/commands/channel/supervisor/inbox.js +160 -0
  117. package/dist/commands/channel/supervisor/inbox.js.map +1 -0
  118. package/dist/commands/channel/supervisor/shutdown.d.ts +68 -0
  119. package/dist/commands/channel/supervisor/shutdown.d.ts.map +1 -0
  120. package/dist/commands/channel/supervisor/shutdown.js +146 -0
  121. package/dist/commands/channel/supervisor/shutdown.js.map +1 -0
  122. package/dist/commands/channel/supervisor/stdout.d.ts +51 -0
  123. package/dist/commands/channel/supervisor/stdout.d.ts.map +1 -0
  124. package/dist/commands/channel/supervisor/stdout.js +121 -0
  125. package/dist/commands/channel/supervisor/stdout.js.map +1 -0
  126. package/dist/commands/channel/supervisor/turns.d.ts +31 -0
  127. package/dist/commands/channel/supervisor/turns.d.ts.map +1 -0
  128. package/dist/commands/channel/supervisor/turns.js +45 -0
  129. package/dist/commands/channel/supervisor/turns.js.map +1 -0
  130. package/dist/commands/channel/supervisor/warning.d.ts +48 -0
  131. package/dist/commands/channel/supervisor/warning.d.ts.map +1 -0
  132. package/dist/commands/channel/supervisor/warning.js +77 -0
  133. package/dist/commands/channel/supervisor/warning.js.map +1 -0
  134. package/dist/commands/channel/supervisor.d.ts +59 -0
  135. package/dist/commands/channel/supervisor.d.ts.map +1 -0
  136. package/dist/commands/channel/supervisor.js +344 -0
  137. package/dist/commands/channel/supervisor.js.map +1 -0
  138. package/dist/commands/channel/text-body.d.ts +13 -0
  139. package/dist/commands/channel/text-body.d.ts.map +1 -0
  140. package/dist/commands/channel/text-body.js +47 -0
  141. package/dist/commands/channel/text-body.js.map +1 -0
  142. package/dist/commands/channel/threads.d.ts +39 -0
  143. package/dist/commands/channel/threads.d.ts.map +1 -0
  144. package/dist/commands/channel/threads.js +106 -0
  145. package/dist/commands/channel/threads.js.map +1 -0
  146. package/dist/commands/channel/title.d.ts +12 -0
  147. package/dist/commands/channel/title.d.ts.map +1 -0
  148. package/dist/commands/channel/title.js +24 -0
  149. package/dist/commands/channel/title.js.map +1 -0
  150. package/dist/commands/channel/wait.d.ts +17 -0
  151. package/dist/commands/channel/wait.d.ts.map +1 -0
  152. package/dist/commands/channel/wait.js +75 -0
  153. package/dist/commands/channel/wait.js.map +1 -0
  154. package/dist/commands/init.d.ts +2 -0
  155. package/dist/commands/init.d.ts.map +1 -1
  156. package/dist/commands/init.js +97 -42
  157. package/dist/commands/init.js.map +1 -1
  158. package/dist/commands/mem.d.ts +13 -117
  159. package/dist/commands/mem.d.ts.map +1 -1
  160. package/dist/commands/mem.js +168 -1074
  161. package/dist/commands/mem.js.map +1 -1
  162. package/dist/commands/uninstall.d.ts.map +1 -1
  163. package/dist/commands/uninstall.js +28 -2
  164. package/dist/commands/uninstall.js.map +1 -1
  165. package/dist/commands/update.d.ts.map +1 -1
  166. package/dist/commands/update.js +31 -111
  167. package/dist/commands/update.js.map +1 -1
  168. package/dist/commands/upgrade.d.ts +28 -0
  169. package/dist/commands/upgrade.d.ts.map +1 -0
  170. package/dist/commands/upgrade.js +84 -0
  171. package/dist/commands/upgrade.js.map +1 -0
  172. package/dist/commands/workflow.d.ts +35 -0
  173. package/dist/commands/workflow.d.ts.map +1 -0
  174. package/dist/commands/workflow.js +219 -0
  175. package/dist/commands/workflow.js.map +1 -0
  176. package/dist/configurators/claude.d.ts.map +1 -1
  177. package/dist/configurators/claude.js +1 -0
  178. package/dist/configurators/claude.js.map +1 -1
  179. package/dist/configurators/codex.d.ts.map +1 -1
  180. package/dist/configurators/codex.js +5 -3
  181. package/dist/configurators/codex.js.map +1 -1
  182. package/dist/configurators/shared.js +4 -4
  183. package/dist/configurators/shared.js.map +1 -1
  184. package/dist/configurators/workflow.d.ts +8 -0
  185. package/dist/configurators/workflow.d.ts.map +1 -1
  186. package/dist/configurators/workflow.js +3 -2
  187. package/dist/configurators/workflow.js.map +1 -1
  188. package/dist/migrations/manifests/0.5.10.json +9 -0
  189. package/dist/migrations/manifests/0.5.11.json +16 -0
  190. package/dist/migrations/manifests/0.5.12.json +9 -0
  191. package/dist/migrations/manifests/0.5.13.json +9 -0
  192. package/dist/migrations/manifests/0.5.14.json +9 -0
  193. package/dist/migrations/manifests/0.5.15.json +9 -0
  194. package/dist/migrations/manifests/0.5.16.json +9 -0
  195. package/dist/migrations/manifests/0.5.17.json +9 -0
  196. package/dist/migrations/manifests/0.5.18.json +9 -0
  197. package/dist/migrations/manifests/0.6.0-beta.10.json +9 -0
  198. package/dist/migrations/manifests/0.6.0-beta.11.json +9 -0
  199. package/dist/migrations/manifests/0.6.0-beta.12.json +9 -0
  200. package/dist/migrations/manifests/0.6.0-beta.13.json +9 -0
  201. package/dist/migrations/manifests/0.6.0-beta.14.json +9 -0
  202. package/dist/migrations/manifests/0.6.0-beta.15.json +9 -0
  203. package/dist/migrations/manifests/0.6.0-beta.16.json +9 -0
  204. package/dist/migrations/manifests/0.6.0-beta.17.json +9 -0
  205. package/dist/migrations/manifests/0.6.0-beta.18.json +16 -0
  206. package/dist/migrations/manifests/0.6.0-beta.19.json +9 -0
  207. package/dist/migrations/manifests/0.6.0-beta.20.json +9 -0
  208. package/dist/migrations/manifests/0.6.0-beta.21.json +9 -0
  209. package/dist/migrations/manifests/0.6.0-beta.3.json +9 -0
  210. package/dist/migrations/manifests/0.6.0-beta.4.json +9 -0
  211. package/dist/migrations/manifests/0.6.0-beta.5.json +9 -0
  212. package/dist/migrations/manifests/0.6.0-beta.6.json +16 -0
  213. package/dist/migrations/manifests/0.6.0-beta.7.json +9 -0
  214. package/dist/migrations/manifests/0.6.0-beta.8.json +9 -0
  215. package/dist/migrations/manifests/0.6.0-beta.9.json +9 -0
  216. package/dist/templates/claude/agents/trellis-check.md +13 -7
  217. package/dist/templates/claude/agents/trellis-implement.md +8 -7
  218. package/dist/templates/claude/settings.json +4 -4
  219. package/dist/templates/codebuddy/agents/trellis-check.md +13 -7
  220. package/dist/templates/codebuddy/agents/trellis-implement.md +8 -7
  221. package/dist/templates/codebuddy/settings.json +4 -4
  222. package/dist/templates/codex/agents/trellis-check.toml +4 -4
  223. package/dist/templates/codex/agents/trellis-implement.toml +4 -4
  224. package/dist/templates/codex/config.toml +9 -16
  225. package/dist/templates/codex/hooks/session-start.py +205 -119
  226. package/dist/templates/codex/hooks.json +2 -2
  227. package/dist/templates/codex/skills/before-dev/SKILL.md +12 -6
  228. package/dist/templates/codex/skills/brainstorm/SKILL.md +69 -457
  229. package/dist/templates/codex/skills/check/SKILL.md +86 -18
  230. package/dist/templates/codex/skills/start/SKILL.md +33 -323
  231. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-context-loading.md +7 -4
  232. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-spec-structure.md +1 -1
  233. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-workflow.md +3 -2
  234. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/context-injection.md +5 -5
  235. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/spec-system.md +1 -1
  236. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/task-system.md +35 -6
  237. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/agents.md +5 -4
  238. package/dist/templates/common/bundled-skills/trellis-spec-bootstarp/SKILL.md +41 -0
  239. package/dist/templates/common/bundled-skills/trellis-spec-bootstarp/references/mcp-setup.md +90 -0
  240. package/dist/templates/common/bundled-skills/trellis-spec-bootstarp/references/repository-analysis.md +59 -0
  241. package/dist/templates/common/bundled-skills/trellis-spec-bootstarp/references/spec-task-planning.md +61 -0
  242. package/dist/templates/common/bundled-skills/trellis-spec-bootstarp/references/spec-writing.md +70 -0
  243. package/dist/templates/common/commands/continue.md +6 -5
  244. package/dist/templates/common/commands/start.md +9 -6
  245. package/dist/templates/common/skills/before-dev.md +12 -6
  246. package/dist/templates/common/skills/brainstorm.md +68 -504
  247. package/dist/templates/common/skills/check.md +7 -1
  248. package/dist/templates/copilot/hooks/session-start.py +219 -101
  249. package/dist/templates/copilot/hooks.json +2 -2
  250. package/dist/templates/copilot/prompts/before-dev.prompt.md +12 -6
  251. package/dist/templates/copilot/prompts/brainstorm.prompt.md +69 -457
  252. package/dist/templates/copilot/prompts/check.prompt.md +86 -18
  253. package/dist/templates/copilot/prompts/parallel.prompt.md +16 -8
  254. package/dist/templates/copilot/prompts/start.prompt.md +33 -367
  255. package/dist/templates/cursor/agents/trellis-check.md +13 -7
  256. package/dist/templates/cursor/agents/trellis-implement.md +8 -7
  257. package/dist/templates/cursor/hooks.json +1 -7
  258. package/dist/templates/droid/droids/trellis-check.md +13 -7
  259. package/dist/templates/droid/droids/trellis-implement.md +8 -7
  260. package/dist/templates/droid/settings.json +4 -4
  261. package/dist/templates/gemini/agents/trellis-check.md +11 -5
  262. package/dist/templates/gemini/agents/trellis-implement.md +7 -6
  263. package/dist/templates/gemini/settings.json +2 -2
  264. package/dist/templates/kiro/agents/trellis-check.json +1 -1
  265. package/dist/templates/kiro/agents/trellis-implement.json +1 -1
  266. package/dist/templates/markdown/spec/guides/code-reuse-thinking-guide.md.txt +127 -9
  267. package/dist/templates/markdown/spec/guides/cross-layer-thinking-guide.md.txt +171 -6
  268. package/dist/templates/markdown/spec/guides/cross-platform-thinking-guide.md.txt +333 -43
  269. package/dist/templates/markdown/spec/guides/index.md.txt +18 -0
  270. package/dist/templates/opencode/agents/trellis-check.md +13 -7
  271. package/dist/templates/opencode/agents/trellis-implement.md +9 -8
  272. package/dist/templates/opencode/lib/session-utils.js +212 -123
  273. package/dist/templates/opencode/lib/trellis-context.js +73 -11
  274. package/dist/templates/opencode/plugins/inject-subagent-context.js +131 -29
  275. package/dist/templates/opencode/plugins/inject-workflow-state.js +9 -5
  276. package/dist/templates/opencode/plugins/session-start.js +9 -1
  277. package/dist/templates/pi/agents/trellis-check.md +5 -4
  278. package/dist/templates/pi/agents/trellis-implement.md +5 -4
  279. package/dist/templates/pi/extensions/trellis/index.ts.txt +1357 -754
  280. package/dist/templates/qoder/agents/trellis-check.md +11 -5
  281. package/dist/templates/qoder/agents/trellis-implement.md +7 -6
  282. package/dist/templates/qoder/settings.json +4 -4
  283. package/dist/templates/shared-hooks/index.d.ts.map +1 -1
  284. package/dist/templates/shared-hooks/index.js +0 -1
  285. package/dist/templates/shared-hooks/index.js.map +1 -1
  286. package/dist/templates/shared-hooks/inject-subagent-context.py +36 -14
  287. package/dist/templates/shared-hooks/inject-workflow-state.py +40 -42
  288. package/dist/templates/shared-hooks/session-start.py +222 -171
  289. package/dist/templates/trellis/config.yaml +38 -0
  290. package/dist/templates/trellis/index.d.ts +1 -0
  291. package/dist/templates/trellis/index.d.ts.map +1 -1
  292. package/dist/templates/trellis/index.js +2 -0
  293. package/dist/templates/trellis/index.js.map +1 -1
  294. package/dist/templates/trellis/scripts/add_session.py +50 -24
  295. package/dist/templates/trellis/scripts/common/config.py +57 -1
  296. package/dist/templates/trellis/scripts/common/safe_commit.py +285 -0
  297. package/dist/templates/trellis/scripts/common/session_context.py +384 -137
  298. package/dist/templates/trellis/scripts/common/task_context.py +3 -3
  299. package/dist/templates/trellis/scripts/common/task_store.py +161 -15
  300. package/dist/templates/trellis/scripts/common/workflow_phase.py +7 -10
  301. package/dist/templates/trellis/scripts/task.py +3 -3
  302. package/dist/templates/trellis/workflow.md +119 -98
  303. package/dist/utils/cwd-guard.d.ts +38 -0
  304. package/dist/utils/cwd-guard.d.ts.map +1 -0
  305. package/dist/utils/cwd-guard.js +62 -0
  306. package/dist/utils/cwd-guard.js.map +1 -0
  307. package/dist/utils/file-writer.d.ts +13 -0
  308. package/dist/utils/file-writer.d.ts.map +1 -1
  309. package/dist/utils/file-writer.js +59 -1
  310. package/dist/utils/file-writer.js.map +1 -1
  311. package/dist/utils/manifest-prune.d.ts +61 -0
  312. package/dist/utils/manifest-prune.d.ts.map +1 -0
  313. package/dist/utils/manifest-prune.js +136 -0
  314. package/dist/utils/manifest-prune.js.map +1 -0
  315. package/dist/utils/task-json.d.ts +9 -42
  316. package/dist/utils/task-json.d.ts.map +1 -1
  317. package/dist/utils/task-json.js +8 -45
  318. package/dist/utils/task-json.js.map +1 -1
  319. package/dist/utils/template-hash.d.ts +32 -6
  320. package/dist/utils/template-hash.d.ts.map +1 -1
  321. package/dist/utils/template-hash.js +53 -31
  322. package/dist/utils/template-hash.js.map +1 -1
  323. package/dist/utils/uninstall-scrubbers.d.ts +1 -0
  324. package/dist/utils/uninstall-scrubbers.d.ts.map +1 -1
  325. package/dist/utils/uninstall-scrubbers.js +21 -0
  326. package/dist/utils/uninstall-scrubbers.js.map +1 -1
  327. package/dist/utils/workflow-resolver.d.ts +86 -0
  328. package/dist/utils/workflow-resolver.d.ts.map +1 -0
  329. package/dist/utils/workflow-resolver.js +265 -0
  330. package/dist/utils/workflow-resolver.js.map +1 -0
  331. package/package.json +9 -8
@@ -23,7 +23,6 @@ from __future__ import annotations
23
23
 
24
24
  import argparse
25
25
  import re
26
- import subprocess
27
26
  import sys
28
27
  from datetime import datetime
29
28
  from pathlib import Path
@@ -37,9 +36,15 @@ from common.paths import (
37
36
  )
38
37
  from common.developer import ensure_developer
39
38
  from common.git import run_git
39
+ from common.safe_commit import (
40
+ print_gitignore_warning,
41
+ safe_git_add,
42
+ safe_trellis_paths_to_add,
43
+ )
40
44
  from common.tasks import load_task
41
45
  from common.config import (
42
46
  get_packages,
47
+ get_session_auto_commit,
43
48
  get_session_commit_message,
44
49
  get_max_journal_lines,
45
50
  is_monorepo,
@@ -314,36 +319,57 @@ def update_index(
314
319
  # =============================================================================
315
320
 
316
321
  def _auto_commit_workspace(repo_root: Path) -> None:
317
- """Stage .trellis/workspace and .trellis/tasks, then commit with a configured message."""
322
+ """Stage Trellis-owned workspace + task paths and commit.
323
+
324
+ Path scope is restricted to specific products (journal files, index.md,
325
+ active task dirs, the archive subtree). We never `git add` the whole
326
+ `.trellis/` tree, and if `.gitignore` blocks the specific paths we
327
+ warn + skip — never retry with ``-f``.
328
+
329
+ Honors ``session_auto_commit`` in ``.trellis/config.yaml``: when set to
330
+ ``false``, this function returns immediately without touching git
331
+ (journal/index files are still written to disk by the caller).
332
+ """
333
+ if not get_session_auto_commit(repo_root):
334
+ print(
335
+ "[OK] session_auto_commit: false — skipping git stage/commit.",
336
+ file=sys.stderr,
337
+ )
338
+ return
339
+
318
340
  commit_msg = get_session_commit_message(repo_root)
319
- add_result = subprocess.run(
320
- ["git", "add", "-A", ".trellis/workspace", ".trellis/tasks"],
321
- cwd=repo_root,
322
- capture_output=True,
323
- text=True,
324
- )
325
- if add_result.returncode != 0:
326
- print(f"[WARN] git add failed (exit {add_result.returncode}): {add_result.stderr.strip()}", file=sys.stderr)
327
- print("[WARN] Please commit .trellis/ changes manually: git add .trellis && git commit", file=sys.stderr)
341
+ paths = safe_trellis_paths_to_add(repo_root)
342
+ if not paths:
343
+ print("[OK] No workspace changes to commit.", file=sys.stderr)
344
+ return
345
+
346
+ success, _, err = safe_git_add(paths, repo_root)
347
+ if not success:
348
+ if err and "ignored by" in err.lower():
349
+ print_gitignore_warning(paths)
350
+ else:
351
+ print(
352
+ f"[WARN] git add failed: {err.strip() if err else 'unknown error'}",
353
+ file=sys.stderr,
354
+ )
328
355
  return
329
- # Check if there are staged changes
330
- result = subprocess.run(
331
- ["git", "diff", "--cached", "--quiet", "--", ".trellis/workspace", ".trellis/tasks"],
332
- cwd=repo_root,
356
+
357
+ # Check if there are staged changes for the paths we just staged.
358
+ rc, _, _ = run_git(
359
+ ["diff", "--cached", "--quiet", "--", *paths], cwd=repo_root
333
360
  )
334
- if result.returncode == 0:
361
+ if rc == 0:
335
362
  print("[OK] No workspace changes to commit.", file=sys.stderr)
336
363
  return
337
- commit_result = subprocess.run(
338
- ["git", "commit", "-m", commit_msg],
339
- cwd=repo_root,
340
- capture_output=True,
341
- text=True,
342
- )
343
- if commit_result.returncode == 0:
364
+
365
+ rc, _, commit_err = run_git(["commit", "-m", commit_msg], cwd=repo_root)
366
+ if rc == 0:
344
367
  print(f"[OK] Auto-committed: {commit_msg}", file=sys.stderr)
345
368
  else:
346
- print(f"[WARN] Auto-commit failed: {commit_result.stderr.strip()}", file=sys.stderr)
369
+ print(
370
+ f"[WARN] Auto-commit failed: {commit_err.strip()}",
371
+ file=sys.stderr,
372
+ )
347
373
 
348
374
 
349
375
  def add_session(
@@ -36,6 +36,29 @@ def _unquote(s: str) -> str:
36
36
  return s
37
37
 
38
38
 
39
+ def _strip_inline_comment(value: str) -> str:
40
+ """Strip ` # …` inline comments while preserving `#` inside quoted strings.
41
+
42
+ YAML treats ` #` (space-hash) as a comment opener; bare `#` inside a token
43
+ is part of the value. Quoted strings are immune.
44
+
45
+ Mirrors :func:`common.trellis_config._strip_inline_comment` so both
46
+ parsers handle ``key: value # comment`` identically.
47
+ """
48
+ in_quote: str | None = None
49
+ for idx, ch in enumerate(value):
50
+ if in_quote:
51
+ if ch == in_quote:
52
+ in_quote = None
53
+ continue
54
+ if ch in ('"', "'"):
55
+ in_quote = ch
56
+ continue
57
+ if ch == "#" and (idx == 0 or value[idx - 1].isspace()):
58
+ return value[:idx]
59
+ return value
60
+
61
+
39
62
  def parse_simple_yaml(content: str) -> dict:
40
63
  """Parse simple YAML with nested dict support (no dependencies).
41
64
 
@@ -93,7 +116,8 @@ def _parse_yaml_block(
93
116
  elif ":" in stripped:
94
117
  key, _, value = stripped.partition(":")
95
118
  key = key.strip()
96
- value = _unquote(value.strip())
119
+ value = _strip_inline_comment(value).strip()
120
+ value = _unquote(value)
97
121
  current_list = None
98
122
 
99
123
  if value:
@@ -142,6 +166,7 @@ def _next_content_line(lines: list[str], start: int) -> tuple[int, str]:
142
166
  # Defaults
143
167
  DEFAULT_SESSION_COMMIT_MESSAGE = "chore: record journal"
144
168
  DEFAULT_MAX_JOURNAL_LINES = 2000
169
+ DEFAULT_SESSION_AUTO_COMMIT = True
145
170
 
146
171
  CONFIG_FILE = "config.yaml"
147
172
 
@@ -187,6 +212,37 @@ def get_max_journal_lines(repo_root: Path | None = None) -> int:
187
212
  return DEFAULT_MAX_JOURNAL_LINES
188
213
 
189
214
 
215
+ def get_session_auto_commit(repo_root: Path | None = None) -> bool:
216
+ """Whether scripts should auto-stage + auto-commit session/task changes.
217
+
218
+ Governs both ``add_session.py:_auto_commit_workspace`` and
219
+ ``task_store.py:_auto_commit_archive``.
220
+
221
+ Default: ``True`` (existing behavior — auto-stage + auto-commit).
222
+ Set ``session_auto_commit: false`` in ``.trellis/config.yaml`` to skip
223
+ auto-staging entirely; the journal/archive files are still written to
224
+ disk, but the user manages ``git add`` / ``git commit`` themselves.
225
+
226
+ Accepts native YAML booleans (``true`` / ``false``) and the string
227
+ aliases ``true / false / yes / no / 1 / 0 / on / off`` (case-insensitive).
228
+ Invalid values fall back to ``True`` with a stderr warning.
229
+ """
230
+ config = _load_config(repo_root)
231
+ raw = config.get("session_auto_commit", DEFAULT_SESSION_AUTO_COMMIT)
232
+ if isinstance(raw, bool):
233
+ return raw
234
+ s = str(raw).strip().lower()
235
+ if s in ("true", "yes", "1", "on"):
236
+ return True
237
+ if s in ("false", "no", "0", "off"):
238
+ return False
239
+ print(
240
+ f"[WARN] invalid session_auto_commit value: {raw!r}; using true (default)",
241
+ file=sys.stderr,
242
+ )
243
+ return DEFAULT_SESSION_AUTO_COMMIT
244
+
245
+
190
246
  def get_hooks(event: str, repo_root: Path | None = None) -> list[str]:
191
247
  """Get hook commands for a lifecycle event.
192
248
 
@@ -0,0 +1,285 @@
1
+ """
2
+ Safe git-add helpers for Trellis-owned paths.
3
+
4
+ Why this module exists
5
+ ----------------------
6
+ A real user incident: a project's `.gitignore` listed `.trellis/` (company-wide
7
+ template / personal habit). When `add_session.py` and `task.py archive` ran
8
+ their auto-commit and `git add` failed with `ignored by .gitignore`, the AI
9
+ agent driving the workflow "fixed" it by retrying with
10
+ `git add -f .trellis/` — which fan-out-included every ignored subtree
11
+ (`.trellis/.backup-*/`, `.trellis/worktrees/`, `.trellis/.template-hashes.json`,
12
+ `.trellis/.runtime/`), committing 548 files / 83474 lines of caches/backups.
13
+
14
+ Design
15
+ ------
16
+ - Scripts only stage SPECIFIC product paths (journal files, index.md, the
17
+ current task dir, the archive dir). Never the whole `.trellis/` tree.
18
+ - If plain `git add <specific>` fails with "ignored by", DO NOT retry with
19
+ ``-f``. The presence of `.trellis/` in `.gitignore` is treated as user
20
+ intent ("keep .trellis/ local-only"). The script warns and skips the
21
+ auto-commit; users who want auto-staging can either fix their `.gitignore`
22
+ or set ``session_auto_commit: false`` and manage git themselves.
23
+ - The warning includes a negative example: ``Do NOT use `git add -f .trellis/` ...``
24
+ so any AI rereading the log doesn't reinvent the bug.
25
+
26
+ History note: 0.5.10 introduced an automatic ``git add -f`` retry on the
27
+ specific paths. That was reverted in 0.5.11 — auto-forcing into a tree the
28
+ user had gitignored violates user intent even when the path list is narrow.
29
+ The wider-grain forbidden command stays forbidden, and the narrow-grain auto
30
+ ``-f`` is gone too.
31
+ """
32
+
33
+ from __future__ import annotations
34
+
35
+ import sys
36
+ from pathlib import Path
37
+
38
+ from .git import run_git
39
+ from .paths import (
40
+ DIR_ARCHIVE,
41
+ DIR_TASKS,
42
+ DIR_WORKFLOW,
43
+ DIR_WORKSPACE,
44
+ FILE_JOURNAL_PREFIX,
45
+ get_developer,
46
+ )
47
+
48
+
49
+ # Paths under .trellis/ that must NEVER be auto-staged. Listed here so the
50
+ # warning to the user can show concrete subpaths to ignore individually
51
+ # instead of ignoring the whole `.trellis/` tree.
52
+ TRELLIS_IGNORED_SUBPATHS = (
53
+ ".trellis/.backup-*",
54
+ ".trellis/worktrees/",
55
+ ".trellis/.template-hashes.json",
56
+ ".trellis/.runtime/",
57
+ ".trellis/.cache/",
58
+ )
59
+
60
+
61
+ def safe_trellis_paths_to_add(repo_root: Path) -> list[str]:
62
+ """Return the list of repo-relative paths the auto-commit should stage.
63
+
64
+ Only includes paths that exist on disk so callers don't pass non-existent
65
+ arguments to git. The caller is responsible for `git diff --cached`
66
+ checking afterwards.
67
+
68
+ Included:
69
+ - .trellis/workspace/<developer>/journal-*.md
70
+ - .trellis/workspace/<developer>/index.md
71
+ - .trellis/tasks/<task-dir>/ (every active task directory)
72
+ - .trellis/tasks/archive/ (whole archive subtree, if present)
73
+
74
+ Excluded (intentionally — these must not be staged):
75
+ - .trellis/.backup-*, .trellis/worktrees/,
76
+ .trellis/.template-hashes.json, .trellis/.runtime/, .trellis/.cache/
77
+ """
78
+ paths: list[str] = []
79
+
80
+ # Workspace journal files + index.md
81
+ developer = get_developer(repo_root)
82
+ if developer:
83
+ ws = repo_root / DIR_WORKFLOW / DIR_WORKSPACE / developer
84
+ if ws.is_dir():
85
+ for f in sorted(ws.glob(f"{FILE_JOURNAL_PREFIX}*.md")):
86
+ if f.is_file():
87
+ paths.append(
88
+ f"{DIR_WORKFLOW}/{DIR_WORKSPACE}/{developer}/{f.name}"
89
+ )
90
+ index_md = ws / "index.md"
91
+ if index_md.is_file():
92
+ paths.append(
93
+ f"{DIR_WORKFLOW}/{DIR_WORKSPACE}/{developer}/index.md"
94
+ )
95
+
96
+ # Active tasks: each direct child of tasks/ that is a directory and not
97
+ # the archive root. The archive subtree is added as a single path below.
98
+ tasks_dir = repo_root / DIR_WORKFLOW / DIR_TASKS
99
+ if tasks_dir.is_dir():
100
+ for child in sorted(tasks_dir.iterdir()):
101
+ if not child.is_dir():
102
+ continue
103
+ if child.name == DIR_ARCHIVE:
104
+ continue
105
+ paths.append(f"{DIR_WORKFLOW}/{DIR_TASKS}/{child.name}")
106
+
107
+ archive_dir = tasks_dir / DIR_ARCHIVE
108
+ if archive_dir.is_dir():
109
+ paths.append(f"{DIR_WORKFLOW}/{DIR_TASKS}/{DIR_ARCHIVE}")
110
+
111
+ return paths
112
+
113
+
114
+ def safe_archive_paths_to_add(
115
+ repo_root: Path,
116
+ task_name: str | None = None,
117
+ modified_children: list[str] | None = None,
118
+ ) -> list[str]:
119
+ """Return paths to stage after `task.py archive`.
120
+
121
+ Scoped to ONLY the paths the archive operation actually touched:
122
+
123
+ - the archive subtree (where the freshly-moved task lives)
124
+ - the source task directory (for source-side deletes; caller pairs
125
+ this with `git rm --cached` since `git add` won't stage deletes
126
+ for a path that no longer exists in the working tree)
127
+ - any child task directories whose `task.json` was edited to drop
128
+ the archived parent (parent-children relationship update)
129
+
130
+ This narrow scope avoids "scope creep" — dirty changes in OTHER
131
+ active task dirs (parallel-window edits) are NOT bundled into the
132
+ archive commit. Callers handle each kind of change in its own
133
+ commit boundary.
134
+
135
+ Backwards-compat: with no arguments, the function walks the whole
136
+ `.trellis/tasks/` subtree the old way (active tasks + archive). New
137
+ callers should always pass `task_name`.
138
+ """
139
+ paths: list[str] = []
140
+ tasks_dir = repo_root / DIR_WORKFLOW / DIR_TASKS
141
+ if not tasks_dir.is_dir():
142
+ return paths
143
+
144
+ archive_dir = tasks_dir / DIR_ARCHIVE
145
+
146
+ if task_name is not None:
147
+ # Narrow scope — only paths that still exist on disk (so
148
+ # `git add` doesn't choke on the moved-away source). The caller
149
+ # handles the source-side deletes via `git rm --cached`
150
+ # explicitly.
151
+ if archive_dir.is_dir():
152
+ paths.append(
153
+ f"{DIR_WORKFLOW}/{DIR_TASKS}/{DIR_ARCHIVE}"
154
+ )
155
+ for child_name in modified_children or []:
156
+ paths.append(f"{DIR_WORKFLOW}/{DIR_TASKS}/{child_name}")
157
+ return paths
158
+
159
+ # Legacy wide scope (no task_name): preserve old behavior so callers
160
+ # that have not been updated keep working.
161
+ if archive_dir.is_dir():
162
+ paths.append(f"{DIR_WORKFLOW}/{DIR_TASKS}/{DIR_ARCHIVE}")
163
+ for child in sorted(tasks_dir.iterdir()):
164
+ if not child.is_dir():
165
+ continue
166
+ if child.name == DIR_ARCHIVE:
167
+ continue
168
+ paths.append(f"{DIR_WORKFLOW}/{DIR_TASKS}/{child.name}")
169
+ return paths
170
+
171
+
172
+ def _stderr_indicates_ignored(stderr: str) -> bool:
173
+ """git add error indicates the path is excluded by .gitignore."""
174
+ if not stderr:
175
+ return False
176
+ lowered = stderr.lower()
177
+ return "ignored by" in lowered
178
+
179
+
180
+ def safe_git_add(
181
+ paths: list[str], repo_root: Path
182
+ ) -> tuple[bool, bool, str]:
183
+ """Run `git add` on specific paths; never retry with -f.
184
+
185
+ Returns ``(success, used_force, stderr)``. The ``used_force`` field is
186
+ kept for signature compatibility with the 0.5.10 implementation but is
187
+ always ``False`` — we never auto-force.
188
+
189
+ Behavior:
190
+ - No paths passed → success, no force, empty stderr.
191
+ - Plain ``git add -- <paths>`` succeeds → return success.
192
+ - Plain fails (any reason — ignored or otherwise) → return failure with
193
+ the stderr. Callers should inspect the stderr (see
194
+ :func:`print_gitignore_warning`) and skip the auto-commit.
195
+ """
196
+ if not paths:
197
+ return True, False, ""
198
+
199
+ rc, _, err = run_git(["add", "--", *paths], cwd=repo_root)
200
+ if rc == 0:
201
+ return True, False, ""
202
+ return False, False, err
203
+
204
+
205
+ def print_gitignore_warning(paths: list[str]) -> None:
206
+ """Explain to the user (and any AI reading the log) what to do.
207
+
208
+ CRITICAL: includes the negative example
209
+ ``Do NOT use `git add -f .trellis/``` — agents reading the warning are
210
+ known to invent that command, which fans out to ignored caches/backups.
211
+ """
212
+ print(
213
+ "[WARN] git add failed because .trellis/ paths are ignored by your .gitignore.",
214
+ file=sys.stderr,
215
+ )
216
+ print(
217
+ "[WARN] Skipping auto-commit. The journal/task files were still written to disk;",
218
+ file=sys.stderr,
219
+ )
220
+ print(
221
+ "[WARN] git was not touched.",
222
+ file=sys.stderr,
223
+ )
224
+ print("[WARN]", file=sys.stderr)
225
+ print(
226
+ "[WARN] Trellis manages these specific paths and they should be tracked:",
227
+ file=sys.stderr,
228
+ )
229
+ if paths:
230
+ for p in paths:
231
+ print(f"[WARN] {p}", file=sys.stderr)
232
+ else:
233
+ print(
234
+ "[WARN] .trellis/workspace/<developer>/{journal-*.md,index.md}",
235
+ file=sys.stderr,
236
+ )
237
+ print(
238
+ "[WARN] .trellis/tasks/<task-dir>/",
239
+ file=sys.stderr,
240
+ )
241
+ print(
242
+ "[WARN] .trellis/tasks/archive/",
243
+ file=sys.stderr,
244
+ )
245
+ print("[WARN]", file=sys.stderr)
246
+ print(
247
+ "[WARN] Recommended: change your .gitignore from `.trellis/` to specific",
248
+ file=sys.stderr,
249
+ )
250
+ print(
251
+ "[WARN] subpaths that should remain ignored, e.g.:",
252
+ file=sys.stderr,
253
+ )
254
+ for sub in TRELLIS_IGNORED_SUBPATHS:
255
+ print(f"[WARN] {sub}", file=sys.stderr)
256
+ print("[WARN]", file=sys.stderr)
257
+ print(
258
+ "[WARN] Or, if you intentionally keep .trellis/ local-only, set in",
259
+ file=sys.stderr,
260
+ )
261
+ print(
262
+ "[WARN] .trellis/config.yaml:",
263
+ file=sys.stderr,
264
+ )
265
+ print(
266
+ "[WARN] session_auto_commit: false",
267
+ file=sys.stderr,
268
+ )
269
+ print(
270
+ "[WARN] so the scripts skip git entirely and you can review / commit",
271
+ file=sys.stderr,
272
+ )
273
+ print(
274
+ "[WARN] manually with `git status` / `git add` / `git commit`.",
275
+ file=sys.stderr,
276
+ )
277
+ print("[WARN]", file=sys.stderr)
278
+ print(
279
+ "[WARN] Do NOT use `git add -f .trellis/` — it pulls in backups, worktrees,",
280
+ file=sys.stderr,
281
+ )
282
+ print(
283
+ "[WARN] and runtime caches that should never be committed.",
284
+ file=sys.stderr,
285
+ )