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