@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,817 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Multi-Agent Pipeline: Status Monitor.
4
+
5
+ Usage:
6
+ python3 status.py Show summary of all tasks (default)
7
+ python3 status.py -a <assignee> Filter tasks by assignee
8
+ python3 status.py --list List all worktrees and agents
9
+ python3 status.py --detail <task> Detailed task status
10
+ python3 status.py --watch <task> Watch agent log in real-time
11
+ python3 status.py --log <task> Show recent log entries
12
+ python3 status.py --registry Show agent registry
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import argparse
18
+ import json
19
+ import os
20
+ import subprocess
21
+ import sys
22
+ import time
23
+ from datetime import datetime
24
+ from pathlib import Path
25
+
26
+ # Add parent directory to path for imports
27
+ sys.path.insert(0, str(Path(__file__).parent.parent))
28
+
29
+ from common.cli_adapter import get_cli_adapter
30
+ from common.developer import ensure_developer
31
+ from common.paths import (
32
+ FILE_TASK_JSON,
33
+ get_repo_root,
34
+ get_tasks_dir,
35
+ )
36
+ from common.phase import get_phase_info
37
+ from common.task_queue import format_task_stats, get_task_stats
38
+ from common.worktree import get_agents_dir
39
+
40
+ # =============================================================================
41
+ # Colors
42
+ # =============================================================================
43
+
44
+
45
+ class Colors:
46
+ RED = "\033[0;31m"
47
+ GREEN = "\033[0;32m"
48
+ YELLOW = "\033[1;33m"
49
+ BLUE = "\033[0;34m"
50
+ CYAN = "\033[0;36m"
51
+ DIM = "\033[2m"
52
+ NC = "\033[0m"
53
+
54
+
55
+ # =============================================================================
56
+ # Helper Functions
57
+ # =============================================================================
58
+
59
+
60
+ def _read_json_file(path: Path) -> dict | None:
61
+ """Read and parse a JSON file."""
62
+ try:
63
+ return json.loads(path.read_text(encoding="utf-8"))
64
+ except (FileNotFoundError, json.JSONDecodeError, OSError):
65
+ return None
66
+
67
+
68
+ def is_running(pid: int | str | None) -> bool:
69
+ """Check if PID is running."""
70
+ if not pid:
71
+ return False
72
+ try:
73
+ pid_int = int(pid)
74
+ os.kill(pid_int, 0)
75
+ return True
76
+ except (ProcessLookupError, ValueError, PermissionError, TypeError):
77
+ return False
78
+
79
+
80
+ def status_color(status: str) -> str:
81
+ """Get status color."""
82
+ colors = {
83
+ "completed": Colors.GREEN,
84
+ "in_progress": Colors.BLUE,
85
+ "planning": Colors.YELLOW,
86
+ }
87
+ return colors.get(status, Colors.DIM)
88
+
89
+
90
+ def get_registry_file(repo_root: Path) -> Path | None:
91
+ """Get registry file path."""
92
+ agents_dir = get_agents_dir(repo_root)
93
+ if agents_dir:
94
+ return agents_dir / "registry.json"
95
+ return None
96
+
97
+
98
+ def find_agent(search: str, repo_root: Path) -> dict | None:
99
+ """Find agent by task name or ID."""
100
+ registry_file = get_registry_file(repo_root)
101
+ if not registry_file or not registry_file.is_file():
102
+ return None
103
+
104
+ data = _read_json_file(registry_file)
105
+ if not data:
106
+ return None
107
+
108
+ for agent in data.get("agents", []):
109
+ # Exact ID match
110
+ if agent.get("id") == search:
111
+ return agent
112
+ # Partial match on task_dir
113
+ task_dir = agent.get("task_dir", "")
114
+ if search in task_dir:
115
+ return agent
116
+
117
+ return None
118
+
119
+
120
+ def calc_elapsed(started: str | None) -> str:
121
+ """Calculate elapsed time from ISO timestamp."""
122
+ if not started:
123
+ return "N/A"
124
+
125
+ try:
126
+ # Parse ISO format
127
+ if "+" in started:
128
+ started = started.split("+")[0]
129
+ if "T" in started:
130
+ start_dt = datetime.fromisoformat(started)
131
+ else:
132
+ return "N/A"
133
+
134
+ now = datetime.now()
135
+ elapsed = (now - start_dt).total_seconds()
136
+
137
+ if elapsed < 60:
138
+ return f"{int(elapsed)}s"
139
+ elif elapsed < 3600:
140
+ mins = int(elapsed // 60)
141
+ secs = int(elapsed % 60)
142
+ return f"{mins}m {secs}s"
143
+ else:
144
+ hours = int(elapsed // 3600)
145
+ mins = int((elapsed % 3600) // 60)
146
+ return f"{hours}h {mins}m"
147
+ except (ValueError, TypeError):
148
+ return "N/A"
149
+
150
+
151
+ def count_modified_files(worktree: str) -> int:
152
+ """Count modified files in worktree."""
153
+ if not Path(worktree).is_dir():
154
+ return 0
155
+
156
+ try:
157
+ result = subprocess.run(
158
+ ["git", "status", "--short"],
159
+ cwd=worktree,
160
+ capture_output=True,
161
+ text=True,
162
+ encoding="utf-8",
163
+ errors="replace",
164
+ )
165
+ return len([line for line in result.stdout.splitlines() if line.strip()])
166
+ except Exception:
167
+ return 0
168
+
169
+
170
+ def tail_follow(file_path: Path) -> None:
171
+ """Follow a file like 'tail -f', cross-platform compatible."""
172
+ with open(file_path, "r", encoding="utf-8", errors="replace") as f:
173
+ # Seek to end of file
174
+ f.seek(0, 2)
175
+
176
+ while True:
177
+ line = f.readline()
178
+ if line:
179
+ print(line, end="", flush=True)
180
+ else:
181
+ time.sleep(0.1)
182
+
183
+
184
+ def get_last_tool(log_file: Path, platform: str = "claude") -> str | None:
185
+ """Get the last tool call from agent log.
186
+
187
+ Supports both Claude Code and OpenCode log formats.
188
+
189
+ Claude Code format:
190
+ {"type": "assistant", "message": {"content": [{"type": "tool_use", "name": "Read"}]}}
191
+
192
+ OpenCode format:
193
+ {"type": "tool_use", "tool": "bash", "state": {"status": "completed"}}
194
+ """
195
+ if not log_file.is_file():
196
+ return None
197
+
198
+ try:
199
+ lines = log_file.read_text(encoding="utf-8").splitlines()
200
+ for line in reversed(lines[-100:]):
201
+ try:
202
+ data = json.loads(line)
203
+
204
+ if platform == "opencode":
205
+ # OpenCode format: {"type": "tool_use", "tool": "bash", ...}
206
+ if data.get("type") == "tool_use":
207
+ return data.get("tool")
208
+ else:
209
+ # Claude Code format: {"type": "assistant", "message": {"content": [...]}}
210
+ if data.get("type") == "assistant":
211
+ content = data.get("message", {}).get("content", [])
212
+ for item in content:
213
+ if item.get("type") == "tool_use":
214
+ return item.get("name")
215
+ except json.JSONDecodeError:
216
+ continue
217
+ except Exception:
218
+ pass
219
+ return None
220
+
221
+
222
+ def get_last_message(log_file: Path, max_len: int = 100, platform: str = "claude") -> str | None:
223
+ """Get the last assistant text from agent log.
224
+
225
+ Supports both Claude Code and OpenCode log formats.
226
+
227
+ Claude Code format:
228
+ {"type": "assistant", "message": {"content": [{"type": "text", "text": "..."}]}}
229
+
230
+ OpenCode format:
231
+ {"type": "text", "text": "..."}
232
+ """
233
+ if not log_file.is_file():
234
+ return None
235
+
236
+ try:
237
+ lines = log_file.read_text(encoding="utf-8").splitlines()
238
+ for line in reversed(lines[-100:]):
239
+ try:
240
+ data = json.loads(line)
241
+
242
+ if platform == "opencode":
243
+ # OpenCode format: {"type": "text", "text": "..."}
244
+ if data.get("type") == "text":
245
+ text = data.get("text", "")
246
+ if text:
247
+ return text[:max_len]
248
+ else:
249
+ # Claude Code format: {"type": "assistant", "message": {"content": [...]}}
250
+ if data.get("type") == "assistant":
251
+ content = data.get("message", {}).get("content", [])
252
+ for item in content:
253
+ if item.get("type") == "text":
254
+ text = item.get("text", "")
255
+ if text:
256
+ return text[:max_len]
257
+ except json.JSONDecodeError:
258
+ continue
259
+ except Exception:
260
+ pass
261
+ return None
262
+
263
+
264
+ # =============================================================================
265
+ # Commands
266
+ # =============================================================================
267
+
268
+
269
+ def cmd_help() -> int:
270
+ """Show help."""
271
+ print("""Multi-Agent Pipeline: Status Monitor
272
+
273
+ Usage:
274
+ python3 status.py Show summary of all tasks
275
+ python3 status.py -a <assignee> Filter tasks by assignee
276
+ python3 status.py --list List all worktrees and agents
277
+ python3 status.py --detail <task> Detailed task status
278
+ python3 status.py --progress <task> Quick progress view with recent activity
279
+ python3 status.py --watch <task> Watch agent log in real-time
280
+ python3 status.py --log <task> Show recent log entries
281
+ python3 status.py --registry Show agent registry
282
+
283
+ Examples:
284
+ python3 status.py -a taosu
285
+ python3 status.py --detail my-task
286
+ python3 status.py --progress my-task
287
+ python3 status.py --watch 01-16-worktree-support
288
+ python3 status.py --log worktree-support
289
+ """)
290
+ return 0
291
+
292
+
293
+ def cmd_list(repo_root: Path) -> int:
294
+ """List worktrees and agents."""
295
+ print(f"{Colors.BLUE}=== Git Worktrees ==={Colors.NC}")
296
+ print()
297
+
298
+ subprocess.run(["git", "worktree", "list"], cwd=repo_root)
299
+ print()
300
+
301
+ print(f"{Colors.BLUE}=== Registered Agents ==={Colors.NC}")
302
+ print()
303
+
304
+ registry_file = get_registry_file(repo_root)
305
+ if not registry_file or not registry_file.is_file():
306
+ print(" (no registry found)")
307
+ return 0
308
+
309
+ data = _read_json_file(registry_file)
310
+ if not data or not data.get("agents"):
311
+ print(" (no agents registered)")
312
+ return 0
313
+
314
+ for agent in data["agents"]:
315
+ agent_id = agent.get("id", "?")
316
+ pid = agent.get("pid")
317
+ wt = agent.get("worktree_path", "?")
318
+ started = agent.get("started_at", "?")
319
+
320
+ if is_running(pid):
321
+ status_icon = f"{Colors.GREEN}●{Colors.NC}"
322
+ else:
323
+ status_icon = f"{Colors.RED}○{Colors.NC}"
324
+
325
+ print(f" {status_icon} {agent_id} (PID: {pid})")
326
+ print(f" {Colors.DIM}Worktree: {wt}{Colors.NC}")
327
+ print(f" {Colors.DIM}Started: {started}{Colors.NC}")
328
+ print()
329
+
330
+ return 0
331
+
332
+
333
+ def cmd_summary(repo_root: Path, filter_assignee: str | None = None) -> int:
334
+ """Show summary of all tasks."""
335
+ ensure_developer(repo_root)
336
+
337
+ tasks_dir = get_tasks_dir(repo_root)
338
+ if not tasks_dir.is_dir():
339
+ print("No tasks directory found")
340
+ return 0
341
+
342
+ registry_file = get_registry_file(repo_root)
343
+
344
+ # Count running agents
345
+ running_count = 0
346
+ total_agents = 0
347
+
348
+ if registry_file and registry_file.is_file():
349
+ data = _read_json_file(registry_file)
350
+ if data:
351
+ agents = data.get("agents", [])
352
+ total_agents = len(agents)
353
+ for agent in agents:
354
+ if is_running(agent.get("pid")):
355
+ running_count += 1
356
+
357
+ # Task queue stats
358
+ task_stats = get_task_stats(repo_root)
359
+
360
+ print(f"{Colors.BLUE}=== Multi-Agent Status ==={Colors.NC}")
361
+ print(
362
+ f" Agents: {Colors.GREEN}{running_count}{Colors.NC} running / {total_agents} registered"
363
+ )
364
+ print(f" Tasks: {format_task_stats(task_stats)}")
365
+ print()
366
+
367
+ # Process tasks
368
+ running_tasks = []
369
+ stopped_tasks = []
370
+ regular_tasks = []
371
+
372
+ registry_data = (
373
+ _read_json_file(registry_file)
374
+ if registry_file and registry_file.is_file()
375
+ else None
376
+ )
377
+
378
+ for d in sorted(tasks_dir.iterdir()):
379
+ if not d.is_dir() or d.name == "archive":
380
+ continue
381
+
382
+ name = d.name
383
+ task_json = d / FILE_TASK_JSON
384
+ status = "unknown"
385
+ assignee = "unassigned"
386
+ priority = "P2"
387
+
388
+ if task_json.is_file():
389
+ data = _read_json_file(task_json)
390
+ if data:
391
+ status = data.get("status", "unknown")
392
+ assignee = data.get("assignee", "unassigned")
393
+ priority = data.get("priority", "P2")
394
+
395
+ # Filter by assignee
396
+ if filter_assignee and assignee != filter_assignee:
397
+ continue
398
+
399
+ # Check agent status
400
+ agent_info = None
401
+ if registry_data:
402
+ for agent in registry_data.get("agents", []):
403
+ if name in agent.get("task_dir", ""):
404
+ agent_info = agent
405
+ break
406
+
407
+ if agent_info:
408
+ pid = agent_info.get("pid")
409
+ worktree = agent_info.get("worktree_path", "")
410
+ started = agent_info.get("started_at")
411
+ agent_platform = agent_info.get("platform", "claude")
412
+
413
+ if is_running(pid):
414
+ # Running agent
415
+ task_dir_rel = agent_info.get("task_dir", "")
416
+ worktree_task_json = Path(worktree) / task_dir_rel / "task.json"
417
+ phase_source = task_json
418
+ if worktree_task_json.is_file():
419
+ phase_source = worktree_task_json
420
+
421
+ phase_info_str = get_phase_info(phase_source)
422
+ elapsed = calc_elapsed(started)
423
+ modified = count_modified_files(worktree)
424
+
425
+ worktree_data = _read_json_file(phase_source)
426
+ branch = worktree_data.get("branch", "N/A") if worktree_data else "N/A"
427
+
428
+ log_file = Path(worktree) / ".agent-log"
429
+ last_tool = get_last_tool(log_file, platform=agent_platform)
430
+
431
+ running_tasks.append(
432
+ {
433
+ "name": name,
434
+ "priority": priority,
435
+ "assignee": assignee,
436
+ "phase_info": phase_info_str,
437
+ "elapsed": elapsed,
438
+ "branch": branch,
439
+ "modified": modified,
440
+ "last_tool": last_tool,
441
+ "pid": pid,
442
+ }
443
+ )
444
+ else:
445
+ # Stopped agent
446
+ task_dir_rel = agent_info.get("task_dir", "")
447
+ worktree_task_json = Path(worktree) / task_dir_rel / "task.json"
448
+ worktree_status = "unknown"
449
+
450
+ if worktree_task_json.is_file():
451
+ wt_data = _read_json_file(worktree_task_json)
452
+ if wt_data:
453
+ worktree_status = wt_data.get("status", "unknown")
454
+
455
+ session_id_file = Path(worktree) / ".session-id"
456
+ log_file = Path(worktree) / ".agent-log"
457
+
458
+ stopped_tasks.append(
459
+ {
460
+ "name": name,
461
+ "worktree": worktree,
462
+ "status": worktree_status,
463
+ "session_id_file": session_id_file,
464
+ "log_file": log_file,
465
+ "platform": agent_info.get("platform", "claude"),
466
+ }
467
+ )
468
+ else:
469
+ # Regular task
470
+ regular_tasks.append(
471
+ {
472
+ "name": name,
473
+ "status": status,
474
+ "priority": priority,
475
+ "assignee": assignee,
476
+ }
477
+ )
478
+
479
+ # Output running agents
480
+ if running_tasks:
481
+ print(f"{Colors.CYAN}Running Agents:{Colors.NC}")
482
+ for t in running_tasks:
483
+ priority_color = (
484
+ Colors.RED
485
+ if t["priority"] == "P0"
486
+ else (Colors.YELLOW if t["priority"] == "P1" else Colors.BLUE)
487
+ )
488
+ print(
489
+ f"{Colors.GREEN}▶{Colors.NC} {Colors.CYAN}{t['name']}{Colors.NC} {Colors.GREEN}[running]{Colors.NC} {priority_color}[{t['priority']}]{Colors.NC} @{t['assignee']}"
490
+ )
491
+ print(f" Phase: {t['phase_info']}")
492
+ print(f" Elapsed: {t['elapsed']}")
493
+ print(f" Branch: {Colors.DIM}{t['branch']}{Colors.NC}")
494
+ print(f" Modified: {t['modified']} file(s)")
495
+ if t["last_tool"]:
496
+ print(f" Activity: {Colors.YELLOW}{t['last_tool']}{Colors.NC}")
497
+ print(f" PID: {Colors.DIM}{t['pid']}{Colors.NC}")
498
+ print()
499
+
500
+ # Output stopped agents
501
+ if stopped_tasks:
502
+ print(f"{Colors.RED}Stopped Agents:{Colors.NC}")
503
+ for t in stopped_tasks:
504
+ if t["status"] == "completed":
505
+ print(
506
+ f"{Colors.GREEN}✓{Colors.NC} {t['name']} {Colors.GREEN}[completed]{Colors.NC}"
507
+ )
508
+ else:
509
+ if t["session_id_file"].is_file():
510
+ session_id = (
511
+ t["session_id_file"].read_text(encoding="utf-8").strip()
512
+ )
513
+ last_msg = get_last_message(t["log_file"], 150, platform=t.get("platform", "claude"))
514
+ print(
515
+ f"{Colors.RED}○{Colors.NC} {t['name']} {Colors.RED}[stopped]{Colors.NC}"
516
+ )
517
+ if last_msg:
518
+ print(f'{Colors.DIM}"{last_msg}"{Colors.NC}')
519
+ # Use CLI adapter for platform-specific resume command
520
+ adapter = get_cli_adapter(t.get("platform", "claude"))
521
+ resume_cmd = adapter.get_resume_command_str(session_id, cwd=t["worktree"])
522
+ print(f"{Colors.YELLOW}{resume_cmd}{Colors.NC}")
523
+ else:
524
+ print(
525
+ f"{Colors.RED}○{Colors.NC} {t['name']} {Colors.RED}[stopped]{Colors.NC} {Colors.DIM}(no session-id){Colors.NC}"
526
+ )
527
+ print()
528
+
529
+ # Separator
530
+ if (running_tasks or stopped_tasks) and regular_tasks:
531
+ print(f"{Colors.DIM}───────────────────────────────────────{Colors.NC}")
532
+ print()
533
+
534
+ # Output regular tasks grouped by assignee
535
+ if regular_tasks:
536
+ # Sort by assignee, priority, status
537
+ regular_tasks.sort(
538
+ key=lambda x: (
539
+ x["assignee"],
540
+ {"P0": 0, "P1": 1, "P2": 2, "P3": 3}.get(x["priority"], 2),
541
+ {"in_progress": 0, "planning": 1, "completed": 2}.get(x["status"], 1),
542
+ )
543
+ )
544
+
545
+ current_assignee = None
546
+ for t in regular_tasks:
547
+ if t["assignee"] != current_assignee:
548
+ if current_assignee is not None:
549
+ print()
550
+ print(f"{Colors.CYAN}@{t['assignee']}:{Colors.NC}")
551
+ current_assignee = t["assignee"]
552
+
553
+ color = status_color(t["status"])
554
+ priority_color = (
555
+ Colors.RED
556
+ if t["priority"] == "P0"
557
+ else (Colors.YELLOW if t["priority"] == "P1" else Colors.BLUE)
558
+ )
559
+ print(
560
+ f" {color}●{Colors.NC} {t['name']} ({t['status']}) {priority_color}[{t['priority']}]{Colors.NC}"
561
+ )
562
+
563
+ if running_tasks:
564
+ print()
565
+ print(f"{Colors.DIM}─────────────────────────────────────{Colors.NC}")
566
+ print(f"{Colors.DIM}Use --progress <name> for quick activity view{Colors.NC}")
567
+ print(f"{Colors.DIM}Use --detail <name> for more info{Colors.NC}")
568
+
569
+ print()
570
+ return 0
571
+
572
+
573
+ def cmd_detail(target: str, repo_root: Path) -> int:
574
+ """Show detailed task status."""
575
+ agent = find_agent(target, repo_root)
576
+ if not agent:
577
+ print(f"Agent not found: {target}")
578
+ return 1
579
+
580
+ agent_id = agent.get("id", "?")
581
+ pid = agent.get("pid")
582
+ worktree = agent.get("worktree_path", "?")
583
+ task_dir = agent.get("task_dir", "?")
584
+ started = agent.get("started_at", "?")
585
+ platform = agent.get("platform", "claude")
586
+
587
+ # Check for session-id
588
+ session_id = ""
589
+ session_id_file = Path(worktree) / ".session-id"
590
+ if session_id_file.is_file():
591
+ session_id = session_id_file.read_text(encoding="utf-8").strip()
592
+
593
+ print(f"{Colors.BLUE}=== Agent Detail: {agent_id} ==={Colors.NC}")
594
+ print()
595
+ print(f" ID: {agent_id}")
596
+ print(f" PID: {pid}")
597
+ print(f" Session: {session_id or 'N/A'}")
598
+ print(f" Worktree: {worktree}")
599
+ print(f" Task Dir: {task_dir}")
600
+ print(f" Started: {started}")
601
+ print()
602
+
603
+ # Status
604
+ if is_running(pid):
605
+ print(f" Status: {Colors.GREEN}Running{Colors.NC}")
606
+ else:
607
+ print(f" Status: {Colors.RED}Stopped{Colors.NC}")
608
+ if session_id:
609
+ print()
610
+ # Use CLI adapter for platform-specific resume command
611
+ adapter = get_cli_adapter(platform)
612
+ resume_cmd = adapter.get_resume_command_str(session_id, cwd=worktree)
613
+ print(f" {Colors.YELLOW}Resume:{Colors.NC} {resume_cmd}")
614
+
615
+ # Task info
616
+ task_json = repo_root / task_dir / "task.json"
617
+ if task_json.is_file():
618
+ print()
619
+ print(f"{Colors.BLUE}=== Task Info ==={Colors.NC}")
620
+ print()
621
+ data = _read_json_file(task_json)
622
+ if data:
623
+ print(f" Status: {data.get('status', 'unknown')}")
624
+ print(f" Branch: {data.get('branch', 'N/A')}")
625
+ print(f" Base Branch: {data.get('base_branch', 'N/A')}")
626
+
627
+ # Git changes
628
+ if Path(worktree).is_dir():
629
+ print()
630
+ print(f"{Colors.BLUE}=== Git Changes ==={Colors.NC}")
631
+ print()
632
+
633
+ result = subprocess.run(
634
+ ["git", "status", "--short"],
635
+ cwd=worktree,
636
+ capture_output=True,
637
+ text=True,
638
+ encoding="utf-8",
639
+ errors="replace",
640
+ )
641
+ changes = result.stdout.strip()
642
+ if changes:
643
+ for line in changes.splitlines()[:10]:
644
+ print(f" {line}")
645
+ total = len(changes.splitlines())
646
+ if total > 10:
647
+ print(f" ... and {total - 10} more")
648
+ else:
649
+ print(" (no changes)")
650
+
651
+ print()
652
+ return 0
653
+
654
+
655
+ def cmd_watch(target: str, repo_root: Path) -> int:
656
+ """Watch agent log in real-time."""
657
+ agent = find_agent(target, repo_root)
658
+ if not agent:
659
+ print(f"Agent not found: {target}")
660
+ return 1
661
+
662
+ worktree = agent.get("worktree_path", "")
663
+ log_file = Path(worktree) / ".agent-log"
664
+
665
+ if not log_file.is_file():
666
+ print(f"Log file not found: {log_file}")
667
+ return 1
668
+
669
+ print(f"{Colors.BLUE}Watching:{Colors.NC} {log_file}")
670
+ print(f"{Colors.DIM}Press Ctrl+C to stop{Colors.NC}")
671
+ print()
672
+
673
+ try:
674
+ tail_follow(log_file)
675
+ except KeyboardInterrupt:
676
+ print() # Clean newline after Ctrl+C
677
+ return 0
678
+
679
+
680
+ def cmd_log(target: str, repo_root: Path) -> int:
681
+ """Show recent log entries."""
682
+ agent = find_agent(target, repo_root)
683
+ if not agent:
684
+ print(f"Agent not found: {target}")
685
+ return 1
686
+
687
+ worktree = agent.get("worktree_path", "")
688
+ platform = agent.get("platform", "claude")
689
+ log_file = Path(worktree) / ".agent-log"
690
+
691
+ if not log_file.is_file():
692
+ print(f"Log file not found: {log_file}")
693
+ return 1
694
+
695
+ print(f"{Colors.BLUE}=== Recent Log: {target} ==={Colors.NC}")
696
+ print(f"{Colors.DIM}Platform: {platform}{Colors.NC}")
697
+ print()
698
+
699
+ lines = log_file.read_text(encoding="utf-8").splitlines()
700
+ for line in lines[-50:]:
701
+ try:
702
+ data = json.loads(line)
703
+ msg_type = data.get("type", "")
704
+
705
+ if platform == "opencode":
706
+ # OpenCode format
707
+ if msg_type == "text":
708
+ text = data.get("text", "")
709
+ if text:
710
+ display = text[:300]
711
+ if len(text) > 300:
712
+ display += "..."
713
+ print(f"{Colors.BLUE}[TEXT]{Colors.NC} {display}")
714
+ elif msg_type == "tool_use":
715
+ tool_name = data.get("tool", "unknown")
716
+ status = data.get("state", {}).get("status", "")
717
+ print(f"{Colors.YELLOW}[TOOL]{Colors.NC} {tool_name} ({status})")
718
+ elif msg_type == "step_start":
719
+ print(f"{Colors.CYAN}[STEP]{Colors.NC} Start")
720
+ elif msg_type == "step_finish":
721
+ reason = data.get("reason", "")
722
+ print(f"{Colors.CYAN}[STEP]{Colors.NC} Finish ({reason})")
723
+ elif msg_type == "error":
724
+ error_msg = data.get("message", "")
725
+ print(f"{Colors.RED}[ERROR]{Colors.NC} {error_msg}")
726
+ else:
727
+ # Claude Code format
728
+ if msg_type == "system":
729
+ subtype = data.get("subtype", "")
730
+ print(f"{Colors.CYAN}[SYSTEM]{Colors.NC} {subtype}")
731
+ elif msg_type == "user":
732
+ content = data.get("message", {}).get("content", "")
733
+ if content:
734
+ print(f"{Colors.GREEN}[USER]{Colors.NC} {content[:200]}")
735
+ elif msg_type == "assistant":
736
+ content = data.get("message", {}).get("content", [])
737
+ if content:
738
+ item = content[0]
739
+ text = item.get("text")
740
+ tool = item.get("name")
741
+ if text:
742
+ display = text[:300]
743
+ if len(text) > 300:
744
+ display += "..."
745
+ print(f"{Colors.BLUE}[ASSISTANT]{Colors.NC} {display}")
746
+ elif tool:
747
+ print(f"{Colors.YELLOW}[TOOL]{Colors.NC} {tool}")
748
+ elif msg_type == "result":
749
+ tool_name = data.get("tool", "unknown")
750
+ print(f"{Colors.DIM}[RESULT]{Colors.NC} {tool_name} completed")
751
+ except json.JSONDecodeError:
752
+ continue
753
+
754
+ return 0
755
+
756
+
757
+ def cmd_registry(repo_root: Path) -> int:
758
+ """Show agent registry."""
759
+ registry_file = get_registry_file(repo_root)
760
+
761
+ print(f"{Colors.BLUE}=== Agent Registry ==={Colors.NC}")
762
+ print()
763
+ print(f"File: {registry_file}")
764
+ print()
765
+
766
+ if registry_file and registry_file.is_file():
767
+ data = _read_json_file(registry_file)
768
+ if data:
769
+ print(json.dumps(data, indent=2))
770
+ else:
771
+ print("(registry not found)")
772
+
773
+ return 0
774
+
775
+
776
+ # =============================================================================
777
+ # Main
778
+ # =============================================================================
779
+
780
+
781
+ def main() -> int:
782
+ """Main entry point."""
783
+ parser = argparse.ArgumentParser(description="Multi-Agent Pipeline: Status Monitor")
784
+ parser.add_argument("-a", "--assignee", help="Filter by assignee")
785
+ parser.add_argument(
786
+ "--list", action="store_true", help="List all worktrees and agents"
787
+ )
788
+ parser.add_argument("--detail", metavar="TASK", help="Detailed task status")
789
+ parser.add_argument("--progress", metavar="TASK", help="Quick progress view")
790
+ parser.add_argument("--watch", metavar="TASK", help="Watch agent log")
791
+ parser.add_argument("--log", metavar="TASK", help="Show recent log entries")
792
+ parser.add_argument("--registry", action="store_true", help="Show agent registry")
793
+ parser.add_argument("target", nargs="?", help="Target task")
794
+
795
+ args = parser.parse_args()
796
+ repo_root = get_repo_root()
797
+
798
+ if args.list:
799
+ return cmd_list(repo_root)
800
+ elif args.detail:
801
+ return cmd_detail(args.detail, repo_root)
802
+ elif args.progress:
803
+ return cmd_detail(args.progress, repo_root) # Similar to detail
804
+ elif args.watch:
805
+ return cmd_watch(args.watch, repo_root)
806
+ elif args.log:
807
+ return cmd_log(args.log, repo_root)
808
+ elif args.registry:
809
+ return cmd_registry(repo_root)
810
+ elif args.target:
811
+ return cmd_detail(args.target, repo_root)
812
+ else:
813
+ return cmd_summary(repo_root, args.assignee)
814
+
815
+
816
+ if __name__ == "__main__":
817
+ sys.exit(main())