@pennixrv/trellis 0.7.0-beta.9 → 0.7.1-beta.1

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 (488) hide show
  1. package/README.md +1 -1
  2. package/dist/cli/index.d.ts.map +1 -1
  3. package/dist/cli/index.js +14 -26
  4. package/dist/cli/index.js.map +1 -1
  5. package/dist/commands/ablate.js +1 -1
  6. package/dist/commands/ablate.js.map +1 -1
  7. package/dist/commands/init.d.ts.map +1 -1
  8. package/dist/commands/init.js +30 -56
  9. package/dist/commands/init.js.map +1 -1
  10. package/dist/commands/task-progress.d.ts +2 -0
  11. package/dist/commands/task-progress.d.ts.map +1 -1
  12. package/dist/commands/task-progress.js +37 -30
  13. package/dist/commands/task-progress.js.map +1 -1
  14. package/dist/commands/{channel → teamwork}/adapters/claude.d.ts +3 -3
  15. package/dist/commands/teamwork/adapters/claude.d.ts.map +1 -0
  16. package/dist/commands/{channel → teamwork}/adapters/claude.js +3 -3
  17. package/dist/commands/teamwork/adapters/claude.js.map +1 -0
  18. package/dist/commands/{channel → teamwork}/adapters/codex.d.ts +1 -15
  19. package/dist/commands/teamwork/adapters/codex.d.ts.map +1 -0
  20. package/dist/commands/{channel → teamwork}/adapters/codex.js +5 -42
  21. package/dist/commands/teamwork/adapters/codex.js.map +1 -0
  22. package/dist/commands/{channel → teamwork}/adapters/index.d.ts +6 -6
  23. package/dist/commands/teamwork/adapters/index.d.ts.map +1 -0
  24. package/dist/commands/{channel → teamwork}/adapters/index.js +16 -52
  25. package/dist/commands/teamwork/adapters/index.js.map +1 -0
  26. package/dist/commands/{channel → teamwork}/adapters/types.d.ts +2 -2
  27. package/dist/commands/teamwork/adapters/types.d.ts.map +1 -0
  28. package/dist/commands/teamwork/adapters/types.js.map +1 -0
  29. package/dist/commands/{channel → teamwork}/agent-loader.d.ts +2 -4
  30. package/dist/commands/teamwork/agent-loader.d.ts.map +1 -0
  31. package/dist/commands/{channel → teamwork}/agent-loader.js +4 -35
  32. package/dist/commands/teamwork/agent-loader.js.map +1 -0
  33. package/dist/commands/teamwork/artifact.d.ts +177 -0
  34. package/dist/commands/teamwork/artifact.d.ts.map +1 -0
  35. package/dist/commands/teamwork/artifact.js +908 -0
  36. package/dist/commands/teamwork/artifact.js.map +1 -0
  37. package/dist/commands/{channel → teamwork}/context-loader.d.ts +1 -1
  38. package/dist/commands/teamwork/context-loader.d.ts.map +1 -0
  39. package/dist/commands/{channel → teamwork}/context-loader.js +12 -12
  40. package/dist/commands/teamwork/context-loader.js.map +1 -0
  41. package/dist/commands/{channel → teamwork}/context-trust.d.ts +6 -6
  42. package/dist/commands/teamwork/context-trust.d.ts.map +1 -0
  43. package/dist/commands/{channel → teamwork}/context-trust.js +16 -16
  44. package/dist/commands/teamwork/context-trust.js.map +1 -0
  45. package/dist/commands/teamwork/context.d.ts +16 -0
  46. package/dist/commands/teamwork/context.d.ts.map +1 -0
  47. package/dist/commands/{channel → teamwork}/context.js +17 -17
  48. package/dist/commands/teamwork/context.js.map +1 -0
  49. package/dist/commands/teamwork/coordinator.d.ts +89 -0
  50. package/dist/commands/teamwork/coordinator.d.ts.map +1 -0
  51. package/dist/commands/teamwork/coordinator.js +1198 -0
  52. package/dist/commands/teamwork/coordinator.js.map +1 -0
  53. package/dist/commands/teamwork/create.d.ts +23 -0
  54. package/dist/commands/teamwork/create.d.ts.map +1 -0
  55. package/dist/commands/teamwork/create.js +39 -0
  56. package/dist/commands/teamwork/create.js.map +1 -0
  57. package/dist/commands/{channel → teamwork}/dev-parse-trace.d.ts +1 -1
  58. package/dist/commands/teamwork/dev-parse-trace.d.ts.map +1 -0
  59. package/dist/commands/{channel → teamwork}/dev-parse-trace.js +1 -1
  60. package/dist/commands/teamwork/dev-parse-trace.js.map +1 -0
  61. package/dist/commands/{channel → teamwork}/guard.d.ts +27 -46
  62. package/dist/commands/teamwork/guard.d.ts.map +1 -0
  63. package/dist/commands/{channel → teamwork}/guard.js +54 -156
  64. package/dist/commands/teamwork/guard.js.map +1 -0
  65. package/dist/commands/teamwork/index.d.ts +3 -0
  66. package/dist/commands/teamwork/index.d.ts.map +1 -0
  67. package/dist/commands/{channel → teamwork}/index.js +135 -165
  68. package/dist/commands/teamwork/index.js.map +1 -0
  69. package/dist/commands/{channel → teamwork}/interrupt.d.ts +1 -1
  70. package/dist/commands/teamwork/interrupt.d.ts.map +1 -0
  71. package/dist/commands/teamwork/interrupt.js +22 -0
  72. package/dist/commands/teamwork/interrupt.js.map +1 -0
  73. package/dist/commands/teamwork/kill.d.ts +7 -0
  74. package/dist/commands/teamwork/kill.d.ts.map +1 -0
  75. package/dist/commands/{channel → teamwork}/kill.js +21 -21
  76. package/dist/commands/teamwork/kill.js.map +1 -0
  77. package/dist/commands/teamwork/list.d.ts +17 -0
  78. package/dist/commands/teamwork/list.d.ts.map +1 -0
  79. package/dist/commands/{channel → teamwork}/list.js +18 -27
  80. package/dist/commands/teamwork/list.js.map +1 -0
  81. package/dist/commands/{channel → teamwork}/messages.d.ts +1 -1
  82. package/dist/commands/teamwork/messages.d.ts.map +1 -0
  83. package/dist/commands/{channel → teamwork}/messages.js +18 -18
  84. package/dist/commands/teamwork/messages.js.map +1 -0
  85. package/dist/commands/teamwork/profiles.d.ts +25 -0
  86. package/dist/commands/teamwork/profiles.d.ts.map +1 -0
  87. package/dist/commands/teamwork/profiles.js +125 -0
  88. package/dist/commands/teamwork/profiles.js.map +1 -0
  89. package/dist/commands/teamwork/rm.d.ts +27 -0
  90. package/dist/commands/teamwork/rm.d.ts.map +1 -0
  91. package/dist/commands/{channel → teamwork}/rm.js +21 -22
  92. package/dist/commands/teamwork/rm.js.map +1 -0
  93. package/dist/commands/teamwork/run.d.ts +29 -0
  94. package/dist/commands/{channel → teamwork}/run.d.ts.map +1 -1
  95. package/dist/commands/teamwork/run.js +129 -0
  96. package/dist/commands/teamwork/run.js.map +1 -0
  97. package/dist/commands/{channel → teamwork}/send.d.ts +1 -1
  98. package/dist/commands/teamwork/send.d.ts.map +1 -0
  99. package/dist/commands/{channel → teamwork}/send.js +7 -7
  100. package/dist/commands/teamwork/send.js.map +1 -0
  101. package/dist/commands/{channel → teamwork}/spawn.d.ts +8 -12
  102. package/dist/commands/teamwork/spawn.d.ts.map +1 -0
  103. package/dist/commands/{channel → teamwork}/spawn.js +51 -112
  104. package/dist/commands/teamwork/spawn.js.map +1 -0
  105. package/dist/commands/teamwork/store/events.d.ts +22 -0
  106. package/dist/commands/teamwork/store/events.d.ts.map +1 -0
  107. package/dist/commands/{channel → teamwork}/store/events.js +8 -8
  108. package/dist/commands/teamwork/store/events.js.map +1 -0
  109. package/dist/commands/teamwork/store/filter.d.ts +3 -0
  110. package/dist/commands/teamwork/store/filter.d.ts.map +1 -0
  111. package/dist/commands/teamwork/store/filter.js +2 -0
  112. package/dist/commands/teamwork/store/filter.js.map +1 -0
  113. package/dist/commands/{channel → teamwork}/store/lock.d.ts +1 -1
  114. package/dist/commands/teamwork/store/lock.d.ts.map +1 -0
  115. package/dist/commands/{channel → teamwork}/store/lock.js +2 -2
  116. package/dist/commands/teamwork/store/lock.js.map +1 -0
  117. package/dist/commands/{channel → teamwork}/store/paths.d.ts +18 -32
  118. package/dist/commands/teamwork/store/paths.d.ts.map +1 -0
  119. package/dist/commands/{channel → teamwork}/store/paths.js +42 -109
  120. package/dist/commands/teamwork/store/paths.js.map +1 -0
  121. package/dist/commands/teamwork/store/schema.d.ts +18 -0
  122. package/dist/commands/teamwork/store/schema.d.ts.map +1 -0
  123. package/dist/commands/teamwork/store/schema.js +23 -0
  124. package/dist/commands/teamwork/store/schema.js.map +1 -0
  125. package/dist/commands/{channel → teamwork}/store/thread-state.d.ts +3 -3
  126. package/dist/commands/teamwork/store/thread-state.d.ts.map +1 -0
  127. package/dist/commands/{channel → teamwork}/store/thread-state.js +1 -1
  128. package/dist/commands/teamwork/store/thread-state.js.map +1 -0
  129. package/dist/commands/teamwork/store/watch.d.ts +19 -0
  130. package/dist/commands/teamwork/store/watch.d.ts.map +1 -0
  131. package/dist/commands/{channel → teamwork}/store/watch.js +8 -8
  132. package/dist/commands/teamwork/store/watch.js.map +1 -0
  133. package/dist/commands/teamwork/supervisor/idle.d.ts.map +1 -0
  134. package/dist/commands/teamwork/supervisor/idle.js.map +1 -0
  135. package/dist/commands/{channel → teamwork}/supervisor/inbox.d.ts +2 -2
  136. package/dist/commands/teamwork/supervisor/inbox.d.ts.map +1 -0
  137. package/dist/commands/{channel → teamwork}/supervisor/inbox.js +14 -14
  138. package/dist/commands/teamwork/supervisor/inbox.js.map +1 -0
  139. package/dist/commands/{channel → teamwork}/supervisor/shutdown.d.ts +3 -6
  140. package/dist/commands/teamwork/supervisor/shutdown.d.ts.map +1 -0
  141. package/dist/commands/{channel → teamwork}/supervisor/shutdown.js +7 -17
  142. package/dist/commands/teamwork/supervisor/shutdown.js.map +1 -0
  143. package/dist/commands/{channel → teamwork}/supervisor/stdout.d.ts +3 -5
  144. package/dist/commands/teamwork/supervisor/stdout.d.ts.map +1 -0
  145. package/dist/commands/{channel → teamwork}/supervisor/stdout.js +12 -23
  146. package/dist/commands/teamwork/supervisor/stdout.js.map +1 -0
  147. package/dist/commands/teamwork/supervisor/turns.d.ts.map +1 -0
  148. package/dist/commands/teamwork/supervisor/turns.js.map +1 -0
  149. package/dist/commands/{channel → teamwork}/supervisor/warning.d.ts +5 -5
  150. package/dist/commands/teamwork/supervisor/warning.d.ts.map +1 -0
  151. package/dist/commands/{channel → teamwork}/supervisor/warning.js +6 -6
  152. package/dist/commands/teamwork/supervisor/warning.js.map +1 -0
  153. package/dist/commands/{channel → teamwork}/supervisor.d.ts +21 -21
  154. package/dist/commands/teamwork/supervisor.d.ts.map +1 -0
  155. package/dist/commands/{channel → teamwork}/supervisor.js +53 -63
  156. package/dist/commands/teamwork/supervisor.js.map +1 -0
  157. package/dist/commands/teamwork/teammate-adapters.d.ts +31 -0
  158. package/dist/commands/teamwork/teammate-adapters.d.ts.map +1 -0
  159. package/dist/commands/teamwork/teammate-adapters.js +96 -0
  160. package/dist/commands/teamwork/teammate-adapters.js.map +1 -0
  161. package/dist/commands/teamwork/teammate-config.d.ts +19 -0
  162. package/dist/commands/teamwork/teammate-config.d.ts.map +1 -0
  163. package/dist/commands/teamwork/teammate-config.js +97 -0
  164. package/dist/commands/teamwork/teammate-config.js.map +1 -0
  165. package/dist/commands/teamwork/teammate-launch.d.ts +58 -0
  166. package/dist/commands/teamwork/teammate-launch.d.ts.map +1 -0
  167. package/dist/commands/teamwork/teammate-launch.js +125 -0
  168. package/dist/commands/teamwork/teammate-launch.js.map +1 -0
  169. package/dist/commands/teamwork/text-body.d.ts +13 -0
  170. package/dist/commands/teamwork/text-body.d.ts.map +1 -0
  171. package/dist/commands/{channel → teamwork}/text-body.js +3 -3
  172. package/dist/commands/teamwork/text-body.js.map +1 -0
  173. package/dist/commands/teamwork/threads.d.ts +35 -0
  174. package/dist/commands/teamwork/threads.d.ts.map +1 -0
  175. package/dist/commands/{channel → teamwork}/threads.js +19 -19
  176. package/dist/commands/teamwork/threads.js.map +1 -0
  177. package/dist/commands/teamwork/title.d.ts +12 -0
  178. package/dist/commands/teamwork/title.d.ts.map +1 -0
  179. package/dist/commands/teamwork/title.js +24 -0
  180. package/dist/commands/teamwork/title.js.map +1 -0
  181. package/dist/commands/{channel → teamwork}/wait.d.ts +1 -5
  182. package/dist/commands/teamwork/wait.d.ts.map +1 -0
  183. package/dist/commands/{channel → teamwork}/wait.js +8 -21
  184. package/dist/commands/teamwork/wait.js.map +1 -0
  185. package/dist/commands/update.d.ts.map +1 -1
  186. package/dist/commands/update.js +93 -90
  187. package/dist/commands/update.js.map +1 -1
  188. package/dist/commands/workflow.d.ts +1 -1
  189. package/dist/commands/workflow.d.ts.map +1 -1
  190. package/dist/commands/workflow.js +9 -10
  191. package/dist/commands/workflow.js.map +1 -1
  192. package/dist/configurators/claude.d.ts +0 -17
  193. package/dist/configurators/claude.d.ts.map +1 -1
  194. package/dist/configurators/claude.js +2 -61
  195. package/dist/configurators/claude.js.map +1 -1
  196. package/dist/configurators/shared.d.ts.map +1 -1
  197. package/dist/configurators/shared.js +2 -2
  198. package/dist/configurators/shared.js.map +1 -1
  199. package/dist/configurators/snow.js +1 -1
  200. package/dist/configurators/snow.js.map +1 -1
  201. package/dist/configurators/workflow.d.ts +1 -1
  202. package/dist/configurators/workflow.js +5 -5
  203. package/dist/configurators/workflow.js.map +1 -1
  204. package/dist/constants/paths.d.ts +1 -1
  205. package/dist/constants/paths.d.ts.map +1 -1
  206. package/dist/constants/paths.js +1 -1
  207. package/dist/constants/paths.js.map +1 -1
  208. package/dist/migrations/manifests/0.7.0-beta.10.json +9 -0
  209. package/dist/migrations/manifests/0.7.0-beta.11.json +9 -0
  210. package/dist/migrations/manifests/0.7.0-beta.12.json +9 -0
  211. package/dist/migrations/manifests/0.7.0-beta.13.json +9 -0
  212. package/dist/migrations/manifests/0.7.0-beta.14.json +9 -0
  213. package/dist/migrations/manifests/0.7.0-beta.15.json +9 -0
  214. package/dist/migrations/manifests/0.7.0-beta.16.json +9 -0
  215. package/dist/migrations/manifests/0.7.0-beta.17.json +9 -0
  216. package/dist/migrations/manifests/0.7.0-beta.18.json +9 -0
  217. package/dist/migrations/manifests/0.7.0-beta.19.json +9 -0
  218. package/dist/migrations/manifests/0.7.0-beta.20.json +9 -0
  219. package/dist/migrations/manifests/0.7.0-beta.21.json +17 -0
  220. package/dist/migrations/manifests/0.7.0-beta.22.json +9 -0
  221. package/dist/migrations/manifests/0.7.0-beta.23.json +9 -0
  222. package/dist/migrations/manifests/0.7.0-beta.24.json +9 -0
  223. package/dist/migrations/manifests/0.7.0-beta.25.json +9 -0
  224. package/dist/migrations/manifests/0.7.0-beta.26.json +9 -0
  225. package/dist/migrations/manifests/0.7.0-beta.27.json +9 -0
  226. package/dist/migrations/manifests/0.7.0-beta.28.json +9 -0
  227. package/dist/migrations/manifests/0.7.0-beta.29.json +9 -0
  228. package/dist/migrations/manifests/0.7.0-beta.30.json +9 -0
  229. package/dist/migrations/manifests/0.7.0-beta.31.json +9 -0
  230. package/dist/migrations/manifests/0.7.0-beta.32.json +9 -0
  231. package/dist/migrations/manifests/0.7.0-beta.33.json +9 -0
  232. package/dist/migrations/manifests/0.7.0-beta.34.json +9 -0
  233. package/dist/migrations/manifests/0.7.0-beta.35.json +9 -0
  234. package/dist/migrations/manifests/0.7.0-beta.36.json +9 -0
  235. package/dist/migrations/manifests/0.7.0-beta.37.json +9 -0
  236. package/dist/migrations/manifests/0.7.0-beta.38.json +9 -0
  237. package/dist/migrations/manifests/0.7.0-beta.39.json +9 -0
  238. package/dist/migrations/manifests/0.7.0-beta.40.json +9 -0
  239. package/dist/migrations/manifests/0.7.0-beta.41.json +9 -0
  240. package/dist/migrations/manifests/0.7.0-beta.42.json +9 -0
  241. package/dist/migrations/manifests/0.7.0-beta.43.json +9 -0
  242. package/dist/migrations/manifests/0.7.0-beta.44.json +9 -0
  243. package/dist/migrations/manifests/0.7.0-beta.45.json +9 -0
  244. package/dist/migrations/manifests/0.7.0-beta.46.json +9 -0
  245. package/dist/migrations/manifests/0.7.0-beta.47.json +9 -0
  246. package/dist/migrations/manifests/0.7.0-beta.48.json +9 -0
  247. package/dist/migrations/manifests/0.7.0-beta.49.json +13 -0
  248. package/dist/migrations/manifests/0.7.0-beta.50.json +12 -0
  249. package/dist/migrations/manifests/0.7.0-beta.51.json +9 -0
  250. package/dist/migrations/manifests/0.7.0-beta.52.json +9 -0
  251. package/dist/migrations/manifests/0.7.0-beta.53.json +27 -0
  252. package/dist/migrations/manifests/0.7.0-beta.54.json +8 -0
  253. package/dist/migrations/manifests/0.7.1-beta.0.json +9 -0
  254. package/dist/migrations/manifests/0.7.1-beta.1.json +186 -0
  255. package/dist/migrations/manifests/0.7.1.json +9 -0
  256. package/dist/templates/claude/hooks/statusline.py +0 -7
  257. package/dist/templates/codex/hooks/session-start.py +31 -38
  258. package/dist/templates/common/bundled-skills/trellis-decision-space-construction-and-merging/SKILL.md +10 -0
  259. package/dist/templates/common/bundled-skills/trellis-decision-space-construction-and-merging/workflow.md +40 -0
  260. package/dist/templates/common/bundled-skills/trellis-delivery-review-and-closeout/SKILL.md +10 -0
  261. package/dist/templates/common/bundled-skills/trellis-delivery-review-and-closeout/workflow.md +55 -0
  262. package/dist/templates/common/bundled-skills/trellis-goal-exploration-and-requirements/SKILL.md +10 -0
  263. package/dist/templates/common/bundled-skills/trellis-goal-exploration-and-requirements/workflow.md +33 -0
  264. package/dist/templates/common/bundled-skills/trellis-meta/SKILL.md +25 -25
  265. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-agents.md +1 -1
  266. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-skills-or-commands.md +13 -19
  267. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-spec-structure.md +1 -1
  268. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-task-lifecycle.md +27 -75
  269. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-workflow.md +17 -56
  270. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/overview.md +1 -1
  271. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/bundled-skills.md +14 -11
  272. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/generated-files.md +0 -33
  273. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/multi-agent-teamwork.md +32 -0
  274. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/spec-system.md +1 -1
  275. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/task-system.md +25 -111
  276. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/workflow.md +19 -60
  277. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/skills-and-commands.md +1 -1
  278. package/dist/templates/common/bundled-skills/trellis-session-insight/SKILL.md +1 -1
  279. package/dist/templates/common/bundled-skills/trellis-session-insight/references/cli-quick-reference.md +1 -1
  280. package/dist/templates/common/bundled-skills/trellis-solution-design-and-convergence/SKILL.md +10 -0
  281. package/dist/templates/common/bundled-skills/trellis-solution-design-and-convergence/workflow.md +36 -0
  282. package/dist/templates/common/bundled-skills/trellis-task-implementation/SKILL.md +10 -0
  283. package/dist/templates/common/bundled-skills/trellis-task-implementation/workflow.md +29 -0
  284. package/dist/templates/common/bundled-skills/trellis-teamwork/SKILL.md +73 -0
  285. package/dist/templates/common/bundled-skills/trellis-teamwork/references/coordination-modes.md +29 -0
  286. package/dist/templates/common/bundled-skills/trellis-teamwork/references/teammate-configuration-matrix.md +31 -0
  287. package/dist/templates/common/bundled-skills/trellis-teamwork/references/teammate-processes.md +37 -0
  288. package/dist/templates/common/bundled-skills/trellis-teamwork/references/teamwork-plan-construction.md +78 -0
  289. package/dist/templates/common/commands/continue.md +9 -49
  290. package/dist/templates/common/commands/finish-work.md +10 -58
  291. package/dist/templates/common/commands/start.md +19 -49
  292. package/dist/templates/common/skills/brainstorm.md +11 -199
  293. package/dist/templates/copilot/hooks/session-start.py +30 -26
  294. package/dist/templates/copilot/prompts/brainstorm.prompt.md +7 -160
  295. package/dist/templates/copilot/prompts/finish-work.prompt.md +11 -88
  296. package/dist/templates/copilot/prompts/record-session.prompt.md +4 -12
  297. package/dist/templates/copilot/prompts/start.prompt.md +12 -10
  298. package/dist/templates/dsh/DSH.md +2 -2
  299. package/dist/templates/extract.js +4 -0
  300. package/dist/templates/extract.js.map +1 -1
  301. package/dist/templates/kiro/agents/trellis.json +2 -2
  302. package/dist/templates/markdown/agents.md +14 -3
  303. package/dist/templates/markdown/spec/guides/cross-layer-thinking-guide.md.txt +9 -8
  304. package/dist/templates/markdown/spec/guides/cross-platform-thinking-guide.md.txt +1 -1
  305. package/dist/templates/markdown/workspace-index.md +0 -1
  306. package/dist/templates/omp/extensions/trellis/index.ts.txt +28 -17
  307. package/dist/templates/opencode/lib/session-utils.js +20 -25
  308. package/dist/templates/opencode/lib/trellis-context.js +4 -76
  309. package/dist/templates/opencode/plugins/inject-subagent-context.js +5 -4
  310. package/dist/templates/opencode/plugins/inject-workflow-state.js +14 -17
  311. package/dist/templates/shared-hooks/inject-shell-session-context.py +3 -3
  312. package/dist/templates/shared-hooks/inject-subagent-context.py +445 -67
  313. package/dist/templates/shared-hooks/inject-workflow-state.py +44 -36
  314. package/dist/templates/shared-hooks/session-start.py +21 -33
  315. package/dist/templates/trellis/agents/delegated-worktree-primary-agent.md +18 -0
  316. package/dist/templates/trellis/agents/teammate-profiles.json +8 -0
  317. package/dist/templates/trellis/agents/teammate.md +63 -0
  318. package/dist/templates/trellis/config.yaml +29 -38
  319. package/dist/templates/trellis/gitignore.txt +1 -0
  320. package/dist/templates/trellis/index.d.ts +8 -12
  321. package/dist/templates/trellis/index.d.ts.map +1 -1
  322. package/dist/templates/trellis/index.js +14 -25
  323. package/dist/templates/trellis/index.js.map +1 -1
  324. package/dist/templates/trellis/scripts/add_session.py +1 -1
  325. package/dist/templates/trellis/scripts/common/__init__.py +0 -1
  326. package/dist/templates/trellis/scripts/common/active_task.py +136 -100
  327. package/dist/templates/trellis/scripts/common/config.py +7 -9
  328. package/dist/templates/trellis/scripts/common/git_context.py +1 -1
  329. package/dist/templates/trellis/scripts/common/io.py +19 -0
  330. package/dist/templates/trellis/scripts/common/paths.py +0 -1
  331. package/dist/templates/trellis/scripts/common/safe_commit.py +11 -90
  332. package/dist/templates/trellis/scripts/common/session_context.py +1 -8
  333. package/dist/templates/trellis/scripts/common/spec_match.py +1 -1
  334. package/dist/templates/trellis/scripts/common/task_context.py +152 -59
  335. package/dist/templates/trellis/scripts/common/task_continuity.py +598 -0
  336. package/dist/templates/trellis/scripts/common/task_store.py +70 -548
  337. package/dist/templates/trellis/scripts/common/task_utils.py +4 -98
  338. package/dist/templates/trellis/scripts/common/task_workflow.py +1096 -0
  339. package/dist/templates/trellis/scripts/common/tasks.py +5 -8
  340. package/dist/templates/trellis/scripts/common/workflow_phase.py +8 -7
  341. package/dist/templates/trellis/scripts/hooks/linear_sync.py +3 -3
  342. package/dist/templates/trellis/scripts/task.py +119 -377
  343. package/dist/templates/trellis/workflow.md +111 -706
  344. package/dist/utils/agent-refs.d.ts +5 -5
  345. package/dist/utils/agent-refs.js +6 -6
  346. package/dist/utils/file-writer.d.ts +3 -2
  347. package/dist/utils/file-writer.d.ts.map +1 -1
  348. package/dist/utils/file-writer.js +3 -2
  349. package/dist/utils/file-writer.js.map +1 -1
  350. package/dist/utils/managed-paths.d.ts +0 -7
  351. package/dist/utils/managed-paths.d.ts.map +1 -1
  352. package/dist/utils/managed-paths.js +0 -40
  353. package/dist/utils/managed-paths.js.map +1 -1
  354. package/dist/utils/manifest-prune.d.ts +2 -1
  355. package/dist/utils/manifest-prune.d.ts.map +1 -1
  356. package/dist/utils/manifest-prune.js +14 -16
  357. package/dist/utils/manifest-prune.js.map +1 -1
  358. package/dist/utils/uninstall-scrubbers.d.ts.map +1 -1
  359. package/dist/utils/uninstall-scrubbers.js +3 -3
  360. package/dist/utils/uninstall-scrubbers.js.map +1 -1
  361. package/dist/utils/workflow-resolver.d.ts +10 -16
  362. package/dist/utils/workflow-resolver.d.ts.map +1 -1
  363. package/dist/utils/workflow-resolver.js +25 -36
  364. package/dist/utils/workflow-resolver.js.map +1 -1
  365. package/package.json +2 -2
  366. package/dist/commands/channel/adapters/claude.d.ts.map +0 -1
  367. package/dist/commands/channel/adapters/claude.js.map +0 -1
  368. package/dist/commands/channel/adapters/codex.d.ts.map +0 -1
  369. package/dist/commands/channel/adapters/codex.js.map +0 -1
  370. package/dist/commands/channel/adapters/index.d.ts.map +0 -1
  371. package/dist/commands/channel/adapters/index.js.map +0 -1
  372. package/dist/commands/channel/adapters/types.d.ts.map +0 -1
  373. package/dist/commands/channel/adapters/types.js.map +0 -1
  374. package/dist/commands/channel/agent-loader.d.ts.map +0 -1
  375. package/dist/commands/channel/agent-loader.js.map +0 -1
  376. package/dist/commands/channel/context-loader.d.ts.map +0 -1
  377. package/dist/commands/channel/context-loader.js.map +0 -1
  378. package/dist/commands/channel/context-trust.d.ts.map +0 -1
  379. package/dist/commands/channel/context-trust.js.map +0 -1
  380. package/dist/commands/channel/context.d.ts +0 -16
  381. package/dist/commands/channel/context.d.ts.map +0 -1
  382. package/dist/commands/channel/context.js.map +0 -1
  383. package/dist/commands/channel/create.d.ts +0 -29
  384. package/dist/commands/channel/create.d.ts.map +0 -1
  385. package/dist/commands/channel/create.js +0 -47
  386. package/dist/commands/channel/create.js.map +0 -1
  387. package/dist/commands/channel/dev-parse-trace.d.ts.map +0 -1
  388. package/dist/commands/channel/dev-parse-trace.js.map +0 -1
  389. package/dist/commands/channel/guard.d.ts.map +0 -1
  390. package/dist/commands/channel/guard.js.map +0 -1
  391. package/dist/commands/channel/index.d.ts +0 -3
  392. package/dist/commands/channel/index.d.ts.map +0 -1
  393. package/dist/commands/channel/index.js.map +0 -1
  394. package/dist/commands/channel/interrupt.d.ts.map +0 -1
  395. package/dist/commands/channel/interrupt.js +0 -22
  396. package/dist/commands/channel/interrupt.js.map +0 -1
  397. package/dist/commands/channel/kill.d.ts +0 -7
  398. package/dist/commands/channel/kill.d.ts.map +0 -1
  399. package/dist/commands/channel/kill.js.map +0 -1
  400. package/dist/commands/channel/list.d.ts +0 -18
  401. package/dist/commands/channel/list.d.ts.map +0 -1
  402. package/dist/commands/channel/list.js.map +0 -1
  403. package/dist/commands/channel/messages.d.ts.map +0 -1
  404. package/dist/commands/channel/messages.js.map +0 -1
  405. package/dist/commands/channel/rm.d.ts +0 -27
  406. package/dist/commands/channel/rm.d.ts.map +0 -1
  407. package/dist/commands/channel/rm.js.map +0 -1
  408. package/dist/commands/channel/run.d.ts +0 -32
  409. package/dist/commands/channel/run.js +0 -141
  410. package/dist/commands/channel/run.js.map +0 -1
  411. package/dist/commands/channel/send.d.ts.map +0 -1
  412. package/dist/commands/channel/send.js.map +0 -1
  413. package/dist/commands/channel/spawn.d.ts.map +0 -1
  414. package/dist/commands/channel/spawn.js.map +0 -1
  415. package/dist/commands/channel/store/events.d.ts +0 -22
  416. package/dist/commands/channel/store/events.d.ts.map +0 -1
  417. package/dist/commands/channel/store/events.js.map +0 -1
  418. package/dist/commands/channel/store/filter.d.ts +0 -3
  419. package/dist/commands/channel/store/filter.d.ts.map +0 -1
  420. package/dist/commands/channel/store/filter.js +0 -2
  421. package/dist/commands/channel/store/filter.js.map +0 -1
  422. package/dist/commands/channel/store/lock.d.ts.map +0 -1
  423. package/dist/commands/channel/store/lock.js.map +0 -1
  424. package/dist/commands/channel/store/paths.d.ts.map +0 -1
  425. package/dist/commands/channel/store/paths.js.map +0 -1
  426. package/dist/commands/channel/store/schema.d.ts +0 -27
  427. package/dist/commands/channel/store/schema.d.ts.map +0 -1
  428. package/dist/commands/channel/store/schema.js +0 -34
  429. package/dist/commands/channel/store/schema.js.map +0 -1
  430. package/dist/commands/channel/store/thread-state.d.ts.map +0 -1
  431. package/dist/commands/channel/store/thread-state.js.map +0 -1
  432. package/dist/commands/channel/store/watch.d.ts +0 -19
  433. package/dist/commands/channel/store/watch.d.ts.map +0 -1
  434. package/dist/commands/channel/store/watch.js.map +0 -1
  435. package/dist/commands/channel/supervisor/idle.d.ts.map +0 -1
  436. package/dist/commands/channel/supervisor/idle.js.map +0 -1
  437. package/dist/commands/channel/supervisor/inbox.d.ts.map +0 -1
  438. package/dist/commands/channel/supervisor/inbox.js.map +0 -1
  439. package/dist/commands/channel/supervisor/shutdown.d.ts.map +0 -1
  440. package/dist/commands/channel/supervisor/shutdown.js.map +0 -1
  441. package/dist/commands/channel/supervisor/stdout.d.ts.map +0 -1
  442. package/dist/commands/channel/supervisor/stdout.js.map +0 -1
  443. package/dist/commands/channel/supervisor/turns.d.ts.map +0 -1
  444. package/dist/commands/channel/supervisor/turns.js.map +0 -1
  445. package/dist/commands/channel/supervisor/warning.d.ts.map +0 -1
  446. package/dist/commands/channel/supervisor/warning.js.map +0 -1
  447. package/dist/commands/channel/supervisor.d.ts.map +0 -1
  448. package/dist/commands/channel/supervisor.js.map +0 -1
  449. package/dist/commands/channel/text-body.d.ts +0 -13
  450. package/dist/commands/channel/text-body.d.ts.map +0 -1
  451. package/dist/commands/channel/text-body.js.map +0 -1
  452. package/dist/commands/channel/threads.d.ts +0 -39
  453. package/dist/commands/channel/threads.d.ts.map +0 -1
  454. package/dist/commands/channel/threads.js.map +0 -1
  455. package/dist/commands/channel/title.d.ts +0 -12
  456. package/dist/commands/channel/title.d.ts.map +0 -1
  457. package/dist/commands/channel/title.js +0 -24
  458. package/dist/commands/channel/title.js.map +0 -1
  459. package/dist/commands/channel/wait.d.ts.map +0 -1
  460. package/dist/commands/channel/wait.js.map +0 -1
  461. package/dist/commands/channel/workers.d.ts +0 -9
  462. package/dist/commands/channel/workers.d.ts.map +0 -1
  463. package/dist/commands/channel/workers.js +0 -31
  464. package/dist/commands/channel/workers.js.map +0 -1
  465. package/dist/templates/common/bundled-skills/trellis-channel/SKILL.md +0 -69
  466. package/dist/templates/common/bundled-skills/trellis-channel/references/command-reference.md +0 -549
  467. package/dist/templates/common/bundled-skills/trellis-channel/references/forum.md +0 -233
  468. package/dist/templates/common/bundled-skills/trellis-channel/references/progress-debugging.md +0 -237
  469. package/dist/templates/common/bundled-skills/trellis-channel/references/subnode-work.md +0 -222
  470. package/dist/templates/common/bundled-skills/trellis-channel/references/workers.md +0 -295
  471. package/dist/templates/common/bundled-skills/trellis-channel/references/workflows.md +0 -131
  472. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/multi-agent-channel.md +0 -70
  473. package/dist/templates/common/bundled-skills/trellis-research-record/SKILL.md +0 -14
  474. package/dist/templates/copilot/prompts/parallel.prompt.md +0 -204
  475. package/dist/templates/trellis/agents/check.md +0 -70
  476. package/dist/templates/trellis/agents/implement.md +0 -71
  477. package/dist/templates/trellis/agents/subnode.env +0 -4
  478. package/dist/templates/trellis/agents/subnode.md +0 -76
  479. package/dist/templates/trellis/scripts/common/context_projection.py +0 -508
  480. package/dist/templates/trellis/scripts/common/continuation_record.py +0 -450
  481. package/dist/templates/trellis/scripts/common/ownership_record.py +0 -439
  482. package/dist/templates/trellis/scripts/subnode_artifact.py +0 -735
  483. package/dist/templates/trellis/scripts/workspace_note.py +0 -126
  484. /package/dist/commands/{channel → teamwork}/adapters/types.js +0 -0
  485. /package/dist/commands/{channel → teamwork}/supervisor/idle.d.ts +0 -0
  486. /package/dist/commands/{channel → teamwork}/supervisor/idle.js +0 -0
  487. /package/dist/commands/{channel → teamwork}/supervisor/turns.d.ts +0 -0
  488. /package/dist/commands/{channel → teamwork}/supervisor/turns.js +0 -0
