@mindfoldhq/trellis 0.3.10 → 0.4.0-beta.10

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 (304) hide show
  1. package/README.md +19 -5
  2. package/dist/cli/index.js +5 -0
  3. package/dist/cli/index.js.map +1 -1
  4. package/dist/commands/init.d.ts +4 -0
  5. package/dist/commands/init.d.ts.map +1 -1
  6. package/dist/commands/init.js +240 -43
  7. package/dist/commands/init.js.map +1 -1
  8. package/dist/commands/update.d.ts.map +1 -1
  9. package/dist/commands/update.js +206 -47
  10. package/dist/commands/update.js.map +1 -1
  11. package/dist/configurators/codebuddy.d.ts +11 -0
  12. package/dist/configurators/codebuddy.d.ts.map +1 -0
  13. package/dist/configurators/codebuddy.js +58 -0
  14. package/dist/configurators/codebuddy.js.map +1 -0
  15. package/dist/configurators/codex.d.ts +7 -4
  16. package/dist/configurators/codex.d.ts.map +1 -1
  17. package/dist/configurators/codex.js +40 -10
  18. package/dist/configurators/codex.js.map +1 -1
  19. package/dist/configurators/copilot.d.ts +9 -0
  20. package/dist/configurators/copilot.d.ts.map +1 -0
  21. package/dist/configurators/copilot.js +34 -0
  22. package/dist/configurators/copilot.js.map +1 -0
  23. package/dist/configurators/index.d.ts +11 -1
  24. package/dist/configurators/index.d.ts.map +1 -1
  25. package/dist/configurators/index.js +72 -4
  26. package/dist/configurators/index.js.map +1 -1
  27. package/dist/configurators/opencode.d.ts +1 -1
  28. package/dist/configurators/opencode.js +1 -1
  29. package/dist/configurators/windsurf.d.ts +8 -0
  30. package/dist/configurators/windsurf.d.ts.map +1 -0
  31. package/dist/configurators/windsurf.js +18 -0
  32. package/dist/configurators/windsurf.js.map +1 -0
  33. package/dist/configurators/workflow.d.ts +6 -2
  34. package/dist/configurators/workflow.d.ts.map +1 -1
  35. package/dist/configurators/workflow.js +90 -58
  36. package/dist/configurators/workflow.js.map +1 -1
  37. package/dist/migrations/index.d.ts +1 -0
  38. package/dist/migrations/index.d.ts.map +1 -1
  39. package/dist/migrations/index.js +2 -0
  40. package/dist/migrations/index.js.map +1 -1
  41. package/dist/migrations/manifests/0.4.0-beta.1.json +228 -0
  42. package/dist/migrations/manifests/0.4.0-beta.10.json +9 -0
  43. package/dist/migrations/manifests/0.4.0-beta.2.json +9 -0
  44. package/dist/migrations/manifests/0.4.0-beta.3.json +9 -0
  45. package/dist/migrations/manifests/0.4.0-beta.4.json +9 -0
  46. package/dist/migrations/manifests/0.4.0-beta.5.json +9 -0
  47. package/dist/migrations/manifests/0.4.0-beta.6.json +9 -0
  48. package/dist/migrations/manifests/0.4.0-beta.7.json +9 -0
  49. package/dist/migrations/manifests/0.4.0-beta.8.json +34 -0
  50. package/dist/migrations/manifests/0.4.0-beta.9.json +9 -0
  51. package/dist/templates/claude/agents/dispatch.md +1 -2
  52. package/dist/templates/claude/agents/implement.md +2 -3
  53. package/dist/templates/claude/commands/trellis/before-dev.md +29 -0
  54. package/dist/templates/claude/commands/trellis/check.md +25 -0
  55. package/dist/templates/claude/commands/trellis/create-command.md +2 -2
  56. package/dist/templates/claude/commands/trellis/onboard.md +13 -13
  57. package/dist/templates/claude/commands/trellis/parallel.md +1 -2
  58. package/dist/templates/claude/commands/trellis/record-session.md +3 -2
  59. package/dist/templates/claude/commands/trellis/start.md +8 -4
  60. package/dist/templates/claude/hooks/inject-subagent-context.py +29 -14
  61. package/dist/templates/claude/hooks/ralph-loop.py +18 -10
  62. package/dist/templates/claude/hooks/session-start.py +201 -9
  63. package/dist/templates/claude/hooks/statusline.py +211 -0
  64. package/dist/templates/claude/settings.json +4 -0
  65. package/dist/templates/codebuddy/commands/trellis/before-dev.md +29 -0
  66. package/dist/templates/codebuddy/commands/trellis/brainstorm.md +487 -0
  67. package/dist/templates/codebuddy/commands/trellis/break-loop.md +107 -0
  68. package/dist/templates/codebuddy/commands/trellis/check-cross-layer.md +153 -0
  69. package/dist/templates/codebuddy/commands/trellis/check.md +25 -0
  70. package/dist/templates/codebuddy/commands/trellis/create-command.md +154 -0
  71. package/dist/templates/codebuddy/commands/trellis/finish-work.md +143 -0
  72. package/dist/templates/codebuddy/commands/trellis/integrate-skill.md +219 -0
  73. package/dist/templates/codebuddy/commands/trellis/onboard.md +358 -0
  74. package/dist/templates/codebuddy/commands/trellis/record-session.md +61 -0
  75. package/dist/templates/codebuddy/commands/trellis/start.md +373 -0
  76. package/dist/templates/codebuddy/commands/trellis/update-spec.md +354 -0
  77. package/dist/templates/codebuddy/index.d.ts +25 -0
  78. package/dist/templates/codebuddy/index.d.ts.map +1 -0
  79. package/dist/templates/codebuddy/index.js +45 -0
  80. package/dist/templates/codebuddy/index.js.map +1 -0
  81. package/dist/templates/codex/agents/check.toml +23 -0
  82. package/dist/templates/codex/agents/implement.toml +19 -0
  83. package/dist/templates/codex/agents/research.toml +26 -0
  84. package/dist/templates/codex/codex-skills/parallel/SKILL.md +194 -0
  85. package/dist/templates/codex/config.toml +5 -0
  86. package/dist/templates/codex/hooks/session-start.py +228 -0
  87. package/dist/templates/codex/hooks.json +16 -0
  88. package/dist/templates/codex/index.d.ts +27 -5
  89. package/dist/templates/codex/index.d.ts.map +1 -1
  90. package/dist/templates/codex/index.js +60 -8
  91. package/dist/templates/codex/index.js.map +1 -1
  92. package/dist/templates/codex/skills/before-dev/SKILL.md +34 -0
  93. package/dist/templates/codex/skills/brainstorm/SKILL.md +1 -1
  94. package/dist/templates/codex/skills/break-loop/SKILL.md +1 -1
  95. package/dist/templates/codex/skills/check/SKILL.md +30 -0
  96. package/dist/templates/codex/skills/check-cross-layer/SKILL.md +1 -1
  97. package/dist/templates/codex/skills/create-command/SKILL.md +3 -3
  98. package/dist/templates/codex/skills/finish-work/SKILL.md +1 -1
  99. package/dist/templates/codex/skills/improve-ut/SKILL.md +69 -0
  100. package/dist/templates/codex/skills/integrate-skill/SKILL.md +1 -1
  101. package/dist/templates/codex/skills/onboard/SKILL.md +12 -12
  102. package/dist/templates/codex/skills/record-session/SKILL.md +4 -3
  103. package/dist/templates/codex/skills/start/SKILL.md +9 -4
  104. package/dist/templates/codex/skills/update-spec/SKILL.md +1 -1
  105. package/dist/templates/copilot/hooks/session-start.py +218 -0
  106. package/dist/templates/copilot/hooks.json +11 -0
  107. package/dist/templates/copilot/index.d.ts +23 -0
  108. package/dist/templates/copilot/index.d.ts.map +1 -0
  109. package/dist/templates/copilot/index.js +54 -0
  110. package/dist/templates/copilot/index.js.map +1 -0
  111. package/dist/templates/copilot/prompts/before-dev.prompt.md +33 -0
  112. package/dist/templates/copilot/prompts/brainstorm.prompt.md +491 -0
  113. package/dist/templates/copilot/prompts/break-loop.prompt.md +129 -0
  114. package/dist/templates/copilot/prompts/check-cross-layer.prompt.md +157 -0
  115. package/dist/templates/copilot/prompts/check.prompt.md +29 -0
  116. package/dist/templates/copilot/prompts/create-command.prompt.md +116 -0
  117. package/dist/templates/copilot/prompts/finish-work.prompt.md +157 -0
  118. package/dist/templates/copilot/prompts/integrate-skill.prompt.md +223 -0
  119. package/dist/templates/copilot/prompts/onboard.prompt.md +362 -0
  120. package/dist/templates/copilot/prompts/parallel.prompt.md +196 -0
  121. package/dist/templates/copilot/prompts/record-session.prompt.md +66 -0
  122. package/dist/templates/copilot/prompts/start.prompt.md +397 -0
  123. package/dist/templates/copilot/prompts/update-spec.prompt.md +358 -0
  124. package/dist/templates/cursor/commands/trellis-before-dev.md +29 -0
  125. package/dist/templates/cursor/commands/trellis-check.md +25 -0
  126. package/dist/templates/cursor/commands/trellis-create-command.md +2 -2
  127. package/dist/templates/cursor/commands/trellis-onboard.md +13 -13
  128. package/dist/templates/cursor/commands/trellis-record-session.md +3 -2
  129. package/dist/templates/cursor/commands/trellis-start.md +7 -16
  130. package/dist/templates/extract.d.ts +36 -0
  131. package/dist/templates/extract.d.ts.map +1 -1
  132. package/dist/templates/extract.js +64 -0
  133. package/dist/templates/extract.js.map +1 -1
  134. package/dist/templates/gemini/commands/trellis/before-dev.toml +33 -0
  135. package/dist/templates/gemini/commands/trellis/check.toml +29 -0
  136. package/dist/templates/gemini/commands/trellis/create-command.toml +2 -2
  137. package/dist/templates/gemini/commands/trellis/onboard.toml +2 -2
  138. package/dist/templates/gemini/commands/trellis/record-session.toml +3 -2
  139. package/dist/templates/gemini/commands/trellis/start.toml +9 -4
  140. package/dist/templates/iflow/agents/dispatch.md +1 -2
  141. package/dist/templates/iflow/agents/implement.md +2 -3
  142. package/dist/templates/iflow/commands/trellis/before-dev.md +29 -0
  143. package/dist/templates/iflow/commands/trellis/check.md +25 -0
  144. package/dist/templates/iflow/commands/trellis/create-command.md +2 -2
  145. package/dist/templates/iflow/commands/trellis/onboard.md +13 -13
  146. package/dist/templates/iflow/commands/trellis/parallel.md +1 -2
  147. package/dist/templates/iflow/commands/trellis/record-session.md +3 -2
  148. package/dist/templates/iflow/commands/trellis/start.md +8 -4
  149. package/dist/templates/iflow/hooks/inject-subagent-context.py +29 -14
  150. package/dist/templates/iflow/hooks/ralph-loop.py +8 -1
  151. package/dist/templates/iflow/hooks/session-start.py +187 -8
  152. package/dist/templates/kilo/workflows/before-dev.md +29 -0
  153. package/dist/templates/kilo/workflows/check.md +25 -0
  154. package/dist/templates/kilo/workflows/create-command.md +2 -2
  155. package/dist/templates/kilo/workflows/onboard.md +13 -13
  156. package/dist/templates/kilo/workflows/parallel.md +1 -2
  157. package/dist/templates/kilo/workflows/record-session.md +3 -2
  158. package/dist/templates/kilo/workflows/start.md +8 -3
  159. package/dist/templates/kiro/skills/before-dev/SKILL.md +34 -0
  160. package/dist/templates/kiro/skills/brainstorm/SKILL.md +1 -1
  161. package/dist/templates/kiro/skills/break-loop/SKILL.md +1 -1
  162. package/dist/templates/kiro/skills/check/SKILL.md +30 -0
  163. package/dist/templates/kiro/skills/check-cross-layer/SKILL.md +1 -1
  164. package/dist/templates/kiro/skills/create-command/SKILL.md +3 -3
  165. package/dist/templates/kiro/skills/finish-work/SKILL.md +1 -1
  166. package/dist/templates/kiro/skills/integrate-skill/SKILL.md +1 -1
  167. package/dist/templates/kiro/skills/onboard/SKILL.md +12 -12
  168. package/dist/templates/kiro/skills/record-session/SKILL.md +4 -3
  169. package/dist/templates/kiro/skills/start/SKILL.md +9 -4
  170. package/dist/templates/kiro/skills/update-spec/SKILL.md +1 -1
  171. package/dist/templates/markdown/agents.md +4 -0
  172. package/dist/templates/markdown/spec/backend/directory-structure.md +1 -1
  173. package/dist/templates/markdown/spec/backend/script-conventions.md +93 -0
  174. package/dist/templates/markdown/workspace-index.md +2 -0
  175. package/dist/templates/opencode/agents/dispatch.md +21 -21
  176. package/dist/templates/opencode/agents/implement.md +2 -2
  177. package/dist/templates/opencode/agents/research.md +1 -2
  178. package/dist/templates/opencode/commands/trellis/before-dev.md +29 -0
  179. package/dist/templates/opencode/commands/trellis/check.md +25 -0
  180. package/dist/templates/opencode/commands/trellis/create-command.md +2 -2
  181. package/dist/templates/opencode/commands/trellis/onboard.md +13 -13
  182. package/dist/templates/opencode/commands/trellis/parallel.md +1 -2
  183. package/dist/templates/opencode/commands/trellis/record-session.md +3 -2
  184. package/dist/templates/opencode/commands/trellis/start.md +8 -3
  185. package/dist/templates/opencode/lib/trellis-context.js +42 -2
  186. package/dist/templates/opencode/{plugin → plugins}/inject-subagent-context.js +45 -18
  187. package/dist/templates/opencode/{plugin → plugins}/session-start.js +156 -28
  188. package/dist/templates/qoder/skills/before-dev/SKILL.md +34 -0
  189. package/dist/templates/qoder/skills/brainstorm/SKILL.md +1 -1
  190. package/dist/templates/qoder/skills/break-loop/SKILL.md +1 -1
  191. package/dist/templates/qoder/skills/check/SKILL.md +30 -0
  192. package/dist/templates/qoder/skills/check-cross-layer/SKILL.md +1 -1
  193. package/dist/templates/qoder/skills/create-command/SKILL.md +3 -3
  194. package/dist/templates/qoder/skills/finish-work/SKILL.md +1 -1
  195. package/dist/templates/qoder/skills/integrate-skill/SKILL.md +1 -1
  196. package/dist/templates/qoder/skills/onboard/SKILL.md +14 -14
  197. package/dist/templates/qoder/skills/record-session/SKILL.md +4 -3
  198. package/dist/templates/qoder/skills/start/SKILL.md +9 -4
  199. package/dist/templates/qoder/skills/update-spec/SKILL.md +1 -1
  200. package/dist/templates/trellis/config.yaml +20 -0
  201. package/dist/templates/trellis/index.d.ts +11 -0
  202. package/dist/templates/trellis/index.d.ts.map +1 -1
  203. package/dist/templates/trellis/index.js +22 -0
  204. package/dist/templates/trellis/index.js.map +1 -1
  205. package/dist/templates/trellis/scripts/add_session.py +111 -13
  206. package/dist/templates/trellis/scripts/common/__init__.py +2 -0
  207. package/dist/templates/trellis/scripts/common/cli_adapter.py +164 -64
  208. package/dist/templates/trellis/scripts/common/config.py +192 -0
  209. package/dist/templates/trellis/scripts/common/developer.py +2 -2
  210. package/dist/templates/trellis/scripts/common/git.py +31 -0
  211. package/dist/templates/trellis/scripts/common/git_context.py +23 -586
  212. package/dist/templates/trellis/scripts/common/io.py +37 -0
  213. package/dist/templates/trellis/scripts/common/log.py +45 -0
  214. package/dist/templates/trellis/scripts/common/packages_context.py +238 -0
  215. package/dist/templates/trellis/scripts/common/paths.py +103 -6
  216. package/dist/templates/trellis/scripts/common/phase.py +50 -49
  217. package/dist/templates/trellis/scripts/common/registry.py +41 -72
  218. package/dist/templates/trellis/scripts/common/session_context.py +562 -0
  219. package/dist/templates/trellis/scripts/common/task_context.py +410 -0
  220. package/dist/templates/trellis/scripts/common/task_queue.py +27 -98
  221. package/dist/templates/trellis/scripts/common/task_store.py +536 -0
  222. package/dist/templates/trellis/scripts/common/task_utils.py +106 -10
  223. package/dist/templates/trellis/scripts/common/tasks.py +109 -0
  224. package/dist/templates/trellis/scripts/common/types.py +112 -0
  225. package/dist/templates/trellis/scripts/create_bootstrap.py +32 -27
  226. package/dist/templates/trellis/scripts/hooks/linear_sync.py +243 -0
  227. package/dist/templates/trellis/scripts/multi_agent/_bootstrap.py +17 -0
  228. package/dist/templates/trellis/scripts/multi_agent/cleanup.py +43 -48
  229. package/dist/templates/trellis/scripts/multi_agent/create_pr.py +336 -45
  230. package/dist/templates/trellis/scripts/multi_agent/plan.py +9 -32
  231. package/dist/templates/trellis/scripts/multi_agent/start.py +142 -68
  232. package/dist/templates/trellis/scripts/multi_agent/status.py +12 -753
  233. package/dist/templates/trellis/scripts/multi_agent/status_display.py +542 -0
  234. package/dist/templates/trellis/scripts/multi_agent/status_monitor.py +225 -0
  235. package/dist/templates/trellis/scripts/task.py +51 -976
  236. package/dist/templates/trellis/scripts-shell-archive/create-bootstrap.sh +1 -1
  237. package/dist/templates/trellis/workflow.md +38 -38
  238. package/dist/templates/windsurf/index.d.ts +21 -0
  239. package/dist/templates/windsurf/index.d.ts.map +1 -0
  240. package/dist/templates/windsurf/index.js +44 -0
  241. package/dist/templates/windsurf/index.js.map +1 -0
  242. package/dist/templates/windsurf/workflows/trellis-before-dev.md +31 -0
  243. package/dist/templates/windsurf/workflows/trellis-brainstorm.md +491 -0
  244. package/dist/templates/windsurf/workflows/trellis-break-loop.md +111 -0
  245. package/dist/templates/windsurf/workflows/trellis-check-cross-layer.md +157 -0
  246. package/dist/templates/windsurf/workflows/trellis-check.md +27 -0
  247. package/dist/templates/windsurf/workflows/trellis-create-command.md +154 -0
  248. package/dist/templates/windsurf/workflows/trellis-finish-work.md +147 -0
  249. package/dist/templates/windsurf/workflows/trellis-integrate-skill.md +220 -0
  250. package/dist/templates/windsurf/workflows/trellis-onboard.md +362 -0
  251. package/dist/templates/windsurf/workflows/trellis-record-session.md +66 -0
  252. package/dist/templates/windsurf/workflows/trellis-start.md +373 -0
  253. package/dist/templates/windsurf/workflows/trellis-update-spec.md +358 -0
  254. package/dist/types/ai-tools.d.ts +15 -3
  255. package/dist/types/ai-tools.d.ts.map +1 -1
  256. package/dist/types/ai-tools.js +42 -2
  257. package/dist/types/ai-tools.js.map +1 -1
  258. package/dist/types/migration.d.ts +3 -1
  259. package/dist/types/migration.d.ts.map +1 -1
  260. package/dist/utils/project-detector.d.ts +28 -0
  261. package/dist/utils/project-detector.d.ts.map +1 -1
  262. package/dist/utils/project-detector.js +371 -0
  263. package/dist/utils/project-detector.js.map +1 -1
  264. package/dist/utils/template-fetcher.d.ts +19 -6
  265. package/dist/utils/template-fetcher.d.ts.map +1 -1
  266. package/dist/utils/template-fetcher.js +99 -17
  267. package/dist/utils/template-fetcher.js.map +1 -1
  268. package/package.json +1 -1
  269. package/dist/templates/claude/commands/trellis/before-backend-dev.md +0 -13
  270. package/dist/templates/claude/commands/trellis/before-frontend-dev.md +0 -13
  271. package/dist/templates/claude/commands/trellis/check-backend.md +0 -13
  272. package/dist/templates/claude/commands/trellis/check-frontend.md +0 -13
  273. package/dist/templates/codex/skills/before-backend-dev/SKILL.md +0 -18
  274. package/dist/templates/codex/skills/before-frontend-dev/SKILL.md +0 -18
  275. package/dist/templates/codex/skills/check-backend/SKILL.md +0 -18
  276. package/dist/templates/codex/skills/check-frontend/SKILL.md +0 -18
  277. package/dist/templates/cursor/commands/trellis-before-backend-dev.md +0 -13
  278. package/dist/templates/cursor/commands/trellis-before-frontend-dev.md +0 -13
  279. package/dist/templates/cursor/commands/trellis-check-backend.md +0 -13
  280. package/dist/templates/cursor/commands/trellis-check-frontend.md +0 -13
  281. package/dist/templates/gemini/commands/trellis/before-backend-dev.toml +0 -17
  282. package/dist/templates/gemini/commands/trellis/before-frontend-dev.toml +0 -17
  283. package/dist/templates/gemini/commands/trellis/check-backend.toml +0 -17
  284. package/dist/templates/gemini/commands/trellis/check-frontend.toml +0 -17
  285. package/dist/templates/iflow/commands/trellis/before-backend-dev.md +0 -13
  286. package/dist/templates/iflow/commands/trellis/before-frontend-dev.md +0 -13
  287. package/dist/templates/iflow/commands/trellis/check-backend.md +0 -13
  288. package/dist/templates/iflow/commands/trellis/check-frontend.md +0 -13
  289. package/dist/templates/kilo/workflows/before-backend-dev.md +0 -13
  290. package/dist/templates/kilo/workflows/before-frontend-dev.md +0 -13
  291. package/dist/templates/kilo/workflows/check-backend.md +0 -13
  292. package/dist/templates/kilo/workflows/check-frontend.md +0 -13
  293. package/dist/templates/kiro/skills/before-backend-dev/SKILL.md +0 -18
  294. package/dist/templates/kiro/skills/before-frontend-dev/SKILL.md +0 -18
  295. package/dist/templates/kiro/skills/check-backend/SKILL.md +0 -18
  296. package/dist/templates/kiro/skills/check-frontend/SKILL.md +0 -18
  297. package/dist/templates/opencode/commands/trellis/before-backend-dev.md +0 -13
  298. package/dist/templates/opencode/commands/trellis/before-frontend-dev.md +0 -13
  299. package/dist/templates/opencode/commands/trellis/check-backend.md +0 -13
  300. package/dist/templates/opencode/commands/trellis/check-frontend.md +0 -13
  301. package/dist/templates/qoder/skills/before-backend-dev/SKILL.md +0 -18
  302. package/dist/templates/qoder/skills/before-frontend-dev/SKILL.md +0 -18
  303. package/dist/templates/qoder/skills/check-backend/SKILL.md +0 -18
  304. package/dist/templates/qoder/skills/check-frontend/SKILL.md +0 -18
