@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,410 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Task JSONL context management.
4
+
5
+ Provides:
6
+ cmd_init_context - Initialize JSONL context files for a task
7
+ cmd_add_context - Add entry to JSONL context file
8
+ cmd_validate - Validate JSONL context files
9
+ cmd_list_context - List JSONL context entries
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import argparse
15
+ import json
16
+ import sys
17
+ from pathlib import Path
18
+
19
+ from .cli_adapter import get_cli_adapter_auto
20
+ from .config import (
21
+ get_packages,
22
+ is_monorepo,
23
+ resolve_package,
24
+ validate_package,
25
+ )
26
+ from .io import read_json, write_json
27
+ from .log import Colors, colored
28
+ from .paths import (
29
+ DIR_SPEC,
30
+ DIR_WORKFLOW,
31
+ FILE_TASK_JSON,
32
+ get_repo_root,
33
+ )
34
+ from .task_utils import resolve_task_dir
35
+
36
+
37
+ # =============================================================================
38
+ # JSONL Default Content Generators
39
+ # =============================================================================
40
+
41
+ def get_implement_base() -> list[dict]:
42
+ """Get base implement context entries."""
43
+ return [
44
+ {"file": f"{DIR_WORKFLOW}/workflow.md", "reason": "Project workflow and conventions"},
45
+ ]
46
+
47
+
48
+ def get_implement_backend(package: str | None = None) -> list[dict]:
49
+ """Get backend implement context entries."""
50
+ spec_base = f"{DIR_SPEC}/{package}" if package else DIR_SPEC
51
+ return [
52
+ {"file": f"{DIR_WORKFLOW}/{spec_base}/backend/index.md", "reason": "Backend development guide"},
53
+ ]
54
+
55
+
56
+ def get_implement_frontend(package: str | None = None) -> list[dict]:
57
+ """Get frontend implement context entries."""
58
+ spec_base = f"{DIR_SPEC}/{package}" if package else DIR_SPEC
59
+ return [
60
+ {"file": f"{DIR_WORKFLOW}/{spec_base}/frontend/index.md", "reason": "Frontend development guide"},
61
+ ]
62
+
63
+
64
+ def get_check_context(repo_root: Path) -> list[dict]:
65
+ """Get check context entries."""
66
+ adapter = get_cli_adapter_auto(repo_root)
67
+
68
+ entries = [
69
+ {"file": adapter.get_trellis_command_path("finish-work"), "reason": "Finish work checklist"},
70
+ {"file": adapter.get_trellis_command_path("check"), "reason": "Code quality check spec"},
71
+ ]
72
+
73
+ return entries
74
+
75
+
76
+ def get_debug_context(repo_root: Path) -> list[dict]:
77
+ """Get debug context entries."""
78
+ adapter = get_cli_adapter_auto(repo_root)
79
+
80
+ entries: list[dict] = [
81
+ {"file": adapter.get_trellis_command_path("check"), "reason": "Code quality check spec"},
82
+ ]
83
+
84
+ return entries
85
+
86
+
87
+ def _write_jsonl(path: Path, entries: list[dict]) -> None:
88
+ """Write entries to JSONL file."""
89
+ lines = [json.dumps(entry, ensure_ascii=False) for entry in entries]
90
+ path.write_text("\n".join(lines) + "\n", encoding="utf-8")
91
+
92
+
93
+ # =============================================================================
94
+ # Command: init-context
95
+ # =============================================================================
96
+
97
+ def cmd_init_context(args: argparse.Namespace) -> int:
98
+ """Initialize JSONL context files for a task."""
99
+ repo_root = get_repo_root()
100
+ target_dir = resolve_task_dir(args.dir, repo_root)
101
+ dev_type = args.type
102
+
103
+ if not dev_type:
104
+ print(colored("Error: Missing arguments", Colors.RED))
105
+ print("Usage: python3 task.py init-context <task-dir> <dev_type>")
106
+ print(" dev_type: backend | frontend | fullstack | test | docs")
107
+ return 1
108
+
109
+ if not target_dir.is_dir():
110
+ print(colored(f"Error: Directory not found: {target_dir}", Colors.RED))
111
+ return 1
112
+
113
+ # Resolve package: --package CLI → task.json.package → default_package
114
+ cli_package: str | None = getattr(args, "package", None)
115
+ package: str | None = None
116
+ if not is_monorepo(repo_root):
117
+ # Single-repo: ignore --package, no package prefix
118
+ if cli_package:
119
+ print(colored("Warning: --package ignored in single-repo project", Colors.YELLOW), file=sys.stderr)
120
+ elif cli_package:
121
+ if not validate_package(cli_package, repo_root):
122
+ packages = get_packages(repo_root)
123
+ available = ", ".join(sorted(packages.keys())) if packages else "(none)"
124
+ print(colored(f"Error: unknown package '{cli_package}'. Available: {available}", Colors.RED), file=sys.stderr)
125
+ return 1
126
+ package = cli_package
127
+ else:
128
+ # Read task.json.package as inferred source
129
+ task_json_path = target_dir / FILE_TASK_JSON
130
+ task_pkg_value = None
131
+ if task_json_path.is_file():
132
+ task_data = read_json(task_json_path)
133
+ if isinstance(task_data, dict):
134
+ task_pkg_value = task_data.get("package")
135
+ # Only pass string values to resolve_package (guard against malformed JSON)
136
+ task_package = task_pkg_value if isinstance(task_pkg_value, str) else None
137
+ package = resolve_package(task_package=task_package, repo_root=repo_root)
138
+
139
+ # Monorepo fallback prohibition
140
+ if package is None:
141
+ packages = get_packages(repo_root)
142
+ available = ", ".join(sorted(packages.keys())) if packages else "(none)"
143
+ print(colored(
144
+ f"Error: monorepo project requires --package (or set default_package in config.yaml). Available: {available}",
145
+ Colors.RED,
146
+ ), file=sys.stderr)
147
+ return 1
148
+
149
+ print(colored("=== Initializing Agent Context Files ===", Colors.BLUE))
150
+ print(f"Target dir: {target_dir}")
151
+ print(f"Dev type: {dev_type}")
152
+ if package:
153
+ print(f"Package: {package}")
154
+ print()
155
+
156
+ # implement.jsonl
157
+ print(colored("Creating implement.jsonl...", Colors.CYAN))
158
+ implement_entries = get_implement_base()
159
+ if dev_type in ("backend", "test"):
160
+ implement_entries.extend(get_implement_backend(package))
161
+ elif dev_type == "frontend":
162
+ implement_entries.extend(get_implement_frontend(package))
163
+ elif dev_type == "fullstack":
164
+ implement_entries.extend(get_implement_backend(package))
165
+ implement_entries.extend(get_implement_frontend(package))
166
+
167
+ implement_file = target_dir / "implement.jsonl"
168
+ _write_jsonl(implement_file, implement_entries)
169
+ print(f" {colored('✓', Colors.GREEN)} {len(implement_entries)} entries")
170
+
171
+ # check.jsonl
172
+ print(colored("Creating check.jsonl...", Colors.CYAN))
173
+ check_entries = get_check_context(repo_root)
174
+ check_file = target_dir / "check.jsonl"
175
+ _write_jsonl(check_file, check_entries)
176
+ print(f" {colored('✓', Colors.GREEN)} {len(check_entries)} entries")
177
+
178
+ # debug.jsonl
179
+ print(colored("Creating debug.jsonl...", Colors.CYAN))
180
+ debug_entries = get_debug_context(repo_root)
181
+ debug_file = target_dir / "debug.jsonl"
182
+ _write_jsonl(debug_file, debug_entries)
183
+ print(f" {colored('✓', Colors.GREEN)} {len(debug_entries)} entries")
184
+
185
+ # Update task.json dev_type and package
186
+ task_json_path = target_dir / FILE_TASK_JSON
187
+ if task_json_path.is_file():
188
+ task_data = read_json(task_json_path)
189
+ if isinstance(task_data, dict):
190
+ task_data["dev_type"] = dev_type
191
+ task_data["package"] = package # Always sync to match resolved value
192
+ write_json(task_json_path, task_data)
193
+
194
+ print()
195
+ print(colored("✓ All context files created", Colors.GREEN))
196
+ print()
197
+
198
+ # Show what was auto-injected
199
+ all_injected = [e["file"] for e in implement_entries]
200
+ print(colored("Auto-injected (defaults only):", Colors.YELLOW))
201
+ for f in all_injected:
202
+ print(f" - {f}")
203
+ print()
204
+
205
+ # Scan spec directory for available spec files the AI should consider
206
+ spec_base = repo_root / DIR_WORKFLOW / DIR_SPEC
207
+ if package:
208
+ spec_base = spec_base / package
209
+ available_specs: list[str] = []
210
+ if spec_base.is_dir():
211
+ for md_file in sorted(spec_base.rglob("*.md")):
212
+ rel = str(md_file.relative_to(repo_root))
213
+ if rel not in all_injected:
214
+ available_specs.append(rel)
215
+
216
+ if available_specs:
217
+ print(colored("Available spec files (not yet injected):", Colors.BLUE))
218
+ for spec in available_specs:
219
+ print(f" - {spec}")
220
+ print()
221
+
222
+ print(colored("Next steps:", Colors.BLUE))
223
+ print(" 1. Review the spec files above and add relevant ones for your task:")
224
+ print(f" python3 task.py add-context <dir> implement <spec-path> \"<reason>\"")
225
+ print(" 2. Set as current: python3 task.py start <dir>")
226
+
227
+ return 0
228
+
229
+
230
+ # =============================================================================
231
+ # Command: add-context
232
+ # =============================================================================
233
+
234
+ def cmd_add_context(args: argparse.Namespace) -> int:
235
+ """Add entry to JSONL context file."""
236
+ repo_root = get_repo_root()
237
+ target_dir = resolve_task_dir(args.dir, repo_root)
238
+
239
+ jsonl_name = args.file
240
+ path = args.path
241
+ reason = args.reason or "Added manually"
242
+
243
+ if not target_dir.is_dir():
244
+ print(colored(f"Error: Directory not found: {target_dir}", Colors.RED))
245
+ return 1
246
+
247
+ # Support shorthand
248
+ if not jsonl_name.endswith(".jsonl"):
249
+ jsonl_name = f"{jsonl_name}.jsonl"
250
+
251
+ jsonl_file = target_dir / jsonl_name
252
+ full_path = repo_root / path
253
+
254
+ entry_type = "file"
255
+ if full_path.is_dir():
256
+ entry_type = "directory"
257
+ if not path.endswith("/"):
258
+ path = f"{path}/"
259
+ elif not full_path.is_file():
260
+ print(colored(f"Error: Path not found: {path}", Colors.RED))
261
+ return 1
262
+
263
+ # Check if already exists
264
+ if jsonl_file.is_file():
265
+ content = jsonl_file.read_text(encoding="utf-8")
266
+ if f'"{path}"' in content:
267
+ print(colored(f"Warning: Entry already exists for {path}", Colors.YELLOW))
268
+ return 0
269
+
270
+ # Add entry
271
+ entry: dict
272
+ if entry_type == "directory":
273
+ entry = {"file": path, "type": "directory", "reason": reason}
274
+ else:
275
+ entry = {"file": path, "reason": reason}
276
+
277
+ with jsonl_file.open("a", encoding="utf-8") as f:
278
+ f.write(json.dumps(entry, ensure_ascii=False) + "\n")
279
+
280
+ print(colored(f"Added {entry_type}: {path}", Colors.GREEN))
281
+ return 0
282
+
283
+
284
+ # =============================================================================
285
+ # Command: validate
286
+ # =============================================================================
287
+
288
+ def cmd_validate(args: argparse.Namespace) -> int:
289
+ """Validate JSONL context files."""
290
+ repo_root = get_repo_root()
291
+ target_dir = resolve_task_dir(args.dir, repo_root)
292
+
293
+ if not target_dir.is_dir():
294
+ print(colored("Error: task directory required", Colors.RED))
295
+ return 1
296
+
297
+ print(colored("=== Validating Context Files ===", Colors.BLUE))
298
+ print(f"Target dir: {target_dir}")
299
+ print()
300
+
301
+ total_errors = 0
302
+ for jsonl_name in ["implement.jsonl", "check.jsonl", "debug.jsonl"]:
303
+ jsonl_file = target_dir / jsonl_name
304
+ errors = _validate_jsonl(jsonl_file, repo_root)
305
+ total_errors += errors
306
+
307
+ print()
308
+ if total_errors == 0:
309
+ print(colored("✓ All validations passed", Colors.GREEN))
310
+ return 0
311
+ else:
312
+ print(colored(f"✗ Validation failed ({total_errors} errors)", Colors.RED))
313
+ return 1
314
+
315
+
316
+ def _validate_jsonl(jsonl_file: Path, repo_root: Path) -> int:
317
+ """Validate a single JSONL file."""
318
+ file_name = jsonl_file.name
319
+ errors = 0
320
+
321
+ if not jsonl_file.is_file():
322
+ print(f" {colored(f'{file_name}: not found (skipped)', Colors.YELLOW)}")
323
+ return 0
324
+
325
+ line_num = 0
326
+ for line in jsonl_file.read_text(encoding="utf-8").splitlines():
327
+ line_num += 1
328
+ if not line.strip():
329
+ continue
330
+
331
+ try:
332
+ data = json.loads(line)
333
+ except json.JSONDecodeError:
334
+ print(f" {colored(f'{file_name}:{line_num}: Invalid JSON', Colors.RED)}")
335
+ errors += 1
336
+ continue
337
+
338
+ file_path = data.get("file")
339
+ entry_type = data.get("type", "file")
340
+
341
+ if not file_path:
342
+ print(f" {colored(f'{file_name}:{line_num}: Missing file field', Colors.RED)}")
343
+ errors += 1
344
+ continue
345
+
346
+ full_path = repo_root / file_path
347
+ if entry_type == "directory":
348
+ if not full_path.is_dir():
349
+ print(f" {colored(f'{file_name}:{line_num}: Directory not found: {file_path}', Colors.RED)}")
350
+ errors += 1
351
+ else:
352
+ if not full_path.is_file():
353
+ print(f" {colored(f'{file_name}:{line_num}: File not found: {file_path}', Colors.RED)}")
354
+ errors += 1
355
+
356
+ if errors == 0:
357
+ print(f" {colored(f'{file_name}: ✓ ({line_num} entries)', Colors.GREEN)}")
358
+ else:
359
+ print(f" {colored(f'{file_name}: ✗ ({errors} errors)', Colors.RED)}")
360
+
361
+ return errors
362
+
363
+
364
+ # =============================================================================
365
+ # Command: list-context
366
+ # =============================================================================
367
+
368
+ def cmd_list_context(args: argparse.Namespace) -> int:
369
+ """List JSONL context entries."""
370
+ repo_root = get_repo_root()
371
+ target_dir = resolve_task_dir(args.dir, repo_root)
372
+
373
+ if not target_dir.is_dir():
374
+ print(colored("Error: task directory required", Colors.RED))
375
+ return 1
376
+
377
+ print(colored("=== Context Files ===", Colors.BLUE))
378
+ print()
379
+
380
+ for jsonl_name in ["implement.jsonl", "check.jsonl", "debug.jsonl"]:
381
+ jsonl_file = target_dir / jsonl_name
382
+ if not jsonl_file.is_file():
383
+ continue
384
+
385
+ print(colored(f"[{jsonl_name}]", Colors.CYAN))
386
+
387
+ count = 0
388
+ for line in jsonl_file.read_text(encoding="utf-8").splitlines():
389
+ if not line.strip():
390
+ continue
391
+
392
+ try:
393
+ data = json.loads(line)
394
+ except json.JSONDecodeError:
395
+ continue
396
+
397
+ count += 1
398
+ file_path = data.get("file", "?")
399
+ entry_type = data.get("type", "file")
400
+ reason = data.get("reason", "-")
401
+
402
+ if entry_type == "directory":
403
+ print(f" {colored(f'{count}.', Colors.GREEN)} [DIR] {file_path}")
404
+ else:
405
+ print(f" {colored(f'{count}.', Colors.GREEN)} {file_path}")
406
+ print(f" {colored('→', Colors.YELLOW)} {reason}")
407
+
408
+ print()
409
+
410
+ return 0
@@ -12,23 +12,32 @@ Provides:
12
12
 