@@ -6,7 +6,6 @@ Provides:
6
6
  ensure_tasks_dir - Ensure tasks directory exists
7
7
  cmd_create - Create a new task
8
8
  cmd_rename - Rename a task and every reference to it
9
- cmd_archive - Archive completed task
10
9
  cmd_set_branch - Set git branch for task
11
10
  cmd_set_base_branch - Set PR target branch
12
11
  cmd_set_scope - Set scope for PR title
@@ -27,26 +26,18 @@ from pathlib import Path
27
26
  from .config import (
28
27
  get_codex_dispatch_mode,
29
28
  get_packages,
30
- get_session_auto_commit,
31
29
  is_monorepo,
32
30
  resolve_package,
33
31
  validate_package,
34
32
  )
35
33
  from .git import (
36
- INDEX_LOCK_RETRY_ATTEMPTS,
37
- branch_exists_locally,
38
- has_git_remote,
39
- index_lock_path,
40
34
  resolve_default_branch,
41
35
  run_git,
42
- run_git_retry_index_lock,
43
- stderr_indicates_index_lock,
44
36
  )
45
37
  from .io import describe_json_read_failure, read_json_checked, write_json
46
38
  from .log import Colors, colored
47
39
  from .paths import (
48
40
  DEVELOPER_HINT,
49
- DIR_ARCHIVE,
50
41
  DIR_TASKS,
51
42
  DIR_WORKFLOW,
52
43
  FILE_TASK_JSON,
@@ -55,14 +46,7 @@ from .paths import (
55
46
  get_repo_root,
56
47
  get_tasks_dir,
57
48
  )
58
- from .safe_commit import (
59
- print_gitignore_warning,
60
- safe_archive_paths_to_add,
61
- safe_git_add,
62
- )
63
49
  from .task_utils import (
64
- archive_destination_for,
65
- archive_task_complete,
66
50
  find_task_by_name,
67
51
  is_within_tasks_dir,
68
52
  resolve_task_dir,
@@ -81,7 +65,7 @@ from .workflow_selection import DIR_WORKFLOWS, WORKFLOW_ID_RE
81
65
  # U+0085 (NEL) and U+001C-U+001F, which JS keeps; JS strips U+FEFF (BOM),
82
66
  # which Python keeps. Stripping the union means anything create accepts is
83
67
  # still non-empty after either side trims it, so a record cannot pass here and
84
- # then be refused as empty by a JS pre-archive validator.
68
+ # then fail equivalent validation in a JavaScript consumer.
85
69
  BLANK_CHARS = (
86
70
  "\t\n\v\f\r" # U+0009-U+000D
87
71
  "\x1c\x1d\x1e\x1f" # U+001C-U+001F separators (Python only)
@@ -114,34 +98,14 @@ def _slugify(title: str) -> str:
114
98
  def ensure_tasks_dir(repo_root: Path) -> Path:
115
99
  """Ensure tasks directory exists."""
116
100
  tasks_dir = get_tasks_dir(repo_root)
117
- archive_dir = tasks_dir / "archive"
118
101
 
119
102
  if not tasks_dir.exists():
120
103
  tasks_dir.mkdir(parents=True)
121
104
  print(colored(f"Created tasks directory: {tasks_dir}", Colors.GREEN), file=sys.stderr)
122
105
 
123
- if not archive_dir.exists():
124
- archive_dir.mkdir(parents=True)
125
-
126
106
  return tasks_dir
127
107
 
128
108
 
129
- def _find_archived_task_by_dir_name(tasks_dir: Path, dir_name: str) -> Path | None:
130
- """Find an archived task directory with the exact active-task dir name."""
131
- archive_dir = tasks_dir / DIR_ARCHIVE
132
- if not archive_dir.is_dir():
133
- return None
134
-
135
- for month_dir in sorted(archive_dir.iterdir()):
136
- if not month_dir.is_dir():
137
- continue
138
- candidate = month_dir / dir_name
139
- if candidate.is_dir():
140
- return candidate
141
-
142
- return None
143
-
144
-
145
109
  def _repo_relative_path(path: Path, repo_root: Path) -> str:
146
110
  """Format a path relative to the repo root when possible."""
147
111
  try:
@@ -197,41 +161,30 @@ def _report_write_failure(path: Path) -> None:
197
161
  )
198
162
 
199
163
 
200
- def _restore_child_links(unlinked: dict[Path, str | None]) -> None:
201
- """Put back the parent links this archive attempt already removed.
164
+ def _task_mutation_allowed(repo_root: Path, task_dir: Path, data: dict) -> bool:
165
+ from .task_continuity import assert_task_mutation_allowed
166
+ from .task_workflow import TaskWorkflowError
202
167
 
203
- The unlink loop below walks a parent's children one at a time, so a
204
- failure part-way through leaves the earlier children carrying
205
- ``parent: null`` while their parent is still in the active tree. That is
206
- the mirror image of the dangling reference the same loop already refuses
207
- to create, and nothing repairs it later either, so undo it before the
208
- failure is reported.
168
+ try:
169
+ assert_task_mutation_allowed(repo_root, task_dir, data)
170
+ except TaskWorkflowError as error:
171
+ print(colored(f"{error.code}: {error}", Colors.RED), file=sys.stderr)
172
+ return False
173
+ return True
209
174
 
210
- Restoring is best-effort: if a child cannot be written back, name it so
211
- the caller can re-link it by hand instead of guessing which one broke.
212
- """
213
- broken: list[str] = []
214
- for child_json, original_parent in unlinked.items():
215
- child_data, _ = read_json_checked(child_json)
216
- if child_data is None:
217
- broken.append(child_json.parent.name)
218
- continue
219
- child_data["parent"] = original_parent
220
- if not write_json(child_json, child_data):
221
- broken.append(child_json.parent.name)
222
- if broken:
223
- print(
224
- colored(
225
- f"Warning: could not restore the parent link on: {', '.join(broken)}. "
226
- "Re-link each one with `python3 .trellis/scripts/task.py "
227
- "add-subtask <parent> <child>`.",
228
- Colors.RED,
229
- ),
230
- file=sys.stderr,
231
- )
175
+
176
+ def _task_identity_change_allowed(repo_root: Path, task_dir: Path, data: dict) -> bool:
177
+ from .task_continuity import assert_task_identity_change_allowed
178
+ from .task_workflow import TaskWorkflowError
179
+
180
+ try:
181
+ assert_task_identity_change_allowed(repo_root, task_dir, data)
182
+ except TaskWorkflowError as error:
183
+ print(colored(f"{error.code}: {error}", Colors.RED), file=sys.stderr)
184
+ return False
185
+ return True
232
186
 
233
187
 
234
- # =============================================================================
235
188
  # Sub-agent platform detection + JSONL context files
236
189
  # =============================================================================
237
190
 
@@ -266,9 +219,9 @@ def _has_subagent_platform(repo_root: Path) -> bool:
266
219
  """Return True if any sub-agent-capable platform is configured.
267
220
 
268
221
  Detected by probing well-known config directories at the repo root. Codex
269
- counts only when ``codex.dispatch_mode: auto`` is explicitly selected
270
- (including the legacy ``sub-agent`` alias); the default inline mode loads
271
- context through skills, not JSONL.
222
+ counts by default through ``codex.dispatch_mode: auto`` (including the
223
+ legacy ``sub-agent`` alias); explicit inline mode loads context through
224
+ skills, not JSONL.
272
225
  """
273
226
  for config_dir in _SUBAGENT_CONFIG_DIRS:
274
227
  if (repo_root / config_dir).is_dir():
@@ -334,13 +287,13 @@ def cmd_create(args: argparse.Namespace) -> int:
334
287
 
335
288
  # Title and description are checked first, before ensure_tasks_dir and any
336
289
  # other write, so a rejected create leaves the tree untouched. Both are
337
- # required non-empty by pre-archive validation; catching that here costs
338
- # one retyped command instead of blocking a PR hours later.
290
+ # required non-empty; catching that here costs one retyped command instead
291
+ # of discovering the invalid task later.
339
292
  if not strip_blank(args.title):
340
293
  print(colored("Error: title is required", Colors.RED), file=sys.stderr)
341
294
  print(
342
295
  'Pass a non-empty <title> (whitespace only does not count): a task '
343
- "with an empty title is refused at archive.",
296
+ "with an empty title cannot be completed.",
344
297
  file=sys.stderr,
345
298
  )
346
299
  return 1
@@ -350,7 +303,7 @@ def cmd_create(args: argparse.Namespace) -> int:
350
303
  print(colored("Error: --description is required", Colors.RED), file=sys.stderr)
351
304
  print(
352
305
  'Pass --description "<what this task delivers>" (whitespace only does '
353
- "not count): a task with an empty description is refused at archive.",
306
+ "not count): a task with an empty description cannot be completed.",
354
307
  file=sys.stderr,
355
308
  )
356
309
  return 1
@@ -486,22 +439,17 @@ def cmd_create(args: argparse.Namespace) -> int:
486
439
  _report_read_failure(parent_json_path, parent_reason)
487
440
  print("No task was created. Fix the parent task.json, then retry.", file=sys.stderr)
488
441
  return 1
442
+ if not _task_mutation_allowed(repo_root, parent_dir, parent_data):
443
+ return 1
489
444
 
490
445
  dir_name = f"{date_prefix}-{slug}"
491
446
  task_dir = tasks_dir / dir_name
492
447
  task_json_path = task_dir / FILE_TASK_JSON
493
448
 
494
- archived_task_dir = _find_archived_task_by_dir_name(tasks_dir, dir_name)
495
- if archived_task_dir:
496
- print(colored(f"Error: Task already archived: {dir_name}", Colors.RED), file=sys.stderr)
497
- print(f"Archived at: {_repo_relative_path(archived_task_dir, repo_root)}", file=sys.stderr)
498
- print("Use a new slug if you intend to create a new task.", file=sys.stderr)
499
- return 1
500
-
501
449
  # Reusing a slug on the same day is an ordinary accident, and continuing
502
450
  # would rewrite the existing task.json below — resetting status, children,
503
451
  # parent, branch and meta, which nothing else can reconstruct. Fail like
504
- # the archived-name collision above unless the caller asks for it.
452
+ # unless the caller asks for it.
505
453
  if task_dir.exists():
506
454
  if not getattr(args, "force", False):
507
455
  print(colored(f"Error: Task already exists: {dir_name}", Colors.RED), file=sys.stderr)
@@ -518,6 +466,13 @@ def cmd_create(args: argparse.Namespace) -> int:
518
466
  ),
519
467
  file=sys.stderr,
520
468
  )