@@ -0,0 +1,109 @@
1
+ """
2
+ Task data access layer.
3
+
4
+ Single source of truth for loading and iterating task directories.
5
+ Replaces scattered task.json parsing across 9+ files.
6
+
7
+ Provides:
8
+ load_task — Load a single task by directory path
9
+ iter_active_tasks — Iterate all non-archived tasks (sorted)
10
+ get_all_statuses — Get {dir_name: status} map for children progress
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from collections.abc import Iterator
16
+ from pathlib import Path
17
+
18
+ from .io import read_json
19
+ from .paths import FILE_TASK_JSON
20
+ from .types import TaskInfo
21
+
22
+
23
+ def load_task(task_dir: Path) -> TaskInfo | None:
24
+ """Load task from a directory containing task.json.
25
+
26
+ Args:
27
+ task_dir: Absolute path to the task directory.
28
+
29
+ Returns:
30
+ TaskInfo if task.json exists and is valid, None otherwise.
31
+ """
32
+ task_json = task_dir / FILE_TASK_JSON
33
+ if not task_json.is_file():
34
+ return None
35
+
36
+ data = read_json(task_json)
37
+ if not data:
38
+ return None
39
+
40
+ return TaskInfo(
41
+ dir_name=task_dir.name,
42
+ directory=task_dir,
43
+ title=data.get("title") or data.get("name") or "unknown",
44
+ status=data.get("status", "unknown"),
45
+ assignee=data.get("assignee", ""),
46
+ priority=data.get("priority", "P2"),
47
+ children=tuple(data.get("children", [])),
48
+ parent=data.get("parent"),
49
+ package=data.get("package"),
50
+ raw=data,
51
+ )
52
+
53
+
54
+ def iter_active_tasks(tasks_dir: Path) -> Iterator[TaskInfo]:
55
+ """Iterate all active (non-archived) tasks, sorted by directory name.
56
+
57
+ Skips the "archive" directory and directories without valid task.json.
58
+
59
+ Args:
60
+ tasks_dir: Path to the tasks directory.
61
+
62
+ Yields:
63
+ TaskInfo for each valid task.
64
+ """
65
+ if not tasks_dir.is_dir():
66
+ return
67
+
68
+ for d in sorted(tasks_dir.iterdir()):
69
+ if not d.is_dir() or d.name == "archive":
70
+ continue
71
+ info = load_task(d)
72
+ if info is not None:
73
+ yield info
74
+
75
+
76
+ def get_all_statuses(tasks_dir: Path) -> dict[str, str]:
77
+ """Get a {dir_name: status} mapping for all active tasks.
78
+
79
+ Useful for computing children progress without loading full TaskInfo.
80
+
81
+ Args:
82
+ tasks_dir: Path to the tasks directory.
83
+
84
+ Returns:
85
+ Dict mapping directory names to status strings.
86
+ """
87
+ return {t.dir_name: t.status for t in iter_active_tasks(tasks_dir)}
88
+
89
+
90
+ def children_progress(
91
+ children: tuple[str, ...] | list[str],
92
+ all_statuses: dict[str, str],
93
+ ) -> str:
94
+ """Format children progress string like " [2/3 done]".
95
+
96
+ Args:
97
+ children: List of child directory names.
98
+ all_statuses: Status map from get_all_statuses().
99
+
100
+ Returns:
101
+ Formatted string, or "" if no children.
102
+ """
103
+ if not children:
104
+ return ""
105
+ done = sum(
106
+ 1 for c in children
107
+ if all_statuses.get(c) in ("completed", "done")
108
+ )
109
+ return f" [{done}/{len(children)} done]"
@@ -0,0 +1,112 @@
1
+ """
2
+ Core type definitions for Trellis task data.
3
+
4
+ Provides:
5
+ TaskData — TypedDict for task.json shape (read-path type hints only)
6
+ TaskInfo — Frozen dataclass for loaded task (the public API type)
7
+ AgentRecord — TypedDict for registry.json agent entries
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from dataclasses import dataclass
13
+ from pathlib import Path
14
+ from typing import TypedDict
15
+
16
+
17
+ # =============================================================================
18
+ # task.json shape (TypedDict — used only for read-path type hints)
19
+ # =============================================================================
20
+
21
+ class TaskData(TypedDict, total=False):
22
+ """Shape of task.json on disk.
23
+
24
+ Used only for type annotations when reading task.json.
25
+ Writes must use the original dict to avoid losing unknown fields.
26
+ """
27
+
28
+ id: str
29
+ name: str
30
+ title: str
31
+ description: str
32
+ status: str
33
+ dev_type: str
34
+ scope: str | None
35
+ package: str | None
36
+ priority: str
37
+ creator: str
38
+ assignee: str
39
+ createdAt: str
40
+ completedAt: str | None
41
+ branch: str | None
42
+ base_branch: str | None
43
+ worktree_path: str | None
44
+ current_phase: int
45
+ next_action: list[dict]
46
+ commit: str | None
47
+ pr_url: str | None
48
+ subtasks: list[str]
49
+ children: list[str]
50
+ parent: str | None
51
+ relatedFiles: list[str]
52
+ notes: str
53
+ meta: dict
54
+
55
+
56
+ # =============================================================================
57
+ # Loaded task object (frozen dataclass — the public API type)
58
+ # =============================================================================
59
+
60
+ @dataclass(frozen=True)
61
+ class TaskInfo:
62
+ """Immutable view of a loaded task.
63
+
64
+ Created by load_task() / iter_active_tasks().
65
+ Contains the commonly accessed fields; the original dict
66
+ is preserved in `raw` for write-back and uncommon field access.
67
+ """
68
+
69
+ dir_name: str
70
+ directory: Path
71
+ title: str
72
+ status: str
73
+ assignee: str
74
+ priority: str
75
+ children: tuple[str, ...]
76
+ parent: str | None
77
+ package: str | None
78
+ raw: dict # original dict — use for writes and uncommon fields
79
+
80
+ @property
81
+ def name(self) -> str:
82
+ """Task name (id or name field)."""
83
+ return self.raw.get("name") or self.raw.get("id") or self.dir_name
84
+
85
+ @property
86
+ def description(self) -> str:
87
+ return self.raw.get("description", "")
88
+
89
+ @property
90
+ def branch(self) -> str | None:
91
+ return self.raw.get("branch")
92
+
93
+ @property
94
+ def meta(self) -> dict:
95
+ return self.raw.get("meta", {})
96
+
97
+
98
+ # =============================================================================
99
+ # registry.json agent entry
100
+ # =============================================================================
101
+
102
+ class AgentRecord(TypedDict, total=False):
103
+ """Shape of an agent entry in registry.json."""
104
+
105
+ id: str
106
+ pid: int
107
+ task_dir: str
108
+ worktree_path: str
109
+ branch: str
110
+ platform: str
111
+ started_at: str
112
+ status: str
@@ -36,6 +36,7 @@ from common.paths import (
36
36
  get_tasks_dir,
37
37
  set_current_task,
38
38
  )
