@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,461 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Multi-Agent Pipeline: Start Worktree Agent.
|
|
4
|
+
|
|
5
|
+
Usage: python3 start.py <task-dir>
|
|
6
|
+
Example: python3 start.py .trellis/tasks/01-21-my-task
|
|
7
|
+
|
|
8
|
+
This script:
|
|
9
|
+
1. Creates worktree (if not exists) with dependency install
|
|
10
|
+
2. Copies environment files (from worktree.yaml config)
|
|
11
|
+
3. Sets .current-task in worktree
|
|
12
|
+
4. Starts claude agent in background
|
|
13
|
+
5. Registers agent to registry.json
|
|
14
|
+
|
|
15
|
+
Prerequisites:
|
|
16
|
+
- task.json must exist with 'branch' field
|
|
17
|
+
- agents/dispatch.md must exist (in .claude/, .cursor/, .iflow/, or .opencode/)
|
|
18
|
+
|
|
19
|
+
Configuration: .trellis/worktree.yaml
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import json
|
|
25
|
+
import os
|
|
26
|
+
import shutil
|
|
27
|
+
import subprocess
|
|
28
|
+
import sys
|
|
29
|
+
import uuid
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
|
|
32
|
+
# Add parent directory to path for imports
|
|
33
|
+
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
34
|
+
|
|
35
|
+
from common.cli_adapter import CLIAdapter, get_cli_adapter
|
|
36
|
+
from common.git_context import _run_git_command
|
|
37
|
+
from common.paths import (
|
|
38
|
+
DIR_WORKFLOW,
|
|
39
|
+
FILE_CURRENT_TASK,
|
|
40
|
+
FILE_TASK_JSON,
|
|
41
|
+
get_repo_root,
|
|
42
|
+
)
|
|
43
|
+
from common.registry import (
|
|
44
|
+
registry_add_agent,
|
|
45
|
+
registry_get_file,
|
|
46
|
+
)
|
|
47
|
+
from common.worktree import (
|
|
48
|
+
get_worktree_base_dir,
|
|
49
|
+
get_worktree_config,
|
|
50
|
+
get_worktree_copy_files,
|
|
51
|
+
get_worktree_post_create_hooks,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# =============================================================================
|
|
55
|
+
# Colors
|
|
56
|
+
# =============================================================================
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class Colors:
|
|
60
|
+
RED = "\033[0;31m"
|
|
61
|
+
GREEN = "\033[0;32m"
|
|
62
|
+
YELLOW = "\033[1;33m"
|
|
63
|
+
BLUE = "\033[0;34m"
|
|
64
|
+
NC = "\033[0m"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def log_info(msg: str) -> None:
|
|
68
|
+
print(f"{Colors.BLUE}[INFO]{Colors.NC} {msg}")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def log_success(msg: str) -> None:
|
|
72
|
+
print(f"{Colors.GREEN}[SUCCESS]{Colors.NC} {msg}")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def log_warn(msg: str) -> None:
|
|
76
|
+
print(f"{Colors.YELLOW}[WARN]{Colors.NC} {msg}")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def log_error(msg: str) -> None:
|
|
80
|
+
print(f"{Colors.RED}[ERROR]{Colors.NC} {msg}")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# =============================================================================
|
|
84
|
+
# Helper Functions
|
|
85
|
+
# =============================================================================
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _read_json_file(path: Path) -> dict | None:
|
|
89
|
+
"""Read and parse a JSON file."""
|
|
90
|
+
try:
|
|
91
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
92
|
+
except (FileNotFoundError, json.JSONDecodeError, OSError):
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _write_json_file(path: Path, data: dict) -> bool:
|
|
97
|
+
"""Write dict to JSON file."""
|
|
98
|
+
try:
|
|
99
|
+
path.write_text(
|
|
100
|
+
json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8"
|
|
101
|
+
)
|
|
102
|
+
return True
|
|
103
|
+
except (OSError, IOError):
|
|
104
|
+
return False
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# =============================================================================
|
|
108
|
+
# Constants
|
|
109
|
+
# =============================================================================
|
|
110
|
+
|
|
111
|
+
DEFAULT_PLATFORM = "claude"
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# =============================================================================
|
|
115
|
+
# Main
|
|
116
|
+
# =============================================================================
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def main() -> int:
|
|
120
|
+
"""Main entry point."""
|
|
121
|
+
import argparse
|
|
122
|
+
|
|
123
|
+
parser = argparse.ArgumentParser(description="Multi-Agent Pipeline: Start Worktree Agent")
|
|
124
|
+
parser.add_argument("task_dir", help="Task directory path")
|
|
125
|
+
parser.add_argument(
|
|
126
|
+
"--platform", "-p",
|
|
127
|
+
choices=["claude", "cursor", "iflow", "opencode"],
|
|
128
|
+
default=DEFAULT_PLATFORM,
|
|
129
|
+
help="Platform to use (default: claude)"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
args = parser.parse_args()
|
|
133
|
+
task_dir_arg = args.task_dir
|
|
134
|
+
platform = args.platform
|
|
135
|
+
|
|
136
|
+
# Initialize CLI adapter
|
|
137
|
+
adapter = get_cli_adapter(platform)
|
|
138
|
+
|
|
139
|
+
project_root = get_repo_root()
|
|
140
|
+
|
|
141
|
+
# Normalize paths
|
|
142
|
+
if task_dir_arg.startswith("/"):
|
|
143
|
+
task_dir_relative = task_dir_arg[len(str(project_root)) + 1 :]
|
|
144
|
+
task_dir_abs = Path(task_dir_arg)
|
|
145
|
+
else:
|
|
146
|
+
task_dir_relative = task_dir_arg
|
|
147
|
+
task_dir_abs = project_root / task_dir_arg
|
|
148
|
+
|
|
149
|
+
task_json_path = task_dir_abs / FILE_TASK_JSON
|
|
150
|
+
|
|
151
|
+
# =============================================================================
|
|
152
|
+
# Validation
|
|
153
|
+
# =============================================================================
|
|
154
|
+
if not task_json_path.is_file():
|
|
155
|
+
log_error(f"task.json not found at {task_json_path}")
|
|
156
|
+
return 1
|
|
157
|
+
|
|
158
|
+
dispatch_md = adapter.get_agent_path("dispatch", project_root)
|
|
159
|
+
if not dispatch_md.is_file():
|
|
160
|
+
log_error(f"dispatch.md not found at {dispatch_md}")
|
|
161
|
+
log_info(f"Platform: {platform}")
|
|
162
|
+
return 1
|
|
163
|
+
|
|
164
|
+
config_file = get_worktree_config(project_root)
|
|
165
|
+
if not config_file.is_file():
|
|
166
|
+
log_error(f"worktree.yaml not found at {config_file}")
|
|
167
|
+
return 1
|
|
168
|
+
|
|
169
|
+
# =============================================================================
|
|
170
|
+
# Read Task Config
|
|
171
|
+
# =============================================================================
|
|
172
|
+
print()
|
|
173
|
+
print(f"{Colors.BLUE}=== Multi-Agent Pipeline: Start ==={Colors.NC}")
|
|
174
|
+
log_info(f"Task: {task_dir_abs}")
|
|
175
|
+
|
|
176
|
+
task_data = _read_json_file(task_json_path)
|
|
177
|
+
if not task_data:
|
|
178
|
+
log_error("Failed to read task.json")
|
|
179
|
+
return 1
|
|
180
|
+
|
|
181
|
+
branch = task_data.get("branch")
|
|
182
|
+
task_name = task_data.get("name")
|
|
183
|
+
task_status = task_data.get("status")
|
|
184
|
+
worktree_path = task_data.get("worktree_path")
|
|
185
|
+
|
|
186
|
+
# Check if task was rejected
|
|
187
|
+
if task_status == "rejected":
|
|
188
|
+
log_error("Task was rejected by Plan Agent")
|
|
189
|
+
rejected_file = task_dir_abs / "REJECTED.md"
|
|
190
|
+
if rejected_file.is_file():
|
|
191
|
+
print()
|
|
192
|
+
print(f"{Colors.YELLOW}Rejection reason:{Colors.NC}")
|
|
193
|
+
print(rejected_file.read_text(encoding="utf-8"))
|
|
194
|
+
print()
|
|
195
|
+
log_info(
|
|
196
|
+
"To retry, delete this directory and run plan.py again with revised requirements"
|
|
197
|
+
)
|
|
198
|
+
return 1
|
|
199
|
+
|
|
200
|
+
# Check if prd.md exists (plan completed successfully)
|
|
201
|
+
prd_file = task_dir_abs / "prd.md"
|
|
202
|
+
if not prd_file.is_file():
|
|
203
|
+
log_error("prd.md not found - Plan Agent may not have completed")
|
|
204
|
+
log_info(f"Check plan log: {task_dir_abs}/.plan-log")
|
|
205
|
+
return 1
|
|
206
|
+
|
|
207
|
+
if not branch:
|
|
208
|
+
log_error("branch field not set in task.json")
|
|
209
|
+
log_info("Please set branch field first, e.g.:")
|
|
210
|
+
log_info(
|
|
211
|
+
" jq '.branch = \"task/my-task\"' task.json > tmp && mv tmp task.json"
|
|
212
|
+
)
|
|
213
|
+
return 1
|
|
214
|
+
|
|
215
|
+
log_info(f"Branch: {branch}")
|
|
216
|
+
log_info(f"Name: {task_name}")
|
|
217
|
+
|
|
218
|
+
# =============================================================================
|
|
219
|
+
# Step 1: Create Worktree (if not exists)
|
|
220
|
+
# =============================================================================
|
|
221
|
+
if not worktree_path or not Path(worktree_path).is_dir():
|
|
222
|
+
log_info("Step 1: Creating worktree...")
|
|
223
|
+
|
|
224
|
+
# Record current branch as base_branch (PR target)
|
|
225
|
+
_, base_branch_out, _ = _run_git_command(
|
|
226
|
+
["branch", "--show-current"], cwd=project_root
|
|
227
|
+
)
|
|
228
|
+
base_branch = base_branch_out.strip()
|
|
229
|
+
log_info(f"Base branch (PR target): {base_branch}")
|
|
230
|
+
|
|
231
|
+
# Calculate worktree path
|
|
232
|
+
worktree_base = get_worktree_base_dir(project_root)
|
|
233
|
+
worktree_base.mkdir(parents=True, exist_ok=True)
|
|
234
|
+
worktree_base = worktree_base.resolve()
|
|
235
|
+
worktree_path_obj = worktree_base / branch
|
|
236
|
+
worktree_path = str(worktree_path_obj)
|
|
237
|
+
|
|
238
|
+
# Create parent directory
|
|
239
|
+
worktree_path_obj.parent.mkdir(parents=True, exist_ok=True)
|
|
240
|
+
|
|
241
|
+
# Create branch if not exists
|
|
242
|
+
ret, _, _ = _run_git_command(
|
|
243
|
+
["show-ref", "--verify", "--quiet", f"refs/heads/{branch}"],
|
|
244
|
+
cwd=project_root,
|
|
245
|
+
)
|
|
246
|
+
if ret == 0:
|
|
247
|
+
log_info("Branch exists, checking out...")
|
|
248
|
+
ret, _, err = _run_git_command(
|
|
249
|
+
["worktree", "add", worktree_path, branch], cwd=project_root
|
|
250
|
+
)
|
|
251
|
+
else:
|
|
252
|
+
log_info(f"Creating new branch: {branch}")
|
|
253
|
+
ret, _, err = _run_git_command(
|
|
254
|
+
["worktree", "add", "-b", branch, worktree_path], cwd=project_root
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
if ret != 0:
|
|
258
|
+
log_error(f"Failed to create worktree: {err}")
|
|
259
|
+
return 1
|
|
260
|
+
|
|
261
|
+
log_success(f"Worktree created: {worktree_path}")
|
|
262
|
+
|
|
263
|
+
# Update task.json with worktree_path and base_branch
|
|
264
|
+
task_data["worktree_path"] = worktree_path
|
|
265
|
+
task_data["base_branch"] = base_branch
|
|
266
|
+
_write_json_file(task_json_path, task_data)
|
|
267
|
+
|
|
268
|
+
# ----- Copy environment files -----
|
|
269
|
+
log_info("Copying environment files...")
|
|
270
|
+
copy_list = get_worktree_copy_files(project_root)
|
|
271
|
+
copy_count = 0
|
|
272
|
+
|
|
273
|
+
for item in copy_list:
|
|
274
|
+
if not item:
|
|
275
|
+
continue
|
|
276
|
+
|
|
277
|
+
source = project_root / item
|
|
278
|
+
target = Path(worktree_path) / item
|
|
279
|
+
|
|
280
|
+
if source.is_file():
|
|
281
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
282
|
+
shutil.copy2(str(source), str(target))
|
|
283
|
+
copy_count += 1
|
|
284
|
+
|
|
285
|
+
if copy_count > 0:
|
|
286
|
+
log_success(f"Copied {copy_count} file(s)")
|
|
287
|
+
|
|
288
|
+
# ----- Copy task directory (may not be committed yet) -----
|
|
289
|
+
log_info("Copying task directory...")
|
|
290
|
+
task_target_dir = Path(worktree_path) / task_dir_relative
|
|
291
|
+
task_target_dir.parent.mkdir(parents=True, exist_ok=True)
|
|
292
|
+
if task_target_dir.exists():
|
|
293
|
+
shutil.rmtree(str(task_target_dir))
|
|
294
|
+
shutil.copytree(str(task_dir_abs), str(task_target_dir))
|
|
295
|
+
log_success("Task directory copied to worktree")
|
|
296
|
+
|
|
297
|
+
# ----- Run post_create hooks -----
|
|
298
|
+
log_info("Running post_create hooks...")
|
|
299
|
+
post_create = get_worktree_post_create_hooks(project_root)
|
|
300
|
+
hook_count = 0
|
|
301
|
+
|
|
302
|
+
for cmd in post_create:
|
|
303
|
+
if not cmd:
|
|
304
|
+
continue
|
|
305
|
+
|
|
306
|
+
log_info(f" Running: {cmd}")
|
|
307
|
+
ret = subprocess.run(cmd, shell=True, cwd=worktree_path)
|
|
308
|
+
if ret.returncode != 0:
|
|
309
|
+
log_error(f"Hook failed: {cmd}")
|
|
310
|
+
return 1
|
|
311
|
+
hook_count += 1
|
|
312
|
+
|
|
313
|
+
if hook_count > 0:
|
|
314
|
+
log_success(f"Ran {hook_count} hook(s)")
|
|
315
|
+
else:
|
|
316
|
+
log_info(f"Step 1: Using existing worktree: {worktree_path}")
|
|
317
|
+
|
|
318
|
+
# =============================================================================
|
|
319
|
+
# Step 2: Set .current-task in Worktree
|
|
320
|
+
# =============================================================================
|
|
321
|
+
log_info("Step 2: Setting current task in worktree...")
|
|
322
|
+
|
|
323
|
+
worktree_workflow_dir = Path(worktree_path) / DIR_WORKFLOW
|
|
324
|
+
worktree_workflow_dir.mkdir(parents=True, exist_ok=True)
|
|
325
|
+
|
|
326
|
+
current_task_file = worktree_workflow_dir / FILE_CURRENT_TASK
|
|
327
|
+
current_task_file.write_text(task_dir_relative, encoding="utf-8")
|
|
328
|
+
log_success(f"Current task set: {task_dir_relative}")
|
|
329
|
+
|
|
330
|
+
# =============================================================================
|
|
331
|
+
# Step 3: Prepare and Start Claude Agent
|
|
332
|
+
# =============================================================================
|
|
333
|
+
log_info(f"Step 3: Starting {adapter.cli_name} agent...")
|
|
334
|
+
|
|
335
|
+
# Update task status
|
|
336
|
+
task_data["status"] = "in_progress"
|
|
337
|
+
_write_json_file(task_json_path, task_data)
|
|
338
|
+
|
|
339
|
+
log_file = Path(worktree_path) / ".agent-log"
|
|
340
|
+
session_id_file = Path(worktree_path) / ".session-id"
|
|
341
|
+
|
|
342
|
+
log_file.touch()
|
|
343
|
+
|
|
344
|
+
# Generate session ID for resume support (Claude Code only)
|
|
345
|
+
# OpenCode generates its own session ID, we'll extract it from logs later
|
|
346
|
+
if adapter.supports_session_id_on_create:
|
|
347
|
+
session_id = str(uuid.uuid4()).lower()
|
|
348
|
+
session_id_file.write_text(session_id, encoding="utf-8")
|
|
349
|
+
log_info(f"Session ID: {session_id}")
|
|
350
|
+
else:
|
|
351
|
+
session_id = None # Will be extracted from logs after startup
|
|
352
|
+
log_info("Session ID will be extracted from logs after startup")
|
|
353
|
+
|
|
354
|
+
# Get proxy environment variables
|
|
355
|
+
https_proxy = os.environ.get("https_proxy", "")
|
|
356
|
+
http_proxy = os.environ.get("http_proxy", "")
|
|
357
|
+
all_proxy = os.environ.get("all_proxy", "")
|
|
358
|
+
|
|
359
|
+
# Start agent in background (cross-platform, no shell script needed)
|
|
360
|
+
env = os.environ.copy()
|
|
361
|
+
env["https_proxy"] = https_proxy
|
|
362
|
+
env["http_proxy"] = http_proxy
|
|
363
|
+
env["all_proxy"] = all_proxy
|
|
364
|
+
|
|
365
|
+
# Set non-interactive env var based on platform
|
|
366
|
+
env.update(adapter.get_non_interactive_env())
|
|
367
|
+
|
|
368
|
+
# Build CLI command using adapter
|
|
369
|
+
# Note: Use explicit prompt to avoid confusion with CI/CD pipelines
|
|
370
|
+
# Also remind the model to follow its agent definition for better cross-model compatibility
|
|
371
|
+
cli_cmd = adapter.build_run_command(
|
|
372
|
+
agent="dispatch",
|
|
373
|
+
prompt="Follow your agent instructions to execute the task workflow. Start by reading .trellis/.current-task to get the task directory, then execute each action in task.json next_action array in order.",
|
|
374
|
+
session_id=session_id if adapter.supports_session_id_on_create else None,
|
|
375
|
+
skip_permissions=True,
|
|
376
|
+
verbose=True,
|
|
377
|
+
json_output=True,
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
with log_file.open("w") as log_f:
|
|
381
|
+
# Use shell=False for cross-platform compatibility
|
|
382
|
+
# creationflags for Windows, start_new_session for Unix
|
|
383
|
+
popen_kwargs = {
|
|
384
|
+
"stdout": log_f,
|
|
385
|
+
"stderr": subprocess.STDOUT,
|
|
386
|
+
"cwd": worktree_path,
|
|
387
|
+
"env": env,
|
|
388
|
+
}
|
|
389
|
+
if sys.platform == "win32":
|
|
390
|
+
popen_kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP
|
|
391
|
+
else:
|
|
392
|
+
popen_kwargs["start_new_session"] = True
|
|
393
|
+
|
|
394
|
+
process = subprocess.Popen(cli_cmd, **popen_kwargs)
|
|
395
|
+
agent_pid = process.pid
|
|
396
|
+
|
|
397
|
+
log_success(f"Agent started with PID: {agent_pid}")
|
|
398
|
+
|
|
399
|
+
# For OpenCode: extract session ID from logs after startup
|
|
400
|
+
if not adapter.supports_session_id_on_create:
|
|
401
|
+
import time
|
|
402
|
+
log_info("Waiting for session ID from logs...")
|
|
403
|
+
# Wait a bit for the log to have session ID
|
|
404
|
+
for _ in range(10): # Try for up to 5 seconds
|
|
405
|
+
time.sleep(0.5)
|
|
406
|
+
try:
|
|
407
|
+
log_content = log_file.read_text(encoding="utf-8", errors="replace")
|
|
408
|
+
session_id = adapter.extract_session_id_from_log(log_content)
|
|
409
|
+
if session_id:
|
|
410
|
+
session_id_file.write_text(session_id, encoding="utf-8")
|
|
411
|
+
log_success(f"Session ID extracted: {session_id}")
|
|
412
|
+
break
|
|
413
|
+
except Exception:
|
|
414
|
+
pass
|
|
415
|
+
else:
|
|
416
|
+
log_warn("Could not extract session ID from logs")
|
|
417
|
+
session_id = "unknown"
|
|
418
|
+
|
|
419
|
+
# =============================================================================
|
|
420
|
+
# Step 4: Register to Registry (in main repo, not worktree)
|
|
421
|
+
# =============================================================================
|
|
422
|
+
log_info("Step 4: Registering agent to registry...")
|
|
423
|
+
|
|
424
|
+
# Generate agent ID
|
|
425
|
+
task_id = task_data.get("id")
|
|
426
|
+
if not task_id:
|
|
427
|
+
task_id = branch.replace("/", "-")
|
|
428
|
+
|
|
429
|
+
registry_add_agent(
|
|
430
|
+
task_id, worktree_path, agent_pid, task_dir_relative, project_root, platform
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
log_success(f"Agent registered: {task_id}")
|
|
434
|
+
|
|
435
|
+
# =============================================================================
|
|
436
|
+
# Summary
|
|
437
|
+
# =============================================================================
|
|
438
|
+
print()
|
|
439
|
+
print(f"{Colors.GREEN}=== Agent Started ==={Colors.NC}")
|
|
440
|
+
print()
|
|
441
|
+
print(f" ID: {task_id}")
|
|
442
|
+
print(f" PID: {agent_pid}")
|
|
443
|
+
print(f" Session: {session_id}")
|
|
444
|
+
print(f" Worktree: {worktree_path}")
|
|
445
|
+
print(f" Task: {task_dir_relative}")
|
|
446
|
+
print(f" Log: {log_file}")
|
|
447
|
+
print(f" Registry: {registry_get_file(project_root)}")
|
|
448
|
+
print()
|
|
449
|
+
print(f"{Colors.YELLOW}To monitor:{Colors.NC} tail -f {log_file}")
|
|
450
|
+
print(f"{Colors.YELLOW}To stop:{Colors.NC} kill {agent_pid}")
|
|
451
|
+
if session_id and session_id != "unknown":
|
|
452
|
+
resume_cmd = adapter.get_resume_command_str(session_id, cwd=worktree_path)
|
|
453
|
+
print(f"{Colors.YELLOW}To resume:{Colors.NC} {resume_cmd}")
|
|
454
|
+
else:
|
|
455
|
+
print(f"{Colors.YELLOW}To resume:{Colors.NC} (session ID not available)")
|
|
456
|
+
|
|
457
|
+
return 0
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
if __name__ == "__main__":
|
|
461
|
+
sys.exit(main())
|