@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,366 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Registry utility functions for multi-agent pipeline.
4
+
5
+ Provides:
6
+ registry_get_file - Get registry file path
7
+ registry_get_agent_by_id - Find agent by ID
8
+ registry_get_agent_by_worktree - Find agent by worktree path
9
+ registry_get_task_dir - Get task dir for a worktree
10
+ registry_remove_by_id - Remove agent by ID
11
+ registry_remove_by_worktree - Remove agent by worktree path
12
+ registry_add_agent - Add agent to registry
13
+ registry_search_agent - Search agent by ID or task_dir
14
+ registry_list_agents - List all agents
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import json
20
+ from datetime import datetime
21
+ from pathlib import Path
22
+
23
+ from .paths import get_repo_root
24
+ from .worktree import get_agents_dir
25
+
26
+
27
+ def _read_json_file(path: Path) -> dict | None:
28
+ """Read and parse a JSON file."""
29
+ try:
30
+ return json.loads(path.read_text(encoding="utf-8"))
31
+ except (FileNotFoundError, json.JSONDecodeError, OSError):
32
+ return None
33
+
34
+
35
+ def _write_json_file(path: Path, data: dict) -> bool:
36
+ """Write dict to JSON file."""
37
+ try:
38
+ path.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8")
39
+ return True
40
+ except (OSError, IOError):
41
+ return False
42
+
43
+
44
+ # =============================================================================
45
+ # Registry File Access
46
+ # =============================================================================
47
+
48
+ def registry_get_file(repo_root: Path | None = None) -> Path | None:
49
+ """Get registry file path.
50
+
51
+ Args:
52
+ repo_root: Repository root path. Defaults to auto-detected.
53
+
54
+ Returns:
55
+ Path to registry.json, or None if agents dir not found.
56
+ """
57
+ if repo_root is None:
58
+ repo_root = get_repo_root()
59
+
60
+ agents_dir = get_agents_dir(repo_root)
61
+ if agents_dir:
62
+ return agents_dir / "registry.json"
63
+ return None
64
+
65
+
66
+ def _ensure_registry(repo_root: Path | None = None) -> Path | None:
67
+ """Ensure registry file exists with valid structure.
68
+
69
+ Args:
70
+ repo_root: Repository root path. Defaults to auto-detected.
71
+
72
+ Returns:
73
+ Path to registry file, or None if cannot create.
74
+ """
75
+ if repo_root is None:
76
+ repo_root = get_repo_root()
77
+
78
+ registry_file = registry_get_file(repo_root)
79
+ if not registry_file:
80
+ return None
81
+
82
+ agents_dir = registry_file.parent
83
+
84
+ try:
85
+ agents_dir.mkdir(parents=True, exist_ok=True)
86
+
87
+ if not registry_file.exists():
88
+ _write_json_file(registry_file, {"agents": []})
89
+
90
+ return registry_file
91
+ except (OSError, IOError):
92
+ return None
93
+
94
+
95
+ # =============================================================================
96
+ # Agent Lookup
97
+ # =============================================================================
98
+
99
+ def registry_get_agent_by_id(
100
+ agent_id: str,
101
+ repo_root: Path | None = None
102
+ ) -> dict | None:
103
+ """Get agent by ID.
104
+
105
+ Args:
106
+ agent_id: Agent ID.
107
+ repo_root: Repository root path. Defaults to auto-detected.
108
+
109
+ Returns:
110
+ Agent dict, or None if not found.
111
+ """
112
+ if repo_root is None:
113
+ repo_root = get_repo_root()
114
+
115
+ registry_file = registry_get_file(repo_root)
116
+ if not registry_file or not registry_file.is_file():
117
+ return None
118
+
119
+ data = _read_json_file(registry_file)
120
+ if not data:
121
+ return None
122
+
123
+ for agent in data.get("agents", []):
124
+ if agent.get("id") == agent_id:
125
+ return agent
126
+
127
+ return None
128
+
129
+
130
+ def registry_get_agent_by_worktree(
131
+ worktree_path: str,
132
+ repo_root: Path | None = None
133
+ ) -> dict | None:
134
+ """Get agent by worktree path.
135
+
136
+ Args:
137
+ worktree_path: Worktree path.
138
+ repo_root: Repository root path. Defaults to auto-detected.
139
+
140
+ Returns:
141
+ Agent dict, or None if not found.
142
+ """
143
+ if repo_root is None:
144
+ repo_root = get_repo_root()
145
+
146
+ registry_file = registry_get_file(repo_root)
147
+ if not registry_file or not registry_file.is_file():
148
+ return None
149
+
150
+ data = _read_json_file(registry_file)
151
+ if not data:
152
+ return None
153
+
154
+ for agent in data.get("agents", []):
155
+ if agent.get("worktree_path") == worktree_path:
156
+ return agent
157
+
158
+ return None
159
+
160
+
161
+ def registry_search_agent(
162
+ search: str,
163
+ repo_root: Path | None = None
164
+ ) -> dict | None:
165
+ """Search agent by ID or task_dir containing search term.
166
+
167
+ Args:
168
+ search: Search term.
169
+ repo_root: Repository root path. Defaults to auto-detected.
170
+
171
+ Returns:
172
+ First matching agent dict, or None if not found.
173
+ """
174
+ if repo_root is None:
175
+ repo_root = get_repo_root()
176
+
177
+ registry_file = registry_get_file(repo_root)
178
+ if not registry_file or not registry_file.is_file():
179
+ return None
180
+
181
+ data = _read_json_file(registry_file)
182
+ if not data:
183
+ return None
184
+
185
+ for agent in data.get("agents", []):
186
+ # Exact ID match
187
+ if agent.get("id") == search:
188
+ return agent
189
+ # Partial match on task_dir
190
+ task_dir = agent.get("task_dir", "")
191
+ if search in task_dir:
192
+ return agent
193
+
194
+ return None
195
+
196
+
197
+ def registry_get_task_dir(
198
+ worktree_path: str,
199
+ repo_root: Path | None = None
200
+ ) -> str | None:
201
+ """Get task directory for a worktree.
202
+
203
+ Args:
204
+ worktree_path: Worktree path.
205
+ repo_root: Repository root path. Defaults to auto-detected.
206
+
207
+ Returns:
208
+ Task directory path, or None if not found.
209
+ """
210
+ agent = registry_get_agent_by_worktree(worktree_path, repo_root)
211
+ if agent:
212
+ return agent.get("task_dir")
213
+ return None
214
+
215
+
216
+ # =============================================================================
217
+ # Agent Modification
218
+ # =============================================================================
219
+
220
+ def registry_remove_by_id(agent_id: str, repo_root: Path | None = None) -> bool:
221
+ """Remove agent by ID.
222
+
223
+ Args:
224
+ agent_id: Agent ID.
225
+ repo_root: Repository root path. Defaults to auto-detected.
226
+
227
+ Returns:
228
+ True on success.
229
+ """
230
+ if repo_root is None:
231
+ repo_root = get_repo_root()
232
+
233
+ registry_file = registry_get_file(repo_root)
234
+ if not registry_file or not registry_file.is_file():
235
+ return True # Nothing to remove
236
+
237
+ data = _read_json_file(registry_file)
238
+ if not data:
239
+ return True
240
+
241
+ agents = data.get("agents", [])
242
+ data["agents"] = [a for a in agents if a.get("id") != agent_id]
243
+
244
+ return _write_json_file(registry_file, data)
245
+
246
+
247
+ def registry_remove_by_worktree(
248
+ worktree_path: str,
249
+ repo_root: Path | None = None
250
+ ) -> bool:
251
+ """Remove agent by worktree path.
252
+
253
+ Args:
254
+ worktree_path: Worktree path.
255
+ repo_root: Repository root path. Defaults to auto-detected.
256
+
257
+ Returns:
258
+ True on success.
259
+ """
260
+ if repo_root is None:
261
+ repo_root = get_repo_root()
262
+
263
+ registry_file = registry_get_file(repo_root)
264
+ if not registry_file or not registry_file.is_file():
265
+ return True # Nothing to remove
266
+
267
+ data = _read_json_file(registry_file)
268
+ if not data:
269
+ return True
270
+
271
+ agents = data.get("agents", [])
272
+ data["agents"] = [a for a in agents if a.get("worktree_path") != worktree_path]
273
+
274
+ return _write_json_file(registry_file, data)
275
+
276
+
277
+ def registry_add_agent(
278
+ agent_id: str,
279
+ worktree_path: str,
280
+ pid: int,
281
+ task_dir: str,
282
+ repo_root: Path | None = None,
283
+ platform: str = "claude",
284
+ ) -> bool:
285
+ """Add agent to registry (replaces if same ID exists).
286
+
287
+ Args:
288
+ agent_id: Agent ID.
289
+ worktree_path: Worktree path.
290
+ pid: Process ID.
291
+ task_dir: Task directory path.
292
+ repo_root: Repository root path. Defaults to auto-detected.
293
+ platform: Platform used (e.g., 'claude', 'opencode', 'codex'). Defaults to 'claude'.
294
+
295
+ Returns:
296
+ True on success.
297
+ """
298
+ if repo_root is None:
299
+ repo_root = get_repo_root()
300
+
301
+ registry_file = _ensure_registry(repo_root)
302
+ if not registry_file:
303
+ return False
304
+
305
+ data = _read_json_file(registry_file)
306
+ if not data:
307
+ data = {"agents": []}
308
+
309
+ # Remove existing agent with same ID
310
+ agents = data.get("agents", [])
311
+ agents = [a for a in agents if a.get("id") != agent_id]
312
+
313
+ # Create new agent record
314
+ started_at = datetime.now().isoformat()
315
+ new_agent = {
316
+ "id": agent_id,
317
+ "worktree_path": worktree_path,
318
+ "pid": pid,
319
+ "started_at": started_at,
320
+ "task_dir": task_dir,
321
+ "platform": platform,
322
+ }
323
+
324
+ agents.append(new_agent)
325
+ data["agents"] = agents
326
+
327
+ return _write_json_file(registry_file, data)
328
+
329
+
330
+ def registry_list_agents(repo_root: Path | None = None) -> list[dict]:
331
+ """List all agents.
332
+
333
+ Args:
334
+ repo_root: Repository root path. Defaults to auto-detected.
335
+
336
+ Returns:
337
+ List of agent dicts.
338
+ """
339
+ if repo_root is None:
340
+ repo_root = get_repo_root()
341
+
342
+ registry_file = registry_get_file(repo_root)
343
+ if not registry_file or not registry_file.is_file():
344
+ return []
345
+
346
+ data = _read_json_file(registry_file)
347
+ if not data:
348
+ return []
349
+
350
+ return data.get("agents", [])
351
+
352
+
353
+ # =============================================================================
354
+ # Main Entry (for testing)
355
+ # =============================================================================
356
+
357
+ if __name__ == "__main__":
358
+ import json as json_mod
359
+
360
+ repo = get_repo_root()
361
+ print(f"Repository root: {repo}")
362
+ print(f"Registry file: {registry_get_file(repo)}")
363
+ print()
364
+ print("Agents:")
365
+ agents = registry_list_agents(repo)
366
+ print(json_mod.dumps(agents, indent=2))
@@ -0,0 +1,255 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Task queue utility functions.
4
+
5
+ Provides:
6
+ list_tasks_by_status - List tasks by status
7
+ list_pending_tasks - List tasks with pending status
8
+ list_tasks_by_assignee - List tasks by assignee
9
+ list_my_tasks - List tasks assigned to current developer
10
+ get_task_stats - Get P0/P1/P2/P3 counts
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import json
16
+ from pathlib import Path
17
+
18
+ from .paths import (
19
+ FILE_TASK_JSON,
20
+ get_repo_root,
21
+ get_developer,
22
+ get_tasks_dir,
23
+ )
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
+ # =============================================================================
35
+ # Public Functions
36
+ # =============================================================================
37
+
38
+ def list_tasks_by_status(
39
+ filter_status: str | None = None,
40
+ repo_root: Path | None = None
41
+ ) -> list[dict]:
42
+ """List tasks by status.
43
+
44
+ Args:
45
+ filter_status: Optional status filter.
46
+ repo_root: Repository root path. Defaults to auto-detected.
47
+
48
+ Returns:
49
+ List of task info dicts with keys: priority, id, title, status, assignee.
50
+ """
51
+ if repo_root is None:
52
+ repo_root = get_repo_root()
53
+
54
+ tasks_dir = get_tasks_dir(repo_root)
55
+ results = []
56
+
57
+ if not tasks_dir.is_dir():
58
+ return results
59
+
60
+ for d in tasks_dir.iterdir():
61
+ if not d.is_dir() or d.name == "archive":
62
+ continue
63
+
64
+ task_json = d / FILE_TASK_JSON
65
+ if not task_json.is_file():
66
+ continue
67
+
68
+ data = _read_json_file(task_json)
69
+ if not data:
70
+ continue
71
+
72
+ task_id = data.get("id", "")
73
+ title = data.get("title") or data.get("name", "")
74
+ priority = data.get("priority", "P2")
75
+ status = data.get("status", "planning")
76
+ assignee = data.get("assignee", "-")
77
+
78
+ # Apply filter
79
+ if filter_status and status != filter_status:
80
+ continue
81
+
82
+ results.append({
83
+ "priority": priority,
84
+ "id": task_id,
85
+ "title": title,
86
+ "status": status,
87
+ "assignee": assignee,
88
+ "dir": d.name,
89
+ })
90
+
91
+ return results
92
+
93
+
94
+ def list_pending_tasks(repo_root: Path | None = None) -> list[dict]:
95
+ """List pending tasks.
96
+
97
+ Args:
98
+ repo_root: Repository root path. Defaults to auto-detected.
99
+
100
+ Returns:
101
+ List of task info dicts.
102
+ """
103
+ return list_tasks_by_status("planning", repo_root)
104
+
105
+
106
+ def list_tasks_by_assignee(
107
+ assignee: str,
108
+ filter_status: str | None = None,
109
+ repo_root: Path | None = None
110
+ ) -> list[dict]:
111
+ """List tasks assigned to a specific developer.
112
+
113
+ Args:
114
+ assignee: Developer name.
115
+ filter_status: Optional status filter.
116
+ repo_root: Repository root path. Defaults to auto-detected.
117
+
118
+ Returns:
119
+ List of task info dicts.
120
+ """
121
+ if repo_root is None:
122
+ repo_root = get_repo_root()
123
+
124
+ tasks_dir = get_tasks_dir(repo_root)
125
+ results = []
126
+
127
+ if not tasks_dir.is_dir():
128
+ return results
129
+
130
+ for d in tasks_dir.iterdir():
131
+ if not d.is_dir() or d.name == "archive":
132
+ continue
133
+
134
+ task_json = d / FILE_TASK_JSON
135
+ if not task_json.is_file():
136
+ continue
137
+
138
+ data = _read_json_file(task_json)
139
+ if not data:
140
+ continue
141
+
142
+ task_assignee = data.get("assignee", "-")
143
+
144
+ # Apply assignee filter
145
+ if task_assignee != assignee:
146
+ continue
147
+
148
+ task_id = data.get("id", "")
149
+ title = data.get("title") or data.get("name", "")
150
+ priority = data.get("priority", "P2")
151
+ status = data.get("status", "planning")
152
+
153
+ # Apply status filter
154
+ if filter_status and status != filter_status:
155
+ continue
156
+
157
+ results.append({
158
+ "priority": priority,
159
+ "id": task_id,
160
+ "title": title,
161
+ "status": status,
162
+ "assignee": task_assignee,
163
+ "dir": d.name,
164
+ })
165
+
166
+ return results
167
+
168
+
169
+ def list_my_tasks(
170
+ filter_status: str | None = None,
171
+ repo_root: Path | None = None
172
+ ) -> list[dict]:
173
+ """List tasks assigned to current developer.
174
+
175
+ Args:
176
+ filter_status: Optional status filter.
177
+ repo_root: Repository root path. Defaults to auto-detected.
178
+
179
+ Returns:
180
+ List of task info dicts.
181
+
182
+ Raises:
183
+ ValueError: If developer not set.
184
+ """
185
+ if repo_root is None:
186
+ repo_root = get_repo_root()
187
+
188
+ developer = get_developer(repo_root)
189
+ if not developer:
190
+ raise ValueError("Developer not set")
191
+
192
+ return list_tasks_by_assignee(developer, filter_status, repo_root)
193
+
194
+
195
+ def get_task_stats(repo_root: Path | None = None) -> dict[str, int]:
196
+ """Get task statistics.
197
+
198
+ Args:
199
+ repo_root: Repository root path. Defaults to auto-detected.
200
+
201
+ Returns:
202
+ Dict with keys: P0, P1, P2, P3, Total.
203
+ """
204
+ if repo_root is None:
205
+ repo_root = get_repo_root()
206
+
207
+ tasks_dir = get_tasks_dir(repo_root)
208
+ stats = {"P0": 0, "P1": 0, "P2": 0, "P3": 0, "Total": 0}
209
+
210
+ if not tasks_dir.is_dir():
211
+ return stats
212
+
213
+ for d in tasks_dir.iterdir():
214
+ if not d.is_dir() or d.name == "archive":
215
+ continue
216
+
217
+ task_json = d / FILE_TASK_JSON
218
+ if not task_json.is_file():
219
+ continue
220
+
221
+ data = _read_json_file(task_json)
222
+ if not data:
223
+ continue
224
+
225
+ priority = data.get("priority", "P2")
226
+ if priority in stats:
227
+ stats[priority] += 1
228
+ stats["Total"] += 1
229
+
230
+ return stats
231
+
232
+
233
+ def format_task_stats(stats: dict[str, int]) -> str:
234
+ """Format task stats as string.
235
+
236
+ Args:
237
+ stats: Stats dict from get_task_stats.
238
+
239
+ Returns:
240
+ Formatted string like "P0:0 P1:1 P2:2 P3:0 Total:3".
241
+ """
242
+ return f"P0:{stats['P0']} P1:{stats['P1']} P2:{stats['P2']} P3:{stats['P3']} Total:{stats['Total']}"
243
+
244
+
245
+ # =============================================================================
246
+ # Main Entry (for testing)
247
+ # =============================================================================
248
+
249
+ if __name__ == "__main__":
250
+ stats = get_task_stats()
251
+ print(format_task_stats(stats))
252
+ print()
253
+ print("Pending tasks:")
254
+ for task in list_pending_tasks():
255
+ print(f" {task['priority']}|{task['id']}|{task['title']}|{task['status']}|{task['assignee']}")