39
+ from common.config import get_spec_base, resolve_package
39
40
 
40
41
 
41
42
  # =============================================================================
@@ -58,7 +59,7 @@ def write_prd_header() -> str:
58
59
  Welcome to Trellis! This is your first task.
59
60
 
60
61
  AI agents use `.trellis/spec/` to understand YOUR project's coding conventions.
61
- **Empty templates = AI writes generic code that doesn't match your project style.**
62
+ **Starting from scratch = AI writes generic code that doesn't match your project style.**
62
63
 
63
64
  Filling these guidelines is a one-time setup that pays off for every future AI session.
64
65
 
@@ -70,36 +71,36 @@ Fill in the guideline files based on your **existing codebase**.
70
71
  """
71
72
 
72
73
 
73
- def write_prd_backend_section() -> str:
74
+ def write_prd_backend_section(spec_base: str) -> str:
74
75
  """Write PRD backend section."""
75
- return """
76
+ return f"""
76
77
 
77
78
  ### Backend Guidelines
78
79
 
79
80
  | File | What to Document |
80
81
  |------|------------------|
81
- | `.trellis/spec/backend/directory-structure.md` | Where different file types go (routes, services, utils) |
82
- | `.trellis/spec/backend/database-guidelines.md` | ORM, migrations, query patterns, naming conventions |
83
- | `.trellis/spec/backend/error-handling.md` | How errors are caught, logged, and returned |
84
- | `.trellis/spec/backend/logging-guidelines.md` | Log levels, format, what to log |
85
- | `.trellis/spec/backend/quality-guidelines.md` | Code review standards, testing requirements |
82
+ | `.trellis/{spec_base}/backend/directory-structure.md` | Where different file types go (routes, services, utils) |
83
+ | `.trellis/{spec_base}/backend/database-guidelines.md` | ORM, migrations, query patterns, naming conventions |
84
+ | `.trellis/{spec_base}/backend/error-handling.md` | How errors are caught, logged, and returned |
85
+ | `.trellis/{spec_base}/backend/logging-guidelines.md` | Log levels, format, what to log |
86
+ | `.trellis/{spec_base}/backend/quality-guidelines.md` | Code review standards, testing requirements |
86
87
  """
