@fifine/aim-studio 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (289) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +159 -0
  3. package/bin/aim.js +3 -0
  4. package/dist/cli/index.d.ts +3 -0
  5. package/dist/cli/index.d.ts.map +1 -0
  6. package/dist/cli/index.js +89 -0
  7. package/dist/cli/index.js.map +1 -0
  8. package/dist/commands/init.d.ts +13 -0
  9. package/dist/commands/init.d.ts.map +1 -0
  10. package/dist/commands/init.js +513 -0
  11. package/dist/commands/init.js.map +1 -0
  12. package/dist/commands/update.d.ts +27 -0
  13. package/dist/commands/update.d.ts.map +1 -0
  14. package/dist/commands/update.js +1275 -0
  15. package/dist/commands/update.js.map +1 -0
  16. package/dist/configurators/claude.d.ts +32 -0
  17. package/dist/configurators/claude.d.ts.map +1 -0
  18. package/dist/configurators/claude.js +98 -0
  19. package/dist/configurators/claude.js.map +1 -0
  20. package/dist/configurators/index.d.ts +51 -0
  21. package/dist/configurators/index.d.ts.map +1 -0
  22. package/dist/configurators/index.js +113 -0
  23. package/dist/configurators/index.js.map +1 -0
  24. package/dist/configurators/shared.d.ts +12 -0
  25. package/dist/configurators/shared.d.ts.map +1 -0
  26. package/dist/configurators/shared.js +21 -0
  27. package/dist/configurators/shared.js.map +1 -0
  28. package/dist/configurators/workflow.d.ts +28 -0
  29. package/dist/configurators/workflow.d.ts.map +1 -0
  30. package/dist/configurators/workflow.js +147 -0
  31. package/dist/configurators/workflow.js.map +1 -0
  32. package/dist/constants/paths.d.ts +68 -0
  33. package/dist/constants/paths.d.ts.map +1 -0
  34. package/dist/constants/paths.js +77 -0
  35. package/dist/constants/paths.js.map +1 -0
  36. package/dist/constants/version.d.ts +9 -0
  37. package/dist/constants/version.d.ts.map +1 -0
  38. package/dist/constants/version.js +15 -0
  39. package/dist/constants/version.js.map +1 -0
  40. package/dist/index.d.ts +9 -0
  41. package/dist/index.d.ts.map +1 -0
  42. package/dist/index.js +9 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/migrations/index.d.ts +54 -0
  45. package/dist/migrations/index.d.ts.map +1 -0
  46. package/dist/migrations/index.js +160 -0
  47. package/dist/migrations/index.js.map +1 -0
  48. package/dist/migrations/manifests/0.0.1.json +9 -0
  49. package/dist/migrations/manifests/0.1.9.json +30 -0
  50. package/dist/migrations/manifests/0.2.0.json +49 -0
  51. package/dist/migrations/manifests/0.2.12.json +9 -0
  52. package/dist/migrations/manifests/0.2.13.json +9 -0
  53. package/dist/migrations/manifests/0.2.14.json +175 -0
  54. package/dist/migrations/manifests/0.2.15.json +33 -0
  55. package/dist/migrations/manifests/0.3.0-beta.0.json +278 -0
  56. package/dist/migrations/manifests/0.3.0-beta.1.json +9 -0
  57. package/dist/migrations/manifests/0.3.0-beta.10.json +9 -0
  58. package/dist/migrations/manifests/0.3.0-beta.11.json +9 -0
  59. package/dist/migrations/manifests/0.3.0-beta.12.json +9 -0
  60. package/dist/migrations/manifests/0.3.0-beta.13.json +9 -0
  61. package/dist/migrations/manifests/0.3.0-beta.14.json +9 -0
  62. package/dist/migrations/manifests/0.3.0-beta.15.json +9 -0
  63. package/dist/migrations/manifests/0.3.0-beta.16.json +9 -0
  64. package/dist/migrations/manifests/0.3.0-beta.2.json +9 -0
  65. package/dist/migrations/manifests/0.3.0-beta.3.json +9 -0
  66. package/dist/migrations/manifests/0.3.0-beta.4.json +9 -0
  67. package/dist/migrations/manifests/0.3.0-beta.5.json +9 -0
  68. package/dist/migrations/manifests/0.3.0-beta.6.json +9 -0
  69. package/dist/migrations/manifests/0.3.0-beta.7.json +11 -0
  70. package/dist/migrations/manifests/0.3.0-beta.8.json +9 -0
  71. package/dist/migrations/manifests/0.3.0-beta.9.json +9 -0
  72. package/dist/migrations/manifests/0.3.0-rc.0.json +9 -0
  73. package/dist/migrations/manifests/0.3.0-rc.1.json +9 -0
  74. package/dist/migrations/manifests/0.3.0-rc.2.json +9 -0
  75. package/dist/templates/CLAUDE.md +71 -0
  76. package/dist/templates/aim/gitignore.txt +29 -0
  77. package/dist/templates/aim/index.d.ts +49 -0
  78. package/dist/templates/aim/index.d.ts.map +1 -0
  79. package/dist/templates/aim/index.js +92 -0
  80. package/dist/templates/aim/index.js.map +1 -0
  81. package/dist/templates/aim/scripts/__init__.py +5 -0
  82. package/dist/templates/aim/scripts/add_session.py +392 -0
  83. package/dist/templates/aim/scripts/common/__init__.py +80 -0
  84. package/dist/templates/aim/scripts/common/cli_adapter.py +435 -0
  85. package/dist/templates/aim/scripts/common/developer.py +190 -0
  86. package/dist/templates/aim/scripts/common/git_context.py +383 -0
  87. package/dist/templates/aim/scripts/common/paths.py +347 -0
  88. package/dist/templates/aim/scripts/common/phase.py +253 -0
  89. package/dist/templates/aim/scripts/common/registry.py +366 -0
  90. package/dist/templates/aim/scripts/common/task_queue.py +255 -0
  91. package/dist/templates/aim/scripts/common/task_utils.py +178 -0
  92. package/dist/templates/aim/scripts/common/worktree.py +219 -0
  93. package/dist/templates/aim/scripts/create_bootstrap.py +290 -0
  94. package/dist/templates/aim/scripts/get_context.py +16 -0
  95. package/dist/templates/aim/scripts/get_developer.py +26 -0
  96. package/dist/templates/aim/scripts/init_developer.py +51 -0
  97. package/dist/templates/aim/scripts/multi_agent/__init__.py +5 -0
  98. package/dist/templates/aim/scripts/multi_agent/cleanup.py +403 -0
  99. package/dist/templates/aim/scripts/multi_agent/create_pr.py +329 -0
  100. package/dist/templates/aim/scripts/multi_agent/plan.py +233 -0
  101. package/dist/templates/aim/scripts/multi_agent/start.py +461 -0
  102. package/dist/templates/aim/scripts/multi_agent/status.py +817 -0
  103. package/dist/templates/aim/scripts/task.py +1068 -0
  104. package/dist/templates/aim/scripts-shell-archive/add-session.sh +384 -0
  105. package/dist/templates/aim/scripts-shell-archive/common/developer.sh +129 -0
  106. package/dist/templates/aim/scripts-shell-archive/common/git-context.sh +263 -0
  107. package/dist/templates/aim/scripts-shell-archive/common/paths.sh +208 -0
  108. package/dist/templates/aim/scripts-shell-archive/common/phase.sh +150 -0
  109. package/dist/templates/aim/scripts-shell-archive/common/registry.sh +247 -0
  110. package/dist/templates/aim/scripts-shell-archive/common/task-queue.sh +142 -0
  111. package/dist/templates/aim/scripts-shell-archive/common/task-utils.sh +151 -0
  112. package/dist/templates/aim/scripts-shell-archive/common/worktree.sh +128 -0
  113. package/dist/templates/aim/scripts-shell-archive/create-bootstrap.sh +299 -0
  114. package/dist/templates/aim/scripts-shell-archive/get-context.sh +7 -0
  115. package/dist/templates/aim/scripts-shell-archive/get-developer.sh +15 -0
  116. package/dist/templates/aim/scripts-shell-archive/init-developer.sh +34 -0
  117. package/dist/templates/aim/scripts-shell-archive/multi-agent/cleanup.sh +396 -0
  118. package/dist/templates/aim/scripts-shell-archive/multi-agent/create-pr.sh +241 -0
  119. package/dist/templates/aim/scripts-shell-archive/multi-agent/plan.sh +207 -0
  120. package/dist/templates/aim/scripts-shell-archive/multi-agent/start.sh +317 -0
  121. package/dist/templates/aim/scripts-shell-archive/multi-agent/status.sh +828 -0
  122. package/dist/templates/aim/scripts-shell-archive/task.sh +1204 -0
  123. package/dist/templates/aim/tasks/.gitkeep +0 -0
  124. package/dist/templates/aim/workflow.md +258 -0
  125. package/dist/templates/aim/worktree.yaml +47 -0
  126. package/dist/templates/claude/agents/check.md +122 -0
  127. package/dist/templates/claude/agents/debug.md +106 -0
  128. package/dist/templates/claude/agents/dispatch.md +230 -0
  129. package/dist/templates/claude/agents/implement.md +96 -0
  130. package/dist/templates/claude/agents/plan.md +396 -0
  131. package/dist/templates/claude/agents/research.md +120 -0
  132. package/dist/templates/claude/agents/story.md +53 -0
  133. package/dist/templates/claude/commands/aim/before-backend-dev.md +13 -0
  134. package/dist/templates/claude/commands/aim/before-frontend-dev.md +13 -0
  135. package/dist/templates/claude/commands/aim/break-loop.md +153 -0
  136. package/dist/templates/claude/commands/aim/check-backend.md +13 -0
  137. package/dist/templates/claude/commands/aim/check-cross-layer.md +153 -0
  138. package/dist/templates/claude/commands/aim/check-frontend.md +13 -0
  139. package/dist/templates/claude/commands/aim/check-story.md +59 -0
  140. package/dist/templates/claude/commands/aim/create-command.md +154 -0
  141. package/dist/templates/claude/commands/aim/export.md +187 -0
  142. package/dist/templates/claude/commands/aim/finish-work.md +104 -0
  143. package/dist/templates/claude/commands/aim/integrate-skill.md +219 -0
  144. package/dist/templates/claude/commands/aim/onboard.md +358 -0
  145. package/dist/templates/claude/commands/aim/parallel.md +217 -0
  146. package/dist/templates/claude/commands/aim/portrait.md +170 -0
  147. package/dist/templates/claude/commands/aim/record-session.md +92 -0
  148. package/dist/templates/claude/commands/aim/start.md +112 -0
  149. package/dist/templates/claude/commands/aim/story.md +140 -0
  150. package/dist/templates/claude/commands/aim/update-spec.md +285 -0
  151. package/dist/templates/claude/commands/aim/visualize.md +182 -0
  152. package/dist/templates/claude/commands/trellis/before-backend-dev.md +13 -0
  153. package/dist/templates/claude/commands/trellis/before-frontend-dev.md +13 -0
  154. package/dist/templates/claude/commands/trellis/break-loop.md +125 -0
  155. package/dist/templates/claude/commands/trellis/check-backend.md +13 -0
  156. package/dist/templates/claude/commands/trellis/check-cross-layer.md +153 -0
  157. package/dist/templates/claude/commands/trellis/check-frontend.md +13 -0
  158. package/dist/templates/claude/commands/trellis/create-command.md +154 -0
  159. package/dist/templates/claude/commands/trellis/finish-work.md +129 -0
  160. package/dist/templates/claude/commands/trellis/integrate-skill.md +219 -0
  161. package/dist/templates/claude/commands/trellis/onboard.md +358 -0
  162. package/dist/templates/claude/commands/trellis/parallel.md +193 -0
  163. package/dist/templates/claude/commands/trellis/record-session.md +62 -0
  164. package/dist/templates/claude/commands/trellis/start.md +280 -0
  165. package/dist/templates/claude/commands/trellis/update-spec.md +285 -0
  166. package/dist/templates/claude/hooks/inject-subagent-context.py +772 -0
  167. package/dist/templates/claude/hooks/ralph-loop.py +388 -0
  168. package/dist/templates/claude/hooks/session-start.py +142 -0
  169. package/dist/templates/claude/index.d.ts +54 -0
  170. package/dist/templates/claude/index.d.ts.map +1 -0
  171. package/dist/templates/claude/index.js +85 -0
  172. package/dist/templates/claude/index.js.map +1 -0
  173. package/dist/templates/claude/settings.json +41 -0
  174. package/dist/templates/extract.d.ts +68 -0
  175. package/dist/templates/extract.d.ts.map +1 -0
  176. package/dist/templates/extract.js +128 -0
  177. package/dist/templates/extract.js.map +1 -0
  178. package/dist/templates/markdown/agents.md +25 -0
  179. package/dist/templates/markdown/gitignore.txt +12 -0
  180. package/dist/templates/markdown/index.d.ts +32 -0
  181. package/dist/templates/markdown/index.d.ts.map +1 -0
  182. package/dist/templates/markdown/index.js +58 -0
  183. package/dist/templates/markdown/index.js.map +1 -0
  184. package/dist/templates/markdown/spec/backend/database-guidelines.md.txt +51 -0
  185. package/dist/templates/markdown/spec/backend/directory-structure.md.txt +54 -0
  186. package/dist/templates/markdown/spec/backend/error-handling.md.txt +51 -0
  187. package/dist/templates/markdown/spec/backend/index.md +40 -0
  188. package/dist/templates/markdown/spec/backend/index.md.txt +38 -0
  189. package/dist/templates/markdown/spec/backend/logging-guidelines.md.txt +51 -0
  190. package/dist/templates/markdown/spec/backend/quality-guidelines.md.txt +51 -0
  191. package/dist/templates/markdown/spec/backend/script-conventions.md +467 -0
  192. package/dist/templates/markdown/spec/frontend/component-guidelines.md.txt +59 -0
  193. package/dist/templates/markdown/spec/frontend/directory-structure.md.txt +54 -0
  194. package/dist/templates/markdown/spec/frontend/hook-guidelines.md.txt +51 -0
  195. package/dist/templates/markdown/spec/frontend/index.md.txt +39 -0
  196. package/dist/templates/markdown/spec/frontend/quality-guidelines.md.txt +51 -0
  197. package/dist/templates/markdown/spec/frontend/state-management.md.txt +51 -0
  198. package/dist/templates/markdown/spec/frontend/type-safety.md.txt +51 -0
  199. package/dist/templates/markdown/spec/guides/code-reuse-thinking-guide.md +118 -0
  200. package/dist/templates/markdown/spec/guides/code-reuse-thinking-guide.md.txt +92 -0
  201. package/dist/templates/markdown/spec/guides/cross-layer-thinking-guide.md.txt +94 -0
  202. package/dist/templates/markdown/spec/guides/cross-platform-thinking-guide.md +394 -0
  203. package/dist/templates/markdown/spec/guides/cross-platform-thinking-guide.md.txt +319 -0
  204. package/dist/templates/markdown/spec/guides/index.md.txt +89 -0
  205. package/dist/templates/markdown/spec/story/character.md.txt +95 -0
  206. package/dist/templates/markdown/spec/story/index.md.txt +31 -0
  207. package/dist/templates/markdown/spec/story/script.md.txt +313 -0
  208. package/dist/templates/markdown/spec/story/world.md.txt +92 -0
  209. package/dist/templates/markdown/workspace-index.md +123 -0
  210. package/dist/templates/markdown/worktree.yaml.txt +58 -0
  211. package/dist/templates/trellis/gitignore.txt +29 -0
  212. package/dist/templates/trellis/index.d.ts +49 -0
  213. package/dist/templates/trellis/index.d.ts.map +1 -0
  214. package/dist/templates/trellis/index.js +92 -0
  215. package/dist/templates/trellis/index.js.map +1 -0
  216. package/dist/templates/trellis/scripts/__init__.py +5 -0
  217. package/dist/templates/trellis/scripts/add_session.py +392 -0
  218. package/dist/templates/trellis/scripts/common/__init__.py +80 -0
  219. package/dist/templates/trellis/scripts/common/cli_adapter.py +435 -0
  220. package/dist/templates/trellis/scripts/common/developer.py +190 -0
  221. package/dist/templates/trellis/scripts/common/git_context.py +383 -0
  222. package/dist/templates/trellis/scripts/common/paths.py +347 -0
  223. package/dist/templates/trellis/scripts/common/phase.py +253 -0
  224. package/dist/templates/trellis/scripts/common/registry.py +366 -0
  225. package/dist/templates/trellis/scripts/common/task_queue.py +255 -0
  226. package/dist/templates/trellis/scripts/common/task_utils.py +178 -0
  227. package/dist/templates/trellis/scripts/common/worktree.py +219 -0
  228. package/dist/templates/trellis/scripts/create_bootstrap.py +290 -0
  229. package/dist/templates/trellis/scripts/get_context.py +16 -0
  230. package/dist/templates/trellis/scripts/get_developer.py +26 -0
  231. package/dist/templates/trellis/scripts/init_developer.py +51 -0
  232. package/dist/templates/trellis/scripts/multi_agent/__init__.py +5 -0
  233. package/dist/templates/trellis/scripts/multi_agent/cleanup.py +403 -0
  234. package/dist/templates/trellis/scripts/multi_agent/create_pr.py +329 -0
  235. package/dist/templates/trellis/scripts/multi_agent/plan.py +233 -0
  236. package/dist/templates/trellis/scripts/multi_agent/start.py +461 -0
  237. package/dist/templates/trellis/scripts/multi_agent/status.py +817 -0
  238. package/dist/templates/trellis/scripts/task.py +1056 -0
  239. package/dist/templates/trellis/scripts-shell-archive/add-session.sh +384 -0
  240. package/dist/templates/trellis/scripts-shell-archive/common/developer.sh +129 -0
  241. package/dist/templates/trellis/scripts-shell-archive/common/git-context.sh +263 -0
  242. package/dist/templates/trellis/scripts-shell-archive/common/paths.sh +208 -0
  243. package/dist/templates/trellis/scripts-shell-archive/common/phase.sh +150 -0
  244. package/dist/templates/trellis/scripts-shell-archive/common/registry.sh +247 -0
  245. package/dist/templates/trellis/scripts-shell-archive/common/task-queue.sh +142 -0
  246. package/dist/templates/trellis/scripts-shell-archive/common/task-utils.sh +151 -0
  247. package/dist/templates/trellis/scripts-shell-archive/common/worktree.sh +128 -0
  248. package/dist/templates/trellis/scripts-shell-archive/create-bootstrap.sh +299 -0
  249. package/dist/templates/trellis/scripts-shell-archive/get-context.sh +7 -0
  250. package/dist/templates/trellis/scripts-shell-archive/get-developer.sh +15 -0
  251. package/dist/templates/trellis/scripts-shell-archive/init-developer.sh +34 -0
  252. package/dist/templates/trellis/scripts-shell-archive/multi-agent/cleanup.sh +396 -0
  253. package/dist/templates/trellis/scripts-shell-archive/multi-agent/create-pr.sh +241 -0
  254. package/dist/templates/trellis/scripts-shell-archive/multi-agent/plan.sh +207 -0
  255. package/dist/templates/trellis/scripts-shell-archive/multi-agent/start.sh +317 -0
  256. package/dist/templates/trellis/scripts-shell-archive/multi-agent/status.sh +828 -0
  257. package/dist/templates/trellis/scripts-shell-archive/task.sh +1204 -0
  258. package/dist/templates/trellis/tasks/.gitkeep +0 -0
  259. package/dist/templates/trellis/workflow.md +416 -0
  260. package/dist/templates/trellis/worktree.yaml +47 -0
  261. package/dist/types/ai-tools.d.ts +48 -0
  262. package/dist/types/ai-tools.d.ts.map +1 -0
  263. package/dist/types/ai-tools.js +32 -0
  264. package/dist/types/ai-tools.js.map +1 -0
  265. package/dist/types/migration.d.ts +86 -0
  266. package/dist/types/migration.d.ts.map +1 -0
  267. package/dist/types/migration.js +8 -0
  268. package/dist/types/migration.js.map +1 -0
  269. package/dist/utils/compare-versions.d.ts +12 -0
  270. package/dist/utils/compare-versions.d.ts.map +1 -0
  271. package/dist/utils/compare-versions.js +76 -0
  272. package/dist/utils/compare-versions.js.map +1 -0
  273. package/dist/utils/file-writer.d.ts +23 -0
  274. package/dist/utils/file-writer.d.ts.map +1 -0
  275. package/dist/utils/file-writer.js +140 -0
  276. package/dist/utils/file-writer.js.map +1 -0
  277. package/dist/utils/project-detector.d.ts +16 -0
  278. package/dist/utils/project-detector.d.ts.map +1 -0
  279. package/dist/utils/project-detector.js +188 -0
  280. package/dist/utils/project-detector.js.map +1 -0
  281. package/dist/utils/template-fetcher.d.ts +51 -0
  282. package/dist/utils/template-fetcher.d.ts.map +1 -0
  283. package/dist/utils/template-fetcher.js +174 -0
  284. package/dist/utils/template-fetcher.js.map +1 -0
  285. package/dist/utils/template-hash.d.ts +78 -0
  286. package/dist/utils/template-hash.d.ts.map +1 -0
  287. package/dist/utils/template-hash.js +239 -0
  288. package/dist/utils/template-hash.js.map +1 -0
  289. package/package.json +87 -0
