@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
@@ -1,735 +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 = 2
26
- MAX_DRAFT_BYTES = 64 * 1024
27
- MAX_REPORT_BYTES = 128 * 1024
28
- MAX_WORKLOG_BYTES = 128 * 1024
29
- ID_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$")
30
- CHECKPOINT_RE = re.compile(r"^<!-- trellis-checkpoint: (?P<payload>\{.*\}) -->$", re.MULTILINE)
31
- SECRET_PATTERNS = (
32
- re.compile(r"-----BEGIN [A-Z ]*PRIVATE KEY-----"),
33
- re.compile(r"\bsk-[A-Za-z0-9]{20,}\b"),
34
- re.compile(r"\bgh[pousr]_[A-Za-z0-9]{20,}\b"),
35
- )
36
- TERMINAL_LIFECYCLES = {"done", "error", "killed", "crashed"}
37
- DISPOSITION_OUTCOMES = {"accepted", "rejected", "deferred"}
38
- REQUIRED_DISPOSITION_CHECKS = {
39
- "report_validation",
40
- "source_recheck",
41
- "protected_target_check",
42
- }
43
-
44
-
45
- class ArtifactError(Exception):
46
- """A user-correctable artifact validation failure."""
47
-
48
-
49
- def _fail(message: str) -> NoReturn:
50
- raise ArtifactError(message)
51
-
52
-
53
- def _read_json_object(path: Path, max_bytes: int, label: str) -> tuple[dict[str, Any], bytes]:
54
- """Read one bounded, non-symlink JSON object."""
55
- if path.is_symlink():
56
- _fail(f"refusing symlinked {label}: {path}")
57
- try:
58
- size = path.stat().st_size
59
- except OSError as exc:
60
- _fail(f"could not stat {label} {path}: {exc}")
61
- if size > max_bytes:
62
- _fail(f"{label} exceeds the {max_bytes} byte limit: {path}")
63
- try:
64
- raw = path.read_bytes()
65
- data = json.loads(raw.decode("utf-8"))
66
- except FileNotFoundError:
67
- _fail(f"{label} does not exist: {path}")
68
- except UnicodeDecodeError:
69
- _fail(f"{label} is not UTF-8: {path}")
70
- except json.JSONDecodeError as exc:
71
- _fail(f"{label} is not valid JSON: {path}: {exc.msg}")
72
- except OSError as exc:
73
- _fail(f"could not read {label} {path}: {exc}")
74
- if not isinstance(data, dict):
75
- _fail(f"{label} must contain a JSON object: {path}")
76
- return data, raw
77
-
78
-
79
- def _require_text(value: Any, field: str, *, max_len: int = 4096) -> str:
80
- if not isinstance(value, str) or not value.strip():
81
- _fail(f"{field} must be a non-empty string")
82
- if len(value) > max_len or "\x00" in value:
83
- _fail(f"{field} is not a bounded text value")
84
- return value
85
-
86
-
87
- def _require_text_list(value: Any, field: str, *, allow_empty: bool = False) -> list[str]:
88
- if not isinstance(value, list) or (not value and not allow_empty):
89
- _fail(f"{field} must be {'a list' if allow_empty else 'a non-empty list'}")
90
- result: list[str] = []
91
- for index, item in enumerate(value):
92
- result.append(_require_text(item, f"{field}[{index}]"))
93
- return result
94
-
95
-
96
- def _require_id(value: Any, field: str) -> str:
97
- text = _require_text(value, field, max_len=64)
98
- if not ID_RE.fullmatch(text):
99
- _fail(f"{field} must match {ID_RE.pattern}")
100
- return text
101
-
102
-
103
- def _reject_obvious_secrets(raw: bytes, label: str) -> None:
104
- text = raw.decode("utf-8", errors="replace")
105
- for pattern in SECRET_PATTERNS:
106
- if pattern.search(text):
107
- _fail(f"{label} appears to contain a credential; remove it before writing the artifact")
108
-
109
-
110
- def _task_id(task_dir: Path) -> str:
111
- task = load_task(task_dir)
112
- if task is None:
113
- _fail(f"task.json is missing or invalid: {task_dir}")
114
- task_id = task.raw.get("id") or task.dir_name
115
- if not isinstance(task_id, str) or not task_id.strip():
116
- _fail(f"task has no usable id: {task_dir}")
117
- if task.status not in {"planning", "in_progress"}:
118
- _fail(f"task is not active (planning or in_progress): {task_dir}")
119
- return task_id
120
-
121
-
122
- def _task_lexical_from_resolved(task_resolved: Path, repo_root: Path) -> Path:
123
- tasks_lexical = get_tasks_dir(repo_root)
124
- try:
125
- relative = task_resolved.relative_to(tasks_lexical.resolve())
126
- except ValueError:
127
- _fail(f"task is outside the Trellis task directory: {task_resolved}")
128
- return tasks_lexical / relative
129
-
130
-
131
- def _assert_no_subpath_symlinks(task_dir: Path, target: Path) -> None:
132
- """Reject symlinks below a task while permitting a symlinked .trellis root."""
133
- if task_dir.is_symlink():
134
- _fail(f"refusing symlinked task directory: {task_dir}")
135
- try:
136
- parts = target.relative_to(task_dir).parts
137
- except ValueError:
138
- _fail(f"artifact path is outside its task: {target}")
139
- current = task_dir
140
- for part in parts:
141
- current = current / part
142
- if current.is_symlink():
143
- _fail(f"refusing symlinked artifact path component: {current}")
144
-
145
-
146
- def _node_dir(task_dir: Path, work_id: str, subnode_id: str) -> Path:
147
- return task_dir / "subnodes" / work_id / subnode_id
148
-
149
-
150
- def _relative_to_repo(path: Path, repo_root: Path) -> str:
151
- try:
152
- return path.relative_to(repo_root).as_posix()
153
- except ValueError:
154
- _fail(f"path is outside repository: {path}")
155
-
156
-
157
- def _validate_snapshot(value: Any) -> None:
158
- if not isinstance(value, list) or not value:
159
- _fail("source_snapshot must be a non-empty list")
160
- for index, item in enumerate(value):
161
- if not isinstance(item, dict):
162
- _fail(f"source_snapshot[{index}] must be an object")
163
- _require_text(item.get("locator"), f"source_snapshot[{index}].locator")
164
- _require_text(
165
- item.get("revision_or_digest"),
166
- f"source_snapshot[{index}].revision_or_digest",
167
- )
168
- _require_text(item.get("observed_at"), f"source_snapshot[{index}].observed_at")
169
-
170
-
171
- def _validate_channel_ref(value: Any) -> None:
172
- if not isinstance(value, dict):
173
- _fail("channel_ref must be an object")
174
- _require_text(value.get("name"), "channel_ref.name", max_len=128)
175
- scope = _require_text(value.get("scope"), "channel_ref.scope", max_len=32)
176
- if scope not in {"project", "global"}:
177
- _fail("channel_ref.scope must be project or global")
178
- _require_text(value.get("worker_handle"), "channel_ref.worker_handle", max_len=128)
179
-
180
-
181
- def _validate_retry_target(
182
- retry_of: Any,
183
- task_dir: Path,
184
- work_id: str,
185
- subnode_id: str,
186
- ) -> str | None:
187
- """Validate the coordinator's explicit, already-created retry source."""
188
- if retry_of is None:
189
- return None
190
- retry_id = _require_id(retry_of, "brief.retry_of")
191
- if retry_id == subnode_id:
192
- _fail("brief.retry_of must name a different subnode")
193
- target_dir = _node_dir(task_dir, work_id, retry_id)
194
- _assert_no_subpath_symlinks(task_dir, target_dir)
195
- target_brief_path = target_dir / "brief.json"
196
- if not target_brief_path.exists():
197
- _fail("brief.retry_of must identify an existing subnode brief in this task and work_id")
198
- target_brief, _raw = _read_json_object(
199
- target_brief_path,
200
- MAX_DRAFT_BYTES,
201
- "brief.retry_of target brief",
202
- )
203
- expected_task_id = _task_id(task_dir)
204
- if target_brief.get("schema_version") != SCHEMA_VERSION:
205
- _fail("brief.retry_of target brief has an unsupported schema_version")
206
- if target_brief.get("task_id") != expected_task_id:
207
- _fail("brief.retry_of target brief does not belong to this task")
208
- if target_brief.get("work_id") != work_id:
209
- _fail("brief.retry_of target brief does not belong to this work_id")
210
- if target_brief.get("subnode_id") != retry_id:
211
- _fail("brief.retry_of target brief identity does not match its artifact directory")
212
- if target_brief.get("role_id") != "subnode":
213
- _fail("brief.retry_of target brief.role_id must be subnode")
214
- return retry_id
215
-
216
-
217
- def _validate_brief_data(
218
- brief: dict[str, Any],
219
- task_dir: Path,
220
- node_dir: Path,
221
- repo_root: Path,
222
- ) -> None:
223
- expected_task_id = _task_id(task_dir)
224
- if brief.get("schema_version") != SCHEMA_VERSION:
225
- _fail(f"brief.schema_version must be {SCHEMA_VERSION}")
226
- if brief.get("task_id") != expected_task_id:
227
- _fail("brief.task_id does not match task.json")
228
- work_id = _require_id(brief.get("work_id"), "brief.work_id")
229
- subnode_id = _require_id(brief.get("subnode_id"), "brief.subnode_id")
230
- if node_dir != _node_dir(task_dir, work_id, subnode_id):
231
- _fail("brief identity does not match its artifact directory")
232
- if brief.get("role_id") != "subnode":
233
- _fail("brief.role_id must be subnode")
234
- _require_text(brief.get("question"), "brief.question")
235
- _require_text(brief.get("independence_reason"), "brief.independence_reason")
236
- _require_text_list(brief.get("scope"), "brief.scope")
237
- _require_text_list(brief.get("protected_targets"), "brief.protected_targets", allow_empty=True)
238
- _require_text(brief.get("lens"), "brief.lens")
239
- _require_text(brief.get("evidence_method"), "brief.evidence_method")
240
- _validate_snapshot(brief.get("source_snapshot"))
241
- _require_text_list(brief.get("dependencies"), "brief.dependencies", allow_empty=True)
242
- _require_text_list(brief.get("stop_conditions"), "brief.stop_conditions")
243
- _require_text(brief.get("deadline"), "brief.deadline", max_len=128)
244
- _validate_channel_ref(brief.get("channel_ref"))
245
- if brief["channel_ref"]["worker_handle"] != subnode_id:
246
- _fail("channel_ref.worker_handle must match brief.subnode_id")
247
- _validate_retry_target(
248
- brief.get("retry_of"),
249
- task_dir,
250
- work_id,
251
- subnode_id,
252
- )
253
- counter_of = brief.get("counter_of")
254
- if counter_of is not None:
255
- counter_id = _require_id(counter_of, "brief.counter_of")
256
- if counter_id == subnode_id:
257
- _fail("brief.counter_of must name a different subnode")
258
- expected_report = _relative_to_repo(node_dir / "report.json", repo_root)
259
- if brief.get("report_path") != expected_report:
260
- _fail("brief.report_path does not match its immutable artifact directory")
261
-
262
-
263
- def _resolve_artifact_file(
264
- value: str,
265
- expected_name: str,
266
- repo_root: Path,
267
- ) -> tuple[Path, Path, Path]:
268
- """Return lexical artifact, node directory, and task directory after checks."""
269
- supplied = Path(value)
270
- lexical = supplied if supplied.is_absolute() else repo_root / supplied
271
- if lexical.name != expected_name:
272
- _fail(f"expected a {expected_name} path, received: {value}")
273
- if lexical.is_symlink():
274
- _fail(f"refusing symlinked artifact: {lexical}")
275
- try:
276
- resolved = lexical.resolve(strict=True)
277
- except (OSError, RuntimeError) as exc:
278
- _fail(f"could not resolve artifact {lexical}: {exc}")
279
- node_resolved = resolved.parent
280
- work_resolved = node_resolved.parent
281
- subnodes_resolved = work_resolved.parent
282
- task_resolved = subnodes_resolved.parent
283
- if subnodes_resolved.name != "subnodes":
284
- _fail(f"artifact is not under a task subnodes directory: {lexical}")
285
- if not is_within_tasks_dir(task_resolved, repo_root):
286
- _fail(f"artifact task is not a direct active task: {lexical}")
287
- task_dir = _task_lexical_from_resolved(task_resolved, repo_root)
288
- expected = _node_dir(task_dir, work_resolved.name, node_resolved.name) / expected_name
289
- try:
290
- same_path = lexical.resolve() == expected.resolve()
291
- except (OSError, RuntimeError) as exc:
292
- _fail(f"could not verify artifact containment: {exc}")
293
- if not same_path:
294
- _fail(f"artifact path does not match its task-owned location: {lexical}")
295
- _assert_no_subpath_symlinks(task_dir, expected)
296
- return expected, expected.parent, task_dir
297
-
298
-
299
- def _validate_brief_file(
300
- value: str,
301
- repo_root: Path,
302
- ) -> tuple[dict[str, Any], bytes, Path, Path, Path]:
303
- brief_path, node_dir, task_dir = _resolve_artifact_file(value, "brief.json", repo_root)
304
- brief, raw = _read_json_object(brief_path, MAX_DRAFT_BYTES, "brief")
305
- _reject_obvious_secrets(raw, "brief")
306
- _validate_brief_data(brief, task_dir, node_dir, repo_root)
307
- return brief, raw, brief_path, node_dir, task_dir
308
-
309
-
310
- def _validate_evidence(value: Any) -> set[str]:
311
- if not isinstance(value, list):
312
- _fail("report.evidence must be a list")
313
- identities: set[tuple[str, str]] = set()
314
- for index, item in enumerate(value):
315
- if not isinstance(item, dict):
316
- _fail(f"report.evidence[{index}] must be an object")
317
- evidence_id = _require_text(item.get("id"), f"report.evidence[{index}].id", max_len=128)
318
- locator = _require_text(item.get("locator"), f"report.evidence[{index}].locator")
319
- _require_text(item.get("summary"), f"report.evidence[{index}].summary")
320
- identity = (evidence_id, locator)
321
- if identity in identities:
322
- _fail("report.evidence contains a duplicate evidence identity")
323
- identities.add(identity)
324
- return {evidence_id for evidence_id, _locator in identities}
325
-
326
-
327
- def _validate_id_list(value: Any, field: str) -> list[str]:
328
- if not isinstance(value, list):
329
- _fail(f"{field} must be a list")
330
- result = []
331
- for index, item in enumerate(value):
332
- result.append(_require_text(item, f"{field}[{index}]", max_len=128))
333
- if len(result) != len(set(result)):
334
- _fail(f"{field} must not contain duplicates")
335
- return result
336
-
337
-
338
- def _validate_typed_notes(value: Any, field: str) -> None:
339
- if not isinstance(value, list):
340
- _fail(f"report.{field} must be a list")
341
- for index, item in enumerate(value):
342
- if not isinstance(item, dict):
343
- _fail(f"report.{field}[{index}] must be an object")
344
- _require_id(item.get("id"), f"report.{field}[{index}].id")
345
- _require_text(item.get("type"), f"report.{field}[{index}].type", max_len=64)
346
- _require_text(item.get("detail"), f"report.{field}[{index}].detail")
347
- _validate_id_list(item.get("evidence_ids", []), f"report.{field}[{index}].evidence_ids")
348
-
349
-
350
- def _validate_checkpoint(node_dir: Path, evidence_ids: set[str], scope: list[str]) -> list[str]:
351
- worklog_path = node_dir / "worklog.md"
352
- if worklog_path.is_symlink():
353
- _fail(f"refusing symlinked worklog: {worklog_path}")
354
- try:
355
- raw = worklog_path.read_bytes()
356
- except FileNotFoundError:
357
- _fail(f"worklog does not exist: {worklog_path}")
358
- except OSError as exc:
359
- _fail(f"could not read worklog {worklog_path}: {exc}")
360
- if len(raw) > MAX_WORKLOG_BYTES:
361
- _fail(f"worklog exceeds the {MAX_WORKLOG_BYTES} byte limit: {worklog_path}")
362
- _reject_obvious_secrets(raw, "worklog")
363
- try:
364
- text = raw.decode("utf-8")
365
- except UnicodeDecodeError:
366
- _fail(f"worklog is not UTF-8: {worklog_path}")
367
- matches = list(CHECKPOINT_RE.finditer(text))
368
- if not matches:
369
- return ["missing_worklog_checkpoint"]
370
- concerns: list[str] = []
371
- checkpoint_ids: set[str] = set()
372
- for match in matches:
373
- try:
374
- checkpoint = json.loads(match.group("payload"))
375
- except json.JSONDecodeError:
376
- concerns.append("malformed_worklog_checkpoint")
377
- continue
378
- if not isinstance(checkpoint, dict):
379
- concerns.append("malformed_worklog_checkpoint")
380
- continue
381
- try:
382
- checkpoint_id = _require_id(checkpoint.get("id"), "worklog checkpoint.id")
383
- if checkpoint_id in checkpoint_ids:
384
- concerns.append("duplicate_worklog_checkpoint")
385
- checkpoint_ids.add(checkpoint_id)
386
- covered_scope = _require_text_list(
387
- checkpoint.get("covered_scope"),
388
- "worklog checkpoint.covered_scope",
389
- allow_empty=True,
390
- )
391
- for item in covered_scope:
392
- if item not in scope:
393
- concerns.append("checkpoint_scope_outside_brief")
394
- checkpoint_evidence = _validate_id_list(
395
- checkpoint.get("evidence_ids"), "worklog checkpoint.evidence_ids"
396
- )
397
- if any(item not in evidence_ids for item in checkpoint_evidence):
398
- concerns.append("checkpoint_evidence_unresolved")
399
- _require_text(
400
- checkpoint.get("conclusion_or_blocker"),
401
- "worklog checkpoint.conclusion_or_blocker",
402
- )
403
- _require_text_list(
404
- checkpoint.get("unknowns"),
405
- "worklog checkpoint.unknowns",
406
- allow_empty=True,
407
- )
408
- _require_text(checkpoint.get("safe_resume_point"), "worklog checkpoint.safe_resume_point")
409
- except ArtifactError:
410
- concerns.append("malformed_worklog_checkpoint")
411
- return sorted(set(concerns))
412
-
413
-
414
- def _validate_report_data(
415
- report: dict[str, Any],
416
- brief: dict[str, Any],
417
- ) -> tuple[set[str], list[str]]:
418
- if report.get("schema_version") != SCHEMA_VERSION:
419
- _fail(f"report.schema_version must be {SCHEMA_VERSION}")
420
- for field in ("task_id", "work_id", "subnode_id", "role_id"):
421
- if report.get(field) != brief.get(field):
422
- _fail(f"report.{field} does not match brief.{field}")
423
- if report.get("scope") != brief.get("scope"):
424
- _fail("report.scope does not match brief.scope")
425
- if report.get("lens") != brief.get("lens"):
426
- _fail("report.lens does not match brief.lens")
427
- status = report.get("status")
428
- if status not in {"complete", "blocked", "incomplete", "error"}:
429
- _fail("report.status must be complete, blocked, incomplete, or error")
430
- evidence = _validate_evidence(report.get("evidence"))
431
- assessment = report.get("scope_assessment")
432
- if not isinstance(assessment, list) or len(assessment) != len(brief["scope"]):
433
- _fail("report.scope_assessment must contain one item for every brief scope item")
434
- concerns: list[str] = []
435
- for index, item in enumerate(assessment):
436
- if not isinstance(item, dict):
437
- _fail(f"report.scope_assessment[{index}] must be an object")
438
- if item.get("scope") != brief["scope"][index]:
439
- _fail(f"report.scope_assessment[{index}].scope does not match brief.scope")
440
- if item.get("status") not in {"covered", "inconclusive", "not-started"}:
441
- _fail(f"report.scope_assessment[{index}].status is invalid")
442
- _require_text(item.get("conclusion"), f"report.scope_assessment[{index}].conclusion")
443
- assessment_evidence = _validate_id_list(
444
- item.get("evidence_ids"), f"report.scope_assessment[{index}].evidence_ids"
445
- )
446
- if any(value not in evidence for value in assessment_evidence):
447
- concerns.append("scope_assessment_evidence_unresolved")
448
- if item["status"] != "covered":
449
- concerns.append("incomplete_scope_coverage")
450
- if item["status"] == "covered" and not assessment_evidence:
451
- concerns.append("covered_scope_without_evidence")
452
- findings = report.get("findings")
453
- if not isinstance(findings, list):
454
- _fail("report.findings must be a list")
455
- finding_ids: set[str] = set()
456
- for index, item in enumerate(findings):
457
- if not isinstance(item, dict):
458
- _fail(f"report.findings[{index}] must be an object")
459
- finding_id = _require_id(item.get("id"), f"report.findings[{index}].id")
460
- if finding_id in finding_ids:
461
- _fail("report.findings contains duplicate ids")
462
- finding_ids.add(finding_id)
463
- _require_text(item.get("conclusion"), f"report.findings[{index}].conclusion")
464
- finding_evidence = _validate_id_list(
465
- item.get("evidence_ids"), f"report.findings[{index}].evidence_ids"
466
- )
467
- if not finding_evidence:
468
- concerns.append("finding_without_evidence")
469
- if any(value not in evidence for value in finding_evidence):
470
- concerns.append("finding_evidence_unresolved")
471
- _validate_typed_notes(report.get("uncertainties"), "uncertainties")
472
- _validate_typed_notes(report.get("corrections"), "corrections")
473
- if status == "complete" and not evidence:
474
- _fail("a complete report requires at least one evidence item")
475
- if status == "complete" and not findings:
476
- _fail("a complete report requires at least one finding")
477
- if status != "complete":
478
- _require_text_list(
479
- report.get("completed_scope"),
480
- "report.completed_scope",
481
- allow_empty=True,
482
- )
483
- _require_text(report.get("blocker"), "report.blocker")
484
- return evidence, sorted(set(concerns))
485
-
486
-
487
- def _validate_report_file(
488
- value: str,
489
- repo_root: Path,
490
- ) -> tuple[dict[str, Any], set[str], dict[str, Any], Path, list[str]]:
491
- report_path, node_dir, _task_dir = _resolve_artifact_file(value, "report.json", repo_root)
492
- brief_path = node_dir / "brief.json"
493
- brief, _brief_raw, _brief_path, _brief_node, _brief_task = _validate_brief_file(
494
- str(brief_path), repo_root
495
- )
496
- expected_report = _relative_to_repo(report_path, repo_root)
497
- if brief.get("report_path") != expected_report:
498
- _fail("brief.report_path does not point to the report being validated")
499
- report, raw = _read_json_object(report_path, MAX_REPORT_BYTES, "report")
500
- _reject_obvious_secrets(raw, "report")
501
- evidence, concerns = _validate_report_data(report, brief)
502
- concerns.extend(_validate_checkpoint(node_dir, evidence, brief["scope"]))
503
- return report, evidence, brief, node_dir, sorted(set(concerns))
504
-
505
-
506
- def _validate_disposition_data(
507
- disposition: dict[str, Any],
508
- report: dict[str, Any],
509
- brief: dict[str, Any],
510
- ) -> None:
511
- if disposition.get("schema_version") != SCHEMA_VERSION:
512
- _fail(f"disposition.schema_version must be {SCHEMA_VERSION}")
513
- for field in ("task_id", "work_id", "subnode_id", "role_id"):
514
- if disposition.get(field) != brief.get(field):
515
- _fail(f"disposition.{field} does not match brief.{field}")
516
- if disposition.get("report_path") != brief.get("report_path"):
517
- _fail("disposition.report_path does not match brief.report_path")
518
- if disposition.get("report_status") != report.get("status"):
519
- _fail("disposition.report_status does not match report.status")
520
- if disposition.get("outcome") not in DISPOSITION_OUTCOMES:
521
- _fail("disposition.outcome must be accepted, rejected, or deferred")
522
- terminal = disposition.get("terminal")
523
- if not isinstance(terminal, dict):
524
- _fail("disposition.terminal must be an object")
525
- if terminal.get("lifecycle") not in TERMINAL_LIFECYCLES:
526
- _fail("disposition.terminal.lifecycle must be done, error, killed, or crashed")
527
- sequence = terminal.get("seq")
528
- if isinstance(sequence, bool) or not isinstance(sequence, int) or sequence < 1:
529
- _fail("disposition.terminal.seq must be a positive integer")
530
- _require_text(terminal.get("observed_at"), "disposition.terminal.observed_at", max_len=128)
531
- checks = _require_text_list(disposition.get("checks"), "disposition.checks")
532
- if len(checks) != len(set(checks)):
533
- _fail("disposition.checks must not contain duplicates")
534
- missing = REQUIRED_DISPOSITION_CHECKS.difference(checks)
535
- if missing:
536
- _fail("disposition.checks is missing: " + ", ".join(sorted(missing)))
537
- _require_text(disposition.get("reason"), "disposition.reason")
538
- _require_text(disposition.get("decided_at"), "disposition.decided_at", max_len=128)
539
-
540
-
541
- def _init(args: argparse.Namespace) -> None:
542
- repo_root = get_repo_root()
543
- task_dir = resolve_task_dir(args.task, repo_root)
544
- if task_dir is None or not is_within_tasks_dir(task_dir, repo_root):
545
- _fail("--task must identify a direct active task directory")
546
- if task_dir.is_symlink():
547
- _fail(f"refusing symlinked task directory: {task_dir}")
548
- work_id = _require_id(args.work_id, "--work-id")
549
- subnode_id = _require_id(args.subnode_id, "--subnode-id")
550
- node_dir = _node_dir(task_dir, work_id, subnode_id)
551
- _assert_no_subpath_symlinks(task_dir, node_dir)
552
- if node_dir.exists() or node_dir.is_symlink():
553
- _fail(f"subnode artifacts already exist: {node_dir}")
554
- draft_path = Path(args.draft)
555
- if not draft_path.is_absolute():
556
- draft_path = repo_root / draft_path
557
- draft, raw = _read_json_object(draft_path, MAX_DRAFT_BYTES, "brief draft")
558
- _reject_obvious_secrets(raw, "brief draft")
559
- expected_task_id = _task_id(task_dir)
560
- brief = dict(draft)
561
- brief["schema_version"] = SCHEMA_VERSION
562
- brief["task_id"] = expected_task_id
563
- brief["work_id"] = work_id
564
- brief["subnode_id"] = subnode_id
565
- brief["role_id"] = "subnode"
566
- brief["report_path"] = _relative_to_repo(node_dir / "report.json", repo_root)
567
- _validate_brief_data(brief, task_dir, node_dir, repo_root)
568
- for directory in (task_dir / "subnodes", task_dir / "subnodes" / work_id, node_dir):
569
- if directory.is_symlink():
570
- _fail(f"refusing symlinked artifact directory: {directory}")
571
- if directory.exists() and not directory.is_dir():
572
- _fail(f"artifact path component is not a directory: {directory}")
573
- try:
574
- directory.mkdir(exist_ok=True)
575
- except OSError as exc:
576
- _fail(f"could not create artifact directory {directory}: {exc}")
577
- brief_path = node_dir / "brief.json"
578
- worklog_path = node_dir / "worklog.md"
579
- brief_text = json.dumps(brief, indent=2, ensure_ascii=False) + "\n"
580
- worklog_text = (
581
- "# Subnode Worklog\n\n"
582
- f"- Task: `{brief['task_id']}`\n"
583
- f"- Work: `{work_id}`\n"
584
- f"- Subnode: `{subnode_id}`\n\n"
585
- "Append substantive observations, corrections, blockers, and completion notes below.\n"
586
- )
587
- if not write_text_atomic(brief_path, brief_text):
588
- _fail(f"could not write immutable brief: {brief_path}")
589
- if not write_text_atomic(worklog_path, worklog_text):
590
- _fail(f"could not write subnode worklog: {worklog_path}")
591
- print(json.dumps({
592
- "brief": _relative_to_repo(brief_path, repo_root),
593
- "worklog": _relative_to_repo(worklog_path, repo_root),
594
- "report": brief["report_path"],
595
- }))
596
-
597
-
598
- def _validate(args: argparse.Namespace) -> None:
599
- repo_root = get_repo_root()
600
- report, _evidence, _brief, _node_dir_value, concerns = _validate_report_file(args.report, repo_root)
601
- if concerns:
602
- print(json.dumps({
603
- "status": "review_concern",
604
- "subnode_id": report["subnode_id"],
605
- "concerns": concerns,
606
- }))
607
- return
608
- print(f"Validated pending-review report: {report['subnode_id']}")
609
-
610
-
611
- def _disposition(args: argparse.Namespace) -> None:
612
- repo_root = get_repo_root()
613
- report, _evidence, brief, node_dir, _concerns = _validate_report_file(args.report, repo_root)
614
- disposition_path = node_dir / "disposition.json"
615
- if disposition_path.exists() or disposition_path.is_symlink():
616
- _fail(f"disposition already exists and cannot be replaced: {disposition_path}")
617
- disposition = {
618
- "schema_version": SCHEMA_VERSION,
619
- "task_id": brief["task_id"],
620
- "work_id": brief["work_id"],
621
- "subnode_id": brief["subnode_id"],
622
- "role_id": brief["role_id"],
623
- "report_path": brief["report_path"],
624
- "report_status": report["status"],
625
- "outcome": args.outcome,
626
- "terminal": {
627
- "lifecycle": args.terminal_lifecycle,
628
- "seq": args.terminal_seq,
629
- "observed_at": args.terminal_at,
630
- },
631
- "checks": args.check,
632
- "reason": args.reason,
633
- "decided_at": datetime.now(timezone.utc).isoformat(),
634
- }
635
- _validate_disposition_data(disposition, report, brief)
636
- try:
637
- with disposition_path.open("x", encoding="utf-8") as stream:
638
- json.dump(disposition, stream, indent=2, ensure_ascii=False)
639
- stream.write("\n")
640
- except FileExistsError:
641
- _fail(f"disposition already exists and cannot be replaced: {disposition_path}")
642
- except OSError as exc:
643
- _fail(f"could not write disposition {disposition_path}: {exc}")
644
- print(f"Recorded coordinator disposition: {brief['subnode_id']} -> {args.outcome}")
645
-
646
-
647
- def _validate_counter(args: argparse.Namespace) -> None:
648
- repo_root = get_repo_root()
649
- primary_dir = Path(args.primary)
650
- counter_dir = Path(args.counter)
651
- if not primary_dir.is_absolute():
652
- primary_dir = repo_root / primary_dir
653
- if not counter_dir.is_absolute():
654
- counter_dir = repo_root / counter_dir
655
- primary_report, primary_evidence, primary_brief, primary_node, _primary_concerns = _validate_report_file(
656
- str(primary_dir / "report.json"), repo_root
657
- )
658
- counter_report, counter_evidence, counter_brief, counter_node, _counter_concerns = _validate_report_file(
659
- str(counter_dir / "report.json"), repo_root
660
- )
661
- if primary_node == counter_node:
662
- _fail("counter validation requires two different subnode directories")
663
- if primary_brief["task_id"] != counter_brief["task_id"]:
664
- _fail("counter subnode must belong to the same task")
665
- if primary_brief["work_id"] != counter_brief["work_id"]:
666
- _fail("counter subnode must belong to the same work_id")
667
- if counter_brief.get("counter_of") != primary_brief["subnode_id"]:
668
- _fail("counter brief.counter_of must identify the primary subnode")
669
- if primary_brief["lens"] == counter_brief["lens"]:
670
- _fail("counter subnode must use a different lens")
671
- if primary_report["status"] != "complete" or counter_report["status"] != "complete":
672
- _fail("counter comparison requires two complete reports")
673
- if primary_evidence & counter_evidence:
674
- _fail("counter report must use evidence identities independent of the primary report")
675
- print(
676
- "Validated independent counter reports: "
677
- f"{primary_brief['subnode_id']} and {counter_brief['subnode_id']}"
678
- )
679
-
680
-
681
- def _parser() -> argparse.ArgumentParser:
682
- parser = argparse.ArgumentParser(description=__doc__)
683
- subparsers = parser.add_subparsers(dest="command", required=True)
684
-
685
- init = subparsers.add_parser("init", help="create immutable brief and empty worklog")
686
- init.add_argument("--task", required=True, help="direct active Trellis task directory")
687
- init.add_argument("--work-id", required=True)
688
- init.add_argument("--subnode-id", required=True)
689
- init.add_argument("--draft", required=True, help="coordinator-owned brief draft JSON")
690
- init.set_defaults(handler=_init)
691
-
692
- validate = subparsers.add_parser("validate", help="validate a pending-review report")
693
- validate.add_argument("--report", required=True)
694
- validate.set_defaults(handler=_validate)
695
-
696
- disposition = subparsers.add_parser(
697
- "disposition",
698
- help="record one coordinator disposition for a validated report",
699
- )
700
- disposition.add_argument("--report", required=True)
701
- disposition.add_argument("--outcome", choices=sorted(DISPOSITION_OUTCOMES), required=True)
702
- disposition.add_argument("--terminal-lifecycle", choices=sorted(TERMINAL_LIFECYCLES), required=True)
703
- disposition.add_argument("--terminal-seq", type=int, required=True)
704
- disposition.add_argument("--terminal-at", required=True)
705
- disposition.add_argument(
706
- "--check",
707
- action="append",
708
- required=True,
709
- help="independent check name (repeat; requires report_validation, source_recheck, and protected_target_check)",
710
- )
711
- disposition.add_argument("--reason", required=True)
712
- disposition.set_defaults(handler=_disposition)
713
-
714
- counter = subparsers.add_parser(
715
- "validate-counter",
716
- help="validate two completed, independent subnode directories",
717
- )
718
- counter.add_argument("--primary", required=True, help="primary subnode artifact directory")
719
- counter.add_argument("--counter", required=True, help="counter subnode artifact directory")
720
- counter.set_defaults(handler=_validate_counter)
721
- return parser
722
-
723
-
724
- def main() -> int:
725
- args = _parser().parse_args()
726
- try:
727
- args.handler(args)
728
- except ArtifactError as exc:
729
- print(f"Error: {exc}", file=sys.stderr)
730
- return 1
731
- return 0
732
-
733
-
734
- if __name__ == "__main__":
735
- sys.exit(main())