87
88
 
88
89
 
89
- def write_prd_frontend_section() -> str:
90
+ def write_prd_frontend_section(spec_base: str) -> str:
90
91
  """Write PRD frontend section."""
91
- return """
92
+ return f"""
92
93
 
93
94
  ### Frontend Guidelines
94
95
 
95
96
  | File | What to Document |
96
97
  |------|------------------|
97
- | `.trellis/spec/frontend/directory-structure.md` | Component/page/hook organization |
98
- | `.trellis/spec/frontend/component-guidelines.md` | Component patterns, props conventions |
99
- | `.trellis/spec/frontend/hook-guidelines.md` | Custom hook naming, patterns |
100
- | `.trellis/spec/frontend/state-management.md` | State library, patterns, what goes where |
101
- | `.trellis/spec/frontend/type-safety.md` | TypeScript conventions, type organization |
102
- | `.trellis/spec/frontend/quality-guidelines.md` | Linting, testing, accessibility |
98
+ | `.trellis/{spec_base}/frontend/directory-structure.md` | Component/page/hook organization |
99
+ | `.trellis/{spec_base}/frontend/component-guidelines.md` | Component patterns, props conventions |
100
+ | `.trellis/{spec_base}/frontend/hook-guidelines.md` | Custom hook naming, patterns |
101
+ | `.trellis/{spec_base}/frontend/state-management.md` | State library, patterns, what goes where |
102
+ | `.trellis/{spec_base}/frontend/type-safety.md` | TypeScript conventions, type organization |
103
+ | `.trellis/{spec_base}/frontend/quality-guidelines.md` | Linting, testing, accessibility |
103
104
  """
104
105
 
105
106
 
@@ -168,17 +169,17 @@ After completing this task:
168
169
  """