13
13
  from __future__ import annotations
14
14
 
15
- import json
16
15
  from pathlib import Path
17
16
 
18
17
  from .paths import (
19
- FILE_TASK_JSON,
20
18
  get_repo_root,
21
19
  get_developer,
22
20
  get_tasks_dir,
23
21
  )
22
+ from .tasks import iter_active_tasks
24
23
 
25
24
 
26
- def _read_json_file(path: Path) -> dict | None:
27
- """Read and parse a JSON file."""
28
- try:
29
- return json.loads(path.read_text(encoding="utf-8"))
30
- except (FileNotFoundError, json.JSONDecodeError, OSError):
31
- return None
25
+ # =============================================================================
26
+ # Internal helper
27
+ # =============================================================================
28
+
29
+ def _task_to_dict(t) -> dict:
30
+ """Convert TaskInfo to the dict format callers expect."""
31
+ return {
32
+ "priority": t.priority,
33
+ "id": t.raw.get("id", ""),
34
+ "title": t.title,
35
+ "status": t.status,
36
+ "assignee": t.assignee or "-",
37
+ "dir": t.dir_name,
38
+ "children": list(t.children),
39
+ "parent": t.parent,
40
+ }
32
41
 
33
42
 
34
43
  # =============================================================================
@@ -54,41 +63,10 @@ def list_tasks_by_status(
54
63
  tasks_dir = get_tasks_dir(repo_root)
55
64
  results = []
56
65
 
57
- if not tasks_dir.is_dir():
58
- return results
59
-
60
- for d in tasks_dir.iterdir():
61
- if not d.is_dir() or d.name == "archive":
62
- continue
63
-
64
- task_json = d / FILE_TASK_JSON
65
- if not task_json.is_file():
66
- continue
67
-
68
- data = _read_json_file(task_json)
69
- if not data:
66
+ for t in iter_active_tasks(tasks_dir):
67
+ if filter_status and t.status != filter_status:
70
68
  continue
71
-
72
- task_id = data.get("id", "")
73
- title = data.get("title") or data.get("name", "")
74
- priority = data.get("priority", "P2")
75
- status = data.get("status", "planning")
76
- assignee = data.get("assignee", "-")
77
-
78
- # Apply filter
79
- if filter_status and status != filter_status:
80
- continue
81
-
82
- results.append({
83
- "priority": priority,
84
- "id": task_id,
85
- "title": title,
86
- "status": status,
87
- "assignee": assignee,
88
- "dir": d.name,
89
- "children": data.get("children", []),
90
- "parent": data.get("parent"),
91
- })
69
+ results.append(_task_to_dict(t))
92
70
 
93
71
  return results
94
72
 
@@ -126,46 +104,12 @@ def list_tasks_by_assignee(
126
104
  tasks_dir = get_tasks_dir(repo_root)
127
105
  results = []
128
106
 
129
- if not tasks_dir.is_dir():
130
- return results
131
-
132
- for d in tasks_dir.iterdir():
133
- if not d.is_dir() or d.name == "archive":
134
- continue
135
-
136
- task_json = d / FILE_TASK_JSON
137
- if not task_json.is_file():
107
+ for t in iter_active_tasks(tasks_dir):
108
+ if (t.assignee or "-") != assignee:
138
109
  continue
139
-
140
- data = _read_json_file(task_json)
141
- if not data:
142
- continue
143
-
144
- task_assignee = data.get("assignee", "-")
145
-
146
- # Apply assignee filter
147
- if task_assignee != assignee:
110
+ if filter_status and t.status != filter_status:
148
111
  continue
149
-
150
- task_id = data.get("id", "")
151
- title = data.get("title") or data.get("name", "")
152
- priority = data.get("priority", "P2")
153
- status = data.get("status", "planning")
154
-
155
- # Apply status filter
156
- if filter_status and status != filter_status:
157
- continue
158
-
159
- results.append({
160
- "priority": priority,
161
- "id": task_id,
162
- "title": title,
163
- "status": status,
164
- "assignee": task_assignee,
165
- "dir": d.name,
166
- "children": data.get("children", []),
167
- "parent": data.get("parent"),
168
- })
112
+ results.append(_task_to_dict(t))
169
113
 
170
114
  return results
171
115
 
@@ -211,24 +155,9 @@ def get_task_stats(repo_root: Path | None = None) -> dict[str, int]:
211
155
  tasks_dir = get_tasks_dir(repo_root)
212
156
  stats = {"P0": 0, "P1": 0, "P2": 0, "P3": 0, "Total": 0}
213
157
 
214
- if not tasks_dir.is_dir():
215
- return stats
216
-
217
- for d in tasks_dir.iterdir():
218
- if not d.is_dir() or d.name == "archive":
219
- continue
220
-
221
- task_json = d / FILE_TASK_JSON
222
- if not task_json.is_file():
223
- continue
224
-
225
- data = _read_json_file(task_json)
226
- if not data:
227
- continue
228
-
229
- priority = data.get("priority", "P2")
230
- if priority in stats:
231
- stats[priority] += 1
158
+ for t in iter_active_tasks(tasks_dir):
159
+ if t.priority in stats:
160
+ stats[t.priority] += 1
232
161
  stats["Total"] += 1
233
162
 
234
163
  return stats