@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,1068 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Task Management Script for Multi-Agent Pipeline.
5
+
6
+ Usage:
7
+ python3 task.py create "<title>" [--slug <name>] [--assignee <dev>] [--priority P0|P1|P2|P3]
8
+ python3 task.py init-context <dir> <type> # Initialize jsonl files
9
+ python3 task.py add-context <dir> <file> <path> [reason] # Add jsonl entry
10
+ python3 task.py validate <dir> # Validate jsonl files
11
+ python3 task.py list-context <dir> # List jsonl entries
12
+ python3 task.py start <dir> # Set as current task
13
+ python3 task.py finish # Clear current task
14
+ python3 task.py set-branch <dir> <branch> # Set git branch
15
+ python3 task.py set-base-branch <dir> <branch> # Set PR target branch
16
+ python3 task.py set-scope <dir> <scope> # Set scope for PR title
17
+ python3 task.py create-pr [dir] [--dry-run] # Create PR from task
18
+ python3 task.py archive <task-name> # Archive completed task
19
+ python3 task.py list # List active tasks
20
+ python3 task.py list-archive [month] # List archived tasks
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import sys
26
+
27
+ # IMPORTANT: Force stdout to use UTF-8 on Windows
28
+ # This fixes UnicodeEncodeError when outputting non-ASCII characters
29
+ if sys.platform == "win32":
30
+ import io as _io
31
+ if hasattr(sys.stdout, "reconfigure"):
32
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace") # type: ignore[union-attr]
33
+ elif hasattr(sys.stdout, "detach"):
34
+ sys.stdout = _io.TextIOWrapper(sys.stdout.detach(), encoding="utf-8", errors="replace") # type: ignore[union-attr]
35
+
36
+ import argparse
37
+ import json
38
+ import re
39
+ import sys
40
+ from datetime import datetime
41
+ from pathlib import Path
42
+
43
+ from common.cli_adapter import get_cli_adapter_auto
44
+ from common.git_context import _run_git_command
45
+ from common.paths import (
46
+ DIR_WORKFLOW,
47
+ DIR_TASKS,
48
+ DIR_SPEC,
49
+ DIR_ARCHIVE,
50
+ FILE_TASK_JSON,
51
+ get_repo_root,
52
+ get_developer,
53
+ get_tasks_dir,
54
+ get_current_task,
55
+ set_current_task,
56
+ clear_current_task,
57
+ generate_task_date_prefix,
58
+ )
59
+ from common.task_utils import (
60
+ find_task_by_name,
61
+ archive_task_complete,
62
+ )
63
+
64
+
65
+ # =============================================================================
66
+ # Colors
67
+ # =============================================================================
68
+
69
+ class Colors:
70
+ RED = "\033[0;31m"
71
+ GREEN = "\033[0;32m"
72
+ YELLOW = "\033[1;33m"
73
+ BLUE = "\033[0;34m"
74
+ CYAN = "\033[0;36m"
75
+ NC = "\033[0m"
76
+
77
+
78
+ def colored(text: str, color: str) -> str:
79
+ """Apply color to text."""
80
+ return f"{color}{text}{Colors.NC}"
81
+
82
+
83
+ # =============================================================================
84
+ # Helper Functions
85
+ # =============================================================================
86
+
87
+ def _read_json_file(path: Path) -> dict | None:
88
+ """Read and parse a JSON file."""
89
+ try:
90
+ return json.loads(path.read_text(encoding="utf-8"))
91
+ except (FileNotFoundError, json.JSONDecodeError, OSError):
92
+ return None
93
+
94
+
95
+ def _write_json_file(path: Path, data: dict) -> bool:
96
+ """Write dict to JSON file."""
97
+ try:
98
+ path.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8")
99
+ return True
100
+ except (OSError, IOError):
101
+ return False
102
+
103
+
104
+ def _slugify(title: str) -> str:
105
+ """Convert title to slug (only works with ASCII)."""
106
+ result = title.lower()
107
+ result = re.sub(r"[^a-z0-9]", "-", result)
108
+ result = re.sub(r"-+", "-", result)
109
+ result = result.strip("-")
110
+ return result
111
+
112
+
113
+ def _resolve_task_dir(target_dir: str, repo_root: Path) -> Path:
114
+ """Resolve task directory to absolute path.
115
+
116
+ Supports:
117
+ - Absolute path: /path/to/task
118
+ - Relative path: .aim-studio/tasks/01-31-my-task
119
+ - Task name: my-task (uses find_task_by_name for lookup)
120
+ """
121
+ if not target_dir:
122
+ return Path()
123
+
124
+ # Absolute path
125
+ if target_dir.startswith("/"):
126
+ return Path(target_dir)
127
+
128
+ # Relative path (contains path separator or starts with .aim-studio)
129
+ if "/" in target_dir or target_dir.startswith(".aim-studio"):
130
+ return repo_root / target_dir
131
+
132
+ # Task name - try to find in tasks directory
133
+ tasks_dir = get_tasks_dir(repo_root)
134
+ found = find_task_by_name(target_dir, tasks_dir)
135
+ if found:
136
+ return found
137
+
138
+ # Fallback to treating as relative path
139
+ return repo_root / target_dir
140
+
141
+
142
+ # =============================================================================
143
+ # JSONL Default Content Generators
144
+ # =============================================================================
145
+
146
+ def get_implement_base() -> list[dict]:
147
+ """Get base implement context entries."""
148
+ return [
149
+ {"file": f"{DIR_WORKFLOW}/workflow.md", "reason": "Project workflow and conventions"},
150
+ {"file": f"{DIR_WORKFLOW}/{DIR_SPEC}/shared/index.md", "reason": "Shared coding standards"},
151
+ ]
152
+
153
+
154
+ def get_implement_backend() -> list[dict]:
155
+ """Get backend implement context entries."""
156
+ return [
157
+ {"file": f"{DIR_WORKFLOW}/{DIR_SPEC}/backend/index.md", "reason": "Backend development guide"},
158
+ {"file": f"{DIR_WORKFLOW}/{DIR_SPEC}/backend/api-module.md", "reason": "API module conventions"},
159
+ {"file": f"{DIR_WORKFLOW}/{DIR_SPEC}/backend/quality.md", "reason": "Code quality requirements"},
160
+ ]
161
+
162
+
163
+ def get_implement_frontend() -> list[dict]:
164
+ """Get frontend implement context entries."""
165
+ return [
166
+ {"file": f"{DIR_WORKFLOW}/{DIR_SPEC}/frontend/index.md", "reason": "Frontend development guide"},
167
+ {"file": f"{DIR_WORKFLOW}/{DIR_SPEC}/frontend/components.md", "reason": "Component conventions"},
168
+ ]
169
+
170
+
171
+ def get_implement_story() -> list[dict]:
172
+ """Get story implement context entries."""
173
+ return [
174
+ {"file": f"{DIR_WORKFLOW}/{DIR_SPEC}/story/index.md", "reason": "Story creation guidelines"},
175
+ {"file": f"{DIR_WORKFLOW}/{DIR_SPEC}/story/character.md", "reason": "Character profile standards"},
176
+ {"file": f"{DIR_WORKFLOW}/{DIR_SPEC}/story/world.md", "reason": "World building standards"},
177
+ {"file": f"{DIR_WORKFLOW}/{DIR_SPEC}/story/script.md", "reason": "Script format standards"},
178
+ ]
179
+
180
+
181
+ def get_check_context(dev_type: str, repo_root: Path) -> list[dict]:
182
+ """Get check context entries."""
183
+ adapter = get_cli_adapter_auto(repo_root)
184
+
185
+ entries = [
186
+ {"file": adapter.get_aim_command_path("finish-work"), "reason": "Finish work checklist"},
187
+ {"file": f"{DIR_WORKFLOW}/{DIR_SPEC}/shared/index.md", "reason": "Shared coding standards"},
188
+ ]
189
+
190
+ if dev_type in ("backend", "fullstack"):
191
+ entries.append({"file": adapter.get_aim_command_path("check-backend"), "reason": "Backend check spec"})
192
+ if dev_type in ("frontend", "fullstack"):
193
+ entries.append({"file": adapter.get_aim_command_path("check-frontend"), "reason": "Frontend check spec"})
194
+
195
+ return entries
196
+
197
+
198
+ def get_debug_context(dev_type: str, repo_root: Path) -> list[dict]:
199
+ """Get debug context entries."""
200
+ adapter = get_cli_adapter_auto(repo_root)
201
+
202
+ entries = [
203
+ {"file": f"{DIR_WORKFLOW}/{DIR_SPEC}/shared/index.md", "reason": "Shared coding standards"},
204
+ ]
205
+
206
+ if dev_type in ("backend", "fullstack"):
207
+ entries.append({"file": adapter.get_aim_command_path("check-backend"), "reason": "Backend check spec"})
208
+ if dev_type in ("frontend", "fullstack"):
209
+ entries.append({"file": adapter.get_aim_command_path("check-frontend"), "reason": "Frontend check spec"})
210
+
211
+ return entries
212
+
213
+
214
+ def _write_jsonl(path: Path, entries: list[dict]) -> None:
215
+ """Write entries to JSONL file."""
216
+ lines = [json.dumps(entry, ensure_ascii=False) for entry in entries]
217
+ path.write_text("\n".join(lines) + "\n", encoding="utf-8")
218
+
219
+
220
+ # =============================================================================
221
+ # Task Operations
222
+ # =============================================================================
223
+
224
+ def ensure_tasks_dir(repo_root: Path) -> Path:
225
+ """Ensure tasks directory exists."""
226
+ tasks_dir = get_tasks_dir(repo_root)
227
+ archive_dir = tasks_dir / "archive"
228
+
229
+ if not tasks_dir.exists():
230
+ tasks_dir.mkdir(parents=True)
231
+ print(colored(f"Created tasks directory: {tasks_dir}", Colors.GREEN), file=sys.stderr)
232
+
233
+ if not archive_dir.exists():
234
+ archive_dir.mkdir(parents=True)
235
+
236
+ return tasks_dir
237
+
238
+
239
+ # =============================================================================
240
+ # Command: create
241
+ # =============================================================================
242
+
243
+ def cmd_create(args: argparse.Namespace) -> int:
244
+ """Create a new task."""
245
+ repo_root = get_repo_root()
246
+
247
+ if not args.title:
248
+ print(colored("Error: title is required", Colors.RED), file=sys.stderr)
249
+ return 1
250
+
251
+ # Default assignee to current developer
252
+ assignee = args.assignee
253
+ if not assignee:
254
+ assignee = get_developer(repo_root)
255
+ if not assignee:
256
+ print(colored("Error: No developer set. Run init_developer.py first or use --assignee", Colors.RED), file=sys.stderr)
257
+ return 1
258
+
259
+ ensure_tasks_dir(repo_root)
260
+
261
+ # Get current developer as creator
262
+ creator = get_developer(repo_root) or assignee
263
+
264
+ # Generate slug if not provided
265
+ slug = args.slug or _slugify(args.title)
266
+ if not slug:
267
+ print(colored("Error: could not generate slug from title", Colors.RED), file=sys.stderr)
268
+ return 1
269
+
270
+ # Create task directory with MM-DD-slug format
271
+ tasks_dir = get_tasks_dir(repo_root)
272
+ date_prefix = generate_task_date_prefix()
273
+ dir_name = f"{date_prefix}-{slug}"
274
+ task_dir = tasks_dir / dir_name
275
+ task_json_path = task_dir / FILE_TASK_JSON
276
+
277
+ if task_dir.exists():
278
+ print(colored(f"Warning: Task directory already exists: {dir_name}", Colors.YELLOW), file=sys.stderr)
279
+ else:
280
+ task_dir.mkdir(parents=True)
281
+
282
+ today = datetime.now().strftime("%Y-%m-%d")
283
+
284
+ # Record current branch as base_branch (PR target)
285
+ _, branch_out, _ = _run_git_command(["branch", "--show-current"], cwd=repo_root)
286
+ current_branch = branch_out.strip() or "main"
287
+
288
+ task_data = {
289
+ "id": slug,
290
+ "name": slug,
291
+ "title": args.title,
292
+ "description": args.description or "",
293
+ "status": "planning",
294
+ "dev_type": None,
295
+ "scope": None,
296
+ "priority": args.priority,
297
+ "creator": creator,
298
+ "assignee": assignee,
299
+ "createdAt": today,
300
+ "completedAt": None,
301
+ "branch": None,
302
+ "base_branch": current_branch,
303
+ "worktree_path": None,
304
+ "current_phase": 0,
305
+ "next_action": [
306
+ {"phase": 1, "action": "implement"},
307
+ {"phase": 2, "action": "check"},
308
+ {"phase": 3, "action": "finish"},
309
+ {"phase": 4, "action": "create-pr"},
310
+ ],
311
+ "commit": None,
312
+ "pr_url": None,
313
+ "subtasks": [],
314
+ "relatedFiles": [],
315
+ "notes": "",
316
+ }
317
+
318
+ _write_json_file(task_json_path, task_data)
319
+
320
+ print(colored(f"Created task: {dir_name}", Colors.GREEN), file=sys.stderr)
321
+ print("", file=sys.stderr)
322
+ print(colored("Next steps:", Colors.BLUE), file=sys.stderr)
323
+ print(" 1. Create prd.md with requirements", file=sys.stderr)
324
+ print(" 2. Run: python3 task.py init-context <dir> <dev_type>", file=sys.stderr)
325
+ print(" 3. Run: python3 task.py start <dir>", file=sys.stderr)
326
+ print("", file=sys.stderr)
327
+
328
+ # Output relative path for script chaining
329
+ print(f"{DIR_WORKFLOW}/{DIR_TASKS}/{dir_name}")
330
+ return 0
331
+
332
+
333
+ # =============================================================================
334
+ # Command: init-context
335
+ # =============================================================================
336
+
337
+ def cmd_init_context(args: argparse.Namespace) -> int:
338
+ """Initialize JSONL context files for a task."""
339
+ repo_root = get_repo_root()
340
+ target_dir = _resolve_task_dir(args.dir, repo_root)
341
+ dev_type = args.type
342
+
343
+ if not dev_type:
344
+ print(colored("Error: Missing arguments", Colors.RED))
345
+ print("Usage: python3 task.py init-context <task-dir> <dev_type>")
346
+ print(" dev_type: backend | frontend | fullstack | story | test | docs")
347
+ return 1
348
+
349
+ if not target_dir.is_dir():
350
+ print(colored(f"Error: Directory not found: {target_dir}", Colors.RED))
351
+ return 1
352
+
353
+ print(colored("=== Initializing Agent Context Files ===", Colors.BLUE))
354
+ print(f"Target dir: {target_dir}")
355
+ print(f"Dev type: {dev_type}")
356
+ print()
357
+
358
+ # implement.jsonl
359
+ print(colored("Creating implement.jsonl...", Colors.CYAN))
360
+ implement_entries = get_implement_base()
361
+ if dev_type in ("backend", "test"):
362
+ implement_entries.extend(get_implement_backend())
363
+ elif dev_type == "frontend":
364
+ implement_entries.extend(get_implement_frontend())
365
+ elif dev_type == "fullstack":
366
+ implement_entries.extend(get_implement_backend())
367
+ implement_entries.extend(get_implement_frontend())
368
+ elif dev_type == "story":
369
+ implement_entries.extend(get_implement_story())
370
+
371
+ implement_file = target_dir / "implement.jsonl"
372
+ _write_jsonl(implement_file, implement_entries)
373
+ print(f" {colored('✓', Colors.GREEN)} {len(implement_entries)} entries")
374
+
375
+ # check.jsonl
376
+ print(colored("Creating check.jsonl...", Colors.CYAN))
377
+ check_entries = get_check_context(dev_type, repo_root)
378
+ check_file = target_dir / "check.jsonl"
379
+ _write_jsonl(check_file, check_entries)
380
+ print(f" {colored('✓', Colors.GREEN)} {len(check_entries)} entries")
381
+
382
+ # debug.jsonl
383
+ print(colored("Creating debug.jsonl...", Colors.CYAN))
384
+ debug_entries = get_debug_context(dev_type, repo_root)
385
+ debug_file = target_dir / "debug.jsonl"
386
+ _write_jsonl(debug_file, debug_entries)
387
+ print(f" {colored('✓', Colors.GREEN)} {len(debug_entries)} entries")
388
+
389
+ print()
390
+ print(colored("✓ All context files created", Colors.GREEN))
391
+ print()
392
+ print(colored("Next steps:", Colors.BLUE))
393
+ print(" 1. Add task-specific specs: python3 task.py add-context <dir> <jsonl> <path>")
394
+ print(" 2. Set as current: python3 task.py start <dir>")
395
+
396
+ return 0
397
+
398
+
399
+ # =============================================================================
400
+ # Command: add-context
401
+ # =============================================================================
402
+
403
+ def cmd_add_context(args: argparse.Namespace) -> int:
404
+ """Add entry to JSONL context file."""
405
+ repo_root = get_repo_root()
406
+ target_dir = _resolve_task_dir(args.dir, repo_root)
407
+
408
+ jsonl_name = args.file
409
+ path = args.path
410
+ reason = args.reason or "Added manually"
411
+
412
+ if not target_dir.is_dir():
413
+ print(colored(f"Error: Directory not found: {target_dir}", Colors.RED))
414
+ return 1
415
+
416
+ # Support shorthand
417
+ if not jsonl_name.endswith(".jsonl"):
418
+ jsonl_name = f"{jsonl_name}.jsonl"
419
+
420
+ jsonl_file = target_dir / jsonl_name
421
+ full_path = repo_root / path
422
+
423
+ entry_type = "file"
424
+ if full_path.is_dir():
425
+ entry_type = "directory"
426
+ if not path.endswith("/"):
427
+ path = f"{path}/"
428
+ elif not full_path.is_file():
429
+ print(colored(f"Error: Path not found: {path}", Colors.RED))
430
+ return 1
431
+
432
+ # Check if already exists
433
+ if jsonl_file.is_file():
434
+ content = jsonl_file.read_text(encoding="utf-8")
435
+ if f'"{path}"' in content:
436
+ print(colored(f"Warning: Entry already exists for {path}", Colors.YELLOW))
437
+ return 0
438
+
439
+ # Add entry
440
+ entry: dict
441
+ if entry_type == "directory":
442
+ entry = {"file": path, "type": "directory", "reason": reason}
443
+ else:
444
+ entry = {"file": path, "reason": reason}
445
+
446
+ with jsonl_file.open("a", encoding="utf-8") as f:
447
+ f.write(json.dumps(entry, ensure_ascii=False) + "\n")
448
+
449
+ print(colored(f"Added {entry_type}: {path}", Colors.GREEN))
450
+ return 0
451
+
452
+
453
+ # =============================================================================
454
+ # Command: validate
455
+ # =============================================================================
456
+
457
+ def cmd_validate(args: argparse.Namespace) -> int:
458
+ """Validate JSONL context files."""
459
+ repo_root = get_repo_root()
460
+ target_dir = _resolve_task_dir(args.dir, repo_root)
461
+
462
+ if not target_dir.is_dir():
463
+ print(colored("Error: task directory required", Colors.RED))
464
+ return 1
465
+
466
+ print(colored("=== Validating Context Files ===", Colors.BLUE))
467
+ print(f"Target dir: {target_dir}")
468
+ print()
469
+
470
+ total_errors = 0
471
+ for jsonl_name in ["implement.jsonl", "check.jsonl", "debug.jsonl"]:
472
+ jsonl_file = target_dir / jsonl_name
473
+ errors = _validate_jsonl(jsonl_file, repo_root)
474
+ total_errors += errors
475
+
476
+ print()
477
+ if total_errors == 0:
478
+ print(colored("✓ All validations passed", Colors.GREEN))
479
+ return 0
480
+ else:
481
+ print(colored(f"✗ Validation failed ({total_errors} errors)", Colors.RED))
482
+ return 1
483
+
484
+
485
+ def _validate_jsonl(jsonl_file: Path, repo_root: Path) -> int:
486
+ """Validate a single JSONL file."""
487
+ file_name = jsonl_file.name
488
+ errors = 0
489
+
490
+ if not jsonl_file.is_file():
491
+ print(f" {colored(f'{file_name}: not found (skipped)', Colors.YELLOW)}")
492
+ return 0
493
+
494
+ line_num = 0
495
+ for line in jsonl_file.read_text(encoding="utf-8").splitlines():
496
+ line_num += 1
497
+ if not line.strip():
498
+ continue
499
+
500
+ try:
501
+ data = json.loads(line)
502
+ except json.JSONDecodeError:
503
+ print(f" {colored(f'{file_name}:{line_num}: Invalid JSON', Colors.RED)}")
504
+ errors += 1
505
+ continue
506
+
507
+ file_path = data.get("file")
508
+ entry_type = data.get("type", "file")
509
+
510
+ if not file_path:
511
+ print(f" {colored(f'{file_name}:{line_num}: Missing file field', Colors.RED)}")
512
+ errors += 1
513
+ continue
514
+
515
+ full_path = repo_root / file_path
516
+ if entry_type == "directory":
517
+ if not full_path.is_dir():
518
+ print(f" {colored(f'{file_name}:{line_num}: Directory not found: {file_path}', Colors.RED)}")
519
+ errors += 1
520
+ else:
521
+ if not full_path.is_file():
522
+ print(f" {colored(f'{file_name}:{line_num}: File not found: {file_path}', Colors.RED)}")
523
+ errors += 1
524
+
525
+ if errors == 0:
526
+ print(f" {colored(f'{file_name}: ✓ ({line_num} entries)', Colors.GREEN)}")
527
+ else:
528
+ print(f" {colored(f'{file_name}: ✗ ({errors} errors)', Colors.RED)}")
529
+
530
+ return errors
531
+
532
+
533
+ # =============================================================================
534
+ # Command: list-context
535
+ # =============================================================================
536
+
537
+ def cmd_list_context(args: argparse.Namespace) -> int:
538
+ """List JSONL context entries."""
539
+ repo_root = get_repo_root()
540
+ target_dir = _resolve_task_dir(args.dir, repo_root)
541
+
542
+ if not target_dir.is_dir():
543
+ print(colored("Error: task directory required", Colors.RED))
544
+ return 1
545
+
546
+ print(colored("=== Context Files ===", Colors.BLUE))
547
+ print()
548
+
549
+ for jsonl_name in ["implement.jsonl", "check.jsonl", "debug.jsonl"]:
550
+ jsonl_file = target_dir / jsonl_name
551
+ if not jsonl_file.is_file():
552
+ continue
553
+
554
+ print(colored(f"[{jsonl_name}]", Colors.CYAN))
555
+
556
+ count = 0
557
+ for line in jsonl_file.read_text(encoding="utf-8").splitlines():
558
+ if not line.strip():
559
+ continue
560
+
561
+ try:
562
+ data = json.loads(line)
563
+ except json.JSONDecodeError:
564
+ continue
565
+
566
+ count += 1
567
+ file_path = data.get("file", "?")
568
+ entry_type = data.get("type", "file")
569
+ reason = data.get("reason", "-")
570
+
571
+ if entry_type == "directory":
572
+ print(f" {colored(f'{count}.', Colors.GREEN)} [DIR] {file_path}")
573
+ else:
574
+ print(f" {colored(f'{count}.', Colors.GREEN)} {file_path}")
575
+ print(f" {colored('→', Colors.YELLOW)} {reason}")
576
+
577
+ print()
578
+
579
+ return 0
580
+
581
+
582
+ # =============================================================================
583
+ # Command: start / finish
584
+ # =============================================================================
585
+
586
+ def cmd_start(args: argparse.Namespace) -> int:
587
+ """Set current task."""
588
+ repo_root = get_repo_root()
589
+ task_input = args.dir
590
+
591
+ if not task_input:
592
+ print(colored("Error: task directory or name required", Colors.RED))
593
+ return 1
594
+
595
+ # Resolve task directory (supports task name, relative path, or absolute path)
596
+ full_path = _resolve_task_dir(task_input, repo_root)
597
+
598
+ if not full_path.is_dir():
599
+ print(colored(f"Error: Task not found: {task_input}", Colors.RED))
600
+ print("Hint: Use task name (e.g., 'my-task') or full path (e.g., '.aim-studio/tasks/01-31-my-task')")
601
+ return 1
602
+
603
+ # Convert to relative path for storage
604
+ try:
605
+ task_dir = str(full_path.relative_to(repo_root))
606
+ except ValueError:
607
+ task_dir = str(full_path)
608
+
609
+ if set_current_task(task_dir, repo_root):
610
+ print(colored(f"✓ Current task set to: {task_dir}", Colors.GREEN))
611
+ print()
612
+ print(colored("The hook will now inject context from this task's jsonl files.", Colors.BLUE))
613
+ return 0
614
+ else:
615
+ print(colored("Error: Failed to set current task", Colors.RED))
616
+ return 1
617
+
618
+
619
+ def cmd_finish(args: argparse.Namespace) -> int:
620
+ """Clear current task."""
621
+ repo_root = get_repo_root()
622
+ current = get_current_task(repo_root)
623
+
624
+ if not current:
625
+ print(colored("No current task set", Colors.YELLOW))
626
+ return 0
627
+
628
+ clear_current_task(repo_root)
629
+ print(colored(f"✓ Cleared current task (was: {current})", Colors.GREEN))
630
+ return 0
631
+
632
+
633
+ # =============================================================================
634
+ # Command: archive
635
+ # =============================================================================
636
+
637
+ def cmd_archive(args: argparse.Namespace) -> int:
638
+ """Archive completed task."""
639
+ repo_root = get_repo_root()
640
+ task_name = args.name
641
+
642
+ if not task_name:
643
+ print(colored("Error: Task name is required", Colors.RED), file=sys.stderr)
644
+ return 1
645
+
646
+ tasks_dir = get_tasks_dir(repo_root)
647
+
648
+ # Find task directory
649
+ task_dir = find_task_by_name(task_name, tasks_dir)
650
+
651
+ if not task_dir or not task_dir.is_dir():
652
+ print(colored(f"Error: Task not found: {task_name}", Colors.RED), file=sys.stderr)
653
+ print("Active tasks:", file=sys.stderr)
654
+ cmd_list(argparse.Namespace(mine=False, status=None))
655
+ return 1
656
+
657
+ dir_name = task_dir.name
658
+ task_json_path = task_dir / FILE_TASK_JSON
659
+
660
+ # Update status before archiving
661
+ today = datetime.now().strftime("%Y-%m-%d")
662
+ if task_json_path.is_file():
663
+ data = _read_json_file(task_json_path)
664
+ if data:
665
+ data["status"] = "completed"
666
+ data["completedAt"] = today
667
+ _write_json_file(task_json_path, data)
668
+
669
+ # Clear if current task
670
+ current = get_current_task(repo_root)
671
+ if current and dir_name in current:
672
+ clear_current_task(repo_root)
673
+
674
+ # Archive
675
+ result = archive_task_complete(task_dir, repo_root)
676
+ if "archived_to" in result:
677
+ archive_dest = Path(result["archived_to"])
678
+ year_month = archive_dest.parent.name
679
+ print(colored(f"Archived: {dir_name} -> archive/{year_month}/", Colors.GREEN), file=sys.stderr)
680
+
681
+ # Return the archive path
682
+ print(f"{DIR_WORKFLOW}/{DIR_TASKS}/{DIR_ARCHIVE}/{year_month}/{dir_name}")
683
+ return 0
684
+
685
+ return 1
686
+
687
+
688
+ # =============================================================================
689
+ # Command: list
690
+ # =============================================================================
691
+
692
+ def cmd_list(args: argparse.Namespace) -> int:
693
+ """List active tasks."""
694
+ repo_root = get_repo_root()
695
+ tasks_dir = get_tasks_dir(repo_root)
696
+ current_task = get_current_task(repo_root)
697
+ developer = get_developer(repo_root)
698
+ filter_mine = args.mine
699
+ filter_status = args.status
700
+
701
+ if filter_mine:
702
+ if not developer:
703
+ print(colored("Error: No developer set. Run init_developer.py first", Colors.RED), file=sys.stderr)
704
+ return 1
705
+ print(colored(f"My tasks (assignee: {developer}):", Colors.BLUE))
706
+ else:
707
+ print(colored("All active tasks:", Colors.BLUE))
708
+ print()
709
+
710
+ count = 0
711
+ if tasks_dir.is_dir():
712
+ for d in sorted(tasks_dir.iterdir()):
713
+ if not d.is_dir() or d.name == "archive":
714
+ continue
715
+
716
+ dir_name = d.name
717
+ task_json = d / FILE_TASK_JSON
718
+ status = "unknown"
719
+ assignee = "-"
720
+ relative_path = f"{DIR_WORKFLOW}/{DIR_TASKS}/{dir_name}"
721
+
722
+ if task_json.is_file():
723
+ data = _read_json_file(task_json)
724
+ if data:
725
+ status = data.get("status", "unknown")
726
+ assignee = data.get("assignee", "-")
727
+
728
+ # Apply --mine filter
729
+ if filter_mine and assignee != developer:
730
+ continue
731
+
732
+ # Apply --status filter
733
+ if filter_status and status != filter_status:
734
+ continue
735
+
736
+ marker = ""
737
+ if relative_path == current_task:
738
+ marker = f" {colored('<- current', Colors.GREEN)}"
739
+
740
+ if filter_mine:
741
+ print(f" - {dir_name}/ ({status}){marker}")
742
+ else:
743
+ print(f" - {dir_name}/ ({status}) [{colored(assignee, Colors.CYAN)}]{marker}")
744
+ count += 1
745
+
746
+ if count == 0:
747
+ if filter_mine:
748
+ print(" (no tasks assigned to you)")
749
+ else:
750
+ print(" (no active tasks)")
751
+
752
+ print()
753
+ print(f"Total: {count} task(s)")
754
+ return 0
755
+
756
+
757
+ # =============================================================================
758
+ # Command: list-archive
759
+ # =============================================================================
760
+
761
+ def cmd_list_archive(args: argparse.Namespace) -> int:
762
+ """List archived tasks."""
763
+ repo_root = get_repo_root()
764
+ tasks_dir = get_tasks_dir(repo_root)
765
+ archive_dir = tasks_dir / "archive"
766
+ month = args.month
767
+
768
+ print(colored("Archived tasks:", Colors.BLUE))
769
+ print()
770
+
771
+ if month:
772
+ month_dir = archive_dir / month
773
+ if month_dir.is_dir():
774
+ print(f"[{month}]")
775
+ for d in sorted(month_dir.iterdir()):
776
+ if d.is_dir():
777
+ print(f" - {d.name}/")
778
+ else:
779
+ print(f" No archives for {month}")
780
+ else:
781
+ if archive_dir.is_dir():
782
+ for month_dir in sorted(archive_dir.iterdir()):
783
+ if month_dir.is_dir():
784
+ month_name = month_dir.name
785
+ count = sum(1 for d in month_dir.iterdir() if d.is_dir())
786
+ print(f"[{month_name}] - {count} task(s)")
787
+
788
+ return 0
789
+
790
+
791
+ # =============================================================================
792
+ # Command: set-branch
793
+ # =============================================================================
794
+
795
+ def cmd_set_branch(args: argparse.Namespace) -> int:
796
+ """Set git branch for task."""
797
+ repo_root = get_repo_root()
798
+ target_dir = _resolve_task_dir(args.dir, repo_root)
799
+ branch = args.branch
800
+
801
+ if not branch:
802
+ print(colored("Error: Missing arguments", Colors.RED))
803
+ print("Usage: python3 task.py set-branch <task-dir> <branch-name>")
804
+ return 1
805
+
806
+ task_json = target_dir / FILE_TASK_JSON
807
+ if not task_json.is_file():
808
+ print(colored(f"Error: task.json not found at {target_dir}", Colors.RED))
809
+ return 1
810
+
811
+ data = _read_json_file(task_json)
812
+ if not data:
813
+ return 1
814
+
815
+ data["branch"] = branch
816
+ _write_json_file(task_json, data)
817
+
818
+ print(colored(f"✓ Branch set to: {branch}", Colors.GREEN))
819
+ print()
820
+ print(colored("Now you can start the multi-agent pipeline:", Colors.BLUE))
821
+ print(f" python3 ./.aim-studio/scripts/multi_agent/start.py {args.dir}")
822
+ return 0
823
+
824
+
825
+ # =============================================================================
826
+ # Command: set-base-branch
827
+ # =============================================================================
828
+
829
+ def cmd_set_base_branch(args: argparse.Namespace) -> int:
830
+ """Set the base branch (PR target) for task."""
831
+ repo_root = get_repo_root()
832
+ target_dir = _resolve_task_dir(args.dir, repo_root)
833
+ base_branch = args.base_branch
834
+
835
+ if not base_branch:
836
+ print(colored("Error: Missing arguments", Colors.RED))
837
+ print("Usage: python3 task.py set-base-branch <task-dir> <base-branch>")
838
+ print("Example: python3 task.py set-base-branch <dir> develop")
839
+ print()
840
+ print("This sets the target branch for PR (the branch your feature will merge into).")
841
+ return 1
842
+
843
+ task_json = target_dir / FILE_TASK_JSON
844
+ if not task_json.is_file():
845
+ print(colored(f"Error: task.json not found at {target_dir}", Colors.RED))
846
+ return 1
847
+
848
+ data = _read_json_file(task_json)
849
+ if not data:
850
+ return 1
851
+
852
+ data["base_branch"] = base_branch
853
+ _write_json_file(task_json, data)
854
+
855
+ print(colored(f"✓ Base branch set to: {base_branch}", Colors.GREEN))
856
+ print(f" PR will target: {base_branch}")
857
+ return 0
858
+
859
+
860
+ # =============================================================================
861
+ # Command: set-scope
862
+ # =============================================================================
863
+
864
+ def cmd_set_scope(args: argparse.Namespace) -> int:
865
+ """Set scope for PR title."""
866
+ repo_root = get_repo_root()
867
+ target_dir = _resolve_task_dir(args.dir, repo_root)
868
+ scope = args.scope
869
+
870
+ if not scope:
871
+ print(colored("Error: Missing arguments", Colors.RED))
872
+ print("Usage: python3 task.py set-scope <task-dir> <scope>")
873
+ return 1
874
+
875
+ task_json = target_dir / FILE_TASK_JSON
876
+ if not task_json.is_file():
877
+ print(colored(f"Error: task.json not found at {target_dir}", Colors.RED))
878
+ return 1
879
+
880
+ data = _read_json_file(task_json)
881
+ if not data:
882
+ return 1
883
+
884
+ data["scope"] = scope
885
+ _write_json_file(task_json, data)
886
+
887
+ print(colored(f"✓ Scope set to: {scope}", Colors.GREEN))
888
+ return 0
889
+
890
+
891
+ # =============================================================================
892
+ # Command: create-pr (delegates to multi-agent script)
893
+ # =============================================================================
894
+
895
+ def cmd_create_pr(args: argparse.Namespace) -> int:
896
+ """Create PR from task - delegates to multi_agent/create_pr.py."""
897
+ import subprocess
898
+ script_dir = Path(__file__).parent
899
+ create_pr_script = script_dir / "multi_agent" / "create_pr.py"
900
+
901
+ cmd = [sys.executable, str(create_pr_script)]
902
+ if args.dir:
903
+ cmd.append(args.dir)
904
+ if args.dry_run:
905
+ cmd.append("--dry-run")
906
+
907
+ result = subprocess.run(cmd)
908
+ return result.returncode
909
+
910
+
911
+ # =============================================================================
912
+ # Help
913
+ # =============================================================================
914
+
915
+ def show_usage() -> None:
916
+ """Show usage help."""
917
+ print("""Task Management Script for Multi-Agent Pipeline
918
+
919
+ Usage:
920
+ python3 task.py create <title> Create new task directory
921
+ python3 task.py init-context <dir> <dev_type> Initialize jsonl files
922
+ python3 task.py add-context <dir> <jsonl> <path> [reason] Add entry to jsonl
923
+ python3 task.py validate <dir> Validate jsonl files
924
+ python3 task.py list-context <dir> List jsonl entries
925
+ python3 task.py start <dir> Set as current task
926
+ python3 task.py finish Clear current task
927
+ python3 task.py set-branch <dir> <branch> Set git branch for multi-agent
928
+ python3 task.py set-scope <dir> <scope> Set scope for PR title
929
+ python3 task.py create-pr [dir] [--dry-run] Create PR from task
930
+ python3 task.py archive <task-name> Archive completed task
931
+ python3 task.py list [--mine] [--status <status>] List tasks
932
+ python3 task.py list-archive [YYYY-MM] List archived tasks
933
+
934
+ Arguments:
935
+ dev_type: backend | frontend | fullstack | test | docs
936
+
937
+ List options:
938
+ --mine, -m Show only tasks assigned to current developer
939
+ --status, -s <s> Filter by status (planning, in_progress, review, completed)
940
+
941
+ Examples:
942
+ python3 task.py create "Add login feature" --slug add-login
943
+ python3 task.py init-context .aim-studio/tasks/01-21-add-login backend
944
+ python3 task.py add-context <dir> implement .aim-studio/spec/backend/auth.md "Auth guidelines"
945
+ python3 task.py set-branch <dir> task/add-login
946
+ python3 task.py start .aim-studio/tasks/01-21-add-login
947
+ python3 task.py create-pr # Uses current task
948
+ python3 task.py create-pr <dir> --dry-run # Preview without changes
949
+ python3 task.py finish
950
+ python3 task.py archive add-login
951
+ python3 task.py list # List all active tasks
952
+ python3 task.py list --mine # List my tasks only
953
+ python3 task.py list --mine --status in_progress # List my in-progress tasks
954
+ """)
955
+
956
+
957
+ # =============================================================================
958
+ # Main Entry
959
+ # =============================================================================
960
+
961
+ def main() -> int:
962
+ """CLI entry point."""
963
+ parser = argparse.ArgumentParser(
964
+ description="Task Management Script for Multi-Agent Pipeline",
965
+ formatter_class=argparse.RawDescriptionHelpFormatter,
966
+ )
967
+ subparsers = parser.add_subparsers(dest="command", help="Commands")
968
+
969
+ # create
970
+ p_create = subparsers.add_parser("create", help="Create new task")
971
+ p_create.add_argument("title", help="Task title")
972
+ p_create.add_argument("--slug", "-s", help="Task slug")
973
+ p_create.add_argument("--assignee", "-a", help="Assignee developer")
974
+ p_create.add_argument("--priority", "-p", default="P2", help="Priority (P0-P3)")
975
+ p_create.add_argument("--description", "-d", help="Task description")
976
+
977
+ # init-context
978
+ p_init = subparsers.add_parser("init-context", help="Initialize context files")
979
+ p_init.add_argument("dir", help="Task directory")
980
+ p_init.add_argument("type", help="Dev type: backend|frontend|fullstack|test|docs")
981
+
982
+ # add-context
983
+ p_add = subparsers.add_parser("add-context", help="Add context entry")
984
+ p_add.add_argument("dir", help="Task directory")
985
+ p_add.add_argument("file", help="JSONL file (implement|check|debug)")
986
+ p_add.add_argument("path", help="File path to add")
987
+ p_add.add_argument("reason", nargs="?", help="Reason for adding")
988
+
989
+ # validate
990
+ p_validate = subparsers.add_parser("validate", help="Validate context files")
991
+ p_validate.add_argument("dir", help="Task directory")
992
+
993
+ # list-context
994
+ p_listctx = subparsers.add_parser("list-context", help="List context entries")
995
+ p_listctx.add_argument("dir", help="Task directory")
996
+
997
+ # start
998
+ p_start = subparsers.add_parser("start", help="Set current task")
999
+ p_start.add_argument("dir", help="Task directory")
1000
+
1001
+ # finish
1002
+ subparsers.add_parser("finish", help="Clear current task")
1003
+
1004
+ # set-branch
1005
+ p_branch = subparsers.add_parser("set-branch", help="Set git branch")
1006
+ p_branch.add_argument("dir", help="Task directory")
1007
+ p_branch.add_argument("branch", help="Branch name")
1008
+
1009
+ # set-base-branch
1010
+ p_base = subparsers.add_parser("set-base-branch", help="Set PR target branch")
1011
+ p_base.add_argument("dir", help="Task directory")
1012
+ p_base.add_argument("base_branch", help="Base branch name (PR target)")
1013
+
1014
+ # set-scope
1015
+ p_scope = subparsers.add_parser("set-scope", help="Set scope")
1016
+ p_scope.add_argument("dir", help="Task directory")
1017
+ p_scope.add_argument("scope", help="Scope name")
1018
+
1019
+ # create-pr
1020
+ p_pr = subparsers.add_parser("create-pr", help="Create PR")
1021
+ p_pr.add_argument("dir", nargs="?", help="Task directory")
1022
+ p_pr.add_argument("--dry-run", action="store_true", help="Dry run mode")
1023
+
1024
+ # archive
1025
+ p_archive = subparsers.add_parser("archive", help="Archive task")
1026
+ p_archive.add_argument("name", help="Task name")
1027
+
1028
+ # list
1029
+ p_list = subparsers.add_parser("list", help="List tasks")
1030
+ p_list.add_argument("--mine", "-m", action="store_true", help="My tasks only")
1031
+ p_list.add_argument("--status", "-s", help="Filter by status")
1032
+
1033
+ # list-archive
1034
+ p_listarch = subparsers.add_parser("list-archive", help="List archived tasks")
1035
+ p_listarch.add_argument("month", nargs="?", help="Month (YYYY-MM)")
1036
+
1037
+ args = parser.parse_args()
1038
+
1039
+ if not args.command:
1040
+ show_usage()
1041
+ return 1
1042
+
1043
+ commands = {
1044
+ "create": cmd_create,
1045
+ "init-context": cmd_init_context,
1046
+ "add-context": cmd_add_context,
1047
+ "validate": cmd_validate,
1048
+ "list-context": cmd_list_context,
1049
+ "start": cmd_start,
1050
+ "finish": cmd_finish,
1051
+ "set-branch": cmd_set_branch,
1052
+ "set-base-branch": cmd_set_base_branch,
1053
+ "set-scope": cmd_set_scope,
1054
+ "create-pr": cmd_create_pr,
1055
+ "archive": cmd_archive,
1056
+ "list": cmd_list,
1057
+ "list-archive": cmd_list_archive,
1058
+ }
1059
+
1060
+ if args.command in commands:
1061
+ return commands[args.command](args)
1062
+ else:
1063
+ show_usage()
1064
+ return 1
1065
+
1066
+
1067
+ if __name__ == "__main__":
1068
+ sys.exit(main())