169
170
 
170
171
 
171
- def write_prd(task_dir: Path, project_type: str) -> None:
172
+ def write_prd(task_dir: Path, project_type: str, spec_base: str) -> None:
172
173
  """Write prd.md file."""
173
174
  content = write_prd_header()
174
175
 
175
176
  if project_type == "frontend":
176
- content += write_prd_frontend_section()
177
+ content += write_prd_frontend_section(spec_base)
177
178
  elif project_type == "backend":
178
- content += write_prd_backend_section()
179
+ content += write_prd_backend_section(spec_base)
179
180
  else: # fullstack
180
- content += write_prd_backend_section()
181
- content += write_prd_frontend_section()
181
+ content += write_prd_backend_section(spec_base)
182
+ content += write_prd_frontend_section(spec_base)
182
183
 
183
184
  content += write_prd_footer()
184
185
 
@@ -190,7 +191,7 @@ def write_prd(task_dir: Path, project_type: str) -> None:
190
191
  # Task JSON
191
192
  # =============================================================================
192
193
 
193
- def write_task_json(task_dir: Path, developer: str, project_type: str) -> None:
194
+ def write_task_json(task_dir: Path, developer: str, project_type: str, spec_base: str) -> None:
194
195
  """Write task.json file."""
195
196
  today = datetime.now().strftime("%Y-%m-%d")
