galangal-orchestrate 0.13.0__py3-none-any.whl
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.
- galangal/__init__.py +36 -0
- galangal/__main__.py +6 -0
- galangal/ai/__init__.py +167 -0
- galangal/ai/base.py +159 -0
- galangal/ai/claude.py +352 -0
- galangal/ai/codex.py +370 -0
- galangal/ai/gemini.py +43 -0
- galangal/ai/subprocess.py +254 -0
- galangal/cli.py +371 -0
- galangal/commands/__init__.py +27 -0
- galangal/commands/complete.py +367 -0
- galangal/commands/github.py +355 -0
- galangal/commands/init.py +177 -0
- galangal/commands/init_wizard.py +762 -0
- galangal/commands/list.py +20 -0
- galangal/commands/pause.py +34 -0
- galangal/commands/prompts.py +89 -0
- galangal/commands/reset.py +41 -0
- galangal/commands/resume.py +30 -0
- galangal/commands/skip.py +62 -0
- galangal/commands/start.py +530 -0
- galangal/commands/status.py +44 -0
- galangal/commands/switch.py +28 -0
- galangal/config/__init__.py +15 -0
- galangal/config/defaults.py +183 -0
- galangal/config/loader.py +163 -0
- galangal/config/schema.py +330 -0
- galangal/core/__init__.py +33 -0
- galangal/core/artifacts.py +136 -0
- galangal/core/state.py +1097 -0
- galangal/core/tasks.py +454 -0
- galangal/core/utils.py +116 -0
- galangal/core/workflow/__init__.py +68 -0
- galangal/core/workflow/core.py +789 -0
- galangal/core/workflow/engine.py +781 -0
- galangal/core/workflow/pause.py +35 -0
- galangal/core/workflow/tui_runner.py +1322 -0
- galangal/exceptions.py +36 -0
- galangal/github/__init__.py +31 -0
- galangal/github/client.py +427 -0
- galangal/github/images.py +324 -0
- galangal/github/issues.py +298 -0
- galangal/logging.py +364 -0
- galangal/prompts/__init__.py +5 -0
- galangal/prompts/builder.py +527 -0
- galangal/prompts/defaults/benchmark.md +34 -0
- galangal/prompts/defaults/contract.md +35 -0
- galangal/prompts/defaults/design.md +54 -0
- galangal/prompts/defaults/dev.md +89 -0
- galangal/prompts/defaults/docs.md +104 -0
- galangal/prompts/defaults/migration.md +59 -0
- galangal/prompts/defaults/pm.md +110 -0
- galangal/prompts/defaults/pm_questions.md +53 -0
- galangal/prompts/defaults/preflight.md +32 -0
- galangal/prompts/defaults/qa.md +65 -0
- galangal/prompts/defaults/review.md +90 -0
- galangal/prompts/defaults/review_codex.md +99 -0
- galangal/prompts/defaults/security.md +84 -0
- galangal/prompts/defaults/test.md +91 -0
- galangal/results.py +176 -0
- galangal/ui/__init__.py +5 -0
- galangal/ui/console.py +126 -0
- galangal/ui/tui/__init__.py +56 -0
- galangal/ui/tui/adapters.py +168 -0
- galangal/ui/tui/app.py +902 -0
- galangal/ui/tui/entry.py +24 -0
- galangal/ui/tui/mixins.py +196 -0
- galangal/ui/tui/modals.py +339 -0
- galangal/ui/tui/styles/app.tcss +86 -0
- galangal/ui/tui/styles/modals.tcss +197 -0
- galangal/ui/tui/types.py +107 -0
- galangal/ui/tui/widgets.py +263 -0
- galangal/validation/__init__.py +5 -0
- galangal/validation/runner.py +1072 -0
- galangal_orchestrate-0.13.0.dist-info/METADATA +985 -0
- galangal_orchestrate-0.13.0.dist-info/RECORD +79 -0
- galangal_orchestrate-0.13.0.dist-info/WHEEL +4 -0
- galangal_orchestrate-0.13.0.dist-info/entry_points.txt +2 -0
- galangal_orchestrate-0.13.0.dist-info/licenses/LICENSE +674 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
"""
|
|
2
|
+
galangal github - GitHub integration commands.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import platform
|
|
7
|
+
|
|
8
|
+
from galangal.ui.console import console, print_error, print_info, print_success, print_warning
|
|
9
|
+
|
|
10
|
+
GH_INSTALL_INSTRUCTIONS = """
|
|
11
|
+
GitHub CLI (gh) is required for GitHub integration.
|
|
12
|
+
|
|
13
|
+
Installation instructions:
|
|
14
|
+
|
|
15
|
+
macOS:
|
|
16
|
+
brew install gh
|
|
17
|
+
|
|
18
|
+
Windows:
|
|
19
|
+
winget install GitHub.cli
|
|
20
|
+
# or download from https://github.com/cli/cli/releases
|
|
21
|
+
|
|
22
|
+
Linux (Debian/Ubuntu):
|
|
23
|
+
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
|
|
24
|
+
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null
|
|
25
|
+
sudo apt update && sudo apt install gh
|
|
26
|
+
|
|
27
|
+
Linux (Fedora):
|
|
28
|
+
sudo dnf install gh
|
|
29
|
+
|
|
30
|
+
Linux (Arch):
|
|
31
|
+
sudo pacman -S github-cli
|
|
32
|
+
|
|
33
|
+
After installation, authenticate with:
|
|
34
|
+
gh auth login
|
|
35
|
+
|
|
36
|
+
For more info: https://cli.github.com
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _get_platform_install_hint() -> str:
|
|
41
|
+
"""Get platform-specific installation hint."""
|
|
42
|
+
system = platform.system().lower()
|
|
43
|
+
if system == "darwin":
|
|
44
|
+
return "brew install gh"
|
|
45
|
+
elif system == "windows":
|
|
46
|
+
return "winget install GitHub.cli"
|
|
47
|
+
elif system == "linux":
|
|
48
|
+
return "See https://cli.github.com for Linux install instructions"
|
|
49
|
+
return "See https://cli.github.com"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def require_github_ready():
|
|
53
|
+
"""
|
|
54
|
+
Check GitHub readiness and print error if not ready.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
GitHubReadyCheck if ready, None if not ready (error already printed).
|
|
58
|
+
"""
|
|
59
|
+
from galangal.github.client import ensure_github_ready
|
|
60
|
+
|
|
61
|
+
check = ensure_github_ready()
|
|
62
|
+
if not check:
|
|
63
|
+
print_error("GitHub integration not ready. Run 'galangal github check' for details.")
|
|
64
|
+
return None
|
|
65
|
+
return check
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def cmd_github_setup(args: argparse.Namespace) -> int:
|
|
69
|
+
"""Set up GitHub integration by creating required labels."""
|
|
70
|
+
from galangal.config.loader import get_config
|
|
71
|
+
from galangal.github.client import GitHubClient
|
|
72
|
+
|
|
73
|
+
console.print("\n[bold]GitHub Integration Setup[/bold]\n")
|
|
74
|
+
|
|
75
|
+
client = GitHubClient()
|
|
76
|
+
|
|
77
|
+
# Step 1: Check if gh is installed
|
|
78
|
+
gh_installed, gh_version = client.check_installation()
|
|
79
|
+
if not gh_installed:
|
|
80
|
+
print_error("GitHub CLI (gh) is not installed")
|
|
81
|
+
console.print(f"\n[dim]Quick install: {_get_platform_install_hint()}[/dim]")
|
|
82
|
+
if getattr(args, "help_install", False):
|
|
83
|
+
console.print(GH_INSTALL_INSTRUCTIONS)
|
|
84
|
+
else:
|
|
85
|
+
console.print(
|
|
86
|
+
"\n[dim]Run 'galangal github setup --help-install' for detailed instructions[/dim]"
|
|
87
|
+
)
|
|
88
|
+
return 1
|
|
89
|
+
|
|
90
|
+
print_success(f"GitHub CLI installed: {gh_version}")
|
|
91
|
+
|
|
92
|
+
# Step 2: Check authentication
|
|
93
|
+
authenticated, auth_user, _ = client.check_auth()
|
|
94
|
+
if not authenticated:
|
|
95
|
+
print_error("Not authenticated with GitHub")
|
|
96
|
+
console.print("\n[dim]Run: gh auth login[/dim]")
|
|
97
|
+
return 1
|
|
98
|
+
|
|
99
|
+
print_success(f"Authenticated as: {auth_user}")
|
|
100
|
+
|
|
101
|
+
# Step 3: Check repository access
|
|
102
|
+
repo_accessible, repo_name = client.check_repo_access()
|
|
103
|
+
if not repo_accessible:
|
|
104
|
+
print_error("Cannot access repository")
|
|
105
|
+
console.print("\n[dim]Ensure you're in a git repo with a GitHub remote[/dim]")
|
|
106
|
+
return 1
|
|
107
|
+
|
|
108
|
+
print_success(f"Repository: {repo_name}")
|
|
109
|
+
|
|
110
|
+
console.print("\n[dim]─────────────────────────────────────[/dim]")
|
|
111
|
+
console.print("\n[bold]Creating labels...[/bold]\n")
|
|
112
|
+
|
|
113
|
+
# Step 4: Create required labels
|
|
114
|
+
config = get_config()
|
|
115
|
+
github_config = config.github
|
|
116
|
+
|
|
117
|
+
labels_to_create = [
|
|
118
|
+
(
|
|
119
|
+
github_config.pickup_label,
|
|
120
|
+
github_config.label_colors.get(github_config.pickup_label, "7C3AED"),
|
|
121
|
+
"Issues for galangal to work on",
|
|
122
|
+
),
|
|
123
|
+
(
|
|
124
|
+
github_config.in_progress_label,
|
|
125
|
+
github_config.label_colors.get(github_config.in_progress_label, "FCD34D"),
|
|
126
|
+
"Issue is being worked on by galangal",
|
|
127
|
+
),
|
|
128
|
+
]
|
|
129
|
+
|
|
130
|
+
created_count = 0
|
|
131
|
+
for label_name, color, description in labels_to_create:
|
|
132
|
+
success, was_created = client.create_label_if_missing(label_name, color, description)
|
|
133
|
+
if success:
|
|
134
|
+
if was_created:
|
|
135
|
+
print_success(f"Created label: {label_name}")
|
|
136
|
+
created_count += 1
|
|
137
|
+
else:
|
|
138
|
+
print_info(f"Label already exists: {label_name}")
|
|
139
|
+
else:
|
|
140
|
+
print_error(f"Failed to create label: {label_name}")
|
|
141
|
+
|
|
142
|
+
# Step 5: Show summary and next steps
|
|
143
|
+
console.print("\n[dim]─────────────────────────────────────[/dim]")
|
|
144
|
+
console.print("\n[bold]Setup complete![/bold]\n")
|
|
145
|
+
|
|
146
|
+
if created_count > 0:
|
|
147
|
+
console.print(f"Created {created_count} new label(s).\n")
|
|
148
|
+
|
|
149
|
+
console.print("[bold]How to use GitHub integration:[/bold]")
|
|
150
|
+
console.print(
|
|
151
|
+
f" 1. Add the '[cyan]{github_config.pickup_label}[/cyan]' label to issues you want galangal to work on"
|
|
152
|
+
)
|
|
153
|
+
console.print(
|
|
154
|
+
" 2. Run '[cyan]galangal start[/cyan]' and select 'GitHub issue' as the task source"
|
|
155
|
+
)
|
|
156
|
+
console.print(
|
|
157
|
+
" 3. Or run '[cyan]galangal github run[/cyan]' to process all labeled issues automatically"
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
console.print("\n[bold]Label to task type mapping:[/bold]")
|
|
161
|
+
mapping = github_config.label_mapping
|
|
162
|
+
console.print(f" bug_fix: {', '.join(mapping.bug)}")
|
|
163
|
+
console.print(f" feature: {', '.join(mapping.feature)}")
|
|
164
|
+
console.print(f" docs: {', '.join(mapping.docs)}")
|
|
165
|
+
console.print(f" refactor: {', '.join(mapping.refactor)}")
|
|
166
|
+
console.print(f" chore: {', '.join(mapping.chore)}")
|
|
167
|
+
console.print(f" hotfix: {', '.join(mapping.hotfix)}")
|
|
168
|
+
|
|
169
|
+
console.print(
|
|
170
|
+
"\n[dim]Customize mappings in .galangal/config.yaml under 'github.label_mapping'[/dim]"
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
return 0
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def cmd_github_check(args: argparse.Namespace) -> int:
|
|
177
|
+
"""Check GitHub CLI installation and repository access."""
|
|
178
|
+
from galangal.github.client import GitHubClient
|
|
179
|
+
|
|
180
|
+
console.print("\n[bold]GitHub Integration Check[/bold]\n")
|
|
181
|
+
|
|
182
|
+
client = GitHubClient()
|
|
183
|
+
result = client.check_setup()
|
|
184
|
+
|
|
185
|
+
# Display results
|
|
186
|
+
console.print("[dim]─────────────────────────────────────[/dim]")
|
|
187
|
+
|
|
188
|
+
# 1. gh CLI installation
|
|
189
|
+
if result.gh_installed:
|
|
190
|
+
print_success(f"gh CLI installed: {result.gh_version}")
|
|
191
|
+
else:
|
|
192
|
+
print_error("gh CLI not installed")
|
|
193
|
+
console.print(" [dim]Install from: https://cli.github.com[/dim]")
|
|
194
|
+
|
|
195
|
+
# 2. Authentication
|
|
196
|
+
if result.authenticated:
|
|
197
|
+
print_success(f"Authenticated as: {result.auth_user}")
|
|
198
|
+
if result.auth_scopes:
|
|
199
|
+
console.print(f" [dim]Scopes: {', '.join(result.auth_scopes)}[/dim]")
|
|
200
|
+
elif result.gh_installed:
|
|
201
|
+
print_error("Not authenticated")
|
|
202
|
+
console.print(" [dim]Run: gh auth login[/dim]")
|
|
203
|
+
|
|
204
|
+
# 3. Repository access
|
|
205
|
+
if result.repo_accessible:
|
|
206
|
+
print_success(f"Repository: {result.repo_name}")
|
|
207
|
+
elif result.authenticated:
|
|
208
|
+
print_error("Cannot access repository")
|
|
209
|
+
console.print(" [dim]Ensure you're in a git repo with a GitHub remote[/dim]")
|
|
210
|
+
|
|
211
|
+
console.print("[dim]─────────────────────────────────────[/dim]\n")
|
|
212
|
+
|
|
213
|
+
# Summary
|
|
214
|
+
if result.is_ready:
|
|
215
|
+
print_success("GitHub integration is ready")
|
|
216
|
+
return 0
|
|
217
|
+
else:
|
|
218
|
+
print_error("GitHub integration not ready")
|
|
219
|
+
if result.errors:
|
|
220
|
+
console.print("\n[bold]Issues to resolve:[/bold]")
|
|
221
|
+
for error in result.errors:
|
|
222
|
+
console.print(f" • {error}")
|
|
223
|
+
return 1
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def cmd_github_issues(args: argparse.Namespace) -> int:
|
|
227
|
+
"""List GitHub issues with the galangal label."""
|
|
228
|
+
from rich.table import Table
|
|
229
|
+
|
|
230
|
+
from galangal.github.client import GitHubError
|
|
231
|
+
from galangal.github.issues import GALANGAL_LABEL, list_issues
|
|
232
|
+
|
|
233
|
+
# First check setup
|
|
234
|
+
if not require_github_ready():
|
|
235
|
+
return 1
|
|
236
|
+
|
|
237
|
+
label = getattr(args, "label", GALANGAL_LABEL) or GALANGAL_LABEL
|
|
238
|
+
|
|
239
|
+
try:
|
|
240
|
+
issues = list_issues(label=label, limit=args.limit if hasattr(args, "limit") else 50)
|
|
241
|
+
except GitHubError as e:
|
|
242
|
+
print_error(f"Failed to list issues: {e}")
|
|
243
|
+
return 1
|
|
244
|
+
|
|
245
|
+
if not issues:
|
|
246
|
+
print_info(f"No open issues found with label '{label}'")
|
|
247
|
+
console.print(f"\n[dim]To tag an issue for galangal, add the '{label}' label.[/dim]")
|
|
248
|
+
return 0
|
|
249
|
+
|
|
250
|
+
# Display table
|
|
251
|
+
table = Table(title=f"Issues with '{label}' label")
|
|
252
|
+
table.add_column("#", style="cyan", width=6)
|
|
253
|
+
table.add_column("Title", style="bold", max_width=50)
|
|
254
|
+
table.add_column("Labels", style="dim")
|
|
255
|
+
table.add_column("Author", style="dim")
|
|
256
|
+
|
|
257
|
+
for issue in issues:
|
|
258
|
+
other_labels = [lbl for lbl in issue.labels if lbl != label]
|
|
259
|
+
labels_str = ", ".join(other_labels[:3])
|
|
260
|
+
if len(other_labels) > 3:
|
|
261
|
+
labels_str += f" +{len(other_labels) - 3}"
|
|
262
|
+
|
|
263
|
+
table.add_row(
|
|
264
|
+
str(issue.number),
|
|
265
|
+
issue.title[:50] + ("..." if len(issue.title) > 50 else ""),
|
|
266
|
+
labels_str,
|
|
267
|
+
issue.author,
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
console.print(table)
|
|
271
|
+
console.print(f"\n[dim]Found {len(issues)} issue(s)[/dim]")
|
|
272
|
+
|
|
273
|
+
return 0
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def cmd_github_run(args: argparse.Namespace) -> int:
|
|
277
|
+
"""Process all galangal-labeled GitHub issues headlessly."""
|
|
278
|
+
from galangal.core.state import load_state
|
|
279
|
+
from galangal.core.tasks import create_task_from_issue
|
|
280
|
+
from galangal.core.workflow import run_workflow
|
|
281
|
+
from galangal.github.client import GitHubError
|
|
282
|
+
from galangal.github.issues import GALANGAL_LABEL, list_issues
|
|
283
|
+
|
|
284
|
+
console.print("\n[bold]GitHub Issues Batch Processor[/bold]\n")
|
|
285
|
+
|
|
286
|
+
# Check setup
|
|
287
|
+
check = require_github_ready()
|
|
288
|
+
if not check:
|
|
289
|
+
return 1
|
|
290
|
+
|
|
291
|
+
# List issues
|
|
292
|
+
label = getattr(args, "label", GALANGAL_LABEL) or GALANGAL_LABEL
|
|
293
|
+
dry_run = getattr(args, "dry_run", False)
|
|
294
|
+
|
|
295
|
+
try:
|
|
296
|
+
issues = list_issues(label=label)
|
|
297
|
+
except GitHubError as e:
|
|
298
|
+
print_error(f"Failed to list issues: {e}")
|
|
299
|
+
return 1
|
|
300
|
+
|
|
301
|
+
if not issues:
|
|
302
|
+
print_info(f"No open issues found with label '{label}'")
|
|
303
|
+
return 0
|
|
304
|
+
|
|
305
|
+
console.print(f"Found {len(issues)} issue(s) to process\n")
|
|
306
|
+
console.print("[dim]─────────────────────────────────────[/dim]")
|
|
307
|
+
|
|
308
|
+
if dry_run:
|
|
309
|
+
print_info("DRY RUN - no tasks will be created\n")
|
|
310
|
+
for issue in issues:
|
|
311
|
+
console.print(f" [cyan]#{issue.number}[/cyan] {issue.title[:60]}")
|
|
312
|
+
return 0
|
|
313
|
+
|
|
314
|
+
# Process each issue
|
|
315
|
+
processed = 0
|
|
316
|
+
failed = 0
|
|
317
|
+
|
|
318
|
+
for issue in issues:
|
|
319
|
+
console.print(f"\n[bold]Processing issue #{issue.number}:[/bold] {issue.title[:50]}")
|
|
320
|
+
|
|
321
|
+
# Create task from issue (handles name generation, screenshots, marking in-progress)
|
|
322
|
+
task_result = create_task_from_issue(issue, repo_name=check.repo_name)
|
|
323
|
+
|
|
324
|
+
if not task_result.success:
|
|
325
|
+
print_error(f"Failed to create task: {task_result.message}")
|
|
326
|
+
failed += 1
|
|
327
|
+
break # Stop on first failure
|
|
328
|
+
|
|
329
|
+
print_success(f"Created task: {task_result.task_name}")
|
|
330
|
+
if task_result.screenshots:
|
|
331
|
+
print_info(f"Downloaded {len(task_result.screenshots)} screenshot(s)")
|
|
332
|
+
print_info("Marked issue as in-progress")
|
|
333
|
+
|
|
334
|
+
# Run workflow
|
|
335
|
+
state = load_state(task_result.task_name)
|
|
336
|
+
if state:
|
|
337
|
+
console.print("[dim]Running workflow...[/dim]")
|
|
338
|
+
result = run_workflow(state)
|
|
339
|
+
|
|
340
|
+
if result in ("done", "complete"):
|
|
341
|
+
print_success(f"Issue #{issue.number} completed successfully")
|
|
342
|
+
processed += 1
|
|
343
|
+
else:
|
|
344
|
+
print_warning(f"Issue #{issue.number} workflow ended with: {result}")
|
|
345
|
+
failed += 1
|
|
346
|
+
break # Stop on first failure
|
|
347
|
+
|
|
348
|
+
# Summary
|
|
349
|
+
console.print("\n[dim]─────────────────────────────────────[/dim]")
|
|
350
|
+
console.print("\n[bold]Summary:[/bold]")
|
|
351
|
+
console.print(f" Processed: {processed}")
|
|
352
|
+
console.print(f" Failed: {failed}")
|
|
353
|
+
console.print(f" Remaining: {len(issues) - processed - failed}")
|
|
354
|
+
|
|
355
|
+
return 0 if failed == 0 else 1
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""
|
|
2
|
+
galangal init - Initialize galangal in a project.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
|
|
7
|
+
import yaml
|
|
8
|
+
from rich.prompt import Confirm, Prompt
|
|
9
|
+
|
|
10
|
+
from galangal.config.defaults import generate_default_config
|
|
11
|
+
from galangal.config.loader import find_project_root
|
|
12
|
+
from galangal.ui.console import console, print_info, print_success, print_warning
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def cmd_init(args: argparse.Namespace) -> int:
|
|
16
|
+
"""Initialize galangal in the current project."""
|
|
17
|
+
console.print(
|
|
18
|
+
"\n[bold cyan]╔══════════════════════════════════════════════════════════════╗[/bold cyan]"
|
|
19
|
+
)
|
|
20
|
+
console.print(
|
|
21
|
+
"[bold cyan]║[/bold cyan] [bold]Galangal Orchestrate[/bold] [bold cyan]║[/bold cyan]"
|
|
22
|
+
)
|
|
23
|
+
console.print(
|
|
24
|
+
"[bold cyan]║[/bold cyan] AI-Driven Development Workflow [bold cyan]║[/bold cyan]"
|
|
25
|
+
)
|
|
26
|
+
console.print(
|
|
27
|
+
"[bold cyan]╚══════════════════════════════════════════════════════════════╝[/bold cyan]\n"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
project_root = find_project_root()
|
|
31
|
+
galangal_dir = project_root / ".galangal"
|
|
32
|
+
config_file = galangal_dir / "config.yaml"
|
|
33
|
+
|
|
34
|
+
# Check for --quick flag (non-interactive mode)
|
|
35
|
+
quick_mode = getattr(args, "quick", False)
|
|
36
|
+
|
|
37
|
+
if galangal_dir.exists():
|
|
38
|
+
existing_config = None
|
|
39
|
+
if config_file.exists():
|
|
40
|
+
try:
|
|
41
|
+
existing_config = yaml.safe_load(config_file.read_text())
|
|
42
|
+
except yaml.YAMLError:
|
|
43
|
+
existing_config = None
|
|
44
|
+
|
|
45
|
+
if existing_config and not quick_mode:
|
|
46
|
+
# Check for missing sections
|
|
47
|
+
from galangal.commands.init_wizard import check_missing_sections
|
|
48
|
+
|
|
49
|
+
missing = check_missing_sections(existing_config)
|
|
50
|
+
|
|
51
|
+
if missing:
|
|
52
|
+
print_info(f"Galangal already initialized in {project_root}")
|
|
53
|
+
print_warning(f"Missing configuration sections: {', '.join(missing)}")
|
|
54
|
+
|
|
55
|
+
if Confirm.ask("Run setup wizard to configure missing sections?", default=True):
|
|
56
|
+
return _run_wizard_update(project_root, galangal_dir, existing_config)
|
|
57
|
+
else:
|
|
58
|
+
print_info(f"Galangal already initialized in {project_root}")
|
|
59
|
+
|
|
60
|
+
if Confirm.ask("Reinitialize with setup wizard?", default=False):
|
|
61
|
+
return _run_wizard_new(project_root, galangal_dir)
|
|
62
|
+
|
|
63
|
+
return 0
|
|
64
|
+
else:
|
|
65
|
+
print_info(f"Galangal already initialized in {project_root}")
|
|
66
|
+
if not Confirm.ask("Reinitialize?", default=False):
|
|
67
|
+
return 0
|
|
68
|
+
|
|
69
|
+
console.print(f"[dim]Project root: {project_root}[/dim]\n")
|
|
70
|
+
|
|
71
|
+
# Decide between wizard and quick mode
|
|
72
|
+
if quick_mode:
|
|
73
|
+
return _run_quick_init(project_root, galangal_dir)
|
|
74
|
+
else:
|
|
75
|
+
use_wizard = Confirm.ask(
|
|
76
|
+
"Run interactive setup wizard? (recommended for first-time setup)",
|
|
77
|
+
default=True,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
if use_wizard:
|
|
81
|
+
return _run_wizard_new(project_root, galangal_dir)
|
|
82
|
+
else:
|
|
83
|
+
return _run_quick_init(project_root, galangal_dir)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _run_wizard_new(project_root, galangal_dir) -> int:
|
|
87
|
+
"""Run the full setup wizard for new initialization."""
|
|
88
|
+
from galangal.commands.init_wizard import run_wizard
|
|
89
|
+
|
|
90
|
+
config = run_wizard(project_root, existing_config=None)
|
|
91
|
+
|
|
92
|
+
# Create directories
|
|
93
|
+
galangal_dir.mkdir(exist_ok=True)
|
|
94
|
+
(galangal_dir / "prompts").mkdir(exist_ok=True)
|
|
95
|
+
|
|
96
|
+
# Write config
|
|
97
|
+
config_content = config.to_yaml()
|
|
98
|
+
(galangal_dir / "config.yaml").write_text(config_content)
|
|
99
|
+
|
|
100
|
+
print_success("Created .galangal/config.yaml")
|
|
101
|
+
print_success("Created .galangal/prompts/ (empty - uses defaults)")
|
|
102
|
+
|
|
103
|
+
# Add to .gitignore
|
|
104
|
+
_update_gitignore(project_root)
|
|
105
|
+
|
|
106
|
+
_show_next_steps()
|
|
107
|
+
return 0
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _run_wizard_update(project_root, galangal_dir, existing_config: dict) -> int:
|
|
111
|
+
"""Run the setup wizard in update mode for missing sections."""
|
|
112
|
+
from galangal.commands.init_wizard import run_wizard
|
|
113
|
+
|
|
114
|
+
config = run_wizard(project_root, existing_config=existing_config)
|
|
115
|
+
|
|
116
|
+
# Write updated config
|
|
117
|
+
config_content = config.to_yaml()
|
|
118
|
+
(galangal_dir / "config.yaml").write_text(config_content)
|
|
119
|
+
|
|
120
|
+
print_success("Updated .galangal/config.yaml")
|
|
121
|
+
|
|
122
|
+
_show_next_steps()
|
|
123
|
+
return 0
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _run_quick_init(project_root, galangal_dir) -> int:
|
|
127
|
+
"""Run quick initialization without wizard (original behavior)."""
|
|
128
|
+
# Get project name
|
|
129
|
+
default_name = project_root.name
|
|
130
|
+
project_name = Prompt.ask("Project name", default=default_name)
|
|
131
|
+
|
|
132
|
+
# Create .galangal directory
|
|
133
|
+
galangal_dir.mkdir(exist_ok=True)
|
|
134
|
+
(galangal_dir / "prompts").mkdir(exist_ok=True)
|
|
135
|
+
|
|
136
|
+
# Generate config
|
|
137
|
+
config_content = generate_default_config(project_name=project_name)
|
|
138
|
+
(galangal_dir / "config.yaml").write_text(config_content)
|
|
139
|
+
|
|
140
|
+
print_success("Created .galangal/config.yaml")
|
|
141
|
+
print_success("Created .galangal/prompts/ (empty - uses defaults)")
|
|
142
|
+
|
|
143
|
+
# Add to .gitignore
|
|
144
|
+
_update_gitignore(project_root)
|
|
145
|
+
|
|
146
|
+
console.print("\n[dim]Tip: Run 'galangal init' again to use the setup wizard.[/dim]")
|
|
147
|
+
_show_next_steps()
|
|
148
|
+
return 0
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _update_gitignore(project_root) -> None:
|
|
152
|
+
"""Add galangal-tasks/ to .gitignore if not present."""
|
|
153
|
+
gitignore = project_root / ".gitignore"
|
|
154
|
+
tasks_entry = "galangal-tasks/"
|
|
155
|
+
|
|
156
|
+
if gitignore.exists():
|
|
157
|
+
content = gitignore.read_text()
|
|
158
|
+
if tasks_entry not in content:
|
|
159
|
+
with open(gitignore, "a") as f:
|
|
160
|
+
f.write(f"\n# Galangal task artifacts\n{tasks_entry}\n")
|
|
161
|
+
print_success(f"Added {tasks_entry} to .gitignore")
|
|
162
|
+
else:
|
|
163
|
+
gitignore.write_text(f"# Galangal task artifacts\n{tasks_entry}\n")
|
|
164
|
+
print_success(f"Created .gitignore with {tasks_entry}")
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _show_next_steps() -> None:
|
|
168
|
+
"""Show next steps after initialization."""
|
|
169
|
+
console.print("\n[bold green]Initialization complete![/bold green]\n")
|
|
170
|
+
console.print("To customize prompts for your project:")
|
|
171
|
+
console.print(
|
|
172
|
+
" [cyan]galangal prompts export[/cyan] # Export defaults to .galangal/prompts/"
|
|
173
|
+
)
|
|
174
|
+
console.print("\nNext steps:")
|
|
175
|
+
console.print(' [cyan]galangal start "Your first task"[/cyan]')
|
|
176
|
+
console.print("\nFor GitHub Issues integration:")
|
|
177
|
+
console.print(" [cyan]galangal github setup[/cyan] # Create labels and configure")
|