@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.
Files changed (173) hide show
  1. package/dist/cli/index.js +0 -2
  2. package/dist/cli/index.js.map +1 -1
  3. package/dist/commands/init.d.ts +0 -1
  4. package/dist/commands/init.d.ts.map +1 -1
  5. package/dist/commands/init.js +31 -203
  6. package/dist/commands/init.js.map +1 -1
  7. package/dist/commands/update.d.ts.map +1 -1
  8. package/dist/commands/update.js +6 -154
  9. package/dist/commands/update.js.map +1 -1
  10. package/dist/configurators/workflow.d.ts +2 -6
  11. package/dist/configurators/workflow.d.ts.map +1 -1
  12. package/dist/configurators/workflow.js +58 -88
  13. package/dist/configurators/workflow.js.map +1 -1
  14. package/dist/migrations/index.d.ts +0 -1
  15. package/dist/migrations/index.d.ts.map +1 -1
  16. package/dist/migrations/index.js +0 -2
  17. package/dist/migrations/index.js.map +1 -1
  18. package/dist/migrations/manifests/0.3.10.json +9 -0
  19. package/dist/templates/claude/agents/dispatch.md +2 -1
  20. package/dist/templates/claude/agents/implement.md +3 -2
  21. package/dist/templates/claude/commands/trellis/before-backend-dev.md +13 -0
  22. package/dist/templates/claude/commands/trellis/before-frontend-dev.md +13 -0
  23. package/dist/templates/claude/commands/trellis/check-backend.md +13 -0
  24. package/dist/templates/claude/commands/trellis/check-frontend.md +13 -0
  25. package/dist/templates/claude/commands/trellis/create-command.md +2 -2
  26. package/dist/templates/claude/commands/trellis/onboard.md +13 -13
  27. package/dist/templates/claude/commands/trellis/parallel.md +2 -1
  28. package/dist/templates/claude/commands/trellis/record-session.md +2 -2
  29. package/dist/templates/claude/commands/trellis/start.md +4 -8
  30. package/dist/templates/claude/hooks/inject-subagent-context.py +13 -21
  31. package/dist/templates/claude/hooks/session-start.py +2 -170
  32. package/dist/templates/codex/skills/before-backend-dev/SKILL.md +18 -0
  33. package/dist/templates/codex/skills/before-frontend-dev/SKILL.md +18 -0
  34. package/dist/templates/codex/skills/check-backend/SKILL.md +18 -0
  35. package/dist/templates/codex/skills/check-frontend/SKILL.md +18 -0
  36. package/dist/templates/codex/skills/create-command/SKILL.md +2 -2
  37. package/dist/templates/codex/skills/onboard/SKILL.md +11 -11
  38. package/dist/templates/codex/skills/record-session/SKILL.md +2 -2
  39. package/dist/templates/codex/skills/start/SKILL.md +3 -8
  40. package/dist/templates/cursor/commands/trellis-before-backend-dev.md +13 -0
  41. package/dist/templates/cursor/commands/trellis-before-frontend-dev.md +13 -0
  42. package/dist/templates/cursor/commands/trellis-check-backend.md +13 -0
  43. package/dist/templates/cursor/commands/trellis-check-frontend.md +13 -0
  44. package/dist/templates/cursor/commands/trellis-create-command.md +2 -2
  45. package/dist/templates/cursor/commands/trellis-onboard.md +13 -13
  46. package/dist/templates/cursor/commands/trellis-record-session.md +2 -2
  47. package/dist/templates/cursor/commands/trellis-start.md +16 -7
  48. package/dist/templates/gemini/commands/trellis/before-backend-dev.toml +17 -0
  49. package/dist/templates/gemini/commands/trellis/before-frontend-dev.toml +17 -0
  50. package/dist/templates/gemini/commands/trellis/check-backend.toml +17 -0
  51. package/dist/templates/gemini/commands/trellis/check-frontend.toml +17 -0
  52. package/dist/templates/gemini/commands/trellis/create-command.toml +2 -2
  53. package/dist/templates/gemini/commands/trellis/onboard.toml +2 -2
  54. package/dist/templates/gemini/commands/trellis/record-session.toml +2 -2
  55. package/dist/templates/gemini/commands/trellis/start.toml +4 -9
  56. package/dist/templates/iflow/agents/dispatch.md +2 -1
  57. package/dist/templates/iflow/agents/implement.md +3 -2
  58. package/dist/templates/iflow/commands/trellis/before-backend-dev.md +13 -0
  59. package/dist/templates/iflow/commands/trellis/before-frontend-dev.md +13 -0
  60. package/dist/templates/iflow/commands/trellis/check-backend.md +13 -0
  61. package/dist/templates/iflow/commands/trellis/check-frontend.md +13 -0
  62. package/dist/templates/iflow/commands/trellis/create-command.md +2 -2
  63. package/dist/templates/iflow/commands/trellis/onboard.md +13 -13
  64. package/dist/templates/iflow/commands/trellis/parallel.md +2 -1
  65. package/dist/templates/iflow/commands/trellis/record-session.md +2 -2
  66. package/dist/templates/iflow/commands/trellis/start.md +4 -8
  67. package/dist/templates/iflow/hooks/inject-subagent-context.py +13 -21
  68. package/dist/templates/iflow/hooks/session-start.py +1 -156
  69. package/dist/templates/kilo/workflows/before-backend-dev.md +13 -0
  70. package/dist/templates/kilo/workflows/before-frontend-dev.md +13 -0
  71. package/dist/templates/kilo/workflows/check-backend.md +13 -0
  72. package/dist/templates/kilo/workflows/check-frontend.md +13 -0
  73. package/dist/templates/kilo/workflows/create-command.md +2 -2
  74. package/dist/templates/kilo/workflows/onboard.md +13 -13
  75. package/dist/templates/kilo/workflows/parallel.md +2 -1
  76. package/dist/templates/kilo/workflows/record-session.md +2 -2
  77. package/dist/templates/kilo/workflows/start.md +3 -8
  78. package/dist/templates/kiro/skills/before-backend-dev/SKILL.md +18 -0
  79. package/dist/templates/kiro/skills/before-frontend-dev/SKILL.md +18 -0
  80. package/dist/templates/kiro/skills/check-backend/SKILL.md +18 -0
  81. package/dist/templates/kiro/skills/check-frontend/SKILL.md +18 -0
  82. package/dist/templates/kiro/skills/create-command/SKILL.md +2 -2
  83. package/dist/templates/kiro/skills/onboard/SKILL.md +11 -11
  84. package/dist/templates/kiro/skills/record-session/SKILL.md +2 -2
  85. package/dist/templates/kiro/skills/start/SKILL.md +3 -8
  86. package/dist/templates/markdown/spec/backend/script-conventions.md +0 -93
  87. package/dist/templates/opencode/agents/dispatch.md +2 -1
  88. package/dist/templates/opencode/agents/implement.md +2 -2
  89. package/dist/templates/opencode/agents/research.md +2 -1
  90. package/dist/templates/opencode/commands/trellis/before-backend-dev.md +13 -0
  91. package/dist/templates/opencode/commands/trellis/before-frontend-dev.md +13 -0
  92. package/dist/templates/opencode/commands/trellis/check-backend.md +13 -0
  93. package/dist/templates/opencode/commands/trellis/check-frontend.md +13 -0
  94. package/dist/templates/opencode/commands/trellis/create-command.md +2 -2
  95. package/dist/templates/opencode/commands/trellis/onboard.md +13 -13
  96. package/dist/templates/opencode/commands/trellis/parallel.md +2 -1
  97. package/dist/templates/opencode/commands/trellis/record-session.md +2 -2
  98. package/dist/templates/opencode/commands/trellis/start.md +3 -8
  99. package/dist/templates/opencode/plugin/inject-subagent-context.js +18 -45
  100. package/dist/templates/opencode/plugin/session-start.js +1 -149
  101. package/dist/templates/qoder/skills/before-backend-dev/SKILL.md +18 -0
  102. package/dist/templates/qoder/skills/before-frontend-dev/SKILL.md +18 -0
  103. package/dist/templates/qoder/skills/check-backend/SKILL.md +18 -0
  104. package/dist/templates/qoder/skills/check-frontend/SKILL.md +18 -0
  105. package/dist/templates/qoder/skills/create-command/SKILL.md +2 -2
  106. package/dist/templates/qoder/skills/onboard/SKILL.md +13 -13
  107. package/dist/templates/qoder/skills/record-session/SKILL.md +2 -2
  108. package/dist/templates/qoder/skills/start/SKILL.md +3 -8
  109. package/dist/templates/trellis/config.yaml +0 -20
  110. package/dist/templates/trellis/index.d.ts +0 -11
  111. package/dist/templates/trellis/index.d.ts.map +1 -1
  112. package/dist/templates/trellis/index.js +0 -22
  113. package/dist/templates/trellis/index.js.map +1 -1
  114. package/dist/templates/trellis/scripts/add_session.py +7 -52
  115. package/dist/templates/trellis/scripts/common/cli_adapter.py +45 -33
  116. package/dist/templates/trellis/scripts/common/config.py +0 -152
  117. package/dist/templates/trellis/scripts/common/git_context.py +586 -23
  118. package/dist/templates/trellis/scripts/common/paths.py +0 -46
  119. package/dist/templates/trellis/scripts/common/phase.py +49 -50
  120. package/dist/templates/trellis/scripts/common/registry.py +72 -41
  121. package/dist/templates/trellis/scripts/common/task_queue.py +98 -27
  122. package/dist/templates/trellis/scripts/common/task_utils.py +6 -96
  123. package/dist/templates/trellis/scripts/create_bootstrap.py +26 -31
  124. package/dist/templates/trellis/scripts/multi_agent/cleanup.py +48 -43
  125. package/dist/templates/trellis/scripts/multi_agent/create_pr.py +45 -336
  126. package/dist/templates/trellis/scripts/multi_agent/plan.py +26 -2
  127. package/dist/templates/trellis/scripts/multi_agent/start.py +57 -126
  128. package/dist/templates/trellis/scripts/multi_agent/status.py +753 -12
  129. package/dist/templates/trellis/scripts/task.py +975 -50
  130. package/dist/templates/trellis/workflow.md +34 -21
  131. package/dist/types/migration.d.ts +1 -3
  132. package/dist/types/migration.d.ts.map +1 -1
  133. package/dist/utils/project-detector.d.ts +0 -23
  134. package/dist/utils/project-detector.d.ts.map +1 -1
  135. package/dist/utils/project-detector.js +0 -364
  136. package/dist/utils/project-detector.js.map +1 -1
  137. package/dist/utils/template-fetcher.d.ts +10 -2
  138. package/dist/utils/template-fetcher.d.ts.map +1 -1
  139. package/dist/utils/template-fetcher.js +43 -12
  140. package/dist/utils/template-fetcher.js.map +1 -1
  141. package/package.json +1 -1
  142. package/dist/migrations/manifests/0.4.0-beta.1.json +0 -228
  143. package/dist/templates/claude/commands/trellis/before-dev.md +0 -29
  144. package/dist/templates/claude/commands/trellis/check.md +0 -25
  145. package/dist/templates/codex/skills/before-dev/SKILL.md +0 -34
  146. package/dist/templates/codex/skills/check/SKILL.md +0 -30
  147. package/dist/templates/cursor/commands/trellis-before-dev.md +0 -29
  148. package/dist/templates/cursor/commands/trellis-check.md +0 -25
  149. package/dist/templates/gemini/commands/trellis/before-dev.toml +0 -33
  150. package/dist/templates/gemini/commands/trellis/check.toml +0 -29
  151. package/dist/templates/iflow/commands/trellis/before-dev.md +0 -29
  152. package/dist/templates/iflow/commands/trellis/check.md +0 -25
  153. package/dist/templates/kilo/workflows/before-dev.md +0 -29
  154. package/dist/templates/kilo/workflows/check.md +0 -25
  155. package/dist/templates/kiro/skills/before-dev/SKILL.md +0 -34
  156. package/dist/templates/kiro/skills/check/SKILL.md +0 -30
  157. package/dist/templates/opencode/commands/trellis/before-dev.md +0 -29
  158. package/dist/templates/opencode/commands/trellis/check.md +0 -25
  159. package/dist/templates/qoder/skills/before-dev/SKILL.md +0 -34
  160. package/dist/templates/qoder/skills/check/SKILL.md +0 -30
  161. package/dist/templates/trellis/scripts/common/git.py +0 -31
  162. package/dist/templates/trellis/scripts/common/io.py +0 -37
  163. package/dist/templates/trellis/scripts/common/log.py +0 -45
  164. package/dist/templates/trellis/scripts/common/packages_context.py +0 -233
  165. package/dist/templates/trellis/scripts/common/session_context.py +0 -466
  166. package/dist/templates/trellis/scripts/common/task_context.py +0 -384
  167. package/dist/templates/trellis/scripts/common/task_store.py +0 -534
  168. package/dist/templates/trellis/scripts/common/tasks.py +0 -109
  169. package/dist/templates/trellis/scripts/common/types.py +0 -112
  170. package/dist/templates/trellis/scripts/hooks/linear_sync.py +0 -243
  171. package/dist/templates/trellis/scripts/multi_agent/_bootstrap.py +0 -17
  172. package/dist/templates/trellis/scripts/multi_agent/status_display.py +0 -542
  173. 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. Handles submodule changes (commit, push, PR) if any submodules are configured