196
197
 
@@ -200,20 +201,20 @@ def write_task_json(task_dir: Path, developer: str, project_type: str) -> None:
200
201
  {"name": "Fill frontend guidelines", "status": "pending"},
201
202
  {"name": "Add code examples", "status": "pending"},
202
203
  ]
203
- related_files = [".trellis/spec/frontend/"]
204
+ related_files = [f".trellis/{spec_base}/frontend/"]
204
205
  elif project_type == "backend":
205
206
  subtasks = [
206
207
  {"name": "Fill backend guidelines", "status": "pending"},
207
208
  {"name": "Add code examples", "status": "pending"},
208
209
  ]
209
- related_files = [".trellis/spec/backend/"]
210
+ related_files = [f".trellis/{spec_base}/backend/"]
210
211
  else: # fullstack
211
212
  subtasks = [
212
213
  {"name": "Fill backend guidelines", "status": "pending"},
213
214
  {"name": "Fill frontend guidelines", "status": "pending"},
214
215
  {"name": "Add code examples", "status": "pending"},
215
216
  ]
216
- related_files = [".trellis/spec/backend/", ".trellis/spec/frontend/"]
217
+ related_files = [f".trellis/{spec_base}/backend/", f".trellis/{spec_base}/frontend/"]
217
218
 
