@pennixrv/trellis 0.7.0-beta.8 → 0.7.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 (484) 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 +48 -89
  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.0-beta.9.json +9 -0
  254. package/dist/migrations/manifests/0.7.1.json +9 -0
  255. package/dist/templates/claude/hooks/statusline.py +0 -7
  256. package/dist/templates/codex/hooks/session-start.py +31 -38
  257. package/dist/templates/common/bundled-skills/trellis-decision-space-construction-and-merging/SKILL.md +10 -0
  258. package/dist/templates/common/bundled-skills/trellis-decision-space-construction-and-merging/workflow.md +40 -0
  259. package/dist/templates/common/bundled-skills/trellis-delivery-review-and-closeout/SKILL.md +10 -0
  260. package/dist/templates/common/bundled-skills/trellis-delivery-review-and-closeout/workflow.md +55 -0
  261. package/dist/templates/common/bundled-skills/trellis-goal-exploration-and-requirements/SKILL.md +10 -0
  262. package/dist/templates/common/bundled-skills/trellis-goal-exploration-and-requirements/workflow.md +33 -0
  263. package/dist/templates/common/bundled-skills/trellis-meta/SKILL.md +25 -25
  264. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-agents.md +1 -1
  265. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-skills-or-commands.md +13 -19
  266. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-spec-structure.md +1 -1
  267. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-task-lifecycle.md +27 -75
  268. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-workflow.md +17 -56
  269. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/overview.md +1 -1
  270. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/bundled-skills.md +14 -11
  271. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/generated-files.md +0 -33
  272. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/multi-agent-teamwork.md +32 -0
  273. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/spec-system.md +1 -1
  274. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/task-system.md +25 -111
  275. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/workflow.md +19 -60
  276. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/skills-and-commands.md +1 -1
  277. package/dist/templates/common/bundled-skills/trellis-session-insight/SKILL.md +1 -1
  278. package/dist/templates/common/bundled-skills/trellis-session-insight/references/cli-quick-reference.md +1 -1
  279. package/dist/templates/common/bundled-skills/trellis-solution-design-and-convergence/SKILL.md +10 -0
  280. package/dist/templates/common/bundled-skills/trellis-solution-design-and-convergence/workflow.md +36 -0
  281. package/dist/templates/common/bundled-skills/trellis-task-implementation/SKILL.md +10 -0
  282. package/dist/templates/common/bundled-skills/trellis-task-implementation/workflow.md +29 -0
  283. package/dist/templates/common/bundled-skills/trellis-teamwork/SKILL.md +73 -0
  284. package/dist/templates/common/bundled-skills/trellis-teamwork/references/coordination-modes.md +29 -0
  285. package/dist/templates/common/bundled-skills/trellis-teamwork/references/teammate-configuration-matrix.md +31 -0
  286. package/dist/templates/common/bundled-skills/trellis-teamwork/references/teammate-processes.md +37 -0
  287. package/dist/templates/common/bundled-skills/trellis-teamwork/references/teamwork-plan-construction.md +78 -0
  288. package/dist/templates/common/commands/continue.md +9 -49
  289. package/dist/templates/common/commands/finish-work.md +10 -58
  290. package/dist/templates/common/commands/start.md +19 -49
  291. package/dist/templates/common/skills/brainstorm.md +11 -191
  292. package/dist/templates/copilot/hooks/session-start.py +30 -26
  293. package/dist/templates/copilot/prompts/brainstorm.prompt.md +7 -150
  294. package/dist/templates/copilot/prompts/finish-work.prompt.md +11 -88
  295. package/dist/templates/copilot/prompts/record-session.prompt.md +4 -12
  296. package/dist/templates/copilot/prompts/start.prompt.md +12 -10
  297. package/dist/templates/dsh/DSH.md +2 -2
  298. package/dist/templates/kiro/agents/trellis.json +2 -2
  299. package/dist/templates/markdown/agents.md +14 -3
  300. package/dist/templates/markdown/spec/guides/cross-layer-thinking-guide.md.txt +9 -8
  301. package/dist/templates/markdown/spec/guides/cross-platform-thinking-guide.md.txt +1 -1
  302. package/dist/templates/markdown/workspace-index.md +0 -1
  303. package/dist/templates/omp/extensions/trellis/index.ts.txt +28 -17
  304. package/dist/templates/opencode/lib/session-utils.js +20 -25
  305. package/dist/templates/opencode/lib/trellis-context.js +4 -76
  306. package/dist/templates/opencode/plugins/inject-subagent-context.js +5 -4
  307. package/dist/templates/opencode/plugins/inject-workflow-state.js +14 -17
  308. package/dist/templates/shared-hooks/inject-shell-session-context.py +3 -3
  309. package/dist/templates/shared-hooks/inject-subagent-context.py +445 -67
  310. package/dist/templates/shared-hooks/inject-workflow-state.py +44 -36
  311. package/dist/templates/shared-hooks/session-start.py +21 -33
  312. package/dist/templates/trellis/agents/delegated-worktree-primary-agent.md +18 -0
  313. package/dist/templates/trellis/agents/teammate-profiles.json +8 -0
  314. package/dist/templates/trellis/agents/teammate.md +63 -0
  315. package/dist/templates/trellis/config.yaml +29 -38
  316. package/dist/templates/trellis/gitignore.txt +1 -0
  317. package/dist/templates/trellis/index.d.ts +8 -12
  318. package/dist/templates/trellis/index.d.ts.map +1 -1
  319. package/dist/templates/trellis/index.js +14 -25
  320. package/dist/templates/trellis/index.js.map +1 -1
  321. package/dist/templates/trellis/scripts/add_session.py +1 -1
  322. package/dist/templates/trellis/scripts/common/__init__.py +0 -1
  323. package/dist/templates/trellis/scripts/common/active_task.py +136 -100
  324. package/dist/templates/trellis/scripts/common/config.py +7 -9
  325. package/dist/templates/trellis/scripts/common/git_context.py +1 -1
  326. package/dist/templates/trellis/scripts/common/io.py +19 -0
  327. package/dist/templates/trellis/scripts/common/paths.py +0 -1
  328. package/dist/templates/trellis/scripts/common/safe_commit.py +11 -90
  329. package/dist/templates/trellis/scripts/common/session_context.py +1 -8
  330. package/dist/templates/trellis/scripts/common/spec_match.py +1 -1
  331. package/dist/templates/trellis/scripts/common/task_context.py +152 -59
  332. package/dist/templates/trellis/scripts/common/task_continuity.py +563 -0
  333. package/dist/templates/trellis/scripts/common/task_store.py +70 -548
  334. package/dist/templates/trellis/scripts/common/task_utils.py +4 -98
  335. package/dist/templates/trellis/scripts/common/task_workflow.py +1096 -0
  336. package/dist/templates/trellis/scripts/common/tasks.py +5 -8
  337. package/dist/templates/trellis/scripts/common/workflow_phase.py +8 -7
  338. package/dist/templates/trellis/scripts/hooks/linear_sync.py +3 -3
  339. package/dist/templates/trellis/scripts/task.py +114 -377
  340. package/dist/templates/trellis/workflow.md +111 -699
  341. package/dist/utils/agent-refs.d.ts +5 -5
  342. package/dist/utils/agent-refs.js +6 -6
  343. package/dist/utils/file-writer.d.ts +3 -2
  344. package/dist/utils/file-writer.d.ts.map +1 -1
  345. package/dist/utils/file-writer.js +3 -2
  346. package/dist/utils/file-writer.js.map +1 -1
  347. package/dist/utils/managed-paths.d.ts +0 -7
  348. package/dist/utils/managed-paths.d.ts.map +1 -1
  349. package/dist/utils/managed-paths.js +0 -40
  350. package/dist/utils/managed-paths.js.map +1 -1
  351. package/dist/utils/manifest-prune.d.ts.map +1 -1
  352. package/dist/utils/manifest-prune.js +2 -11
  353. package/dist/utils/manifest-prune.js.map +1 -1
  354. package/dist/utils/uninstall-scrubbers.d.ts.map +1 -1
  355. package/dist/utils/uninstall-scrubbers.js +3 -3
  356. package/dist/utils/uninstall-scrubbers.js.map +1 -1
  357. package/dist/utils/workflow-resolver.d.ts +10 -16
  358. package/dist/utils/workflow-resolver.d.ts.map +1 -1
  359. package/dist/utils/workflow-resolver.js +25 -36
  360. package/dist/utils/workflow-resolver.js.map +1 -1
  361. package/package.json +2 -2
  362. package/dist/commands/channel/adapters/claude.d.ts.map +0 -1
  363. package/dist/commands/channel/adapters/claude.js.map +0 -1
  364. package/dist/commands/channel/adapters/codex.d.ts.map +0 -1
  365. package/dist/commands/channel/adapters/codex.js.map +0 -1
  366. package/dist/commands/channel/adapters/index.d.ts.map +0 -1
  367. package/dist/commands/channel/adapters/index.js.map +0 -1
  368. package/dist/commands/channel/adapters/types.d.ts.map +0 -1
  369. package/dist/commands/channel/adapters/types.js.map +0 -1
  370. package/dist/commands/channel/agent-loader.d.ts.map +0 -1
  371. package/dist/commands/channel/agent-loader.js.map +0 -1
  372. package/dist/commands/channel/context-loader.d.ts.map +0 -1
  373. package/dist/commands/channel/context-loader.js.map +0 -1
  374. package/dist/commands/channel/context-trust.d.ts.map +0 -1
  375. package/dist/commands/channel/context-trust.js.map +0 -1
  376. package/dist/commands/channel/context.d.ts +0 -16
  377. package/dist/commands/channel/context.d.ts.map +0 -1
  378. package/dist/commands/channel/context.js.map +0 -1
  379. package/dist/commands/channel/create.d.ts +0 -29
  380. package/dist/commands/channel/create.d.ts.map +0 -1
  381. package/dist/commands/channel/create.js +0 -47
  382. package/dist/commands/channel/create.js.map +0 -1
  383. package/dist/commands/channel/dev-parse-trace.d.ts.map +0 -1
  384. package/dist/commands/channel/dev-parse-trace.js.map +0 -1
  385. package/dist/commands/channel/guard.d.ts.map +0 -1
  386. package/dist/commands/channel/guard.js.map +0 -1
  387. package/dist/commands/channel/index.d.ts +0 -3
  388. package/dist/commands/channel/index.d.ts.map +0 -1
  389. package/dist/commands/channel/index.js.map +0 -1
  390. package/dist/commands/channel/interrupt.d.ts.map +0 -1
  391. package/dist/commands/channel/interrupt.js +0 -22
  392. package/dist/commands/channel/interrupt.js.map +0 -1
  393. package/dist/commands/channel/kill.d.ts +0 -7
  394. package/dist/commands/channel/kill.d.ts.map +0 -1
  395. package/dist/commands/channel/kill.js.map +0 -1
  396. package/dist/commands/channel/list.d.ts +0 -18
  397. package/dist/commands/channel/list.d.ts.map +0 -1
  398. package/dist/commands/channel/list.js.map +0 -1
  399. package/dist/commands/channel/messages.d.ts.map +0 -1
  400. package/dist/commands/channel/messages.js.map +0 -1
  401. package/dist/commands/channel/rm.d.ts +0 -27
  402. package/dist/commands/channel/rm.d.ts.map +0 -1
  403. package/dist/commands/channel/rm.js.map +0 -1
  404. package/dist/commands/channel/run.d.ts +0 -32
  405. package/dist/commands/channel/run.js +0 -141
  406. package/dist/commands/channel/run.js.map +0 -1
  407. package/dist/commands/channel/send.d.ts.map +0 -1
  408. package/dist/commands/channel/send.js.map +0 -1
  409. package/dist/commands/channel/spawn.d.ts.map +0 -1
  410. package/dist/commands/channel/spawn.js.map +0 -1
  411. package/dist/commands/channel/store/events.d.ts +0 -22
  412. package/dist/commands/channel/store/events.d.ts.map +0 -1
  413. package/dist/commands/channel/store/events.js.map +0 -1
  414. package/dist/commands/channel/store/filter.d.ts +0 -3
  415. package/dist/commands/channel/store/filter.d.ts.map +0 -1
  416. package/dist/commands/channel/store/filter.js +0 -2
  417. package/dist/commands/channel/store/filter.js.map +0 -1
  418. package/dist/commands/channel/store/lock.d.ts.map +0 -1
  419. package/dist/commands/channel/store/lock.js.map +0 -1
  420. package/dist/commands/channel/store/paths.d.ts.map +0 -1
  421. package/dist/commands/channel/store/paths.js.map +0 -1
  422. package/dist/commands/channel/store/schema.d.ts +0 -27
  423. package/dist/commands/channel/store/schema.d.ts.map +0 -1
  424. package/dist/commands/channel/store/schema.js +0 -34
  425. package/dist/commands/channel/store/schema.js.map +0 -1
  426. package/dist/commands/channel/store/thread-state.d.ts.map +0 -1
  427. package/dist/commands/channel/store/thread-state.js.map +0 -1
  428. package/dist/commands/channel/store/watch.d.ts +0 -19
  429. package/dist/commands/channel/store/watch.d.ts.map +0 -1
  430. package/dist/commands/channel/store/watch.js.map +0 -1
  431. package/dist/commands/channel/supervisor/idle.d.ts.map +0 -1
  432. package/dist/commands/channel/supervisor/idle.js.map +0 -1
  433. package/dist/commands/channel/supervisor/inbox.d.ts.map +0 -1
  434. package/dist/commands/channel/supervisor/inbox.js.map +0 -1
  435. package/dist/commands/channel/supervisor/shutdown.d.ts.map +0 -1
  436. package/dist/commands/channel/supervisor/shutdown.js.map +0 -1
  437. package/dist/commands/channel/supervisor/stdout.d.ts.map +0 -1
  438. package/dist/commands/channel/supervisor/stdout.js.map +0 -1
  439. package/dist/commands/channel/supervisor/turns.d.ts.map +0 -1
  440. package/dist/commands/channel/supervisor/turns.js.map +0 -1
  441. package/dist/commands/channel/supervisor/warning.d.ts.map +0 -1
  442. package/dist/commands/channel/supervisor/warning.js.map +0 -1
  443. package/dist/commands/channel/supervisor.d.ts.map +0 -1
  444. package/dist/commands/channel/supervisor.js.map +0 -1
  445. package/dist/commands/channel/text-body.d.ts +0 -13
  446. package/dist/commands/channel/text-body.d.ts.map +0 -1
  447. package/dist/commands/channel/text-body.js.map +0 -1
  448. package/dist/commands/channel/threads.d.ts +0 -39
  449. package/dist/commands/channel/threads.d.ts.map +0 -1
  450. package/dist/commands/channel/threads.js.map +0 -1
  451. package/dist/commands/channel/title.d.ts +0 -12
  452. package/dist/commands/channel/title.d.ts.map +0 -1
  453. package/dist/commands/channel/title.js +0 -24
  454. package/dist/commands/channel/title.js.map +0 -1
  455. package/dist/commands/channel/wait.d.ts.map +0 -1
  456. package/dist/commands/channel/wait.js.map +0 -1
  457. package/dist/commands/channel/workers.d.ts +0 -9
  458. package/dist/commands/channel/workers.d.ts.map +0 -1
  459. package/dist/commands/channel/workers.js +0 -31
  460. package/dist/commands/channel/workers.js.map +0 -1
  461. package/dist/templates/common/bundled-skills/trellis-channel/SKILL.md +0 -69
  462. package/dist/templates/common/bundled-skills/trellis-channel/references/command-reference.md +0 -549
  463. package/dist/templates/common/bundled-skills/trellis-channel/references/forum.md +0 -233
  464. package/dist/templates/common/bundled-skills/trellis-channel/references/progress-debugging.md +0 -237
  465. package/dist/templates/common/bundled-skills/trellis-channel/references/subnode-work.md +0 -193
  466. package/dist/templates/common/bundled-skills/trellis-channel/references/workers.md +0 -295
  467. package/dist/templates/common/bundled-skills/trellis-channel/references/workflows.md +0 -131
  468. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/multi-agent-channel.md +0 -70
  469. package/dist/templates/common/bundled-skills/trellis-research-record/SKILL.md +0 -14
  470. package/dist/templates/copilot/prompts/parallel.prompt.md +0 -204
  471. package/dist/templates/trellis/agents/check.md +0 -70
  472. package/dist/templates/trellis/agents/implement.md +0 -71
  473. package/dist/templates/trellis/agents/subnode.env +0 -4
  474. package/dist/templates/trellis/agents/subnode.md +0 -69
  475. package/dist/templates/trellis/scripts/common/context_projection.py +0 -508
  476. package/dist/templates/trellis/scripts/common/continuation_record.py +0 -450
  477. package/dist/templates/trellis/scripts/common/ownership_record.py +0 -439
  478. package/dist/templates/trellis/scripts/subnode_artifact.py +0 -597
  479. package/dist/templates/trellis/scripts/workspace_note.py +0 -126
  480. /package/dist/commands/{channel → teamwork}/adapters/types.js +0 -0
  481. /package/dist/commands/{channel → teamwork}/supervisor/idle.d.ts +0 -0
  482. /package/dist/commands/{channel → teamwork}/supervisor/idle.js +0 -0
  483. /package/dist/commands/{channel → teamwork}/supervisor/turns.d.ts +0 -0
  484. /package/dist/commands/{channel → teamwork}/supervisor/turns.js +0 -0