469
+ if task_json_path.is_file():
470
+ existing_data, existing_reason = read_json_checked(task_json_path)
471
+ if existing_data is None:
472
+ _report_read_failure(task_json_path, existing_reason)
473
+ return 1
474
+ if not _task_mutation_allowed(repo_root, task_dir, existing_data):
475
+ return 1
521
476
  created_dir = False
522
477
  else:
523
478
  task_dir.mkdir(parents=True)
@@ -887,7 +842,7 @@ def _plan_backrefs(
887
842
  unreadable: list[Path] = []
888
843
 
889
844
  for candidate in sorted(tasks_dir.iterdir()):
890
- if not candidate.is_dir() or candidate.name == DIR_ARCHIVE:
845
+ if not candidate.is_dir():
891
846
  continue
892
847
  if candidate == task_dir:
893
848
  continue
@@ -1017,6 +972,10 @@ def _apply_rename(plan: _RenamePlan, repo_root: Path) -> int:
1017
972
  finishes; a move-first ordering would instead leave the old name
1018
973
  unresolvable and the remaining edits to be made by hand.
1019
974
  """
975
+ if not _task_mutation_allowed(repo_root, plan.task_dir, plan.task_data):
976
+ return 1
977
+ if plan.identity and not _task_identity_change_allowed(repo_root, plan.task_dir, plan.task_data):
978
+ return 1
1020
979
  if plan.identity:
1021
980
  data = dict(plan.task_data)
1022
981
  for field_name, _old_value, new_value in plan.identity:
@@ -1056,8 +1015,7 @@ def _apply_rename(plan: _RenamePlan, repo_root: Path) -> int:
1056
1015
  # Last, and after the move: a session pointing at the old path would now
1057
1016
  # resolve to a missing directory, so `current` would report the task stale
1058
1017
  # and the context hook would inject nothing until the user ran `start`
1059
- # again. Archive clears these pointers because the task is leaving; rename
1060
- # moves them, because the task is still the one being worked on.
1018
+ # again. Rename moves the pointers because the task is still being worked on.
1061
1019
  from .active_task import repoint_task_in_sessions
1062
1020
 
1063
1021
  repoint_task_in_sessions(str(plan.task_dir), str(plan.new_dir), repo_root)
@@ -1076,13 +1034,12 @@ def cmd_rename(args: argparse.Namespace) -> int:
1076
1034
  print(colored(f"Error: Task not found: {args.name}", Colors.RED), file=sys.stderr)
1077
1035
  return 1
1078
1036
 
1079
- # Narrower than resolve_task_dir's containment: an archived task has left
1080
- # the active set, so its back-references are no longer maintained here.
1037
+ # Restrict renames to task records directly under the tasks directory.
1081
1038
  if not is_within_tasks_dir(task_dir, repo_root):
1082
1039
  print(
1083
1040
  colored(
1084
1041
  f"Error: refusing to rename '{args.name}': "
1085
- f"{task_dir} is not an active task under {tasks_dir}",
1042
+ f"{task_dir} is not a task directly under {tasks_dir}",
1086
1043
  Colors.RED,
1087
1044
  ),
1088
1045
  file=sys.stderr,
@@ -1114,16 +1071,6 @@ def cmd_rename(args: argparse.Namespace) -> int:
1114
1071
  print("Pick a different slug.", file=sys.stderr)
1115
1072
  return 1
1116
1073
 
1117
- archived_task_dir = _find_archived_task_by_dir_name(tasks_dir, new_name)
1118
- if archived_task_dir:
1119
- print(
1120
- colored(f"Error: Task already archived: {new_name}", Colors.RED),
1121
- file=sys.stderr,
1122
- )
1123
- print(f"Archived at: {_repo_relative_path(archived_task_dir, repo_root)}", file=sys.stderr)
1124
- print("Pick a different slug.", file=sys.stderr)
1125
- return 1
1126
-
1127
1074
  task_json_path = task_dir / FILE_TASK_JSON
1128
1075
  if not task_json_path.is_file():
1129
1076
  print(
@@ -1194,455 +1141,6 @@ def cmd_rename(args: argparse.Namespace) -> int:
1194
1141
  )
1195
1142
  return 0
1196
1143
 
1197
-
1198
- # =============================================================================
1199
- # Command: archive
1200
- # =============================================================================
1201
-
1202
- def _task_branch_field(data: dict, key: str) -> str:
1203
- """Read a branch field as a trimmed string ("" when unset or not a string)."""
1204
- value = data.get(key)
1205
- return strip_blank(value) if isinstance(value, str) else ""
1206
-
1207
-
1208
- def _validate_branch_metadata(
1209
- data: dict,
1210
- task_name: str,
1211
- repo_root: Path,
1212
- skip: bool,
1213
- ) -> bool:
1214
- """Check branch metadata before the task leaves the active tree.
1215
-
1216
- Returns False when archiving must stop. Archive is the last gate that sees
1217
- a task, so metadata nobody can reconstruct afterwards is refused here
1218
- rather than repaired by hand later (#399 follow-up).
1219
-
1220
- "PR-backed" is deliberately pragmatic: a task carrying a base_branch in a
1221
- repo that has a remote was created expecting a PR, so a missing `branch`
1222
- means the metadata was never recorded — not that the work had no branch.
1223
- Local-only repos and tasks without a base_branch are left alone.
1224
-
1225
- A recorded branch that no longer exists locally stays a warning: after a
1226
- merge the feature branch is normally deleted, and refusing to archive then
1227
- would be backwards.
1228
- """
1229
- branch = _task_branch_field(data, "branch")
1230
- base_branch = _task_branch_field(data, "base_branch")
1231
- task_py = f"python3 {DIR_WORKFLOW}/scripts/task.py"
1232
-
1233
- if branch and not branch_exists_locally(branch, repo_root):
1234
- print(
1235
- colored(
1236
- f"Warning: recorded branch '{branch}' no longer exists locally "
1237
- "(likely merged and deleted).",
1238
- Colors.YELLOW,
1239
- ),
1240
- file=sys.stderr,
1241
- )
1242
-
1243
- if skip:
1244
- return True
1245
-
1246
- if branch and base_branch and branch == base_branch:
1247
- print(
1248
- colored(
1249
- f"Error: refusing to archive '{task_name}': branch and base_branch "
1250
- f"are both '{branch}'. A PR cannot target its own branch, so this "
1251
- "metadata cannot describe the work that was merged.",
1252
- Colors.RED,
1253
- ),
1254
- file=sys.stderr,
1255
- )
1256
- print("Repair whichever field is wrong:", file=sys.stderr)
1257
- print(f" {task_py} set-branch {task_name} <feature-branch>", file=sys.stderr)
1258
- print(f" {task_py} set-base-branch {task_name} <target-branch>", file=sys.stderr)
1259
- print(
1260
- f" {task_py} archive {task_name} --skip-branch-validation"
1261
- " # only if this task was never PR-backed",
1262
- file=sys.stderr,
1263
- )
1264
- return False
1265
-
1266
- if not branch and base_branch and has_git_remote(repo_root):
1267
- print(
1268
- colored(
1269
- f"Error: refusing to archive '{task_name}': no branch is recorded, "
1270
- f"but the task targets base_branch '{base_branch}' in a repo with a "
1271
- "remote — the branch it was built on was never written down.",
1272
- Colors.RED,
1273
- ),
1274
- file=sys.stderr,
1275
- )
1276
- print("Repair with:", file=sys.stderr)
1277
- print(f" {task_py} set-branch {task_name} <branch>", file=sys.stderr)
1278
- print(
1279
- f" {task_py} archive {task_name} --skip-branch-validation"
1280
- " # only if the work landed without a branch of its own",
1281
- file=sys.stderr,
1282
- )
1283
- return False
1284
-
1285
- return True
1286
-
1287
-
1288
- def cmd_archive(args: argparse.Namespace) -> int:
1289
- """Archive completed task."""
1290
- repo_root = get_repo_root()
1291
- task_name = args.name
1292
-
1293
- if not task_name:
1294
- print(colored("Error: Task name is required", Colors.RED), file=sys.stderr)
1295
- return 1
1296
-
1297
- tasks_dir = get_tasks_dir(repo_root)
1298
-
1299
- # Resolve task directory (supports task name, relative path, or absolute path)
1300
- task_dir = resolve_task_dir(task_name, repo_root)
1301
-
1302
- if task_dir is None or not task_dir.is_dir():
1303
- if task_dir is None:
1304
- # resolve_task_dir already reported why; keep the archive-specific
1305
- # refusal so it reads the same as the is_within_tasks_dir guard.
1306
- print(colored(
1307
- f"Error: refusing to archive '{task_name}': "
1308
- f"it does not resolve to a task under {tasks_dir}",
1309
- Colors.RED), file=sys.stderr)
1310
- else:
1311
- print(colored(f"Error: Task not found: {task_name}", Colors.RED), file=sys.stderr)
1312
- print("Active tasks:", file=sys.stderr)
1313
- # Import lazily to avoid circular dependency
1314
- from .tasks import iter_active_tasks
1315
- for t in iter_active_tasks(tasks_dir):
1316
- print(f" - {t.dir_name}/", file=sys.stderr)
1317
- return 1
1318
-
1319
- # Refuse to archive anything that isn't a real task directly under
1320
- # .trellis/tasks/. resolve_task_dir keeps the target inside the tasks dir;
1321
- # this narrows it further to a direct child, so an already-archived task
1322
- # (.trellis/tasks/archive/<month>/<task>) is not archived a second time.
1323
- if not is_within_tasks_dir(task_dir, repo_root):
1324
- print(colored(
1325
- f"Error: refusing to archive '{task_name}': "
1326
- f"{task_dir} is not a task under {tasks_dir}",
1327
- Colors.RED), file=sys.stderr)
1328
- return 1
1329
-
1330
- # Check the destination before anything below mutates task state. The
1331
- # mover refuses a collision too, but by then this command has already
1332
- # marked the task completed, re-parented its children and cleared the
1333
- # sessions pointing at it — all of which would have to be undone by hand.
1334
- archive_dest_check = archive_destination_for(task_dir)
1335
- if archive_dest_check.exists():
1336
- print(colored(
1337
- f"Error: refusing to archive '{task_name}': "
1338
- f"archive destination already exists: "
1339
- f"{_repo_relative_path(archive_dest_check, repo_root)}",
1340
- Colors.RED), file=sys.stderr)
1341
- print(f"Task remains at: {_repo_relative_path(task_dir, repo_root)}", file=sys.stderr)
1342
- print("Move or rename the existing archived task, then retry.", file=sys.stderr)
1343
- return 1
1344
-
1345
- dir_name = task_dir.name
1346
- task_json_path = task_dir / FILE_TASK_JSON
1347
-
1348
- # Update status before archiving
1349
- today = datetime.now().strftime("%Y-%m-%d")
1350
- # Names of child task dirs whose task.json gets modified below; passed
1351
- # into safe_archive_paths_to_add so they're staged in this commit.
1352
- modified_children: list[str] = []
1353
- if task_json_path.is_file():
1354
- data, read_reason = read_json_checked(task_json_path)
1355
- if data is None:
1356
- # Archiving is still the right outcome for a task whose task.json
1357
- # is broken — but say so, or the missing "completed" status looks
1358
- # like the archive silently did half its job.
1359
- problem, _ = describe_json_read_failure(task_json_path, read_reason)
1360
- print(
1361
- colored(
1362
- f"Warning: {problem}; archiving without updating status/children.",
1363
- Colors.YELLOW,
1364
- ),
1365
- file=sys.stderr,
1366
- )
1367
- else:
1368
- # Before any mutation: branch metadata is unrecoverable once the
1369
- # task leaves the active tree. Stale branches only warn.
1370
- if not _validate_branch_metadata(
1371
- data,
1372
- task_name,
1373
- repo_root,
1374
- getattr(args, "skip_branch_validation", False),
1375
- ):
1376
- print(
1377
- f"Not archived: {_repo_relative_path(task_dir, repo_root)} is unchanged.",
1378
- file=sys.stderr,
1379
- )
1380
- return 1
1381
-
1382
- data["status"] = "completed"
1383
- data["completedAt"] = today
1384
- if not write_json(task_json_path, data):
1385
- _report_write_failure(task_json_path)
1386
- print(
1387
- f"Not archived: {_repo_relative_path(task_dir, repo_root)} is unchanged. "
1388
- "Archiving a task still marked in progress would hide it from `list` "
1389
- "with the wrong status.",
1390
- file=sys.stderr,
1391
- )
1392
- return 1
1393
-
1394
- # Handle subtask relationships on archive.
1395
- # Keep this task in its parent's children list so progress
1396
- # counters (children_progress) stay consistent — children
1397
- # missing from the active set are treated as completed.
1398
- task_children = data.get("children", [])
1399
-
1400
- # If this is a parent, clear parent field in all children.
1401
- # Remember each link removed so a later failure in this loop can
1402
- # put it back (see _restore_child_links). Keyed by the child's
1403
- # task.json: a `children` list that names the same child twice
1404
- # would otherwise record a second, already-cleared snapshot and
1405
- # the restore would write that `null` back over the real parent.
1406
- unlinked_children: dict[Path, str | None] = {}
1407
- if task_children:
1408
- for child_name in task_children:
1409
- child_dir_path = find_task_by_name(child_name, tasks_dir)
1410
- if child_dir_path:
1411
- child_json = child_dir_path / FILE_TASK_JSON
1412
- if child_json.is_file():
1413
- child_data, child_reason = read_json_checked(child_json)
1414
- if child_data is None:
1415
- problem, _ = describe_json_read_failure(child_json, child_reason)
1416
- print(
1417
- colored(
1418
- f"Warning: {problem}; child '{child_dir_path.name}' "
1419
- "keeps its parent reference.",
1420
- Colors.YELLOW,
1421
- ),
1422
- file=sys.stderr,
1423
- )
1424
- continue
1425
- # Only the first visit to a child records its original
1426
- # parent: a `children` list naming the same child twice
1427
- # would otherwise snapshot the already-cleared value and
1428
- # the restore would write that `null` back over the link.
1429
- first_visit = child_json not in unlinked_children
1430
- original_parent = child_data.get("parent")
1431
- if first_visit:
1432
- unlinked_children[child_json] = original_parent
1433
- child_data["parent"] = None
1434
- if not write_json(child_json, child_data):
1435
- # Stop before the move: a child pointing at a
1436
- # parent that has left .trellis/tasks/ is a
1437
- # dangling reference nothing repairs later.
1438
- # Put back the children already unlinked above —
1439
- # their parent is staying, so losing the link the
1440
- # other way round is just as unrecoverable.
1441
- # Retrying is safe — every step so far is
1442
- # idempotent.
1443
- if first_visit:
1444
- # The link never came off, so there is nothing
1445
- # to put back for this child.
1446
- del unlinked_children[child_json]
1447
- _restore_child_links(unlinked_children)
1448
- _report_write_failure(child_json)
1449
- print(
1450
- f"Not archived: {_repo_relative_path(task_dir, repo_root)} is "
1451
- f"marked completed but stays in place because child "
1452
- f"'{child_dir_path.name}' could not be unlinked. "
1453
- "Fix the child, then run archive again.",
1454
- file=sys.stderr,
1455
- )
1456
- return 1
1457
- modified_children.append(child_dir_path.name)
1458
-
1459
- # Clear any session that still points at this task before the path moves.
1460
- from .active_task import clear_task_from_sessions
1461
- clear_task_from_sessions(str(task_dir), repo_root)
1462
-
1463
- # Archive
1464
- result = archive_task_complete(task_dir, repo_root)
1465
- if "archived_to" in result:
1466
- archive_dest = Path(result["archived_to"])
1467
- year_month = archive_dest.parent.name
1468
- print(colored(f"Archived: {dir_name} -> archive/{year_month}/", Colors.GREEN), file=sys.stderr)
1469
-
1470
- # Auto-commit unless --no-commit
1471
- if not getattr(args, "no_commit", False):
1472
- if not _auto_commit_archive(dir_name, repo_root, modified_children):
1473
- print(
1474
- colored(
1475
- "Archive moved on disk, but git auto-commit did not complete. "
1476
- "Resolve `git status` before continuing.",
1477
- Colors.RED,
1478
- ),
1479
- file=sys.stderr,
1480
- )
1481
- return 1
1482
-
1483
- # Return the archive path
1484
- print(f"{DIR_WORKFLOW}/{DIR_TASKS}/{DIR_ARCHIVE}/{year_month}/{dir_name}")
1485
-
1486
- # Run hooks with the archived path
1487
- archived_json = archive_dest / FILE_TASK_JSON
1488
- run_task_hooks("after_archive", archived_json, repo_root)
1489
- return 0
1490
-
1491
- return 1
1492
-
1493
-
1494
- def _auto_commit_archive(
1495
- task_name: str,
1496
- repo_root: Path,
1497
- modified_children: list[str] | None = None,
1498
- ) -> bool:
1499
- """Stage Trellis-owned task paths and commit after archive.
1500
-
1501
- Scoped narrowly to the archived task's source + destination paths
1502
- plus any child task dirs whose ``task.json`` was edited (parent →
1503
- children relationship update). Dirty changes in OTHER active task
1504
- dirs are NOT bundled into the archive commit.
1505
-
1506
- If ``.gitignore`` blocks the paths, we warn + skip — we do NOT
1507
- retry with ``git add -f``. The warning explicitly forbids
1508
- ``git add -f .trellis/`` (which would fan out to caches/backups)
1509
- and points users at ``session_auto_commit: false``.
1510
-
1511
- Honors ``session_auto_commit`` in ``.trellis/config.yaml``: when
1512
- set to ``false``, this function returns immediately without
1513
- touching git (the archive directory move on disk is unaffected).
1514
- """
1515
- if not get_session_auto_commit(repo_root):
1516
- print(
1517
- "[OK] session_auto_commit: false — skipping git stage/commit.",
1518
- file=sys.stderr,
1519
- )
1520
- return True
1521
-
1522
- source_rel = f"{DIR_WORKFLOW}/{DIR_TASKS}/{task_name}"
1523
- rc, tracked_out, _ = run_git(
1524
- ["ls-files", "--", source_rel],
1525
- cwd=repo_root,
1526
- )
1527
- source_was_tracked = rc == 0 and bool(tracked_out.strip())
1528
-
1529
- paths = safe_archive_paths_to_add(
1530
- repo_root, task_name=task_name, modified_children=modified_children
1531
- )
1532
- if not paths:
1533
- print("[OK] No task changes to commit.", file=sys.stderr)
1534
- return True
1535
-
1536
- success, _, err = safe_git_add(paths, repo_root, retry_on_index_lock=True)
1537
- if not success:
1538
- if err and "ignored by" in err.lower():
1539
- print_gitignore_warning(paths)
1540
- elif stderr_indicates_index_lock(err):
1541
- _print_index_lock_warning(
1542
- "git add", task_name, repo_root, [*paths, source_rel]
1543
- )
1544
- else:
1545
- print(
1546
- f"[WARN] git add failed: {err.strip() if err else 'unknown error'}",
1547
- file=sys.stderr,
1548
- )
1549
- return not source_was_tracked
1550
-
1551
- # Belt-and-suspenders for the phantom-delete bug: `safe_git_add` uses
1552
- # `git add` (no -A) which only stages additions/modifications. The
1553
- # source task directory was moved away by `shutil.move`, so its files
1554
- # need an explicit `git rm --cached` to stage the deletions in this
1555
- # same commit — otherwise they sit as uncommitted "phantom deletes"
1556
- # against HEAD until something later picks them up.
1557
- #
1558
- # `--ignore-unmatch` makes this a no-op when the task was never tracked
1559
- # (e.g. archiving a task that lived only in working tree).
1560
- rc, _, err = run_git_retry_index_lock(
1561
- ["rm", "-r", "--cached", "--ignore-unmatch", "--", source_rel],
1562
- cwd=repo_root,
1563
- )
1564
- if rc != 0 and stderr_indicates_index_lock(err):
1565
- # Committing now would record the archived copy without the
1566
- # source-side deletes — a half-archived tree in history.
1567
- _print_index_lock_warning(
1568
- "git rm --cached", task_name, repo_root, [*paths, source_rel]
1569
- )
1570
- return not source_was_tracked
1571
-
1572
- commit_paths = [*paths, *([source_rel] if source_was_tracked else [])]
1573
- rc, _, _ = run_git(
1574
- ["diff", "--cached", "--quiet", "--", *commit_paths],
1575
- cwd=repo_root,
1576
- )
1577
- if rc == 0:
1578
- print("[OK] No task changes to commit.", file=sys.stderr)
1579
- return True
1580
-
1581
- commit_msg = f"chore(task): archive {task_name}"
1582
- # Commit with an explicit pathspec: a bare `git commit` would sweep any
1583
- # unrelated entries the developer had staged before archiving into the
1584
- # chore commit (#579). Include `source_rel` only when it was tracked, so
1585
- # the source-side deletions land in that commit without passing an absent,
1586
- # never-tracked path to git.
1587
- rc, _, err = run_git_retry_index_lock(
1588
- ["commit", "-m", commit_msg, "--", *commit_paths], cwd=repo_root
1589
- )
1590
- if rc == 0:
1591
- print(f"[OK] Auto-committed: {commit_msg}", file=sys.stderr)
1592
- return True
1593
- elif stderr_indicates_index_lock(err):
1594
- _print_index_lock_warning(
1595
- "git commit", task_name, repo_root, [*paths, source_rel]
1596
- )
1597
- return not source_was_tracked
1598
- else:
1599
- print(f"[WARN] Auto-commit failed: {err.strip()}", file=sys.stderr)
1600
- return not source_was_tracked
1601
-
1602
-
1603
- def _print_index_lock_warning(
1604
- action: str, task_name: str, repo_root: Path, paths: list[str]
1605
- ) -> None:
1606
- """Report an archive auto-commit that gave up on a held index.lock.
1607
-
1608
- The move itself already succeeded, so the state is consistent — the task
1609
- lives in archive/ and its changes are staged-or-not but never half of
1610
- both in a commit. Only the commit is outstanding, which the user (or an
1611
- agent reading the log) has to finish by hand.
1612
-
1613
- ``paths`` are the paths the auto-commit would have staged, so the manual
1614
- command keeps the same narrow scope — a blanket ``git add -A -- .trellis/``
1615
- would sweep dirty changes from other active tasks into the archive commit.
1616
- """
1617
- lock = index_lock_path(repo_root)
1618
- print(
1619
- f"[WARN] {action} gave up after {INDEX_LOCK_RETRY_ATTEMPTS} attempts: "
1620
- f"another process is holding {lock}",
1621
- file=sys.stderr,
1622
- )
1623
- print(
1624
- "[WARN] The task was moved into archive/ on disk; only the commit is pending.",
1625
- file=sys.stderr,
1626
- )
1627
- print(
1628
- "[WARN] Close whatever holds the lock (an IDE git integration, a status",
1629
- file=sys.stderr,
1630
- )
1631
- print(
1632
- f"[WARN] daemon, another session), or delete {lock} if it is stale, then",
1633
- file=sys.stderr,
1634
- )
1635
- print(
1636
- f'[WARN] commit manually: git add -A -- {" ".join(paths)} && '
1637
- f'git commit -m "chore(task): archive {task_name}"',
1638
- file=sys.stderr,
1639
- )
1640
-
1641
-
1642
- # =============================================================================
1643
- # Command: add-subtask
1644
- # =============================================================================
1645
-
1646
1144
  def cmd_add_subtask(args: argparse.Namespace) -> int:
1647
1145
  """Link a child task to a parent task."""
1648
1146
  repo_root = get_repo_root()
@@ -1679,6 +1177,8 @@ def cmd_add_subtask(args: argparse.Namespace) -> int:
1679
1177
  if child_data is None:
1680
1178
  _report_read_failure(child_json_path, child_reason)
1681
1179
  return 1
1180
+ if not _task_mutation_allowed(repo_root, parent_dir, parent_data) or not _task_mutation_allowed(repo_root, child_dir, child_data):
1181
+ return 1
1682
1182
 
1683
1183
  # Check if child already has a parent
1684
1184
  existing_parent = child_data.get("parent")
@@ -1756,6 +1256,20 @@ def cmd_remove_subtask(args: argparse.Namespace) -> int:
1756
1256
  if child_data is None:
1757
1257
  _report_read_failure(child_json_path, child_reason)
1758
1258
  return 1
1259
+ if not _task_mutation_allowed(repo_root, parent_dir, parent_data) or not _task_mutation_allowed(repo_root, child_dir, child_data):
1260
+ return 1
1261
+ if parent_data.get("status") == "completed":
1262
+ print("Error: A completed parent cannot unlink a child after closeout acceptance.", file=sys.stderr)
1263
+ return 1
1264
+ if parent_data.get("status") == "cancelled":
1265
+ workflow = parent_data.get("meta", {}).get("task_workflow", {})
1266
+ phases = workflow.get("phases", {}) if isinstance(workflow, dict) else {}
1267
+ closeout = phases.get("delivery_review_and_closeout", {}) if isinstance(phases, dict) else {}
1268
+ acceptance = closeout.get("acceptance", {}) if isinstance(closeout, dict) else {}
1269
+ choices = acceptance.get("child_dispositions", {}) if isinstance(acceptance, dict) else {}
1270
+ if child_data.get("parent") != parent_dir.name or not isinstance(choices, dict) or choices.get(child_dir.name) != "independent":
1271
+ print("Error: A cancelled parent can unlink only a child recorded as independently continuing in its accepted closeout.", file=sys.stderr)
1272
+ return 1
1759
1273
 
1760
1274
  # Remove child from parent's children list
1761
1275
  parent_children = _ensure_children_list(parent_data)
@@ -1818,6 +1332,8 @@ def cmd_set_branch(args: argparse.Namespace) -> int:
1818
1332
  if data is None:
1819
1333
  _report_read_failure(task_json, reason)
1820
1334
  return 1
1335
+ if not _task_mutation_allowed(repo_root, target_dir, data):
1336
+ return 1
1821
1337
 
1822
1338
  data["branch"] = branch
1823
1339
  if not write_json(task_json, data):
@@ -1863,6 +1379,8 @@ def cmd_set_base_branch(args: argparse.Namespace) -> int:
1863
1379
  if data is None:
1864
1380
  _report_read_failure(task_json, reason)
1865
1381
  return 1
1382
+ if not _task_mutation_allowed(repo_root, target_dir, data):
1383
+ return 1
1866
1384
 
1867
1385
  data["base_branch"] = base_branch
1868
1386
  if not write_json(task_json, data):
@@ -1906,6 +1424,8 @@ def cmd_set_scope(args: argparse.Namespace) -> int:
1906
1424
  if data is None:
1907
1425
  _report_read_failure(task_json, reason)
1908
1426
  return 1
1427
+ if not _task_mutation_allowed(repo_root, target_dir, data):
1428
+ return 1
1909
1429
 
1910
1430
  data["scope"] = scope
1911
1431
  if not write_json(task_json, data):
@@ -1949,6 +1469,8 @@ def cmd_set_meta(args: argparse.Namespace) -> int:
1949
1469
  if data is None:
1950
1470
  _report_read_failure(task_json, reason)
1951
1471
  return 1
1472
+ if not _task_mutation_allowed(repo_root, target_dir, data):
1473
+ return 1
1952
1474
 
1953
1475
  meta = data.get("meta")
1954
1476
  if not isinstance(meta, dict):