218
219
  task_data = {
219
220
  "id": TASK_NAME,
@@ -264,6 +265,10 @@ def main() -> int:
264
265
  print(f"Run: python3 ./{DIR_WORKFLOW}/{DIR_SCRIPTS}/init_developer.py <your-name>")
265
266
  return 1
266
267
 
268
+ # Resolve spec base path (monorepo: spec/<package>, single-repo: spec)
269
+ package = resolve_package(repo_root=repo_root)
270
+ spec_base = get_spec_base(package, repo_root)
271
+
267
272
  tasks_dir = get_tasks_dir(repo_root)
268
273
  task_dir = tasks_dir / TASK_NAME
269
274
  relative_path = f"{DIR_WORKFLOW}/{DIR_TASKS}/{TASK_NAME}"
@@ -277,8 +282,8 @@ def main() -> int:
277
282
  task_dir.mkdir(parents=True, exist_ok=True)
278
283
 
279
284
  # Write files
280
- write_task_json(task_dir, developer, project_type)
281
- write_prd(task_dir, project_type)
285
+ write_task_json(task_dir, developer, project_type, spec_base)
286
+ write_prd(task_dir, project_type, spec_base)
282
287
 
283
288
  # Set as current task
284
289
  set_current_task(relative_path, repo_root)
@@ -0,0 +1,243 @@
1
+ #!/usr/bin/env python3
2
+ """Linear sync hook for Trellis task lifecycle.
3
+
4
+ Syncs task events to Linear via the `linearis` CLI.
5
+
6
+ Usage (called automatically by task.py hooks):
7
+ python3 .trellis/scripts/hooks/linear_sync.py create
8
+ python3 .trellis/scripts/hooks/linear_sync.py start
9
+ python3 .trellis/scripts/hooks/linear_sync.py archive
10
+
11
+ Manual usage:
12
+ TASK_JSON_PATH=.trellis/tasks/<name>/task.json python3 .trellis/scripts/hooks/linear_sync.py sync
13
+
14
+ Environment:
15
+ TASK_JSON_PATH - Absolute path to task.json (set by task.py)
16
+
17
+ Configuration:
18
+ .trellis/hooks.local.json - Local config (gitignored), example:
19
+ {
20
+ "linear": {
21
+ "team": "TEAM_KEY",
22
+ "project": "Project Name",
23
+ "assignees": {
24
+ "dev-name": "linear-user-id"
25
+ }
26
+ }
27
+ }
28
+ """
29
+
30
+ from __future__ import annotations
31
+
32
+ import json
33
+ import os
34
+ import subprocess
35
+ import sys
36
+ from pathlib import Path
37
+
38
+ # ─── Configuration ────────────────────────────────────────────────────────────
39
+
40
+ # Trellis priority → Linear priority (1=Urgent, 2=High, 3=Medium, 4=Low)
41
+ PRIORITY_MAP = {"P0": 1, "P1": 2, "P2": 3, "P3": 4}
42
+
43
+ # Linear status names (must match your team's workflow)
44
+ STATUS_IN_PROGRESS = "In Progress"
45
+ STATUS_DONE = "Done"
46
+
47
+
48
+ def _load_config() -> dict:
49
+ """Load local hook config from .trellis/hooks.local.json."""
50
+ task_json_path = os.environ.get("TASK_JSON_PATH", "")
51
+ if task_json_path:
52
+ # Walk up from task.json to find .trellis/
53
+ trellis_dir = Path(task_json_path).parent.parent.parent
54
+ else:
55
+ trellis_dir = Path(".trellis")
56
+
57
+ config_path = trellis_dir / "hooks.local.json"
58
+ try:
59
+ with open(config_path, encoding="utf-8") as f:
60
+ return json.load(f)
61
+ except (OSError, json.JSONDecodeError):
62
+ return {}
63
+
64
+
65
+ CONFIG = _load_config()
66
+ LINEAR_CFG = CONFIG.get("linear", {})
67
+
68
+ TEAM = LINEAR_CFG.get("team", "")
69
+ PROJECT = LINEAR_CFG.get("project", "")
70
+ ASSIGNEE_MAP = LINEAR_CFG.get("assignees", {})
71
+
72
+ # ─── Helpers ──────────────────────────────────────────────────────────────────
73
+
74
+
75
+ def _read_task() -> tuple[dict, str]:
76
+ path = os.environ.get("TASK_JSON_PATH", "")
77
+ if not path:
78
+ print("TASK_JSON_PATH not set", file=sys.stderr)
79
+ sys.exit(1)
80
+ with open(path, encoding="utf-8") as f:
81
+ return json.load(f), path
82
+
83
+
84
+ def _write_task(data: dict, path: str) -> None:
85
+ with open(path, "w", encoding="utf-8") as f:
86
+ json.dump(data, f, indent=2, ensure_ascii=False)
87
+ f.write("\n")
88
+
89
+
90
+ def _linearis(*args: str) -> dict | None:
91
+ result = subprocess.run(
92
+ ["linearis", *args],
93
+ capture_output=True,
94
+ text=True,
95
+ encoding="utf-8",
96
+ errors="replace",
97
+ )
98
+ if result.returncode != 0:
99
+ print(f"linearis error: {result.stderr.strip()}", file=sys.stderr)
100
+ sys.exit(1)
101
+ stdout = result.stdout.strip()
102
+ if stdout:
103
+ return json.loads(stdout)
104
+ return None
105
+
106
+
107
+ def _get_linear_issue(task: dict) -> str | None:
108
+ meta = task.get("meta")
109
+ if isinstance(meta, dict):
110
+ return meta.get("linear_issue")
111
+ return None
112
+
113
+
114
+ # ─── Actions ──────────────────────────────────────────────────────────────────
115
+
116
+
117
+ def cmd_create() -> None:
118
+ if not TEAM:
119
+ print("No linear.team configured in hooks.local.json", file=sys.stderr)
120
+ sys.exit(1)
121
+
122
+ task, path = _read_task()
123
+
124
+ # Skip if already linked
125
+ if _get_linear_issue(task):
126
+ print(f"Already linked: {_get_linear_issue(task)}")
127
+ return
128
+
129
+ title = task.get("title") or task.get("name") or "Untitled"
130
+ args = ["issues", "create", title, "--team", TEAM]
131
+
132
+ # Map priority
133
+ priority = PRIORITY_MAP.get(task.get("priority", ""), 0)
134
+ if priority:
135
+ args.extend(["-p", str(priority)])
136
+
137
+ # Set project
138
+ if PROJECT:
139
+ args.extend(["--project", PROJECT])
140
+
141
+ # Assign to Linear user
142
+ assignee = task.get("assignee", "")
143
+ linear_user_id = ASSIGNEE_MAP.get(assignee)
144
+ if linear_user_id:
145
+ args.extend(["--assignee", linear_user_id])
146
+
147
+ # Link to parent's Linear issue if available
148
+ parent_issue = _resolve_parent_linear_issue(task)
149
+ if parent_issue:
150
+ args.extend(["--parent-ticket", parent_issue])
151
+
152
+ result = _linearis(*args)
153
+ if result and "identifier" in result:
154
+ if not isinstance(task.get("meta"), dict):
155
+ task["meta"] = {}
156
+ task["meta"]["linear_issue"] = result["identifier"]
157
+ _write_task(task, path)
158
+ print(f"Created Linear issue: {result['identifier']}")
159
+
160
+
161
+ def cmd_start() -> None:
162
+ task, _ = _read_task()
163
+ issue = _get_linear_issue(task)
164
+ if not issue:
165
+ return
166
+ _linearis("issues", "update", issue, "-s", STATUS_IN_PROGRESS)
167
+ print(f"Updated {issue} -> {STATUS_IN_PROGRESS}")
168
+ cmd_sync()
169
+
170
+
171
+ def cmd_archive() -> None:
172
+ task, _ = _read_task()
173
+ issue = _get_linear_issue(task)
174
+ if not issue:
175
+ return
176
+ _linearis("issues", "update", issue, "-s", STATUS_DONE)
177
+ print(f"Updated {issue} -> {STATUS_DONE}")
178
+
179
+
180
+ def cmd_sync() -> None:
181
+ """Sync prd.md content to Linear issue description."""
182
+ task, _ = _read_task()
183
+ issue = _get_linear_issue(task)
184
+ if not issue:
185
+ print("No linear_issue in meta, run create first", file=sys.stderr)
186
+ sys.exit(1)
187
+
188
+ # Find prd.md next to task.json
189
+ task_json_path = os.environ.get("TASK_JSON_PATH", "")
190
+ prd_path = Path(task_json_path).parent / "prd.md"
191
+ if not prd_path.is_file():
192
+ print(f"No prd.md found at {prd_path}", file=sys.stderr)
193
+ sys.exit(1)
194
+
195
+ description = prd_path.read_text(encoding="utf-8").strip()
196
+ _linearis("issues", "update", issue, "-d", description)
197
+ print(f"Synced prd.md to {issue} description")
198
+
199
+
200
+ # ─── Parent Issue Resolution ─────────────────────────────────────────────────
201
+
202
+
203
+ def _resolve_parent_linear_issue(task: dict) -> str | None:
204
+ """Find parent task's Linear issue identifier."""
205
+ parent_name = task.get("parent")
206
+ if not parent_name:
207
+ return None
208
+
209
+ task_json_path = os.environ.get("TASK_JSON_PATH", "")
210
+ if not task_json_path:
211
+ return None
212
+
213
+ current_task_dir = Path(task_json_path).parent
214
+ tasks_dir = current_task_dir.parent
215
+ parent_json = tasks_dir / parent_name / "task.json"
216
+
217
+ if parent_json.exists():
218
+ try:
219
+ with open(parent_json, encoding="utf-8") as f:
220
+ parent_task = json.load(f)
221
+ return _get_linear_issue(parent_task)
222
+ except (json.JSONDecodeError, OSError):
223
+ pass
224
+ return None
225
+
226
+
227
+ # ─── Main ─────────────────────────────────────────────────────────────────────
228
+
229
+ if __name__ == "__main__":
230
+ action = sys.argv[1] if len(sys.argv) > 1 else ""
231
+ actions = {
232
+ "create": cmd_create,
233
+ "start": cmd_start,
234
+ "archive": cmd_archive,
235
+ "sync": cmd_sync,
236
+ }
237
+ fn = actions.get(action)
238
+ if fn:
239
+ fn()
240
+ else:
241
+ print(f"Unknown action: {action}", file=sys.stderr)
242
+ print(f"Valid actions: {', '.join(actions)}", file=sys.stderr)
243
+ sys.exit(1)
@@ -0,0 +1,17 @@
1
+ """Bootstrap path setup for multi_agent scripts.
2
+
3
+ Import this module before importing from common/:
4
+
5
+ import _bootstrap # noqa: F401
6
+
7
+ This adds the parent scripts/ directory to sys.path so that
8
+ `from common.xxx import yyy` works when running scripts directly
9
+ via `python3 .trellis/scripts/multi_agent/some_script.py`.
10
+ """
11
+
12
+ import sys
13
+ from pathlib import Path
14
+
15
+ _scripts_dir = str(Path(__file__).resolve().parent.parent)
16
+ if _scripts_dir not in sys.path:
17
+ sys.path.insert(0, _scripts_dir)