@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,347 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Common path utilities for Trellis workflow.
|
|
4
|
+
|
|
5
|
+
Provides:
|
|
6
|
+
get_repo_root - Get repository root directory
|
|
7
|
+
get_developer - Get developer name
|
|
8
|
+
get_workspace_dir - Get developer workspace directory
|
|
9
|
+
get_tasks_dir - Get tasks directory
|
|
10
|
+
get_active_journal_file - Get current journal file
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import re
|
|
16
|
+
from datetime import datetime
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# =============================================================================
|
|
21
|
+
# Path Constants (change here to rename directories)
|
|
22
|
+
# =============================================================================
|
|
23
|
+
|
|
24
|
+
# Directory names
|
|
25
|
+
DIR_WORKFLOW = ".trellis"
|
|
26
|
+
DIR_WORKSPACE = "workspace"
|
|
27
|
+
DIR_TASKS = "tasks"
|
|
28
|
+
DIR_ARCHIVE = "archive"
|
|
29
|
+
DIR_SPEC = "spec"
|
|
30
|
+
DIR_SCRIPTS = "scripts"
|
|
31
|
+
|
|
32
|
+
# File names
|
|
33
|
+
FILE_DEVELOPER = ".developer"
|
|
34
|
+
FILE_CURRENT_TASK = ".current-task"
|
|
35
|
+
FILE_TASK_JSON = "task.json"
|
|
36
|
+
FILE_JOURNAL_PREFIX = "journal-"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# =============================================================================
|
|
40
|
+
# Repository Root
|
|
41
|
+
# =============================================================================
|
|
42
|
+
|
|
43
|
+
def get_repo_root(start_path: Path | None = None) -> Path:
|
|
44
|
+
"""Find the nearest directory containing .trellis/ folder.
|
|
45
|
+
|
|
46
|
+
This handles nested git repos correctly (e.g., test project inside another repo).
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
start_path: Starting directory to search from. Defaults to current directory.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Path to repository root, or current directory if no .trellis/ found.
|
|
53
|
+
"""
|
|
54
|
+
current = (start_path or Path.cwd()).resolve()
|
|
55
|
+
|
|
56
|
+
while current != current.parent:
|
|
57
|
+
if (current / DIR_WORKFLOW).is_dir():
|
|
58
|
+
return current
|
|
59
|
+
current = current.parent
|
|
60
|
+
|
|
61
|
+
# Fallback to current directory if no .trellis/ found
|
|
62
|
+
return Path.cwd().resolve()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# =============================================================================
|
|
66
|
+
# Developer
|
|
67
|
+
# =============================================================================
|
|
68
|
+
|
|
69
|
+
def get_developer(repo_root: Path | None = None) -> str | None:
|
|
70
|
+
"""Get developer name from .developer file.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
repo_root: Repository root path. Defaults to auto-detected.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Developer name or None if not initialized.
|
|
77
|
+
"""
|
|
78
|
+
if repo_root is None:
|
|
79
|
+
repo_root = get_repo_root()
|
|
80
|
+
|
|
81
|
+
dev_file = repo_root / DIR_WORKFLOW / FILE_DEVELOPER
|
|
82
|
+
|
|
83
|
+
if not dev_file.is_file():
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
content = dev_file.read_text(encoding="utf-8")
|
|
88
|
+
for line in content.splitlines():
|
|
89
|
+
if line.startswith("name="):
|
|
90
|
+
return line.split("=", 1)[1].strip()
|
|
91
|
+
except (OSError, IOError):
|
|
92
|
+
pass
|
|
93
|
+
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def check_developer(repo_root: Path | None = None) -> bool:
|
|
98
|
+
"""Check if developer is initialized.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
repo_root: Repository root path. Defaults to auto-detected.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
True if developer is initialized.
|
|
105
|
+
"""
|
|
106
|
+
return get_developer(repo_root) is not None
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# =============================================================================
|
|
110
|
+
# Tasks Directory
|
|
111
|
+
# =============================================================================
|
|
112
|
+
|
|
113
|
+
def get_tasks_dir(repo_root: Path | None = None) -> Path:
|
|
114
|
+
"""Get tasks directory path.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
repo_root: Repository root path. Defaults to auto-detected.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Path to tasks directory.
|
|
121
|
+
"""
|
|
122
|
+
if repo_root is None:
|
|
123
|
+
repo_root = get_repo_root()
|
|
124
|
+
return repo_root / DIR_WORKFLOW / DIR_TASKS
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# =============================================================================
|
|
128
|
+
# Workspace Directory
|
|
129
|
+
# =============================================================================
|
|
130
|
+
|
|
131
|
+
def get_workspace_dir(repo_root: Path | None = None) -> Path | None:
|
|
132
|
+
"""Get developer workspace directory.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
repo_root: Repository root path. Defaults to auto-detected.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
Path to workspace directory or None if developer not set.
|
|
139
|
+
"""
|
|
140
|
+
if repo_root is None:
|
|
141
|
+
repo_root = get_repo_root()
|
|
142
|
+
|
|
143
|
+
developer = get_developer(repo_root)
|
|
144
|
+
if developer:
|
|
145
|
+
return repo_root / DIR_WORKFLOW / DIR_WORKSPACE / developer
|
|
146
|
+
return None
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
# =============================================================================
|
|
150
|
+
# Journal File
|
|
151
|
+
# =============================================================================
|
|
152
|
+
|
|
153
|
+
def get_active_journal_file(repo_root: Path | None = None) -> Path | None:
|
|
154
|
+
"""Get the current active journal file.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
repo_root: Repository root path. Defaults to auto-detected.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
Path to active journal file or None if not found.
|
|
161
|
+
"""
|
|
162
|
+
if repo_root is None:
|
|
163
|
+
repo_root = get_repo_root()
|
|
164
|
+
|
|
165
|
+
workspace_dir = get_workspace_dir(repo_root)
|
|
166
|
+
if workspace_dir is None or not workspace_dir.is_dir():
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
latest: Path | None = None
|
|
170
|
+
highest = 0
|
|
171
|
+
|
|
172
|
+
for f in workspace_dir.glob(f"{FILE_JOURNAL_PREFIX}*.md"):
|
|
173
|
+
if not f.is_file():
|
|
174
|
+
continue
|
|
175
|
+
|
|
176
|
+
# Extract number from filename
|
|
177
|
+
name = f.stem # e.g., "journal-1"
|
|
178
|
+
match = re.search(r"(\d+)$", name)
|
|
179
|
+
if match:
|
|
180
|
+
num = int(match.group(1))
|
|
181
|
+
if num > highest:
|
|
182
|
+
highest = num
|
|
183
|
+
latest = f
|
|
184
|
+
|
|
185
|
+
return latest
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def count_lines(file_path: Path) -> int:
|
|
189
|
+
"""Count lines in a file.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
file_path: Path to file.
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
Number of lines, or 0 if file doesn't exist.
|
|
196
|
+
"""
|
|
197
|
+
if not file_path.is_file():
|
|
198
|
+
return 0
|
|
199
|
+
|
|
200
|
+
try:
|
|
201
|
+
return len(file_path.read_text(encoding="utf-8").splitlines())
|
|
202
|
+
except (OSError, IOError):
|
|
203
|
+
return 0
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
# =============================================================================
|
|
207
|
+
# Current Task Management
|
|
208
|
+
# =============================================================================
|
|
209
|
+
|
|
210
|
+
def _get_current_task_file(repo_root: Path | None = None) -> Path:
|
|
211
|
+
"""Get .current-task file path.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
repo_root: Repository root path. Defaults to auto-detected.
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
Path to .current-task file.
|
|
218
|
+
"""
|
|
219
|
+
if repo_root is None:
|
|
220
|
+
repo_root = get_repo_root()
|
|
221
|
+
return repo_root / DIR_WORKFLOW / FILE_CURRENT_TASK
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def get_current_task(repo_root: Path | None = None) -> str | None:
|
|
225
|
+
"""Get current task directory path (relative to repo_root).
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
repo_root: Repository root path. Defaults to auto-detected.
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
Relative path to current task directory or None.
|
|
232
|
+
"""
|
|
233
|
+
current_file = _get_current_task_file(repo_root)
|
|
234
|
+
|
|
235
|
+
if not current_file.is_file():
|
|
236
|
+
return None
|
|
237
|
+
|
|
238
|
+
try:
|
|
239
|
+
return current_file.read_text(encoding="utf-8").strip()
|
|
240
|
+
except (OSError, IOError):
|
|
241
|
+
return None
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def get_current_task_abs(repo_root: Path | None = None) -> Path | None:
|
|
245
|
+
"""Get current task directory absolute path.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
repo_root: Repository root path. Defaults to auto-detected.
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
Absolute path to current task directory or None.
|
|
252
|
+
"""
|
|
253
|
+
if repo_root is None:
|
|
254
|
+
repo_root = get_repo_root()
|
|
255
|
+
|
|
256
|
+
relative = get_current_task(repo_root)
|
|
257
|
+
if relative:
|
|
258
|
+
return repo_root / relative
|
|
259
|
+
return None
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def set_current_task(task_path: str, repo_root: Path | None = None) -> bool:
|
|
263
|
+
"""Set current task.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
task_path: Task directory path (relative to repo_root).
|
|
267
|
+
repo_root: Repository root path. Defaults to auto-detected.
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
True on success, False on error.
|
|
271
|
+
"""
|
|
272
|
+
if repo_root is None:
|
|
273
|
+
repo_root = get_repo_root()
|
|
274
|
+
|
|
275
|
+
if not task_path:
|
|
276
|
+
return False
|
|
277
|
+
|
|
278
|
+
# Verify task directory exists
|
|
279
|
+
full_path = repo_root / task_path
|
|
280
|
+
if not full_path.is_dir():
|
|
281
|
+
return False
|
|
282
|
+
|
|
283
|
+
current_file = _get_current_task_file(repo_root)
|
|
284
|
+
|
|
285
|
+
try:
|
|
286
|
+
current_file.write_text(task_path, encoding="utf-8")
|
|
287
|
+
return True
|
|
288
|
+
except (OSError, IOError):
|
|
289
|
+
return False
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def clear_current_task(repo_root: Path | None = None) -> bool:
|
|
293
|
+
"""Clear current task.
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
repo_root: Repository root path. Defaults to auto-detected.
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
True on success.
|
|
300
|
+
"""
|
|
301
|
+
current_file = _get_current_task_file(repo_root)
|
|
302
|
+
|
|
303
|
+
try:
|
|
304
|
+
if current_file.is_file():
|
|
305
|
+
current_file.unlink()
|
|
306
|
+
return True
|
|
307
|
+
except (OSError, IOError):
|
|
308
|
+
return False
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def has_current_task(repo_root: Path | None = None) -> bool:
|
|
312
|
+
"""Check if has current task.
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
repo_root: Repository root path. Defaults to auto-detected.
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
True if current task is set.
|
|
319
|
+
"""
|
|
320
|
+
return get_current_task(repo_root) is not None
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
# =============================================================================
|
|
324
|
+
# Task ID Generation
|
|
325
|
+
# =============================================================================
|
|
326
|
+
|
|
327
|
+
def generate_task_date_prefix() -> str:
|
|
328
|
+
"""Generate task ID based on date (MM-DD format).
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
Date prefix string (e.g., "01-21").
|
|
332
|
+
"""
|
|
333
|
+
return datetime.now().strftime("%m-%d")
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
# =============================================================================
|
|
337
|
+
# Main Entry (for testing)
|
|
338
|
+
# =============================================================================
|
|
339
|
+
|
|
340
|
+
if __name__ == "__main__":
|
|
341
|
+
repo = get_repo_root()
|
|
342
|
+
print(f"Repository root: {repo}")
|
|
343
|
+
print(f"Developer: {get_developer(repo)}")
|
|
344
|
+
print(f"Tasks dir: {get_tasks_dir(repo)}")
|
|
345
|
+
print(f"Workspace dir: {get_workspace_dir(repo)}")
|
|
346
|
+
print(f"Journal file: {get_active_journal_file(repo)}")
|
|
347
|
+
print(f"Current task: {get_current_task(repo)}")
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Phase Management Utilities.
|
|
4
|
+
|
|
5
|
+
Centralized phase tracking for multi-agent pipeline.
|
|
6
|
+
|
|
7
|
+
Provides:
|
|
8
|
+
get_current_phase - Returns current phase number
|
|
9
|
+
get_total_phases - Returns total phase count
|
|
10
|
+
get_phase_action - Returns action name for phase
|
|
11
|
+
get_phase_info - Returns "N/M (action)" format
|
|
12
|
+
set_phase - Sets current_phase
|
|
13
|
+
advance_phase - Advances to next phase
|
|
14
|
+
get_phase_for_action - Returns phase number for action
|
|
15
|
+
map_subagent_to_action - Map subagent type to action name
|
|
16
|
+
is_phase_completed - Check if phase is completed
|
|
17
|
+
is_current_action - Check if at specific action
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import json
|
|
23
|
+
from pathlib import Path
|
|
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
|
+
def _write_json_file(path: Path, data: dict) -> bool:
|
|
35
|
+
"""Write dict to JSON file."""
|
|
36
|
+
try:
|
|
37
|
+
path.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8")
|
|
38
|
+
return True
|
|
39
|
+
except (OSError, IOError):
|
|
40
|
+
return False
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# =============================================================================
|
|
44
|
+
# Phase Functions
|
|
45
|
+
# =============================================================================
|
|
46
|
+
|
|
47
|
+
def get_current_phase(task_json: Path) -> int:
|
|
48
|
+
"""Get current phase number.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
task_json: Path to task.json file.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Current phase number, or 0 if not found.
|
|
55
|
+
"""
|
|
56
|
+
data = _read_json_file(task_json)
|
|
57
|
+
if not data:
|
|
58
|
+
return 0
|
|
59
|
+
return data.get("current_phase", 0) or 0
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def get_total_phases(task_json: Path) -> int:
|
|
63
|
+
"""Get total number of phases.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
task_json: Path to task.json file.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Total phase count, or 0 if not found.
|
|
70
|
+
"""
|
|
71
|
+
data = _read_json_file(task_json)
|
|
72
|
+
if not data:
|
|
73
|
+
return 0
|
|
74
|
+
|
|
75
|
+
next_action = data.get("next_action", [])
|
|
76
|
+
if isinstance(next_action, list):
|
|
77
|
+
return len(next_action)
|
|
78
|
+
return 0
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def get_phase_action(task_json: Path, phase: int) -> str:
|
|
82
|
+
"""Get action name for a specific phase.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
task_json: Path to task.json file.
|
|
86
|
+
phase: Phase number.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Action name, or "unknown" if not found.
|
|
90
|
+
"""
|
|
91
|
+
data = _read_json_file(task_json)
|
|
92
|
+
if not data:
|
|
93
|
+
return "unknown"
|
|
94
|
+
|
|
95
|
+
next_action = data.get("next_action", [])
|
|
96
|
+
if isinstance(next_action, list):
|
|
97
|
+
for item in next_action:
|
|
98
|
+
if isinstance(item, dict) and item.get("phase") == phase:
|
|
99
|
+
return item.get("action", "unknown")
|
|
100
|
+
return "unknown"
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def get_phase_info(task_json: Path) -> str:
|
|
104
|
+
"""Get formatted phase info: "N/M (action)".
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
task_json: Path to task.json file.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
Formatted string like "1/4 (implement)".
|
|
111
|
+
"""
|
|
112
|
+
data = _read_json_file(task_json)
|
|
113
|
+
if not data:
|
|
114
|
+
return "N/A"
|
|
115
|
+
|
|
116
|
+
current_phase = data.get("current_phase", 0) or 0
|
|
117
|
+
total_phases = get_total_phases(task_json)
|
|
118
|
+
action_name = get_phase_action(task_json, current_phase)
|
|
119
|
+
|
|
120
|
+
if current_phase == 0 or current_phase is None:
|
|
121
|
+
return f"0/{total_phases} (pending)"
|
|
122
|
+
else:
|
|
123
|
+
return f"{current_phase}/{total_phases} ({action_name})"
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def set_phase(task_json: Path, phase: int) -> bool:
|
|
127
|
+
"""Set current phase to a specific value.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
task_json: Path to task.json file.
|
|
131
|
+
phase: Phase number to set.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
True on success, False on error.
|
|
135
|
+
"""
|
|
136
|
+
data = _read_json_file(task_json)
|
|
137
|
+
if not data:
|
|
138
|
+
return False
|
|
139
|
+
|
|
140
|
+
data["current_phase"] = phase
|
|
141
|
+
return _write_json_file(task_json, data)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def advance_phase(task_json: Path) -> bool:
|
|
145
|
+
"""Advance to next phase.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
task_json: Path to task.json file.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
True on success, False on error or at final phase.
|
|
152
|
+
"""
|
|
153
|
+
data = _read_json_file(task_json)
|
|
154
|
+
if not data:
|
|
155
|
+
return False
|
|
156
|
+
|
|
157
|
+
current = data.get("current_phase", 0) or 0
|
|
158
|
+
total = get_total_phases(task_json)
|
|
159
|
+
next_phase = current + 1
|
|
160
|
+
|
|
161
|
+
if next_phase > total:
|
|
162
|
+
return False # Already at final phase
|
|
163
|
+
|
|
164
|
+
data["current_phase"] = next_phase
|
|
165
|
+
return _write_json_file(task_json, data)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def get_phase_for_action(task_json: Path, action: str) -> int:
|
|
169
|
+
"""Get phase number for a specific action name.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
task_json: Path to task.json file.
|
|
173
|
+
action: Action name.
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
Phase number, or 0 if not found.
|
|
177
|
+
"""
|
|
178
|
+
data = _read_json_file(task_json)
|
|
179
|
+
if not data:
|
|
180
|
+
return 0
|
|
181
|
+
|
|
182
|
+
next_action = data.get("next_action", [])
|
|
183
|
+
if isinstance(next_action, list):
|
|
184
|
+
for item in next_action:
|
|
185
|
+
if isinstance(item, dict) and item.get("action") == action:
|
|
186
|
+
return item.get("phase", 0)
|
|
187
|
+
return 0
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def map_subagent_to_action(subagent_type: str) -> str:
|
|
191
|
+
"""Map subagent type to action name.
|
|
192
|
+
|
|
193
|
+
Used by hooks to determine which action a subagent corresponds to.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
subagent_type: Subagent type string.
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
Corresponding action name.
|
|
200
|
+
"""
|
|
201
|
+
mapping = {
|
|
202
|
+
"implement": "implement",
|
|
203
|
+
"check": "check",
|
|
204
|
+
"debug": "debug",
|
|
205
|
+
"research": "research",
|
|
206
|
+
}
|
|
207
|
+
return mapping.get(subagent_type, subagent_type)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def is_phase_completed(task_json: Path, phase: int) -> bool:
|
|
211
|
+
"""Check if a phase is completed (current_phase > phase).
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
task_json: Path to task.json file.
|
|
215
|
+
phase: Phase number to check.
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
True if phase is completed.
|
|
219
|
+
"""
|
|
220
|
+
current = get_current_phase(task_json)
|
|
221
|
+
return current > phase
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def is_current_action(task_json: Path, action: str) -> bool:
|
|
225
|
+
"""Check if we're at a specific action.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
task_json: Path to task.json file.
|
|
229
|
+
action: Action name to check.
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
True if current phase matches the action.
|
|
233
|
+
"""
|
|
234
|
+
current = get_current_phase(task_json)
|
|
235
|
+
action_phase = get_phase_for_action(task_json, action)
|
|
236
|
+
return current == action_phase
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
# =============================================================================
|
|
240
|
+
# Main Entry (for testing)
|
|
241
|
+
# =============================================================================
|
|
242
|
+
|
|
243
|
+
if __name__ == "__main__":
|
|
244
|
+
import sys
|
|
245
|
+
|
|
246
|
+
if len(sys.argv) > 1:
|
|
247
|
+
path = Path(sys.argv[1])
|
|
248
|
+
print(f"Task JSON: {path}")
|
|
249
|
+
print(f"Phase info: {get_phase_info(path)}")
|
|
250
|
+
print(f"Current phase: {get_current_phase(path)}")
|
|
251
|
+
print(f"Total phases: {get_total_phases(path)}")
|
|
252
|
+
else:
|
|
253
|
+
print("Usage: python3 phase.py <task.json>")
|