@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.
- package/LICENSE +21 -0
- package/README.md +159 -0
- package/bin/aim.js +3 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +89 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/commands/init.d.ts +13 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +513 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/update.d.ts +27 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +1275 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/configurators/claude.d.ts +32 -0
- package/dist/configurators/claude.d.ts.map +1 -0
- package/dist/configurators/claude.js +98 -0
- package/dist/configurators/claude.js.map +1 -0
- package/dist/configurators/index.d.ts +51 -0
- package/dist/configurators/index.d.ts.map +1 -0
- package/dist/configurators/index.js +113 -0
- package/dist/configurators/index.js.map +1 -0
- package/dist/configurators/shared.d.ts +12 -0
- package/dist/configurators/shared.d.ts.map +1 -0
- package/dist/configurators/shared.js +21 -0
- package/dist/configurators/shared.js.map +1 -0
- package/dist/configurators/workflow.d.ts +28 -0
- package/dist/configurators/workflow.d.ts.map +1 -0
- package/dist/configurators/workflow.js +147 -0
- package/dist/configurators/workflow.js.map +1 -0
- package/dist/constants/paths.d.ts +68 -0
- package/dist/constants/paths.d.ts.map +1 -0
- package/dist/constants/paths.js +77 -0
- package/dist/constants/paths.js.map +1 -0
- package/dist/constants/version.d.ts +9 -0
- package/dist/constants/version.d.ts.map +1 -0
- package/dist/constants/version.js +15 -0
- package/dist/constants/version.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/migrations/index.d.ts +54 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +160 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/migrations/manifests/0.0.1.json +9 -0
- package/dist/migrations/manifests/0.1.9.json +30 -0
- package/dist/migrations/manifests/0.2.0.json +49 -0
- package/dist/migrations/manifests/0.2.12.json +9 -0
- package/dist/migrations/manifests/0.2.13.json +9 -0
- package/dist/migrations/manifests/0.2.14.json +175 -0
- package/dist/migrations/manifests/0.2.15.json +33 -0
- package/dist/migrations/manifests/0.3.0-beta.0.json +278 -0
- package/dist/migrations/manifests/0.3.0-beta.1.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.10.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.11.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.12.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.13.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.14.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.15.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.16.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.2.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.3.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.4.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.5.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.6.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.7.json +11 -0
- package/dist/migrations/manifests/0.3.0-beta.8.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.9.json +9 -0
- package/dist/migrations/manifests/0.3.0-rc.0.json +9 -0
- package/dist/migrations/manifests/0.3.0-rc.1.json +9 -0
- package/dist/migrations/manifests/0.3.0-rc.2.json +9 -0
- package/dist/templates/CLAUDE.md +71 -0
- package/dist/templates/aim/gitignore.txt +29 -0
- package/dist/templates/aim/index.d.ts +49 -0
- package/dist/templates/aim/index.d.ts.map +1 -0
- package/dist/templates/aim/index.js +92 -0
- package/dist/templates/aim/index.js.map +1 -0
- package/dist/templates/aim/scripts/__init__.py +5 -0
- package/dist/templates/aim/scripts/add_session.py +392 -0
- package/dist/templates/aim/scripts/common/__init__.py +80 -0
- package/dist/templates/aim/scripts/common/cli_adapter.py +435 -0
- package/dist/templates/aim/scripts/common/developer.py +190 -0
- package/dist/templates/aim/scripts/common/git_context.py +383 -0
- package/dist/templates/aim/scripts/common/paths.py +347 -0
- package/dist/templates/aim/scripts/common/phase.py +253 -0
- package/dist/templates/aim/scripts/common/registry.py +366 -0
- package/dist/templates/aim/scripts/common/task_queue.py +255 -0
- package/dist/templates/aim/scripts/common/task_utils.py +178 -0
- package/dist/templates/aim/scripts/common/worktree.py +219 -0
- package/dist/templates/aim/scripts/create_bootstrap.py +290 -0
- package/dist/templates/aim/scripts/get_context.py +16 -0
- package/dist/templates/aim/scripts/get_developer.py +26 -0
- package/dist/templates/aim/scripts/init_developer.py +51 -0
- package/dist/templates/aim/scripts/multi_agent/__init__.py +5 -0
- package/dist/templates/aim/scripts/multi_agent/cleanup.py +403 -0
- package/dist/templates/aim/scripts/multi_agent/create_pr.py +329 -0
- package/dist/templates/aim/scripts/multi_agent/plan.py +233 -0
- package/dist/templates/aim/scripts/multi_agent/start.py +461 -0
- package/dist/templates/aim/scripts/multi_agent/status.py +817 -0
- package/dist/templates/aim/scripts/task.py +1068 -0
- package/dist/templates/aim/scripts-shell-archive/add-session.sh +384 -0
- package/dist/templates/aim/scripts-shell-archive/common/developer.sh +129 -0
- package/dist/templates/aim/scripts-shell-archive/common/git-context.sh +263 -0
- package/dist/templates/aim/scripts-shell-archive/common/paths.sh +208 -0
- package/dist/templates/aim/scripts-shell-archive/common/phase.sh +150 -0
- package/dist/templates/aim/scripts-shell-archive/common/registry.sh +247 -0
- package/dist/templates/aim/scripts-shell-archive/common/task-queue.sh +142 -0
- package/dist/templates/aim/scripts-shell-archive/common/task-utils.sh +151 -0
- package/dist/templates/aim/scripts-shell-archive/common/worktree.sh +128 -0
- package/dist/templates/aim/scripts-shell-archive/create-bootstrap.sh +299 -0
- package/dist/templates/aim/scripts-shell-archive/get-context.sh +7 -0
- package/dist/templates/aim/scripts-shell-archive/get-developer.sh +15 -0
- package/dist/templates/aim/scripts-shell-archive/init-developer.sh +34 -0
- package/dist/templates/aim/scripts-shell-archive/multi-agent/cleanup.sh +396 -0
- package/dist/templates/aim/scripts-shell-archive/multi-agent/create-pr.sh +241 -0
- package/dist/templates/aim/scripts-shell-archive/multi-agent/plan.sh +207 -0
- package/dist/templates/aim/scripts-shell-archive/multi-agent/start.sh +317 -0
- package/dist/templates/aim/scripts-shell-archive/multi-agent/status.sh +828 -0
- package/dist/templates/aim/scripts-shell-archive/task.sh +1204 -0
- package/dist/templates/aim/tasks/.gitkeep +0 -0
- package/dist/templates/aim/workflow.md +258 -0
- package/dist/templates/aim/worktree.yaml +47 -0
- package/dist/templates/claude/agents/check.md +122 -0
- package/dist/templates/claude/agents/debug.md +106 -0
- package/dist/templates/claude/agents/dispatch.md +230 -0
- package/dist/templates/claude/agents/implement.md +96 -0
- package/dist/templates/claude/agents/plan.md +396 -0
- package/dist/templates/claude/agents/research.md +120 -0
- package/dist/templates/claude/agents/story.md +53 -0
- package/dist/templates/claude/commands/aim/before-backend-dev.md +13 -0
- package/dist/templates/claude/commands/aim/before-frontend-dev.md +13 -0
- package/dist/templates/claude/commands/aim/break-loop.md +153 -0
- package/dist/templates/claude/commands/aim/check-backend.md +13 -0
- package/dist/templates/claude/commands/aim/check-cross-layer.md +153 -0
- package/dist/templates/claude/commands/aim/check-frontend.md +13 -0
- package/dist/templates/claude/commands/aim/check-story.md +59 -0
- package/dist/templates/claude/commands/aim/create-command.md +154 -0
- package/dist/templates/claude/commands/aim/export.md +187 -0
- package/dist/templates/claude/commands/aim/finish-work.md +104 -0
- package/dist/templates/claude/commands/aim/integrate-skill.md +219 -0
- package/dist/templates/claude/commands/aim/onboard.md +358 -0
- package/dist/templates/claude/commands/aim/parallel.md +217 -0
- package/dist/templates/claude/commands/aim/portrait.md +170 -0
- package/dist/templates/claude/commands/aim/record-session.md +92 -0
- package/dist/templates/claude/commands/aim/start.md +112 -0
- package/dist/templates/claude/commands/aim/story.md +140 -0
- package/dist/templates/claude/commands/aim/update-spec.md +285 -0
- package/dist/templates/claude/commands/aim/visualize.md +182 -0
- package/dist/templates/claude/commands/trellis/before-backend-dev.md +13 -0
- package/dist/templates/claude/commands/trellis/before-frontend-dev.md +13 -0
- package/dist/templates/claude/commands/trellis/break-loop.md +125 -0
- package/dist/templates/claude/commands/trellis/check-backend.md +13 -0
- package/dist/templates/claude/commands/trellis/check-cross-layer.md +153 -0
- package/dist/templates/claude/commands/trellis/check-frontend.md +13 -0
- package/dist/templates/claude/commands/trellis/create-command.md +154 -0
- package/dist/templates/claude/commands/trellis/finish-work.md +129 -0
- package/dist/templates/claude/commands/trellis/integrate-skill.md +219 -0
- package/dist/templates/claude/commands/trellis/onboard.md +358 -0
- package/dist/templates/claude/commands/trellis/parallel.md +193 -0
- package/dist/templates/claude/commands/trellis/record-session.md +62 -0
- package/dist/templates/claude/commands/trellis/start.md +280 -0
- package/dist/templates/claude/commands/trellis/update-spec.md +285 -0
- package/dist/templates/claude/hooks/inject-subagent-context.py +772 -0
- package/dist/templates/claude/hooks/ralph-loop.py +388 -0
- package/dist/templates/claude/hooks/session-start.py +142 -0
- package/dist/templates/claude/index.d.ts +54 -0
- package/dist/templates/claude/index.d.ts.map +1 -0
- package/dist/templates/claude/index.js +85 -0
- package/dist/templates/claude/index.js.map +1 -0
- package/dist/templates/claude/settings.json +41 -0
- package/dist/templates/extract.d.ts +68 -0
- package/dist/templates/extract.d.ts.map +1 -0
- package/dist/templates/extract.js +128 -0
- package/dist/templates/extract.js.map +1 -0
- package/dist/templates/markdown/agents.md +25 -0
- package/dist/templates/markdown/gitignore.txt +12 -0
- package/dist/templates/markdown/index.d.ts +32 -0
- package/dist/templates/markdown/index.d.ts.map +1 -0
- package/dist/templates/markdown/index.js +58 -0
- package/dist/templates/markdown/index.js.map +1 -0
- package/dist/templates/markdown/spec/backend/database-guidelines.md.txt +51 -0
- package/dist/templates/markdown/spec/backend/directory-structure.md.txt +54 -0
- package/dist/templates/markdown/spec/backend/error-handling.md.txt +51 -0
- package/dist/templates/markdown/spec/backend/index.md +40 -0
- package/dist/templates/markdown/spec/backend/index.md.txt +38 -0
- package/dist/templates/markdown/spec/backend/logging-guidelines.md.txt +51 -0
- package/dist/templates/markdown/spec/backend/quality-guidelines.md.txt +51 -0
- package/dist/templates/markdown/spec/backend/script-conventions.md +467 -0
- package/dist/templates/markdown/spec/frontend/component-guidelines.md.txt +59 -0
- package/dist/templates/markdown/spec/frontend/directory-structure.md.txt +54 -0
- package/dist/templates/markdown/spec/frontend/hook-guidelines.md.txt +51 -0
- package/dist/templates/markdown/spec/frontend/index.md.txt +39 -0
- package/dist/templates/markdown/spec/frontend/quality-guidelines.md.txt +51 -0
- package/dist/templates/markdown/spec/frontend/state-management.md.txt +51 -0
- package/dist/templates/markdown/spec/frontend/type-safety.md.txt +51 -0
- package/dist/templates/markdown/spec/guides/code-reuse-thinking-guide.md +118 -0
- package/dist/templates/markdown/spec/guides/code-reuse-thinking-guide.md.txt +92 -0
- package/dist/templates/markdown/spec/guides/cross-layer-thinking-guide.md.txt +94 -0
- package/dist/templates/markdown/spec/guides/cross-platform-thinking-guide.md +394 -0
- package/dist/templates/markdown/spec/guides/cross-platform-thinking-guide.md.txt +319 -0
- package/dist/templates/markdown/spec/guides/index.md.txt +89 -0
- package/dist/templates/markdown/spec/story/character.md.txt +95 -0
- package/dist/templates/markdown/spec/story/index.md.txt +31 -0
- package/dist/templates/markdown/spec/story/script.md.txt +313 -0
- package/dist/templates/markdown/spec/story/world.md.txt +92 -0
- package/dist/templates/markdown/workspace-index.md +123 -0
- package/dist/templates/markdown/worktree.yaml.txt +58 -0
- package/dist/templates/trellis/gitignore.txt +29 -0
- package/dist/templates/trellis/index.d.ts +49 -0
- package/dist/templates/trellis/index.d.ts.map +1 -0
- package/dist/templates/trellis/index.js +92 -0
- package/dist/templates/trellis/index.js.map +1 -0
- package/dist/templates/trellis/scripts/__init__.py +5 -0
- package/dist/templates/trellis/scripts/add_session.py +392 -0
- package/dist/templates/trellis/scripts/common/__init__.py +80 -0
- package/dist/templates/trellis/scripts/common/cli_adapter.py +435 -0
- package/dist/templates/trellis/scripts/common/developer.py +190 -0
- package/dist/templates/trellis/scripts/common/git_context.py +383 -0
- package/dist/templates/trellis/scripts/common/paths.py +347 -0
- package/dist/templates/trellis/scripts/common/phase.py +253 -0
- package/dist/templates/trellis/scripts/common/registry.py +366 -0
- package/dist/templates/trellis/scripts/common/task_queue.py +255 -0
- package/dist/templates/trellis/scripts/common/task_utils.py +178 -0
- package/dist/templates/trellis/scripts/common/worktree.py +219 -0
- package/dist/templates/trellis/scripts/create_bootstrap.py +290 -0
- package/dist/templates/trellis/scripts/get_context.py +16 -0
- package/dist/templates/trellis/scripts/get_developer.py +26 -0
- package/dist/templates/trellis/scripts/init_developer.py +51 -0
- package/dist/templates/trellis/scripts/multi_agent/__init__.py +5 -0
- package/dist/templates/trellis/scripts/multi_agent/cleanup.py +403 -0
- package/dist/templates/trellis/scripts/multi_agent/create_pr.py +329 -0
- package/dist/templates/trellis/scripts/multi_agent/plan.py +233 -0
- package/dist/templates/trellis/scripts/multi_agent/start.py +461 -0
- package/dist/templates/trellis/scripts/multi_agent/status.py +817 -0
- package/dist/templates/trellis/scripts/task.py +1056 -0
- package/dist/templates/trellis/scripts-shell-archive/add-session.sh +384 -0
- package/dist/templates/trellis/scripts-shell-archive/common/developer.sh +129 -0
- package/dist/templates/trellis/scripts-shell-archive/common/git-context.sh +263 -0
- package/dist/templates/trellis/scripts-shell-archive/common/paths.sh +208 -0
- package/dist/templates/trellis/scripts-shell-archive/common/phase.sh +150 -0
- package/dist/templates/trellis/scripts-shell-archive/common/registry.sh +247 -0
- package/dist/templates/trellis/scripts-shell-archive/common/task-queue.sh +142 -0
- package/dist/templates/trellis/scripts-shell-archive/common/task-utils.sh +151 -0
- package/dist/templates/trellis/scripts-shell-archive/common/worktree.sh +128 -0
- package/dist/templates/trellis/scripts-shell-archive/create-bootstrap.sh +299 -0
- package/dist/templates/trellis/scripts-shell-archive/get-context.sh +7 -0
- package/dist/templates/trellis/scripts-shell-archive/get-developer.sh +15 -0
- package/dist/templates/trellis/scripts-shell-archive/init-developer.sh +34 -0
- package/dist/templates/trellis/scripts-shell-archive/multi-agent/cleanup.sh +396 -0
- package/dist/templates/trellis/scripts-shell-archive/multi-agent/create-pr.sh +241 -0
- package/dist/templates/trellis/scripts-shell-archive/multi-agent/plan.sh +207 -0
- package/dist/templates/trellis/scripts-shell-archive/multi-agent/start.sh +317 -0
- package/dist/templates/trellis/scripts-shell-archive/multi-agent/status.sh +828 -0
- package/dist/templates/trellis/scripts-shell-archive/task.sh +1204 -0
- package/dist/templates/trellis/tasks/.gitkeep +0 -0
- package/dist/templates/trellis/workflow.md +416 -0
- package/dist/templates/trellis/worktree.yaml +47 -0
- package/dist/types/ai-tools.d.ts +48 -0
- package/dist/types/ai-tools.d.ts.map +1 -0
- package/dist/types/ai-tools.js +32 -0
- package/dist/types/ai-tools.js.map +1 -0
- package/dist/types/migration.d.ts +86 -0
- package/dist/types/migration.d.ts.map +1 -0
- package/dist/types/migration.js +8 -0
- package/dist/types/migration.js.map +1 -0
- package/dist/utils/compare-versions.d.ts +12 -0
- package/dist/utils/compare-versions.d.ts.map +1 -0
- package/dist/utils/compare-versions.js +76 -0
- package/dist/utils/compare-versions.js.map +1 -0
- package/dist/utils/file-writer.d.ts +23 -0
- package/dist/utils/file-writer.d.ts.map +1 -0
- package/dist/utils/file-writer.js +140 -0
- package/dist/utils/file-writer.js.map +1 -0
- package/dist/utils/project-detector.d.ts +16 -0
- package/dist/utils/project-detector.d.ts.map +1 -0
- package/dist/utils/project-detector.js +188 -0
- package/dist/utils/project-detector.js.map +1 -0
- package/dist/utils/template-fetcher.d.ts +51 -0
- package/dist/utils/template-fetcher.d.ts.map +1 -0
- package/dist/utils/template-fetcher.js +174 -0
- package/dist/utils/template-fetcher.js.map +1 -0
- package/dist/utils/template-hash.d.ts +78 -0
- package/dist/utils/template-hash.d.ts.map +1 -0
- package/dist/utils/template-hash.js +239 -0
- package/dist/utils/template-hash.js.map +1 -0
- 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())
|