@@ -0,0 +1,347 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Common path utilities for Trellis workflow.
4
+
5
+ Provides:
6
+ get_repo_root - Get repository root directory
7
+ get_developer - Get developer name
8
+ get_workspace_dir - Get developer workspace directory
9
+ get_tasks_dir - Get tasks directory
10
+ get_active_journal_file - Get current journal file
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import re
16
+ from datetime import datetime
17
+ from pathlib import Path
18
+
19
+
20
+ # =============================================================================
21
+ # Path Constants (change here to rename directories)
22
+ # =============================================================================
23
+
24
+ # Directory names
25
+ DIR_WORKFLOW = ".trellis"
26
+ DIR_WORKSPACE = "workspace"
27
+ DIR_TASKS = "tasks"
28
+ DIR_ARCHIVE = "archive"
29
+ DIR_SPEC = "spec"
30
+ DIR_SCRIPTS = "scripts"
31
+
32
+ # File names
33
+ FILE_DEVELOPER = ".developer"
34
+ FILE_CURRENT_TASK = ".current-task"
35
+ FILE_TASK_JSON = "task.json"
36
+ FILE_JOURNAL_PREFIX = "journal-"
37
+
38
+
39
+ # =============================================================================
40
+ # Repository Root
41
+ # =============================================================================
42
+
43
+ def get_repo_root(start_path: Path | None = None) -> Path:
44
+ """Find the nearest directory containing .trellis/ folder.
45
+
46
+ This handles nested git repos correctly (e.g., test project inside another repo).
47
+
48
+ Args:
49
+ start_path: Starting directory to search from. Defaults to current directory.
50
+
51
+ Returns:
52
+ Path to repository root, or current directory if no .trellis/ found.
53
+ """
54
+ current = (start_path or Path.cwd()).resolve()
55
+
56
+ while current != current.parent:
57
+ if (current / DIR_WORKFLOW).is_dir():
58
+ return current
59
+ current = current.parent
60
+
61
+ # Fallback to current directory if no .trellis/ found
62
+ return Path.cwd().resolve()
63
+
64
+
65
+ # =============================================================================
66
+ # Developer
67
+ # =============================================================================
68
+
69
+ def get_developer(repo_root: Path | None = None) -> str | None:
70
+ """Get developer name from .developer file.
71
+
72
+ Args:
73
+ repo_root: Repository root path. Defaults to auto-detected.
74
+
75
+ Returns:
76
+ Developer name or None if not initialized.
77
+ """
78
+ if repo_root is None:
79
+ repo_root = get_repo_root()
80
+
81
+ dev_file = repo_root / DIR_WORKFLOW / FILE_DEVELOPER
82
+
83
+ if not dev_file.is_file():
84
+ return None
85
+
86
+ try:
87
+ content = dev_file.read_text(encoding="utf-8")
88
+ for line in content.splitlines():
89
+ if line.startswith("name="):
90
+ return line.split("=", 1)[1].strip()
91
+ except (OSError, IOError):
92
+ pass
93
+
94
+ return None
95
+
96
+
97
+ def check_developer(repo_root: Path | None = None) -> bool:
98
+ """Check if developer is initialized.
99
+
100
+ Args:
101
+ repo_root: Repository root path. Defaults to auto-detected.
102
+
103
+ Returns:
104
+ True if developer is initialized.
105
+ """
106
+ return get_developer(repo_root) is not None
107
+
108
+
109
+ # =============================================================================
110
+ # Tasks Directory
111
+ # =============================================================================
112
+
113
+ def get_tasks_dir(repo_root: Path | None = None) -> Path:
114
+ """Get tasks directory path.
115
+
116
+ Args:
117
+ repo_root: Repository root path. Defaults to auto-detected.
118
+
119
+ Returns:
120
+ Path to tasks directory.
121
+ """
122
+ if repo_root is None:
123
+ repo_root = get_repo_root()
124
+ return repo_root / DIR_WORKFLOW / DIR_TASKS
125
+
126
+
127
+ # =============================================================================
128
+ # Workspace Directory
129
+ # =============================================================================
130
+
131
+ def get_workspace_dir(repo_root: Path | None = None) -> Path | None:
132
+ """Get developer workspace directory.
133
+
134
+ Args:
135
+ repo_root: Repository root path. Defaults to auto-detected.
136
+
137
+ Returns:
138
+ Path to workspace directory or None if developer not set.
139
+ """
140
+ if repo_root is None:
141
+ repo_root = get_repo_root()
142
+
143
+ developer = get_developer(repo_root)
144
+ if developer:
145
+ return repo_root / DIR_WORKFLOW / DIR_WORKSPACE / developer
146
+ return None
147
+
148
+
149
+ # =============================================================================
150
+ # Journal File
151
+ # =============================================================================
152
+
153
+ def get_active_journal_file(repo_root: Path | None = None) -> Path | None:
154
+ """Get the current active journal file.
155
+
156
+ Args:
157
+ repo_root: Repository root path. Defaults to auto-detected.
158
+
159
+ Returns:
160
+ Path to active journal file or None if not found.
161
+ """
162
+ if repo_root is None:
163
+ repo_root = get_repo_root()
164
+
165
+ workspace_dir = get_workspace_dir(repo_root)
166
+ if workspace_dir is None or not workspace_dir.is_dir():
167
+ return None
168
+
169
+ latest: Path | None = None
170
+ highest = 0
171
+
172
+ for f in workspace_dir.glob(f"{FILE_JOURNAL_PREFIX}*.md"):
173
+ if not f.is_file():
174
+ continue
175
+
176
+ # Extract number from filename
177
+ name = f.stem # e.g., "journal-1"
178
+ match = re.search(r"(\d+)$", name)
179
+ if match:
180
+ num = int(match.group(1))
181
+ if num > highest:
182
+ highest = num
183
+ latest = f
184
+
185
+ return latest
186
+
187
+
188
+ def count_lines(file_path: Path) -> int:
189
+ """Count lines in a file.
190
+
191
+ Args:
192
+ file_path: Path to file.
193
+
194
+ Returns:
195
+ Number of lines, or 0 if file doesn't exist.
196
+ """
197
+ if not file_path.is_file():
198
+ return 0
199
+
200
+ try:
201
+ return len(file_path.read_text(encoding="utf-8").splitlines())
202
+ except (OSError, IOError):
203
+ return 0
204
+
205
+
206
+ # =============================================================================
207
+ # Current Task Management
208
+ # =============================================================================
209
+
210
+ def _get_current_task_file(repo_root: Path | None = None) -> Path:
211
+ """Get .current-task file path.
212
+
213
+ Args:
214
+ repo_root: Repository root path. Defaults to auto-detected.
215
+
216
+ Returns:
217
+ Path to .current-task file.
218
+ """
219
+ if repo_root is None:
220
+ repo_root = get_repo_root()
221
+ return repo_root / DIR_WORKFLOW / FILE_CURRENT_TASK
222
+
223
+
224
+ def get_current_task(repo_root: Path | None = None) -> str | None:
225
+ """Get current task directory path (relative to repo_root).
226
+
227
+ Args:
228
+ repo_root: Repository root path. Defaults to auto-detected.
229
+
230
+ Returns:
231
+ Relative path to current task directory or None.
232
+ """
233
+ current_file = _get_current_task_file(repo_root)
234
+
235
+ if not current_file.is_file():
236
+ return None
237
+
238
+ try:
239
+ return current_file.read_text(encoding="utf-8").strip()
240
+ except (OSError, IOError):
241
+ return None
242
+
243
+
244
+ def get_current_task_abs(repo_root: Path | None = None) -> Path | None:
245
+ """Get current task directory absolute path.
246
+
247
+ Args:
248
+ repo_root: Repository root path. Defaults to auto-detected.
249
+
250
+ Returns:
251
+ Absolute path to current task directory or None.
252
+ """
253
+ if repo_root is None:
254
+ repo_root = get_repo_root()
255
+
256
+ relative = get_current_task(repo_root)
257
+ if relative:
258
+ return repo_root / relative
259
+ return None
260
+
261
+
262
+ def set_current_task(task_path: str, repo_root: Path | None = None) -> bool:
263
+ """Set current task.
264
+
265
+ Args:
266
+ task_path: Task directory path (relative to repo_root).
267
+ repo_root: Repository root path. Defaults to auto-detected.
268
+
269
+ Returns:
270
+ True on success, False on error.
271
+ """
272
+ if repo_root is None:
273
+ repo_root = get_repo_root()
274
+
275
+ if not task_path:
276
+ return False
277
+
278
+ # Verify task directory exists
279
+ full_path = repo_root / task_path
280
+ if not full_path.is_dir():
281
+ return False
282
+
283
+ current_file = _get_current_task_file(repo_root)
284
+
285
+ try:
286
+ current_file.write_text(task_path, encoding="utf-8")
287
+ return True
288
+ except (OSError, IOError):
289
+ return False
290
+
291
+
292
+ def clear_current_task(repo_root: Path | None = None) -> bool:
293
+ """Clear current task.
294
+
295
+ Args:
296
+ repo_root: Repository root path. Defaults to auto-detected.
297
+
298
+ Returns:
299
+ True on success.
300
+ """
301
+ current_file = _get_current_task_file(repo_root)
302
+
303
+ try:
304
+ if current_file.is_file():
305
+ current_file.unlink()
306
+ return True
307
+ except (OSError, IOError):
308
+ return False
309
+
310
+
311
+ def has_current_task(repo_root: Path | None = None) -> bool:
312
+ """Check if has current task.
313
+
314
+ Args:
315
+ repo_root: Repository root path. Defaults to auto-detected.
316
+
317
+ Returns:
318
+ True if current task is set.
319
+ """
320
+ return get_current_task(repo_root) is not None
321
+
322
+
323
+ # =============================================================================
324
+ # Task ID Generation
325
+ # =============================================================================
326
+
327
+ def generate_task_date_prefix() -> str:
328
+ """Generate task ID based on date (MM-DD format).
329
+
330
+ Returns:
331
+ Date prefix string (e.g., "01-21").
332
+ """
333
+ return datetime.now().strftime("%m-%d")
334
+
335
+
336
+ # =============================================================================
337
+ # Main Entry (for testing)
338
+ # =============================================================================
339
+
340
+ if __name__ == "__main__":
341
+ repo = get_repo_root()
342
+ print(f"Repository root: {repo}")
343
+ print(f"Developer: {get_developer(repo)}")
344
+ print(f"Tasks dir: {get_tasks_dir(repo)}")
345
+ print(f"Workspace dir: {get_workspace_dir(repo)}")
346
+ print(f"Journal file: {get_active_journal_file(repo)}")
347
+ print(f"Current task: {get_current_task(repo)}")
@@ -0,0 +1,253 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Phase Management Utilities.
4
+
5
+ Centralized phase tracking for multi-agent pipeline.
6
+
7
+ Provides:
8
+ get_current_phase - Returns current phase number
9
+ get_total_phases - Returns total phase count
10
+ get_phase_action - Returns action name for phase
11
+ get_phase_info - Returns "N/M (action)" format
12
+ set_phase - Sets current_phase
13
+ advance_phase - Advances to next phase
14
+ get_phase_for_action - Returns phase number for action
15
+ map_subagent_to_action - Map subagent type to action name
16
+ is_phase_completed - Check if phase is completed
17
+ is_current_action - Check if at specific action
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import json
23
+ from pathlib import Path
24
+
25
+
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
32
+
33
+
34
+ def _write_json_file(path: Path, data: dict) -> bool:
35
+ """Write dict to JSON file."""
36
+ try:
37
+ path.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8")
38
+ return True
39
+ except (OSError, IOError):
40
+ return False
41
+
42
+
43
+ # =============================================================================
44
+ # Phase Functions
45
+ # =============================================================================
46
+
47
+ def get_current_phase(task_json: Path) -> int:
48
+ """Get current phase number.
49
+
50
+ Args:
51
+ task_json: Path to task.json file.
52
+
53
+ Returns:
54
+ Current phase number, or 0 if not found.
55
+ """
56
+ data = _read_json_file(task_json)
57
+ if not data:
58
+ return 0
59
+ return data.get("current_phase", 0) or 0
60
+
61
+
62
+ def get_total_phases(task_json: Path) -> int:
63
+ """Get total number of phases.
64
+
65
+ Args:
66
+ task_json: Path to task.json file.
67
+
68
+ Returns:
69
+ Total phase count, or 0 if not found.
70
+ """
71
+ data = _read_json_file(task_json)
72
+ if not data:
73
+ return 0
74
+
75
+ next_action = data.get("next_action", [])
76
+ if isinstance(next_action, list):
77
+ return len(next_action)
78
+ return 0
79
+
80
+
81
+ def get_phase_action(task_json: Path, phase: int) -> str:
82
+ """Get action name for a specific phase.
83
+
84
+ Args:
85
+ task_json: Path to task.json file.
86
+ phase: Phase number.
87
+
88
+ Returns:
89
+ Action name, or "unknown" if not found.
90
+ """
91
+ data = _read_json_file(task_json)
92
+ if not data:
93
+ return "unknown"
94
+
95
+ next_action = data.get("next_action", [])
96
+ if isinstance(next_action, list):
97
+ for item in next_action:
98
+ if isinstance(item, dict) and item.get("phase") == phase:
99
+ return item.get("action", "unknown")
100
+ return "unknown"
101
+
102
+
103
+ def get_phase_info(task_json: Path) -> str:
104
+ """Get formatted phase info: "N/M (action)".
105
+
106
+ Args:
107
+ task_json: Path to task.json file.
108
+
109
+ Returns:
110
+ Formatted string like "1/4 (implement)".
111
+ """
112
+ data = _read_json_file(task_json)
113
+ if not data:
114
+ return "N/A"
115
+
116
+ current_phase = data.get("current_phase", 0) or 0
117
+ total_phases = get_total_phases(task_json)
118
+ action_name = get_phase_action(task_json, current_phase)
119
+
120
+ if current_phase == 0 or current_phase is None:
121
+ return f"0/{total_phases} (pending)"
122
+ else:
123
+ return f"{current_phase}/{total_phases} ({action_name})"
124
+
125
+
126
+ def set_phase(task_json: Path, phase: int) -> bool:
127
+ """Set current phase to a specific value.
128
+
129
+ Args:
130
+ task_json: Path to task.json file.
131
+ phase: Phase number to set.
132
+
133
+ Returns:
134
+ True on success, False on error.
135
+ """
136
+ data = _read_json_file(task_json)
137
+ if not data:
138
+ return False
139
+
140
+ data["current_phase"] = phase
141
+ return _write_json_file(task_json, data)
142
+
143
+
144
+ def advance_phase(task_json: Path) -> bool:
145
+ """Advance to next phase.
146
+
147
+ Args:
148
+ task_json: Path to task.json file.
149
+
150
+ Returns:
151
+ True on success, False on error or at final phase.
152
+ """
153
+ data = _read_json_file(task_json)
154
+ if not data:
155
+ return False
156
+
157
+ current = data.get("current_phase", 0) or 0
158
+ total = get_total_phases(task_json)
159
+ next_phase = current + 1
160
+
161
+ if next_phase > total:
162
+ return False # Already at final phase
163
+
164
+ data["current_phase"] = next_phase
165
+ return _write_json_file(task_json, data)
166
+
167
+
168
+ def get_phase_for_action(task_json: Path, action: str) -> int:
169
+ """Get phase number for a specific action name.
170
+
171
+ Args:
172
+ task_json: Path to task.json file.
173
+ action: Action name.
174
+
175
+ Returns:
176
+ Phase number, or 0 if not found.
177
+ """
178
+ data = _read_json_file(task_json)
179
+ if not data:
180
+ return 0
181
+
182
+ next_action = data.get("next_action", [])
183
+ if isinstance(next_action, list):
184
+ for item in next_action:
185
+ if isinstance(item, dict) and item.get("action") == action:
186
+ return item.get("phase", 0)
187
+ return 0
188
+
189
+
190
+ def map_subagent_to_action(subagent_type: str) -> str:
191
+ """Map subagent type to action name.
192
+
193
+ Used by hooks to determine which action a subagent corresponds to.
194
+
195
+ Args:
196
+ subagent_type: Subagent type string.
197
+
198
+ Returns:
199
+ Corresponding action name.
200
+ """
201
+ mapping = {
202
+ "implement": "implement",
203
+ "check": "check",
204
+ "debug": "debug",
205
+ "research": "research",
206
+ }
207
+ return mapping.get(subagent_type, subagent_type)
208
+
209
+
210
+ def is_phase_completed(task_json: Path, phase: int) -> bool:
211
+ """Check if a phase is completed (current_phase > phase).
212
+
213
+ Args:
214
+ task_json: Path to task.json file.
215
+ phase: Phase number to check.
216
+
217
+ Returns:
218
+ True if phase is completed.
219
+ """
220
+ current = get_current_phase(task_json)
221
+ return current > phase
222
+
223
+
224
+ def is_current_action(task_json: Path, action: str) -> bool:
225
+ """Check if we're at a specific action.
226
+
227
+ Args:
228
+ task_json: Path to task.json file.
229
+ action: Action name to check.
230
+
231
+ Returns:
232
+ True if current phase matches the action.
233
+ """
234
+ current = get_current_phase(task_json)
235
+ action_phase = get_phase_for_action(task_json, action)
236
+ return current == action_phase
237
+
238
+
239
+ # =============================================================================
240
+ # Main Entry (for testing)
241
+ # =============================================================================
242
+
243
+ if __name__ == "__main__":
244
+ import sys
245
+
246
+ if len(sys.argv) > 1:
247
+ path = Path(sys.argv[1])
248
+ print(f"Task JSON: {path}")
249
+ print(f"Phase info: {get_phase_info(path)}")
250
+ print(f"Current phase: {get_current_phase(path)}")
251
+ print(f"Total phases: {get_total_phases(path)}")
252
+ else:
253
+ print("Usage: python3 phase.py <task.json>")