@mindfoldhq/trellis 0.3.10-beta.0 → 0.3.10
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/dist/cli/index.js +0 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/init.d.ts +0 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +31 -203
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +6 -154
- package/dist/commands/update.js.map +1 -1
- package/dist/configurators/workflow.d.ts +2 -6
- package/dist/configurators/workflow.d.ts.map +1 -1
- package/dist/configurators/workflow.js +58 -88
- package/dist/configurators/workflow.js.map +1 -1
- package/dist/migrations/index.d.ts +0 -1
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/migrations/index.js +0 -2
- package/dist/migrations/index.js.map +1 -1
- package/dist/migrations/manifests/0.3.10.json +9 -0
- package/dist/templates/claude/agents/dispatch.md +2 -1
- package/dist/templates/claude/agents/implement.md +3 -2
- 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/check-backend.md +13 -0
- package/dist/templates/claude/commands/trellis/check-frontend.md +13 -0
- package/dist/templates/claude/commands/trellis/create-command.md +2 -2
- package/dist/templates/claude/commands/trellis/onboard.md +13 -13
- package/dist/templates/claude/commands/trellis/parallel.md +2 -1
- package/dist/templates/claude/commands/trellis/record-session.md +2 -2
- package/dist/templates/claude/commands/trellis/start.md +4 -8
- package/dist/templates/claude/hooks/inject-subagent-context.py +13 -21
- package/dist/templates/claude/hooks/session-start.py +2 -170
- package/dist/templates/codex/skills/before-backend-dev/SKILL.md +18 -0
- package/dist/templates/codex/skills/before-frontend-dev/SKILL.md +18 -0
- package/dist/templates/codex/skills/check-backend/SKILL.md +18 -0
- package/dist/templates/codex/skills/check-frontend/SKILL.md +18 -0
- package/dist/templates/codex/skills/create-command/SKILL.md +2 -2
- package/dist/templates/codex/skills/onboard/SKILL.md +11 -11
- package/dist/templates/codex/skills/record-session/SKILL.md +2 -2
- package/dist/templates/codex/skills/start/SKILL.md +3 -8
- package/dist/templates/cursor/commands/trellis-before-backend-dev.md +13 -0
- package/dist/templates/cursor/commands/trellis-before-frontend-dev.md +13 -0
- package/dist/templates/cursor/commands/trellis-check-backend.md +13 -0
- package/dist/templates/cursor/commands/trellis-check-frontend.md +13 -0
- package/dist/templates/cursor/commands/trellis-create-command.md +2 -2
- package/dist/templates/cursor/commands/trellis-onboard.md +13 -13
- package/dist/templates/cursor/commands/trellis-record-session.md +2 -2
- package/dist/templates/cursor/commands/trellis-start.md +16 -7
- package/dist/templates/gemini/commands/trellis/before-backend-dev.toml +17 -0
- package/dist/templates/gemini/commands/trellis/before-frontend-dev.toml +17 -0
- package/dist/templates/gemini/commands/trellis/check-backend.toml +17 -0
- package/dist/templates/gemini/commands/trellis/check-frontend.toml +17 -0
- package/dist/templates/gemini/commands/trellis/create-command.toml +2 -2
- package/dist/templates/gemini/commands/trellis/onboard.toml +2 -2
- package/dist/templates/gemini/commands/trellis/record-session.toml +2 -2
- package/dist/templates/gemini/commands/trellis/start.toml +4 -9
- package/dist/templates/iflow/agents/dispatch.md +2 -1
- package/dist/templates/iflow/agents/implement.md +3 -2
- package/dist/templates/iflow/commands/trellis/before-backend-dev.md +13 -0
- package/dist/templates/iflow/commands/trellis/before-frontend-dev.md +13 -0
- package/dist/templates/iflow/commands/trellis/check-backend.md +13 -0
- package/dist/templates/iflow/commands/trellis/check-frontend.md +13 -0
- package/dist/templates/iflow/commands/trellis/create-command.md +2 -2
- package/dist/templates/iflow/commands/trellis/onboard.md +13 -13
- package/dist/templates/iflow/commands/trellis/parallel.md +2 -1
- package/dist/templates/iflow/commands/trellis/record-session.md +2 -2
- package/dist/templates/iflow/commands/trellis/start.md +4 -8
- package/dist/templates/iflow/hooks/inject-subagent-context.py +13 -21
- package/dist/templates/iflow/hooks/session-start.py +1 -156
- package/dist/templates/kilo/workflows/before-backend-dev.md +13 -0
- package/dist/templates/kilo/workflows/before-frontend-dev.md +13 -0
- package/dist/templates/kilo/workflows/check-backend.md +13 -0
- package/dist/templates/kilo/workflows/check-frontend.md +13 -0
- package/dist/templates/kilo/workflows/create-command.md +2 -2
- package/dist/templates/kilo/workflows/onboard.md +13 -13
- package/dist/templates/kilo/workflows/parallel.md +2 -1
- package/dist/templates/kilo/workflows/record-session.md +2 -2
- package/dist/templates/kilo/workflows/start.md +3 -8
- package/dist/templates/kiro/skills/before-backend-dev/SKILL.md +18 -0
- package/dist/templates/kiro/skills/before-frontend-dev/SKILL.md +18 -0
- package/dist/templates/kiro/skills/check-backend/SKILL.md +18 -0
- package/dist/templates/kiro/skills/check-frontend/SKILL.md +18 -0
- package/dist/templates/kiro/skills/create-command/SKILL.md +2 -2
- package/dist/templates/kiro/skills/onboard/SKILL.md +11 -11
- package/dist/templates/kiro/skills/record-session/SKILL.md +2 -2
- package/dist/templates/kiro/skills/start/SKILL.md +3 -8
- package/dist/templates/markdown/spec/backend/script-conventions.md +0 -93
- package/dist/templates/opencode/agents/dispatch.md +2 -1
- package/dist/templates/opencode/agents/implement.md +2 -2
- package/dist/templates/opencode/agents/research.md +2 -1
- package/dist/templates/opencode/commands/trellis/before-backend-dev.md +13 -0
- package/dist/templates/opencode/commands/trellis/before-frontend-dev.md +13 -0
- package/dist/templates/opencode/commands/trellis/check-backend.md +13 -0
- package/dist/templates/opencode/commands/trellis/check-frontend.md +13 -0
- package/dist/templates/opencode/commands/trellis/create-command.md +2 -2
- package/dist/templates/opencode/commands/trellis/onboard.md +13 -13
- package/dist/templates/opencode/commands/trellis/parallel.md +2 -1
- package/dist/templates/opencode/commands/trellis/record-session.md +2 -2
- package/dist/templates/opencode/commands/trellis/start.md +3 -8
- package/dist/templates/opencode/plugin/inject-subagent-context.js +18 -45
- package/dist/templates/opencode/plugin/session-start.js +1 -149
- package/dist/templates/qoder/skills/before-backend-dev/SKILL.md +18 -0
- package/dist/templates/qoder/skills/before-frontend-dev/SKILL.md +18 -0
- package/dist/templates/qoder/skills/check-backend/SKILL.md +18 -0
- package/dist/templates/qoder/skills/check-frontend/SKILL.md +18 -0
- package/dist/templates/qoder/skills/create-command/SKILL.md +2 -2
- package/dist/templates/qoder/skills/onboard/SKILL.md +13 -13
- package/dist/templates/qoder/skills/record-session/SKILL.md +2 -2
- package/dist/templates/qoder/skills/start/SKILL.md +3 -8
- package/dist/templates/trellis/config.yaml +0 -20
- package/dist/templates/trellis/index.d.ts +0 -11
- package/dist/templates/trellis/index.d.ts.map +1 -1
- package/dist/templates/trellis/index.js +0 -22
- package/dist/templates/trellis/index.js.map +1 -1
- package/dist/templates/trellis/scripts/add_session.py +7 -52
- package/dist/templates/trellis/scripts/common/cli_adapter.py +45 -33
- package/dist/templates/trellis/scripts/common/config.py +0 -152
- package/dist/templates/trellis/scripts/common/git_context.py +586 -23
- package/dist/templates/trellis/scripts/common/paths.py +0 -46
- package/dist/templates/trellis/scripts/common/phase.py +49 -50
- package/dist/templates/trellis/scripts/common/registry.py +72 -41
- package/dist/templates/trellis/scripts/common/task_queue.py +98 -27
- package/dist/templates/trellis/scripts/common/task_utils.py +6 -96
- package/dist/templates/trellis/scripts/create_bootstrap.py +26 -31
- package/dist/templates/trellis/scripts/multi_agent/cleanup.py +48 -43
- package/dist/templates/trellis/scripts/multi_agent/create_pr.py +45 -336
- package/dist/templates/trellis/scripts/multi_agent/plan.py +26 -2
- package/dist/templates/trellis/scripts/multi_agent/start.py +57 -126
- package/dist/templates/trellis/scripts/multi_agent/status.py +753 -12
- package/dist/templates/trellis/scripts/task.py +975 -50
- package/dist/templates/trellis/workflow.md +34 -21
- package/dist/types/migration.d.ts +1 -3
- package/dist/types/migration.d.ts.map +1 -1
- package/dist/utils/project-detector.d.ts +0 -23
- package/dist/utils/project-detector.d.ts.map +1 -1
- package/dist/utils/project-detector.js +0 -364
- package/dist/utils/project-detector.js.map +1 -1
- package/dist/utils/template-fetcher.d.ts +10 -2
- package/dist/utils/template-fetcher.d.ts.map +1 -1
- package/dist/utils/template-fetcher.js +43 -12
- package/dist/utils/template-fetcher.js.map +1 -1
- package/package.json +1 -1
- package/dist/migrations/manifests/0.4.0-beta.1.json +0 -228
- package/dist/templates/claude/commands/trellis/before-dev.md +0 -29
- package/dist/templates/claude/commands/trellis/check.md +0 -25
- package/dist/templates/codex/skills/before-dev/SKILL.md +0 -34
- package/dist/templates/codex/skills/check/SKILL.md +0 -30
- package/dist/templates/cursor/commands/trellis-before-dev.md +0 -29
- package/dist/templates/cursor/commands/trellis-check.md +0 -25
- package/dist/templates/gemini/commands/trellis/before-dev.toml +0 -33
- package/dist/templates/gemini/commands/trellis/check.toml +0 -29
- package/dist/templates/iflow/commands/trellis/before-dev.md +0 -29
- package/dist/templates/iflow/commands/trellis/check.md +0 -25
- package/dist/templates/kilo/workflows/before-dev.md +0 -29
- package/dist/templates/kilo/workflows/check.md +0 -25
- package/dist/templates/kiro/skills/before-dev/SKILL.md +0 -34
- package/dist/templates/kiro/skills/check/SKILL.md +0 -30
- package/dist/templates/opencode/commands/trellis/before-dev.md +0 -29
- package/dist/templates/opencode/commands/trellis/check.md +0 -25
- package/dist/templates/qoder/skills/before-dev/SKILL.md +0 -34
- package/dist/templates/qoder/skills/check/SKILL.md +0 -30
- package/dist/templates/trellis/scripts/common/git.py +0 -31
- package/dist/templates/trellis/scripts/common/io.py +0 -37
- package/dist/templates/trellis/scripts/common/log.py +0 -45
- package/dist/templates/trellis/scripts/common/packages_context.py +0 -233
- package/dist/templates/trellis/scripts/common/session_context.py +0 -466
- package/dist/templates/trellis/scripts/common/task_context.py +0 -384
- package/dist/templates/trellis/scripts/common/task_store.py +0 -534
- package/dist/templates/trellis/scripts/common/tasks.py +0 -109
- package/dist/templates/trellis/scripts/common/types.py +0 -112
- package/dist/templates/trellis/scripts/hooks/linear_sync.py +0 -243
- package/dist/templates/trellis/scripts/multi_agent/_bootstrap.py +0 -17
- package/dist/templates/trellis/scripts/multi_agent/status_display.py +0 -542
- package/dist/templates/trellis/scripts/multi_agent/status_monitor.py +0 -225
|
@@ -6,11 +6,10 @@ Usage:
|
|
|
6
6
|
python3 create_pr.py [task-dir] [--dry-run]
|
|
7
7
|
|
|
8
8
|
This script:
|
|
9
|
-
1.
|
|
10
|
-
2.
|
|
11
|
-
3.
|
|
12
|
-
4.
|
|
13
|
-
5. Updates task.json with status="completed", pr_url, submodule_prs, and current_phase
|
|
9
|
+
1. Stages and commits all changes (excluding workspace/)
|
|
10
|
+
2. Pushes to origin
|
|
11
|
+
3. Creates a Draft PR using `gh pr create`
|
|
12
|
+
4. Updates task.json with status="completed", pr_url, and current_phase
|
|
14
13
|
|
|
15
14
|
Note: This is the only action that performs git commit, as it's the final
|
|
16
15
|
step after all implementation and checks are complete.
|
|
@@ -19,16 +18,15 @@ step after all implementation and checks are complete.
|
|
|
19
18
|
from __future__ import annotations
|
|
20
19
|
|
|
21
20
|
import argparse
|
|
21
|
+
import json
|
|
22
22
|
import subprocess
|
|
23
23
|
import sys
|
|
24
24
|
from pathlib import Path
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
# Add parent directory to path for imports
|
|
27
|
+
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
27
28
|
|
|
28
|
-
from common.
|
|
29
|
-
from common.git import run_git
|
|
30
|
-
from common.io import read_json, write_json
|
|
31
|
-
from common.log import Colors
|
|
29
|
+
from common.git_context import _run_git_command
|
|
32
30
|
from common.paths import (
|
|
33
31
|
DIR_WORKFLOW,
|
|
34
32
|
FILE_TASK_JSON,
|
|
@@ -37,277 +35,41 @@ from common.paths import (
|
|
|
37
35
|
)
|
|
38
36
|
from common.phase import get_phase_for_action
|
|
39
37
|
|
|
40
|
-
# Colors, read_json, write_json
|
|
41
|
-
# are now imported from common.log and common.io above.
|
|
42
|
-
|
|
43
|
-
|
|
44
38
|
# =============================================================================
|
|
45
|
-
#
|
|
39
|
+
# Colors
|
|
46
40
|
# =============================================================================
|
|
47
41
|
|
|
48
|
-
# Warning message prepended to main PR body when submodule PRs exist
|
|
49
|
-
_SUBMODULE_SQUASH_WARNING_MARKER = (
|
|
50
|
-
"Merge submodule PR(s) first. If squash-merged, update submodule ref after merge."
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def _get_submodule_default_branch(submodule_abs: Path) -> str:
|
|
55
|
-
"""Get the default branch of a submodule repository.
|
|
56
|
-
|
|
57
|
-
Uses `git symbolic-ref refs/remotes/origin/HEAD` for portability
|
|
58
|
-
(no grep, no English-dependent output).
|
|
59
|
-
|
|
60
|
-
Returns:
|
|
61
|
-
Default branch name (e.g. "main"), falls back to "main" on failure.
|
|
62
|
-
"""
|
|
63
|
-
ret, out, _ = run_git(
|
|
64
|
-
["symbolic-ref", "refs/remotes/origin/HEAD"], cwd=submodule_abs
|
|
65
|
-
)
|
|
66
|
-
if ret == 0 and out.strip():
|
|
67
|
-
# Output: "refs/remotes/origin/main" -> "main"
|
|
68
|
-
ref = out.strip()
|
|
69
|
-
prefix = "refs/remotes/origin/"
|
|
70
|
-
if ref.startswith(prefix):
|
|
71
|
-
return ref[len(prefix):]
|
|
72
|
-
return "main"
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def _process_submodule_changes(
|
|
76
|
-
repo_root: Path,
|
|
77
|
-
current_branch: str,
|
|
78
|
-
commit_prefix: str,
|
|
79
|
-
scope: str,
|
|
80
|
-
task_name: str,
|
|
81
|
-
task_data: dict,
|
|
82
|
-
task_json: Path,
|
|
83
|
-
dry_run: bool,
|
|
84
|
-
) -> tuple[dict[str, str], list[str], bool]:
|
|
85
|
-
"""Process submodule changes: commit, push, create PRs.
|
|
86
|
-
|
|
87
|
-
Returns:
|
|
88
|
-
Tuple of (submodule_prs dict, changed_submodule_paths list, success bool).
|
|
89
|
-
On failure, submodule_prs contains URLs persisted so far.
|
|
90
|
-
"""
|
|
91
|
-
submodule_packages = get_submodule_packages(repo_root)
|
|
92
|
-
if not submodule_packages:
|
|
93
|
-
return {}, [], True
|
|
94
|
-
|
|
95
|
-
# Load existing submodule_prs for incremental merge
|
|
96
|
-
raw_prs = task_data.get("submodule_prs")
|
|
97
|
-
submodule_prs: dict[str, str] = dict(raw_prs) if isinstance(raw_prs, dict) else {}
|
|
98
|
-
|
|
99
|
-
# Detect which submodules have changes
|
|
100
|
-
changed: list[tuple[str, str]] = [] # (name, path)
|
|
101
|
-
for pkg_name, pkg_path in submodule_packages.items():
|
|
102
|
-
sub_abs = repo_root / pkg_path
|
|
103
|
-
if not sub_abs.is_dir():
|
|
104
|
-
continue
|
|
105
|
-
|
|
106
|
-
ret, status_out, _ = run_git(
|
|
107
|
-
["status", "--porcelain"], cwd=sub_abs
|
|
108
|
-
)
|
|
109
|
-
if ret != 0:
|
|
110
|
-
continue
|
|
111
|
-
if status_out.strip():
|
|
112
|
-
changed.append((pkg_name, pkg_path))
|
|
113
42
|
|
|
114
|
-
|
|
115
|
-
|
|
43
|
+
class Colors:
|
|
44
|
+
RED = "\033[0;31m"
|
|
45
|
+
GREEN = "\033[0;32m"
|
|
46
|
+
YELLOW = "\033[1;33m"
|
|
47
|
+
BLUE = "\033[0;34m"
|
|
48
|
+
NC = "\033[0m"
|
|
116
49
|
|
|
117
|
-
# Determine submodule branch name: <repo-dir-name>/<main-branch>
|
|
118
|
-
repo_dir_name = repo_root.name
|
|
119
|
-
sub_branch = f"{repo_dir_name}/{current_branch}"
|
|
120
50
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
print()
|
|
51
|
+
# =============================================================================
|
|
52
|
+
# Helper Functions
|
|
53
|
+
# =============================================================================
|
|
125
54
|
|
|
126
|
-
changed_paths: list[str] = []
|
|
127
|
-
|
|
128
|
-
for pkg_name, pkg_path in changed:
|
|
129
|
-
sub_abs = repo_root / pkg_path
|
|
130
|
-
sub_base = _get_submodule_default_branch(sub_abs)
|
|
131
|
-
sub_commit_msg = f"{commit_prefix}({scope}): {task_name}"
|
|
132
|
-
|
|
133
|
-
print(f"{Colors.YELLOW}Processing submodule: {pkg_name} ({pkg_path}){Colors.NC}")
|
|
134
|
-
print(f" Submodule base branch: {sub_base}")
|
|
135
|
-
print(f" Submodule branch: {sub_branch}")
|
|
136
|
-
|
|
137
|
-
if dry_run:
|
|
138
|
-
print(f" [DRY-RUN] Would checkout branch: {sub_branch}")
|
|
139
|
-
print(f" [DRY-RUN] Would commit: {sub_commit_msg}")
|
|
140
|
-
print(f" [DRY-RUN] Would push to: origin/{sub_branch}")
|
|
141
|
-
print(f" [DRY-RUN] Would create PR: {sub_branch} -> {sub_base}")
|
|
142
|
-
submodule_prs[pkg_name] = "https://github.com/example/repo/pull/DRY-RUN"
|
|
143
|
-
changed_paths.append(pkg_path)
|
|
144
|
-
continue
|
|
145
|
-
|
|
146
|
-
# --- Checkout or create branch in submodule ---
|
|
147
|
-
ret, _, _ = run_git(
|
|
148
|
-
["show-ref", "--verify", "--quiet", f"refs/heads/{sub_branch}"],
|
|
149
|
-
cwd=sub_abs,
|
|
150
|
-
)
|
|
151
|
-
if ret == 0:
|
|
152
|
-
# Branch exists, checkout
|
|
153
|
-
ret, _, err = run_git(
|
|
154
|
-
["checkout", sub_branch], cwd=sub_abs
|
|
155
|
-
)
|
|
156
|
-
if ret != 0:
|
|
157
|
-
print(f"{Colors.RED}Failed to checkout branch in {pkg_name}: {err}{Colors.NC}")
|
|
158
|
-
return submodule_prs, changed_paths, False
|
|
159
|
-
|
|
160
|
-
# Check for divergence (reuse risk)
|
|
161
|
-
ret_anc, _, _ = run_git(
|
|
162
|
-
["merge-base", "--is-ancestor", sub_base, sub_branch],
|
|
163
|
-
cwd=sub_abs,
|
|
164
|
-
)
|
|
165
|
-
if ret_anc != 0:
|
|
166
|
-
print(
|
|
167
|
-
f" {Colors.YELLOW}[WARN] submodule branch has diverged history, "
|
|
168
|
-
f"consider recreating{Colors.NC}"
|
|
169
|
-
)
|
|
170
|
-
else:
|
|
171
|
-
# Create new branch
|
|
172
|
-
ret, _, err = run_git(
|
|
173
|
-
["checkout", "-b", sub_branch], cwd=sub_abs
|
|
174
|
-
)
|
|
175
|
-
if ret != 0:
|
|
176
|
-
print(f"{Colors.RED}Failed to create branch in {pkg_name}: {err}{Colors.NC}")
|
|
177
|
-
return submodule_prs, changed_paths, False
|
|
178
55
|
|
|
179
|
-
|
|
180
|
-
|
|
56
|
+
def _read_json_file(path: Path) -> dict | None:
|
|
57
|
+
"""Read and parse a JSON file."""
|
|
58
|
+
try:
|
|
59
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
60
|
+
except (FileNotFoundError, json.JSONDecodeError, OSError):
|
|
61
|
+
return None
|
|
181
62
|
|
|
182
|
-
ret, _, _ = run_git(["diff", "--cached", "--quiet"], cwd=sub_abs)
|
|
183
|
-
if ret != 0:
|
|
184
|
-
# Has staged changes
|
|
185
|
-
ret, _, err = run_git(
|
|
186
|
-
["commit", "-m", sub_commit_msg], cwd=sub_abs
|
|
187
|
-
)
|
|
188
|
-
if ret != 0:
|
|
189
|
-
print(f"{Colors.RED}Failed to commit in {pkg_name}: {err}{Colors.NC}")
|
|
190
|
-
return submodule_prs, changed_paths, False
|
|
191
|
-
print(f" {Colors.GREEN}Committed in {pkg_name}{Colors.NC}")
|
|
192
|
-
else:
|
|
193
|
-
print(f" No staged changes in {pkg_name}, skipping commit")
|
|
194
63
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
64
|
+
def _write_json_file(path: Path, data: dict) -> bool:
|
|
65
|
+
"""Write dict to JSON file."""
|
|
66
|
+
try:
|
|
67
|
+
path.write_text(
|
|
68
|
+
json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8"
|
|
198
69
|
)
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
print(f" {Colors.GREEN}Pushed {pkg_name} to origin/{sub_branch}{Colors.NC}")
|
|
203
|
-
|
|
204
|
-
# --- Create or reuse PR ---
|
|
205
|
-
result = subprocess.run(
|
|
206
|
-
[
|
|
207
|
-
"gh", "pr", "list",
|
|
208
|
-
"--head", sub_branch,
|
|
209
|
-
"--base", sub_base,
|
|
210
|
-
"--json", "url",
|
|
211
|
-
"--jq", ".[0].url",
|
|
212
|
-
],
|
|
213
|
-
capture_output=True,
|
|
214
|
-
text=True,
|
|
215
|
-
encoding="utf-8",
|
|
216
|
-
errors="replace",
|
|
217
|
-
cwd=str(sub_abs),
|
|
218
|
-
)
|
|
219
|
-
existing_sub_pr = result.stdout.strip()
|
|
220
|
-
|
|
221
|
-
if existing_sub_pr:
|
|
222
|
-
print(f" {Colors.YELLOW}PR already exists: {existing_sub_pr}{Colors.NC}")
|
|
223
|
-
sub_pr_url = existing_sub_pr
|
|
224
|
-
else:
|
|
225
|
-
result = subprocess.run(
|
|
226
|
-
[
|
|
227
|
-
"gh", "pr", "create",
|
|
228
|
-
"--draft",
|
|
229
|
-
"--base", sub_base,
|
|
230
|
-
"--title", f"{commit_prefix}({scope}): {task_name} [{pkg_name}]",
|
|
231
|
-
"--body", f"Submodule changes for {task_name}",
|
|
232
|
-
],
|
|
233
|
-
capture_output=True,
|
|
234
|
-
text=True,
|
|
235
|
-
encoding="utf-8",
|
|
236
|
-
errors="replace",
|
|
237
|
-
cwd=str(sub_abs),
|
|
238
|
-
)
|
|
239
|
-
if result.returncode != 0:
|
|
240
|
-
print(
|
|
241
|
-
f"{Colors.RED}Failed to create PR for {pkg_name}: "
|
|
242
|
-
f"{result.stderr}{Colors.NC}"
|
|
243
|
-
)
|
|
244
|
-
return submodule_prs, changed_paths, False
|
|
245
|
-
|
|
246
|
-
sub_pr_url = result.stdout.strip()
|
|
247
|
-
print(f" {Colors.GREEN}PR created for {pkg_name}: {sub_pr_url}{Colors.NC}")
|
|
248
|
-
|
|
249
|
-
# Persist immediately (incremental, supports re-entry)
|
|
250
|
-
submodule_prs[pkg_name] = sub_pr_url
|
|
251
|
-
task_data["submodule_prs"] = submodule_prs
|
|
252
|
-
write_json(task_json, task_data)
|
|
253
|
-
|
|
254
|
-
changed_paths.append(pkg_path)
|
|
255
|
-
|
|
256
|
-
return submodule_prs, changed_paths, True
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
def _build_submodule_warning(submodule_prs: dict[str, str]) -> str:
|
|
260
|
-
"""Build the squash-merge warning block for the main PR body."""
|
|
261
|
-
pr_lines = "\n".join(f"> - {name}: {url}" for name, url in submodule_prs.items())
|
|
262
|
-
return (
|
|
263
|
-
f"> {_SUBMODULE_SQUASH_WARNING_MARKER}\n"
|
|
264
|
-
f">\n"
|
|
265
|
-
f"> Submodule PRs:\n"
|
|
266
|
-
f"{pr_lines}\n"
|
|
267
|
-
f"\n---\n\n"
|
|
268
|
-
)
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
def _ensure_submodule_warning_on_existing_pr(
|
|
272
|
-
submodule_prs: dict[str, str],
|
|
273
|
-
dry_run: bool,
|
|
274
|
-
) -> None:
|
|
275
|
-
"""Read-modify-write: add squash warning to existing PR if missing."""
|
|
276
|
-
if dry_run:
|
|
277
|
-
print("[DRY-RUN] Would check/add submodule warning to existing PR")
|
|
278
|
-
return
|
|
279
|
-
|
|
280
|
-
# Read current PR body
|
|
281
|
-
result = subprocess.run(
|
|
282
|
-
[
|
|
283
|
-
"gh", "pr", "view",
|
|
284
|
-
"--json", "body",
|
|
285
|
-
"--jq", ".body",
|
|
286
|
-
],
|
|
287
|
-
capture_output=True,
|
|
288
|
-
text=True,
|
|
289
|
-
encoding="utf-8",
|
|
290
|
-
errors="replace",
|
|
291
|
-
)
|
|
292
|
-
if result.returncode != 0:
|
|
293
|
-
return
|
|
294
|
-
|
|
295
|
-
current_body = result.stdout.strip()
|
|
296
|
-
if _SUBMODULE_SQUASH_WARNING_MARKER in current_body:
|
|
297
|
-
return # Warning already present
|
|
298
|
-
|
|
299
|
-
# Prepend warning to existing body
|
|
300
|
-
warning = _build_submodule_warning(submodule_prs)
|
|
301
|
-
new_body = warning + current_body
|
|
302
|
-
|
|
303
|
-
subprocess.run(
|
|
304
|
-
["gh", "pr", "edit", "--body", new_body],
|
|
305
|
-
capture_output=True,
|
|
306
|
-
text=True,
|
|
307
|
-
encoding="utf-8",
|
|
308
|
-
errors="replace",
|
|
309
|
-
)
|
|
310
|
-
print(f" {Colors.GREEN}Added submodule merge warning to existing PR{Colors.NC}")
|
|
70
|
+
return True
|
|
71
|
+
except (OSError, IOError):
|
|
72
|
+
return False
|
|
311
73
|
|
|
312
74
|
|
|
313
75
|
# =============================================================================
|
|
@@ -365,7 +127,7 @@ def main() -> int:
|
|
|
365
127
|
print()
|
|
366
128
|
|
|
367
129
|
# Read task config
|
|
368
|
-
task_data =
|
|
130
|
+
task_data = _read_json_file(task_json)
|
|
369
131
|
if not task_data:
|
|
370
132
|
print(f"{Colors.RED}Error: Failed to read task.json{Colors.NC}")
|
|
371
133
|
return 1
|
|
@@ -396,66 +158,36 @@ def main() -> int:
|
|
|
396
158
|
print()
|
|
397
159
|
|
|
398
160
|
# Get current branch
|
|
399
|
-
_, branch_out, _ =
|
|
161
|
+
_, branch_out, _ = _run_git_command(["branch", "--show-current"])
|
|
400
162
|
current_branch = branch_out.strip()
|
|
401
163
|
print(f"Current branch: {current_branch}")
|
|
402
164
|
|
|
403
|
-
# =============================================================================
|
|
404
|
-
# Submodule PR Flow (runs BEFORE main repo staging)
|
|
405
|
-
# =============================================================================
|
|
406
|
-
submodule_prs, changed_submodule_paths, sub_success = _process_submodule_changes(
|
|
407
|
-
repo_root=repo_root,
|
|
408
|
-
current_branch=current_branch,
|
|
409
|
-
commit_prefix=commit_prefix,
|
|
410
|
-
scope=scope,
|
|
411
|
-
task_name=task_name,
|
|
412
|
-
task_data=task_data,
|
|
413
|
-
task_json=task_json,
|
|
414
|
-
dry_run=args.dry_run,
|
|
415
|
-
)
|
|
416
|
-
|
|
417
|
-
if not sub_success:
|
|
418
|
-
print(
|
|
419
|
-
f"\n{Colors.RED}Submodule PR flow failed. "
|
|
420
|
-
f"Skipping main repo commit/PR.{Colors.NC}"
|
|
421
|
-
)
|
|
422
|
-
print("Already-created submodule PRs have been saved to task.json.")
|
|
423
|
-
return 1
|
|
424
|
-
|
|
425
|
-
# =============================================================================
|
|
426
|
-
# Main Repo: Stage, Commit, Push, PR
|
|
427
|
-
# =============================================================================
|
|
428
|
-
|
|
429
165
|
# Check for changes
|
|
430
166
|
print(f"{Colors.YELLOW}Checking for changes...{Colors.NC}")
|
|
431
167
|
|
|
432
168
|
# Stage changes
|
|
433
|
-
|
|
169
|
+
_run_git_command(["add", "-A"])
|
|
434
170
|
|
|
435
171
|
# Exclude workspace and temp files
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
# If submodules changed, ensure their ref updates are staged
|
|
440
|
-
for sub_path in changed_submodule_paths:
|
|
441
|
-
run_git(["add", sub_path])
|
|
172
|
+
_run_git_command(["reset", f"{DIR_WORKFLOW}/workspace/"])
|
|
173
|
+
_run_git_command(["reset", ".agent-log", ".session-id"])
|
|
442
174
|
|
|
443
175
|
# Check if there are staged changes
|
|
444
|
-
ret, _, _ =
|
|
176
|
+
ret, _, _ = _run_git_command(["diff", "--cached", "--quiet"])
|
|
445
177
|
has_staged_changes = ret != 0
|
|
446
178
|
|
|
447
179
|
if not has_staged_changes:
|
|
448
180
|
print(f"{Colors.YELLOW}No staged changes to commit{Colors.NC}")
|
|
449
181
|
|
|
450
182
|
# Check for unpushed commits
|
|
451
|
-
ret, log_out, _ =
|
|
183
|
+
ret, log_out, _ = _run_git_command(
|
|
452
184
|
["log", f"origin/{current_branch}..HEAD", "--oneline"]
|
|
453
185
|
)
|
|
454
186
|
unpushed = len([line for line in log_out.splitlines() if line.strip()])
|
|
455
187
|
|
|
456
188
|
if unpushed == 0:
|
|
457
189
|
if args.dry_run:
|
|
458
|
-
|
|
190
|
+
_run_git_command(["reset", "HEAD"])
|
|
459
191
|
print(f"{Colors.RED}No changes to create PR{Colors.NC}")
|
|
460
192
|
return 1
|
|
461
193
|
|
|
@@ -468,11 +200,11 @@ def main() -> int:
|
|
|
468
200
|
if args.dry_run:
|
|
469
201
|
print(f"[DRY-RUN] Would commit with message: {commit_msg}")
|
|
470
202
|
print("[DRY-RUN] Staged files:")
|
|
471
|
-
_, staged_out, _ =
|
|
203
|
+
_, staged_out, _ = _run_git_command(["diff", "--cached", "--name-only"])
|
|
472
204
|
for line in staged_out.splitlines():
|
|
473
205
|
print(f" - {line}")
|
|
474
206
|
else:
|
|
475
|
-
|
|
207
|
+
_run_git_command(["commit", "-m", commit_msg])
|
|
476
208
|
print(f"{Colors.GREEN}Committed: {commit_msg}{Colors.NC}")
|
|
477
209
|
|
|
478
210
|
# Push to remote
|
|
@@ -480,7 +212,7 @@ def main() -> int:
|
|
|
480
212
|
if args.dry_run:
|
|
481
213
|
print(f"[DRY-RUN] Would push to: origin/{current_branch}")
|
|
482
214
|
else:
|
|
483
|
-
ret, _, err =
|
|
215
|
+
ret, _, err = _run_git_command(["push", "-u", "origin", current_branch])
|
|
484
216
|
if ret != 0:
|
|
485
217
|
print(f"{Colors.RED}Failed to push: {err}{Colors.NC}")
|
|
486
218
|
return 1
|
|
@@ -491,9 +223,6 @@ def main() -> int:
|
|
|
491
223
|
pr_title = f"{commit_prefix}({scope}): {task_name}"
|
|
492
224
|
pr_url = ""
|
|
493
225
|
|
|
494
|
-
# Build PR body with optional submodule warning
|
|
495
|
-
has_submodule_prs = bool(submodule_prs)
|
|
496
|
-
|
|
497
226
|
if args.dry_run:
|
|
498
227
|
print("[DRY-RUN] Would create PR:")
|
|
499
228
|
print(f" Title: {pr_title}")
|
|
@@ -502,8 +231,6 @@ def main() -> int:
|
|
|
502
231
|
prd_file = target_dir_path / "prd.md"
|
|
503
232
|
if prd_file.is_file():
|
|
504
233
|
print(" Body: (from prd.md)")
|
|
505
|
-
if has_submodule_prs:
|
|
506
|
-
print(" Body includes submodule merge warning")
|
|
507
234
|
pr_url = "https://github.com/example/repo/pull/DRY-RUN"
|
|
508
235
|
else:
|
|
509
236
|
# Check if PR already exists
|
|
@@ -531,12 +258,6 @@ def main() -> int:
|
|
|
531
258
|
if existing_pr:
|
|
532
259
|
print(f"{Colors.YELLOW}PR already exists: {existing_pr}{Colors.NC}")
|
|
533
260
|
pr_url = existing_pr
|
|
534
|
-
|
|
535
|
-
# Read-modify-write: add submodule warning if missing
|
|
536
|
-
if has_submodule_prs:
|
|
537
|
-
_ensure_submodule_warning_on_existing_pr(
|
|
538
|
-
submodule_prs, args.dry_run
|
|
539
|
-
)
|
|
540
261
|
else:
|
|
541
262
|
# Read PRD as PR body
|
|
542
263
|
pr_body = ""
|
|
@@ -544,10 +265,6 @@ def main() -> int:
|
|
|
544
265
|
if prd_file.is_file():
|
|
545
266
|
pr_body = prd_file.read_text(encoding="utf-8")
|
|
546
267
|
|
|
547
|
-
# Prepend submodule warning if applicable
|
|
548
|
-
if has_submodule_prs:
|
|
549
|
-
pr_body = _build_submodule_warning(submodule_prs) + pr_body
|
|
550
|
-
|
|
551
268
|
# Create PR
|
|
552
269
|
result = subprocess.run(
|
|
553
270
|
[
|
|
@@ -581,8 +298,6 @@ def main() -> int:
|
|
|
581
298
|
print("[DRY-RUN] Would update task.json:")
|
|
582
299
|
print(" status: completed")
|
|
583
300
|
print(f" pr_url: {pr_url}")
|
|
584
|
-
if has_submodule_prs:
|
|
585
|
-
print(f" submodule_prs: {submodule_prs}")
|
|
586
301
|
print(" current_phase: (set to create-pr phase)")
|
|
587
302
|
else:
|
|
588
303
|
# Get the phase number for create-pr action
|
|
@@ -593,25 +308,19 @@ def main() -> int:
|
|
|
593
308
|
task_data["status"] = "completed"
|
|
594
309
|
task_data["pr_url"] = pr_url
|
|
595
310
|
task_data["current_phase"] = create_pr_phase
|
|
596
|
-
if has_submodule_prs:
|
|
597
|
-
task_data["submodule_prs"] = submodule_prs
|
|
598
311
|
|
|
599
|
-
|
|
312
|
+
_write_json_file(task_json, task_data)
|
|
600
313
|
print(
|
|
601
314
|
f"{Colors.GREEN}Task status updated to 'completed', phase {create_pr_phase}{Colors.NC}"
|
|
602
315
|
)
|
|
603
316
|
|
|
604
317
|
# In dry-run, reset the staging area
|
|
605
318
|
if args.dry_run:
|
|
606
|
-
|
|
319
|
+
_run_git_command(["reset", "HEAD"])
|
|
607
320
|
|
|
608
321
|
print()
|
|
609
322
|
print(f"{Colors.GREEN}=== PR Created Successfully ==={Colors.NC}")
|
|
610
323
|
print(f"PR URL: {pr_url}")
|
|
611
|
-
if has_submodule_prs:
|
|
612
|
-
print("Submodule PRs:")
|
|
613
|
-
for name, url in submodule_prs.items():
|
|
614
|
-
print(f" - {name}: {url}")
|
|
615
324
|
|
|
616
325
|
return 0
|
|
617
326
|
|
|
@@ -24,14 +24,38 @@ import subprocess
|
|
|
24
24
|
import sys
|
|
25
25
|
from pathlib import Path
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
# Add parent directory to path for imports
|
|
28
|
+
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
28
29
|
|
|
29
30
|
from common.cli_adapter import get_cli_adapter
|
|
30
|
-
from common.log import Colors, log_info, log_success, log_error
|
|
31
31
|
from common.paths import get_repo_root
|
|
32
32
|
from common.developer import ensure_developer
|
|
33
33
|
|
|
34
34
|
|
|
35
|
+
# =============================================================================
|
|
36
|
+
# Colors
|
|
37
|
+
# =============================================================================
|
|
38
|
+
|
|
39
|
+
class Colors:
|
|
40
|
+
RED = "\033[0;31m"
|
|
41
|
+
GREEN = "\033[0;32m"
|
|
42
|
+
YELLOW = "\033[1;33m"
|
|
43
|
+
BLUE = "\033[0;34m"
|
|
44
|
+
NC = "\033[0m"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def log_info(msg: str) -> None:
|
|
48
|
+
print(f"{Colors.BLUE}[INFO]{Colors.NC} {msg}")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def log_success(msg: str) -> None:
|
|
52
|
+
print(f"{Colors.GREEN}[SUCCESS]{Colors.NC} {msg}")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def log_error(msg: str) -> None:
|
|
56
|
+
print(f"{Colors.RED}[ERROR]{Colors.NC} {msg}")
|
|
57
|
+
|
|
58
|
+
|
|
35
59
|
# =============================================================================
|
|
36
60
|
# Constants
|
|
37
61
|
# =============================================================================
|