@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,562 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Session context generation (default + record modes).
4
+
5
+ Provides:
6
+ get_context_json - JSON output for default mode
7
+ get_context_text - Text output for default mode
8
+ get_context_record_json - JSON for record mode
9
+ get_context_text_record - Text for record mode
10
+ output_json - Print JSON
11
+ output_text - Print text
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import json
17
+ from pathlib import Path
18
+
19
+ from .config import get_git_packages
20
+ from .git import run_git
21
+ from .packages_context import get_packages_section
22
+ from .tasks import iter_active_tasks, load_task, get_all_statuses, children_progress
23
+ from .paths import (
24
+ DIR_SCRIPTS,
25
+ DIR_SPEC,
26
+ DIR_TASKS,
27
+ DIR_WORKFLOW,
28
+ DIR_WORKSPACE,
29
+ count_lines,
30
+ get_active_journal_file,
31
+ get_current_task,
32
+ get_developer,
33
+ get_repo_root,
34
+ get_tasks_dir,
35
+ )
36
+
37
+
38
+ # =============================================================================
39
+ # Helpers
40
+ # =============================================================================
41
+
42
+ def _collect_package_git_info(repo_root: Path) -> list[dict]:
43
+ """Collect git status and recent commits for packages with independent git repos.
44
+
45
+ Only packages marked with ``git: true`` in config.yaml are included.
46
+
47
+ Returns:
48
+ List of dicts with keys: name, path, branch, isClean,
49
+ uncommittedChanges, recentCommits.
50
+ Empty list if no git-repo packages are configured.
51
+ """
52
+ git_pkgs = get_git_packages(repo_root)
53
+ if not git_pkgs:
54
+ return []
55
+
56
+ result = []
57
+ for pkg_name, pkg_path in git_pkgs.items():
58
+ pkg_dir = repo_root / pkg_path
59
+ if not (pkg_dir / ".git").exists():
60
+ continue
61
+
62
+ _, branch_out, _ = run_git(["branch", "--show-current"], cwd=pkg_dir)
63
+ branch = branch_out.strip() or "unknown"
64
+
65
+ _, status_out, _ = run_git(["status", "--porcelain"], cwd=pkg_dir)
66
+ changes = len([l for l in status_out.splitlines() if l.strip()])
67
+
68
+ _, log_out, _ = run_git(["log", "--oneline", "-5"], cwd=pkg_dir)
69
+ commits = []
70
+ for line in log_out.splitlines():
71
+ if line.strip():
72
+ parts = line.split(" ", 1)
73
+ if len(parts) >= 2:
74
+ commits.append({"hash": parts[0], "message": parts[1]})
75
+ elif len(parts) == 1:
76
+ commits.append({"hash": parts[0], "message": ""})
77
+
78
+ result.append({
79
+ "name": pkg_name,
80
+ "path": pkg_path,
81
+ "branch": branch,
82
+ "isClean": changes == 0,
83
+ "uncommittedChanges": changes,
84
+ "recentCommits": commits,
85
+ })
86
+
87
+ return result
88
+
89
+
90
+ def _append_package_git_context(lines: list[str], package_git_info: list[dict]) -> None:
91
+ """Append Git status and recent commits for package repositories."""
92
+ for pkg in package_git_info:
93
+ lines.append(f"## GIT STATUS ({pkg['name']}: {pkg['path']})")
94
+ lines.append(f"Branch: {pkg['branch']}")
95
+ if pkg["isClean"]:
96
+ lines.append("Working directory: Clean")
97
+ else:
98
+ lines.append(
99
+ f"Working directory: {pkg['uncommittedChanges']} uncommitted change(s)"
100
+ )
101
+ lines.append("")
102
+ lines.append(f"## RECENT COMMITS ({pkg['name']}: {pkg['path']})")
103
+ if pkg["recentCommits"]:
104
+ for commit in pkg["recentCommits"]:
105
+ lines.append(f"{commit['hash']} {commit['message']}")
106
+ else:
107
+ lines.append("(no commits)")
108
+ lines.append("")
109
+
110
+
111
+ # =============================================================================
112
+ # JSON Output
113
+ # =============================================================================
114
+
115
+ def get_context_json(repo_root: Path | None = None) -> dict:
116
+ """Get context as a dictionary.
117
+
118
+ Args:
119
+ repo_root: Repository root path. Defaults to auto-detected.
120
+
121
+ Returns:
122
+ Context dictionary.
123
+ """
124
+ if repo_root is None:
125
+ repo_root = get_repo_root()
126
+
127
+ developer = get_developer(repo_root)
128
+ tasks_dir = get_tasks_dir(repo_root)
129
+ journal_file = get_active_journal_file(repo_root)
130
+
131
+ journal_lines = 0
132
+ journal_relative = ""
133
+ if journal_file and developer:
134
+ journal_lines = count_lines(journal_file)
135
+ journal_relative = (
136
+ f"{DIR_WORKFLOW}/{DIR_WORKSPACE}/{developer}/{journal_file.name}"
137
+ )
138
+
139
+ # Git info
140
+ _, branch_out, _ = run_git(["branch", "--show-current"], cwd=repo_root)
141
+ branch = branch_out.strip() or "unknown"
142
+
143
+ _, status_out, _ = run_git(["status", "--porcelain"], cwd=repo_root)
144
+ git_status_count = len([line for line in status_out.splitlines() if line.strip()])
145
+ is_clean = git_status_count == 0
146
+
147
+ # Recent commits
148
+ _, log_out, _ = run_git(["log", "--oneline", "-5"], cwd=repo_root)
149
+ commits = []
150
+ for line in log_out.splitlines():
151
+ if line.strip():
152
+ parts = line.split(" ", 1)
153
+ if len(parts) >= 2:
154
+ commits.append({"hash": parts[0], "message": parts[1]})
155
+ elif len(parts) == 1:
156
+ commits.append({"hash": parts[0], "message": ""})
157
+
158
+ # Tasks
159
+ tasks = [
160
+ {
161
+ "dir": t.dir_name,
162
+ "name": t.name,
163
+ "status": t.status,
164
+ "children": list(t.children),
165
+ "parent": t.parent,
166
+ }
167
+ for t in iter_active_tasks(tasks_dir)
168
+ ]
169
+
170
+ # Package git repos (independent sub-repositories)
171
+ pkg_git_info = _collect_package_git_info(repo_root)
172
+
173
+ result = {
174
+ "developer": developer or "",
175
+ "git": {
176
+ "branch": branch,
177
+ "isClean": is_clean,
178
+ "uncommittedChanges": git_status_count,
179
+ "recentCommits": commits,
180
+ },
181
+ "tasks": {
182
+ "active": tasks,
183
+ "directory": f"{DIR_WORKFLOW}/{DIR_TASKS}",
184
+ },
185
+ "journal": {
186
+ "file": journal_relative,
187
+ "lines": journal_lines,
188
+ "nearLimit": journal_lines > 1800,
189
+ },
190
+ }
191
+
192
+ if pkg_git_info:
193
+ result["packageGit"] = pkg_git_info
194
+
195
+ return result
196
+
197
+
198
+ def output_json(repo_root: Path | None = None) -> None:
199
+ """Output context in JSON format.
200
+
201
+ Args:
202
+ repo_root: Repository root path. Defaults to auto-detected.
203
+ """
204
+ context = get_context_json(repo_root)
205
+ print(json.dumps(context, indent=2, ensure_ascii=False))
206
+
207
+
208
+ # =============================================================================
209
+ # Text Output
210
+ # =============================================================================
211
+
212
+ def get_context_text(repo_root: Path | None = None) -> str:
213
+ """Get context as formatted text.
214
+
215
+ Args:
216
+ repo_root: Repository root path. Defaults to auto-detected.
217
+
218
+ Returns:
219
+ Formatted text output.
220
+ """
221
+ if repo_root is None:
222
+ repo_root = get_repo_root()
223
+
224
+ lines = []
225
+ lines.append("========================================")
226
+ lines.append("SESSION CONTEXT")
227
+ lines.append("========================================")
228
+ lines.append("")
229
+
230
+ developer = get_developer(repo_root)
231
+
232
+ # Developer section
233
+ lines.append("## DEVELOPER")
234
+ if not developer:
235
+ lines.append(
236
+ f"ERROR: Not initialized. Run: python3 ./{DIR_WORKFLOW}/{DIR_SCRIPTS}/init_developer.py <name>"
237
+ )
238
+ return "\n".join(lines)
239
+
240
+ lines.append(f"Name: {developer}")
241
+ lines.append("")
242
+
243
+ # Git status
244
+ lines.append("## GIT STATUS")
245
+ _, branch_out, _ = run_git(["branch", "--show-current"], cwd=repo_root)
246
+ branch = branch_out.strip() or "unknown"
247
+ lines.append(f"Branch: {branch}")
248
+
249
+ _, status_out, _ = run_git(["status", "--porcelain"], cwd=repo_root)
250
+ status_lines = [line for line in status_out.splitlines() if line.strip()]
251
+ status_count = len(status_lines)
252
+
253
+ if status_count == 0:
254
+ lines.append("Working directory: Clean")
255
+ else:
256
+ lines.append(f"Working directory: {status_count} uncommitted change(s)")
257
+ lines.append("")
258
+ lines.append("Changes:")
259
+ _, short_out, _ = run_git(["status", "--short"], cwd=repo_root)
260
+ for line in short_out.splitlines()[:10]:
261
+ lines.append(line)
262
+ lines.append("")
263
+
264
+ # Recent commits
265
+ lines.append("## RECENT COMMITS")
266
+ _, log_out, _ = run_git(["log", "--oneline", "-5"], cwd=repo_root)
267
+ if log_out.strip():
268
+ for line in log_out.splitlines():
269
+ lines.append(line)
270
+ else:
271
+ lines.append("(no commits)")
272
+ lines.append("")
273
+
274
+ # Package git repos — independent sub-repositories
275
+ _append_package_git_context(lines, _collect_package_git_info(repo_root))
276
+
277
+ # Current task
278
+ lines.append("## CURRENT TASK")
279
+ current_task = get_current_task(repo_root)
280
+ if current_task:
281
+ current_task_dir = repo_root / current_task
282
+ lines.append(f"Path: {current_task}")
283
+
284
+ ct = load_task(current_task_dir)
285
+ if ct:
286
+ lines.append(f"Name: {ct.name}")
287
+ lines.append(f"Status: {ct.status}")
288
+ lines.append(f"Created: {ct.raw.get('createdAt', 'unknown')}")
289
+ if ct.description:
290
+ lines.append(f"Description: {ct.description}")
291
+
292
+ # Check for prd.md
293
+ prd_file = current_task_dir / "prd.md"
294
+ if prd_file.is_file():
295
+ lines.append("")
296
+ lines.append("[!] This task has prd.md - read it for task details")
297
+ else:
298
+ lines.append("(none)")
299
+ lines.append("")
300
+
301
+ # Active tasks
302
+ lines.append("## ACTIVE TASKS")
303
+ tasks_dir = get_tasks_dir(repo_root)
304
+ task_count = 0
305
+
306
+ # Collect all task data for hierarchy display
307
+ all_tasks = {t.dir_name: t for t in iter_active_tasks(tasks_dir)}
308
+ all_statuses = {name: t.status for name, t in all_tasks.items()}
309
+
310
+ def _print_task_tree(name: str, indent: int = 0) -> None:
311
+ nonlocal task_count
312
+ t = all_tasks[name]
313
+ progress = children_progress(t.children, all_statuses)
314
+ prefix = " " * indent
315
+ lines.append(f"{prefix}- {name}/ ({t.status}){progress} @{t.assignee or '-'}")
316
+ task_count += 1
317
+ for child in t.children:
318
+ if child in all_tasks:
319
+ _print_task_tree(child, indent + 1)
320
+
321
+ for dir_name in sorted(all_tasks.keys()):
322
+ if not all_tasks[dir_name].parent:
323
+ _print_task_tree(dir_name)
324
+
325
+ if task_count == 0:
326
+ lines.append("(no active tasks)")
327
+ lines.append(f"Total: {task_count} active task(s)")
328
+ lines.append("")
329
+
330
+ # My tasks
331
+ lines.append("## MY TASKS (Assigned to me)")
332
+ my_task_count = 0
333
+
334
+ for t in all_tasks.values():
335
+ if t.assignee == developer and t.status != "done":
336
+ progress = children_progress(t.children, all_statuses)
337
+ lines.append(f"- [{t.priority}] {t.title} ({t.status}){progress}")
338
+ my_task_count += 1
339
+
340
+ if my_task_count == 0:
341
+ lines.append("(no tasks assigned to you)")
342
+ lines.append("")
343
+
344
+ # Journal file
345
+ lines.append("## JOURNAL FILE")
346
+ journal_file = get_active_journal_file(repo_root)
347
+ if journal_file:
348
+ journal_lines = count_lines(journal_file)
349
+ relative = f"{DIR_WORKFLOW}/{DIR_WORKSPACE}/{developer}/{journal_file.name}"
350
+ lines.append(f"Active file: {relative}")
351
+ lines.append(f"Line count: {journal_lines} / 2000")
352
+ if journal_lines > 1800:
353
+ lines.append("[!] WARNING: Approaching 2000 line limit!")
354
+ else:
355
+ lines.append("No journal file found")
356
+ lines.append("")
357
+
358
+ # Packages
359
+ packages_text = get_packages_section(repo_root)
360
+ if packages_text:
361
+ lines.append(packages_text)
362
+ lines.append("")
363
+
364
+ # Paths
365
+ lines.append("## PATHS")
366
+ lines.append(f"Workspace: {DIR_WORKFLOW}/{DIR_WORKSPACE}/{developer}/")
367
+ lines.append(f"Tasks: {DIR_WORKFLOW}/{DIR_TASKS}/")
368
+ lines.append(f"Spec: {DIR_WORKFLOW}/{DIR_SPEC}/")
369
+ lines.append("")
370
+
371
+ lines.append("========================================")
372
+
373
+ return "\n".join(lines)
374
+
375
+
376
+ # =============================================================================
377
+ # Record Mode
378
+ # =============================================================================
379
+
380
+ def get_context_record_json(repo_root: Path | None = None) -> dict:
381
+ """Get record-mode context as a dictionary.
382
+
383
+ Focused on: my active tasks, git status, current task.
384
+ """
385
+ if repo_root is None:
386
+ repo_root = get_repo_root()
387
+
388
+ developer = get_developer(repo_root)
389
+ tasks_dir = get_tasks_dir(repo_root)
390
+
391
+ # Git info
392
+ _, branch_out, _ = run_git(["branch", "--show-current"], cwd=repo_root)
393
+ branch = branch_out.strip() or "unknown"
394
+
395
+ _, status_out, _ = run_git(["status", "--porcelain"], cwd=repo_root)
396
+ git_status_count = len([line for line in status_out.splitlines() if line.strip()])
397
+
398
+ _, log_out, _ = run_git(["log", "--oneline", "-5"], cwd=repo_root)
399
+ commits = []
400
+ for line in log_out.splitlines():
401
+ if line.strip():
402
+ parts = line.split(" ", 1)
403
+ if len(parts) >= 2:
404
+ commits.append({"hash": parts[0], "message": parts[1]})
405
+
406
+ # My tasks (single pass — collect statuses and filter by assignee)
407
+ all_tasks_list = list(iter_active_tasks(tasks_dir))
408
+ all_statuses = {t.dir_name: t.status for t in all_tasks_list}
409
+
410
+ my_tasks = []
411
+ for t in all_tasks_list:
412
+ if t.assignee == developer:
413
+ done = sum(
414
+ 1 for c in t.children
415
+ if all_statuses.get(c) in ("completed", "done")
416
+ )
417
+ my_tasks.append({
418
+ "dir": t.dir_name,
419
+ "title": t.title,
420
+ "status": t.status,
421
+ "priority": t.priority,
422
+ "children": list(t.children),
423
+ "childrenDone": done,
424
+ "parent": t.parent,
425
+ "meta": t.meta,
426
+ })
427
+
428
+ # Current task
429
+ current_task_info = None
430
+ current_task = get_current_task(repo_root)
431
+ if current_task:
432
+ ct = load_task(repo_root / current_task)
433
+ if ct:
434
+ current_task_info = {
435
+ "path": current_task,
436
+ "name": ct.name,
437
+ "status": ct.status,
438
+ }
439
+
440
+ # Package git repos
441
+ pkg_git_info = _collect_package_git_info(repo_root)
442
+
443
+ result = {
444
+ "developer": developer or "",
445
+ "git": {
446
+ "branch": branch,
447
+ "isClean": git_status_count == 0,
448
+ "uncommittedChanges": git_status_count,
449
+ "recentCommits": commits,
450
+ },
451
+ "myTasks": my_tasks,
452
+ "currentTask": current_task_info,
453
+ }
454
+
455
+ if pkg_git_info:
456
+ result["packageGit"] = pkg_git_info
457
+
458
+ return result
459
+
460
+
461
+ def get_context_text_record(repo_root: Path | None = None) -> str:
462
+ """Get context as formatted text for record-session mode.
463
+
464
+ Focused output: MY ACTIVE TASKS first (with [!!!] emphasis),
465
+ then GIT STATUS, RECENT COMMITS, CURRENT TASK.
466
+ """
467
+ if repo_root is None:
468
+ repo_root = get_repo_root()
469
+
470
+ lines: list[str] = []
471
+ lines.append("========================================")
472
+ lines.append("SESSION CONTEXT (RECORD MODE)")
473
+ lines.append("========================================")
474
+ lines.append("")
475
+
476
+ developer = get_developer(repo_root)
477
+ if not developer:
478
+ lines.append(
479
+ f"ERROR: Not initialized. Run: python3 ./{DIR_WORKFLOW}/{DIR_SCRIPTS}/init_developer.py <name>"
480
+ )
481
+ return "\n".join(lines)
482
+
483
+ # MY ACTIVE TASKS — first and prominent
484
+ lines.append(f"## [!!!] MY ACTIVE TASKS (Assigned to {developer})")
485
+ lines.append("[!] Review whether any should be archived before recording this session.")
486
+ lines.append("")
487
+
488
+ tasks_dir = get_tasks_dir(repo_root)
489
+ my_task_count = 0
490
+
491
+ # Single pass — collect all tasks and filter by assignee
492
+ all_statuses = get_all_statuses(tasks_dir)
493
+
494
+ for t in iter_active_tasks(tasks_dir):
495
+ if t.assignee == developer:
496
+ progress = children_progress(t.children, all_statuses)
497
+ lines.append(f"- [{t.priority}] {t.title} ({t.status}){progress} — {t.dir_name}")
498
+ my_task_count += 1
499
+
500
+ if my_task_count == 0:
501
+ lines.append("(no active tasks assigned to you)")
502
+ lines.append("")
503
+
504
+ # GIT STATUS
505
+ lines.append("## GIT STATUS")
506
+ _, branch_out, _ = run_git(["branch", "--show-current"], cwd=repo_root)
507
+ branch = branch_out.strip() or "unknown"
508
+ lines.append(f"Branch: {branch}")
509
+
510
+ _, status_out, _ = run_git(["status", "--porcelain"], cwd=repo_root)
511
+ status_lines = [line for line in status_out.splitlines() if line.strip()]
512
+ status_count = len(status_lines)
513
+
514
+ if status_count == 0:
515
+ lines.append("Working directory: Clean")
516
+ else:
517
+ lines.append(f"Working directory: {status_count} uncommitted change(s)")
518
+ lines.append("")
519
+ lines.append("Changes:")
520
+ _, short_out, _ = run_git(["status", "--short"], cwd=repo_root)
521
+ for line in short_out.splitlines()[:10]:
522
+ lines.append(line)
523
+ lines.append("")
524
+
525
+ # RECENT COMMITS
526
+ lines.append("## RECENT COMMITS")
527
+ _, log_out, _ = run_git(["log", "--oneline", "-5"], cwd=repo_root)
528
+ if log_out.strip():
529
+ for line in log_out.splitlines():
530
+ lines.append(line)
531
+ else:
532
+ lines.append("(no commits)")
533
+ lines.append("")
534
+
535
+ # Package git repos — independent sub-repositories
536
+ _append_package_git_context(lines, _collect_package_git_info(repo_root))
537
+
538
+ # CURRENT TASK
539
+ lines.append("## CURRENT TASK")
540
+ current_task = get_current_task(repo_root)
541
+ if current_task:
542
+ lines.append(f"Path: {current_task}")
543
+ ct = load_task(repo_root / current_task)
544
+ if ct:
545
+ lines.append(f"Name: {ct.name}")
546
+ lines.append(f"Status: {ct.status}")
547
+ else:
548
+ lines.append("(none)")
549
+ lines.append("")
550
+
551
+ lines.append("========================================")
552
+
553
+ return "\n".join(lines)
554
+
555
+
556
+ def output_text(repo_root: Path | None = None) -> None:
557
+ """Output context in text format.
558
+
559
+ Args:
560
+ repo_root: Repository root path. Defaults to auto-detected.
561
+ """
562
+ print(get_context_text(repo_root))