10
- 2. Stages and commits all main-repo changes (excluding workspace/)
11
- 3. Pushes to origin
12
- 4. Creates a Draft PR using `gh pr create`
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
- import _bootstrap # noqa: F401 — adds parent scripts/ dir to sys.path
26
+ # Add parent directory to path for imports
27
+ sys.path.insert(0, str(Path(__file__).parent.parent))
27
28
 
28
- from common.config import get_submodule_packages
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
- # Submodule PR Helpers
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
- if not changed:
115
- return submodule_prs, [], True
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
- print(f"\n{Colors.BLUE}=== Submodule Changes Detected ==={Colors.NC}")
122
- for pkg_name, pkg_path in changed:
123
- print(f" - {pkg_name} ({pkg_path})")
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
- # --- Stage and commit ---
180
- run_git(["add", "-A"], cwd=sub_abs)
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
- # --- Push ---
196
- ret, _, err = run_git(
197
- ["push", "-u", "origin", sub_branch], cwd=sub_abs
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
- if ret != 0:
200
- print(f"{Colors.RED}Failed to push {pkg_name}: {err}{Colors.NC}")
201
- return submodule_prs, changed_paths, False
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 = read_json(task_json)
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, _ = run_git(["branch", "--show-current"])
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
- run_git(["add", "-A"])
169
+ _run_git_command(["add", "-A"])
434
170
 
435
171
  # Exclude workspace and temp files
436
- run_git(["reset", f"{DIR_WORKFLOW}/workspace/"])
437
- run_git(["reset", ".agent-log", ".session-id"])
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, _, _ = run_git(["diff", "--cached", "--quiet"])
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, _ = run_git(
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
- run_git(["reset", "HEAD"])
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, _ = run_git(["diff", "--cached", "--name-only"])
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
- run_git(["commit", "-m", commit_msg])
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 = run_git(["push", "-u", "origin", current_branch])
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
- write_json(task_json, task_data)
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
- run_git(["reset", "HEAD"])
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
- import _bootstrap # noqa: F401 — adds parent scripts/ dir to sys.path
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
  # =============================================================================