@@ -1,597 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Create and validate durable artifacts for one bounded Trellis subnode.
3
-
4
- The coordinator creates an immutable brief before dispatching a subnode. The
5
- subnode appends its worklog and writes its own final report. This helper never
6
- dispatches workers, makes the acceptance judgment, or mutates task state.
7
- """
8
-
9
- from __future__ import annotations
10
-
11
- import argparse
12
- import json
13
- import re
14
- import sys
15
- from datetime import datetime, timezone
16
- from pathlib import Path
17
- from typing import Any, NoReturn
18
-
19
- from common.io import write_text_atomic
20
- from common.paths import get_repo_root, get_tasks_dir
21
- from common.task_utils import is_within_tasks_dir, resolve_task_dir
22
- from common.tasks import load_task
23
-
24
-
25
- SCHEMA_VERSION = 1
26
- MAX_DRAFT_BYTES = 64 * 1024
27
- MAX_REPORT_BYTES = 128 * 1024
28
- ID_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$")
29
- SECRET_PATTERNS = (
30
- re.compile(r"-----BEGIN [A-Z ]*PRIVATE KEY-----"),
31
- re.compile(r"\bsk-[A-Za-z0-9]{20,}\b"),
32
- re.compile(r"\bgh[pousr]_[A-Za-z0-9]{20,}\b"),
33
- )
34
- TERMINAL_LIFECYCLES = {"done", "error", "killed", "crashed"}
35
- DISPOSITION_OUTCOMES = {"accepted", "rejected", "deferred"}
36
- REQUIRED_DISPOSITION_CHECKS = {
37
- "report_validation",
38
- "source_recheck",
39
- "protected_target_check",
40
- }
41
-
42
-
43
- class ArtifactError(Exception):
44
- """A user-correctable artifact validation failure."""
45
-
46
-
47
- def _fail(message: str) -> NoReturn:
48
- raise ArtifactError(message)
49
-
50
-
51
- def _read_json_object(path: Path, max_bytes: int, label: str) -> tuple[dict[str, Any], bytes]:
52
- """Read one bounded, non-symlink JSON object."""
53
- if path.is_symlink():
54
- _fail(f"refusing symlinked {label}: {path}")
55
- try:
56
- size = path.stat().st_size
57
- except OSError as exc:
58
- _fail(f"could not stat {label} {path}: {exc}")
59
- if size > max_bytes:
60
- _fail(f"{label} exceeds the {max_bytes} byte limit: {path}")
61
- try:
62
- raw = path.read_bytes()
63
- data = json.loads(raw.decode("utf-8"))
64
- except FileNotFoundError:
65
- _fail(f"{label} does not exist: {path}")
66
- except UnicodeDecodeError:
67
- _fail(f"{label} is not UTF-8: {path}")
68
- except json.JSONDecodeError as exc:
69
- _fail(f"{label} is not valid JSON: {path}: {exc.msg}")
70
- except OSError as exc:
71
- _fail(f"could not read {label} {path}: {exc}")
72
- if not isinstance(data, dict):
73
- _fail(f"{label} must contain a JSON object: {path}")
74
- return data, raw
75
-
76
-
77
- def _require_text(value: Any, field: str, *, max_len: int = 4096) -> str:
78
- if not isinstance(value, str) or not value.strip():
79
- _fail(f"{field} must be a non-empty string")
80
- if len(value) > max_len or "\x00" in value:
81
- _fail(f"{field} is not a bounded text value")
82
- return value
83
-
84
-
85
- def _require_text_list(value: Any, field: str, *, allow_empty: bool = False) -> list[str]:
86
- if not isinstance(value, list) or (not value and not allow_empty):
87
- _fail(f"{field} must be {'a list' if allow_empty else 'a non-empty list'}")
88
- result: list[str] = []
89
- for index, item in enumerate(value):
90
- result.append(_require_text(item, f"{field}[{index}]"))
91
- return result
92
-
93
-
94
- def _require_id(value: Any, field: str) -> str:
95
- text = _require_text(value, field, max_len=64)
96
- if not ID_RE.fullmatch(text):
97
- _fail(f"{field} must match {ID_RE.pattern}")
98
- return text
99
-
100
-
101
- def _reject_obvious_secrets(raw: bytes, label: str) -> None:
102
- text = raw.decode("utf-8", errors="replace")
103
- for pattern in SECRET_PATTERNS:
104
- if pattern.search(text):
105
- _fail(f"{label} appears to contain a credential; remove it before writing the artifact")
106
-
107
-
108
- def _task_id(task_dir: Path) -> str:
109
- task = load_task(task_dir)
110
- if task is None:
111
- _fail(f"task.json is missing or invalid: {task_dir}")
112
- task_id = task.raw.get("id") or task.dir_name
113
- if not isinstance(task_id, str) or not task_id.strip():
114
- _fail(f"task has no usable id: {task_dir}")
115
- if task.status not in {"planning", "in_progress"}:
116
- _fail(f"task is not active (planning or in_progress): {task_dir}")
117
- return task_id
118
-
119
-
120
- def _task_lexical_from_resolved(task_resolved: Path, repo_root: Path) -> Path:
121
- tasks_lexical = get_tasks_dir(repo_root)
122
- try:
123
- relative = task_resolved.relative_to(tasks_lexical.resolve())
124
- except ValueError:
125
- _fail(f"task is outside the Trellis task directory: {task_resolved}")
126
- return tasks_lexical / relative
127
-
128
-
129
- def _assert_no_subpath_symlinks(task_dir: Path, target: Path) -> None:
130
- """Reject symlinks below a task while permitting a symlinked .trellis root."""
131
- if task_dir.is_symlink():
132
- _fail(f"refusing symlinked task directory: {task_dir}")
133
- try:
134
- parts = target.relative_to(task_dir).parts
135
- except ValueError:
136
- _fail(f"artifact path is outside its task: {target}")
137
- current = task_dir
138
- for part in parts:
139
- current = current / part
140
- if current.is_symlink():
141
- _fail(f"refusing symlinked artifact path component: {current}")
142
-
143
-
144
- def _node_dir(task_dir: Path, work_id: str, subnode_id: str) -> Path:
145
- return task_dir / "subnodes" / work_id / subnode_id
146
-
147
-
148
- def _relative_to_repo(path: Path, repo_root: Path) -> str:
149
- try:
150
- return path.relative_to(repo_root).as_posix()
151
- except ValueError:
152
- _fail(f"path is outside repository: {path}")
153
-
154
-
155
- def _validate_snapshot(value: Any) -> None:
156
- if not isinstance(value, list) or not value:
157
- _fail("source_snapshot must be a non-empty list")
158
- for index, item in enumerate(value):
159
- if not isinstance(item, dict):
160
- _fail(f"source_snapshot[{index}] must be an object")
161
- _require_text(item.get("locator"), f"source_snapshot[{index}].locator")
162
- _require_text(
163
- item.get("revision_or_digest"),
164
- f"source_snapshot[{index}].revision_or_digest",
165
- )
166
- _require_text(item.get("observed_at"), f"source_snapshot[{index}].observed_at")
167
-
168
-
169
- def _validate_channel_ref(value: Any) -> None:
170
- if not isinstance(value, dict):
171
- _fail("channel_ref must be an object")
172
- _require_text(value.get("name"), "channel_ref.name", max_len=128)
173
- scope = _require_text(value.get("scope"), "channel_ref.scope", max_len=32)
174
- if scope not in {"project", "global"}:
175
- _fail("channel_ref.scope must be project or global")
176
- _require_text(value.get("worker_handle"), "channel_ref.worker_handle", max_len=128)
177
-
178
-
179
- def _validate_retry_target(
180
- retry_of: Any,
181
- task_dir: Path,
182
- work_id: str,
183
- subnode_id: str,
184
- ) -> str | None:
185
- """Validate the coordinator's explicit, already-created retry source."""
186
- if retry_of is None:
187
- return None
188
- retry_id = _require_id(retry_of, "brief.retry_of")
189
- if retry_id == subnode_id:
190
- _fail("brief.retry_of must name a different subnode")
191
- target_dir = _node_dir(task_dir, work_id, retry_id)
192
- _assert_no_subpath_symlinks(task_dir, target_dir)
193
- target_brief_path = target_dir / "brief.json"
194
- if not target_brief_path.exists():
195
- _fail("brief.retry_of must identify an existing subnode brief in this task and work_id")
196
- target_brief, _raw = _read_json_object(
197
- target_brief_path,
198
- MAX_DRAFT_BYTES,
199
- "brief.retry_of target brief",
200
- )
201
- expected_task_id = _task_id(task_dir)
202
- if target_brief.get("schema_version") != SCHEMA_VERSION:
203
- _fail("brief.retry_of target brief has an unsupported schema_version")
204
- if target_brief.get("task_id") != expected_task_id:
205
- _fail("brief.retry_of target brief does not belong to this task")
206
- if target_brief.get("work_id") != work_id:
207
- _fail("brief.retry_of target brief does not belong to this work_id")
208
- if target_brief.get("subnode_id") != retry_id:
209
- _fail("brief.retry_of target brief identity does not match its artifact directory")
210
- if target_brief.get("role_id") != "subnode":
211
- _fail("brief.retry_of target brief.role_id must be subnode")
212
- return retry_id
213
-
214
-
215
- def _validate_brief_data(
216
- brief: dict[str, Any],
217
- task_dir: Path,
218
- node_dir: Path,
219
- repo_root: Path,
220
- ) -> None:
221
- expected_task_id = _task_id(task_dir)
222
- if brief.get("schema_version") != SCHEMA_VERSION:
223
- _fail(f"brief.schema_version must be {SCHEMA_VERSION}")
224
- if brief.get("task_id") != expected_task_id:
225
- _fail("brief.task_id does not match task.json")
226
- work_id = _require_id(brief.get("work_id"), "brief.work_id")
227
- subnode_id = _require_id(brief.get("subnode_id"), "brief.subnode_id")
228
- if node_dir != _node_dir(task_dir, work_id, subnode_id):
229
- _fail("brief identity does not match its artifact directory")
230
- if brief.get("role_id") != "subnode":
231
- _fail("brief.role_id must be subnode")
232
- _require_text(brief.get("question"), "brief.question")
233
- _require_text(brief.get("independence_reason"), "brief.independence_reason")
234
- _require_text_list(brief.get("scope"), "brief.scope")
235
- _require_text_list(brief.get("protected_targets"), "brief.protected_targets", allow_empty=True)
236
- _require_text(brief.get("lens"), "brief.lens")
237
- _require_text(brief.get("evidence_method"), "brief.evidence_method")
238
- _validate_snapshot(brief.get("source_snapshot"))
239
- _require_text_list(brief.get("dependencies"), "brief.dependencies", allow_empty=True)
240
- _require_text_list(brief.get("stop_conditions"), "brief.stop_conditions")
241
- _require_text(brief.get("deadline"), "brief.deadline", max_len=128)
242
- _validate_channel_ref(brief.get("channel_ref"))
243
- if brief["channel_ref"]["worker_handle"] != subnode_id:
244
- _fail("channel_ref.worker_handle must match brief.subnode_id")
245
- _validate_retry_target(
246
- brief.get("retry_of"),
247
- task_dir,
248
- work_id,
249
- subnode_id,
250
- )
251
- counter_of = brief.get("counter_of")
252
- if counter_of is not None:
253
- counter_id = _require_id(counter_of, "brief.counter_of")
254
- if counter_id == subnode_id:
255
- _fail("brief.counter_of must name a different subnode")
256
- expected_report = _relative_to_repo(node_dir / "report.json", repo_root)
257
- if brief.get("report_path") != expected_report:
258
- _fail("brief.report_path does not match its immutable artifact directory")
259
-
260
-
261
- def _resolve_artifact_file(
262
- value: str,
263
- expected_name: str,
264
- repo_root: Path,
265
- ) -> tuple[Path, Path, Path]:
266
- """Return lexical artifact, node directory, and task directory after checks."""
267
- supplied = Path(value)
268
- lexical = supplied if supplied.is_absolute() else repo_root / supplied
269
- if lexical.name != expected_name:
270
- _fail(f"expected a {expected_name} path, received: {value}")
271
- if lexical.is_symlink():
272
- _fail(f"refusing symlinked artifact: {lexical}")
273
- try:
274
- resolved = lexical.resolve(strict=True)
275
- except (OSError, RuntimeError) as exc:
276
- _fail(f"could not resolve artifact {lexical}: {exc}")
277
- node_resolved = resolved.parent
278
- work_resolved = node_resolved.parent
279
- subnodes_resolved = work_resolved.parent
280
- task_resolved = subnodes_resolved.parent
281
- if subnodes_resolved.name != "subnodes":
282
- _fail(f"artifact is not under a task subnodes directory: {lexical}")
283
- if not is_within_tasks_dir(task_resolved, repo_root):
284
- _fail(f"artifact task is not a direct active task: {lexical}")
285
- task_dir = _task_lexical_from_resolved(task_resolved, repo_root)
286
- expected = _node_dir(task_dir, work_resolved.name, node_resolved.name) / expected_name
287
- try:
288
- same_path = lexical.resolve() == expected.resolve()
289
- except (OSError, RuntimeError) as exc:
290
- _fail(f"could not verify artifact containment: {exc}")
291
- if not same_path:
292
- _fail(f"artifact path does not match its task-owned location: {lexical}")
293
- _assert_no_subpath_symlinks(task_dir, expected)
294
- return expected, expected.parent, task_dir
295
-
296
-
297
- def _validate_brief_file(
298
- value: str,
299
- repo_root: Path,
300
- ) -> tuple[dict[str, Any], bytes, Path, Path, Path]:
301
- brief_path, node_dir, task_dir = _resolve_artifact_file(value, "brief.json", repo_root)
302
- brief, raw = _read_json_object(brief_path, MAX_DRAFT_BYTES, "brief")
303
- _reject_obvious_secrets(raw, "brief")
304
- _validate_brief_data(brief, task_dir, node_dir, repo_root)
305
- return brief, raw, brief_path, node_dir, task_dir
306
-
307
-
308
- def _validate_evidence(value: Any) -> set[tuple[str, str]]:
309
- if not isinstance(value, list):
310
- _fail("report.evidence must be a list")
311
- identities: set[tuple[str, str]] = set()
312
- for index, item in enumerate(value):
313
- if not isinstance(item, dict):
314
- _fail(f"report.evidence[{index}] must be an object")
315
- evidence_id = _require_text(item.get("id"), f"report.evidence[{index}].id", max_len=128)
316
- locator = _require_text(item.get("locator"), f"report.evidence[{index}].locator")
317
- _require_text(item.get("summary"), f"report.evidence[{index}].summary")
318
- identity = (evidence_id, locator)
319
- if identity in identities:
320
- _fail("report.evidence contains a duplicate evidence identity")
321
- identities.add(identity)
322
- return identities
323
-
324
-
325
- def _validate_report_data(
326
- report: dict[str, Any],
327
- brief: dict[str, Any],
328
- ) -> set[tuple[str, str]]:
329
- if report.get("schema_version") != SCHEMA_VERSION:
330
- _fail(f"report.schema_version must be {SCHEMA_VERSION}")
331
- for field in ("task_id", "work_id", "subnode_id", "role_id"):
332
- if report.get(field) != brief.get(field):
333
- _fail(f"report.{field} does not match brief.{field}")
334
- if report.get("scope") != brief.get("scope"):
335
- _fail("report.scope does not match brief.scope")
336
- if report.get("lens") != brief.get("lens"):
337
- _fail("report.lens does not match brief.lens")
338
- status = report.get("status")
339
- if status not in {"complete", "blocked", "incomplete", "error"}:
340
- _fail("report.status must be complete, blocked, incomplete, or error")
341
- evidence = _validate_evidence(report.get("evidence"))
342
- for field in ("findings", "uncertainties", "corrections"):
343
- if not isinstance(report.get(field), list):
344
- _fail(f"report.{field} must be a list")
345
- if status == "complete" and not evidence:
346
- _fail("a complete report requires at least one evidence item")
347
- if status != "complete":
348
- _require_text_list(
349
- report.get("completed_scope"),
350
- "report.completed_scope",
351
- allow_empty=True,
352
- )
353
- _require_text(report.get("blocker"), "report.blocker")
354
- return evidence
355
-
356
-
357
- def _validate_report_file(
358
- value: str,
359
- repo_root: Path,
360
- ) -> tuple[dict[str, Any], set[tuple[str, str]], dict[str, Any], Path]:
361
- report_path, node_dir, _task_dir = _resolve_artifact_file(value, "report.json", repo_root)
362
- brief_path = node_dir / "brief.json"
363
- brief, _brief_raw, _brief_path, _brief_node, _brief_task = _validate_brief_file(
364
- str(brief_path), repo_root
365
- )
366
- expected_report = _relative_to_repo(report_path, repo_root)
367
- if brief.get("report_path") != expected_report:
368
- _fail("brief.report_path does not point to the report being validated")
369
- report, raw = _read_json_object(report_path, MAX_REPORT_BYTES, "report")
370
- _reject_obvious_secrets(raw, "report")
371
- evidence = _validate_report_data(report, brief)
372
- return report, evidence, brief, node_dir
373
-
374
-
375
- def _validate_disposition_data(
376
- disposition: dict[str, Any],
377
- report: dict[str, Any],
378
- brief: dict[str, Any],
379
- ) -> None:
380
- if disposition.get("schema_version") != SCHEMA_VERSION:
381
- _fail(f"disposition.schema_version must be {SCHEMA_VERSION}")
382
- for field in ("task_id", "work_id", "subnode_id", "role_id"):
383
- if disposition.get(field) != brief.get(field):
384
- _fail(f"disposition.{field} does not match brief.{field}")
385
- if disposition.get("report_path") != brief.get("report_path"):
386
- _fail("disposition.report_path does not match brief.report_path")
387
- if disposition.get("report_status") != report.get("status"):
388
- _fail("disposition.report_status does not match report.status")
389
- if disposition.get("outcome") not in DISPOSITION_OUTCOMES:
390
- _fail("disposition.outcome must be accepted, rejected, or deferred")
391
- terminal = disposition.get("terminal")
392
- if not isinstance(terminal, dict):
393
- _fail("disposition.terminal must be an object")
394
- if terminal.get("lifecycle") not in TERMINAL_LIFECYCLES:
395
- _fail("disposition.terminal.lifecycle must be done, error, killed, or crashed")
396
- sequence = terminal.get("seq")
397
- if isinstance(sequence, bool) or not isinstance(sequence, int) or sequence < 1:
398
- _fail("disposition.terminal.seq must be a positive integer")
399
- _require_text(terminal.get("observed_at"), "disposition.terminal.observed_at", max_len=128)
400
- checks = _require_text_list(disposition.get("checks"), "disposition.checks")
401
- if len(checks) != len(set(checks)):
402
- _fail("disposition.checks must not contain duplicates")
403
- missing = REQUIRED_DISPOSITION_CHECKS.difference(checks)
404
- if missing:
405
- _fail("disposition.checks is missing: " + ", ".join(sorted(missing)))
406
- _require_text(disposition.get("reason"), "disposition.reason")
407
- _require_text(disposition.get("decided_at"), "disposition.decided_at", max_len=128)
408
-
409
-
410
- def _init(args: argparse.Namespace) -> None:
411
- repo_root = get_repo_root()
412
- task_dir = resolve_task_dir(args.task, repo_root)
413
- if task_dir is None or not is_within_tasks_dir(task_dir, repo_root):
414
- _fail("--task must identify a direct active task directory")
415
- if task_dir.is_symlink():
416
- _fail(f"refusing symlinked task directory: {task_dir}")
417
- work_id = _require_id(args.work_id, "--work-id")
418
- subnode_id = _require_id(args.subnode_id, "--subnode-id")
419
- node_dir = _node_dir(task_dir, work_id, subnode_id)
420
- _assert_no_subpath_symlinks(task_dir, node_dir)
421
- if node_dir.exists() or node_dir.is_symlink():
422
- _fail(f"subnode artifacts already exist: {node_dir}")
423
- draft_path = Path(args.draft)
424
- if not draft_path.is_absolute():
425
- draft_path = repo_root / draft_path
426
- draft, raw = _read_json_object(draft_path, MAX_DRAFT_BYTES, "brief draft")
427
- _reject_obvious_secrets(raw, "brief draft")
428
- expected_task_id = _task_id(task_dir)
429
- brief = dict(draft)
430
- brief["schema_version"] = SCHEMA_VERSION
431
- brief["task_id"] = expected_task_id
432
- brief["work_id"] = work_id
433
- brief["subnode_id"] = subnode_id
434
- brief["role_id"] = "subnode"
435
- brief["report_path"] = _relative_to_repo(node_dir / "report.json", repo_root)
436
- _validate_brief_data(brief, task_dir, node_dir, repo_root)
437
- for directory in (task_dir / "subnodes", task_dir / "subnodes" / work_id, node_dir):
438
- if directory.is_symlink():
439
- _fail(f"refusing symlinked artifact directory: {directory}")
440
- if directory.exists() and not directory.is_dir():
441
- _fail(f"artifact path component is not a directory: {directory}")
442
- try:
443
- directory.mkdir(exist_ok=True)
444
- except OSError as exc:
445
- _fail(f"could not create artifact directory {directory}: {exc}")
446
- brief_path = node_dir / "brief.json"
447
- worklog_path = node_dir / "worklog.md"
448
- brief_text = json.dumps(brief, indent=2, ensure_ascii=False) + "\n"
449
- worklog_text = (
450
- "# Subnode Worklog\n\n"
451
- f"- Task: `{brief['task_id']}`\n"
452
- f"- Work: `{work_id}`\n"
453
- f"- Subnode: `{subnode_id}`\n\n"
454
- "Append substantive observations, corrections, blockers, and completion notes below.\n"
455
- )
456
- if not write_text_atomic(brief_path, brief_text):
457
- _fail(f"could not write immutable brief: {brief_path}")
458
- if not write_text_atomic(worklog_path, worklog_text):
459
- _fail(f"could not write subnode worklog: {worklog_path}")
460
- print(json.dumps({
461
- "brief": _relative_to_repo(brief_path, repo_root),
462
- "worklog": _relative_to_repo(worklog_path, repo_root),
463
- "report": brief["report_path"],
464
- }))
465
-
466
-
467
- def _validate(args: argparse.Namespace) -> None:
468
- repo_root = get_repo_root()
469
- report, _evidence, _brief, _node_dir_value = _validate_report_file(args.report, repo_root)
470
- print(f"Validated pending-review report: {report['subnode_id']}")
471
-
472
-
473
- def _disposition(args: argparse.Namespace) -> None:
474
- repo_root = get_repo_root()
475
- report, _evidence, brief, node_dir = _validate_report_file(args.report, repo_root)
476
- disposition_path = node_dir / "disposition.json"
477
- if disposition_path.exists() or disposition_path.is_symlink():
478
- _fail(f"disposition already exists and cannot be replaced: {disposition_path}")
479
- disposition = {
480
- "schema_version": SCHEMA_VERSION,
481
- "task_id": brief["task_id"],
482
- "work_id": brief["work_id"],
483
- "subnode_id": brief["subnode_id"],
484
- "role_id": brief["role_id"],
485
- "report_path": brief["report_path"],
486
- "report_status": report["status"],
487
- "outcome": args.outcome,
488
- "terminal": {
489
- "lifecycle": args.terminal_lifecycle,
490
- "seq": args.terminal_seq,
491
- "observed_at": args.terminal_at,
492
- },
493
- "checks": args.check,
494
- "reason": args.reason,
495
- "decided_at": datetime.now(timezone.utc).isoformat(),
496
- }
497
- _validate_disposition_data(disposition, report, brief)
498
- try:
499
- with disposition_path.open("x", encoding="utf-8") as stream:
500
- json.dump(disposition, stream, indent=2, ensure_ascii=False)
501
- stream.write("\n")
502
- except FileExistsError:
503
- _fail(f"disposition already exists and cannot be replaced: {disposition_path}")
504
- except OSError as exc:
505
- _fail(f"could not write disposition {disposition_path}: {exc}")
506
- print(f"Recorded coordinator disposition: {brief['subnode_id']} -> {args.outcome}")
507
-
508
-
509
- def _validate_counter(args: argparse.Namespace) -> None:
510
- repo_root = get_repo_root()
511
- primary_dir = Path(args.primary)
512
- counter_dir = Path(args.counter)
513
- if not primary_dir.is_absolute():
514
- primary_dir = repo_root / primary_dir
515
- if not counter_dir.is_absolute():
516
- counter_dir = repo_root / counter_dir
517
- primary_report, primary_evidence, primary_brief, primary_node = _validate_report_file(
518
- str(primary_dir / "report.json"), repo_root
519
- )
520
- counter_report, counter_evidence, counter_brief, counter_node = _validate_report_file(
521
- str(counter_dir / "report.json"), repo_root
522
- )
523
- if primary_node == counter_node:
524
- _fail("counter validation requires two different subnode directories")
525
- if primary_brief["task_id"] != counter_brief["task_id"]:
526
- _fail("counter subnode must belong to the same task")
527
- if primary_brief["work_id"] != counter_brief["work_id"]:
528
- _fail("counter subnode must belong to the same work_id")
529
- if counter_brief.get("counter_of") != primary_brief["subnode_id"]:
530
- _fail("counter brief.counter_of must identify the primary subnode")
531
- if primary_brief["lens"] == counter_brief["lens"]:
532
- _fail("counter subnode must use a different lens")
533
- if primary_report["status"] != "complete" or counter_report["status"] != "complete":
534
- _fail("counter comparison requires two complete reports")
535
- if primary_evidence & counter_evidence:
536
- _fail("counter report must use evidence identities independent of the primary report")
537
- print(
538
- "Validated independent counter reports: "
539
- f"{primary_brief['subnode_id']} and {counter_brief['subnode_id']}"
540
- )
541
-
542
-
543
- def _parser() -> argparse.ArgumentParser:
544
- parser = argparse.ArgumentParser(description=__doc__)
545
- subparsers = parser.add_subparsers(dest="command", required=True)
546
-
547
- init = subparsers.add_parser("init", help="create immutable brief and empty worklog")
548
- init.add_argument("--task", required=True, help="direct active Trellis task directory")
549
- init.add_argument("--work-id", required=True)
550
- init.add_argument("--subnode-id", required=True)
551
- init.add_argument("--draft", required=True, help="coordinator-owned brief draft JSON")
552
- init.set_defaults(handler=_init)
553
-
554
- validate = subparsers.add_parser("validate", help="validate a pending-review report")
555
- validate.add_argument("--report", required=True)
556
- validate.set_defaults(handler=_validate)
557
-
558
- disposition = subparsers.add_parser(
559
- "disposition",
560
- help="record one coordinator disposition for a validated report",
561
- )
562
- disposition.add_argument("--report", required=True)
563
- disposition.add_argument("--outcome", choices=sorted(DISPOSITION_OUTCOMES), required=True)
564
- disposition.add_argument("--terminal-lifecycle", choices=sorted(TERMINAL_LIFECYCLES), required=True)
565
- disposition.add_argument("--terminal-seq", type=int, required=True)
566
- disposition.add_argument("--terminal-at", required=True)
567
- disposition.add_argument(
568
- "--check",
569
- action="append",
570
- required=True,
571
- help="independent check name (repeat; requires report_validation, source_recheck, and protected_target_check)",
572
- )
573
- disposition.add_argument("--reason", required=True)
574
- disposition.set_defaults(handler=_disposition)
575
-
576
- counter = subparsers.add_parser(
577
- "validate-counter",
578
- help="validate two completed, independent subnode directories",
579
- )
580
- counter.add_argument("--primary", required=True, help="primary subnode artifact directory")
581
- counter.add_argument("--counter", required=True, help="counter subnode artifact directory")
582
- counter.set_defaults(handler=_validate_counter)
583
- return parser
584
-
585
-
586
- def main() -> int:
587
- args = _parser().parse_args()
588
- try:
589
- args.handler(args)
590
- except ArtifactError as exc:
591
- print(f"Error: {exc}", file=sys.stderr)
592
- return 1
593
- return 0
594
-
595
-
596
- if __name__ == "__main__":
597
- sys.exit(main())