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.
Files changed (79) hide show
  1. galangal/__init__.py +36 -0
  2. galangal/__main__.py +6 -0
  3. galangal/ai/__init__.py +167 -0
  4. galangal/ai/base.py +159 -0
  5. galangal/ai/claude.py +352 -0
  6. galangal/ai/codex.py +370 -0
  7. galangal/ai/gemini.py +43 -0
  8. galangal/ai/subprocess.py +254 -0
  9. galangal/cli.py +371 -0
  10. galangal/commands/__init__.py +27 -0
  11. galangal/commands/complete.py +367 -0
  12. galangal/commands/github.py +355 -0
  13. galangal/commands/init.py +177 -0
  14. galangal/commands/init_wizard.py +762 -0
  15. galangal/commands/list.py +20 -0
  16. galangal/commands/pause.py +34 -0
  17. galangal/commands/prompts.py +89 -0
  18. galangal/commands/reset.py +41 -0
  19. galangal/commands/resume.py +30 -0
  20. galangal/commands/skip.py +62 -0
  21. galangal/commands/start.py +530 -0
  22. galangal/commands/status.py +44 -0
  23. galangal/commands/switch.py +28 -0
  24. galangal/config/__init__.py +15 -0
  25. galangal/config/defaults.py +183 -0
  26. galangal/config/loader.py +163 -0
  27. galangal/config/schema.py +330 -0
  28. galangal/core/__init__.py +33 -0
  29. galangal/core/artifacts.py +136 -0
  30. galangal/core/state.py +1097 -0
  31. galangal/core/tasks.py +454 -0
  32. galangal/core/utils.py +116 -0
  33. galangal/core/workflow/__init__.py +68 -0
  34. galangal/core/workflow/core.py +789 -0
  35. galangal/core/workflow/engine.py +781 -0
  36. galangal/core/workflow/pause.py +35 -0
  37. galangal/core/workflow/tui_runner.py +1322 -0
  38. galangal/exceptions.py +36 -0
  39. galangal/github/__init__.py +31 -0
  40. galangal/github/client.py +427 -0
  41. galangal/github/images.py +324 -0
  42. galangal/github/issues.py +298 -0
  43. galangal/logging.py +364 -0
  44. galangal/prompts/__init__.py +5 -0
  45. galangal/prompts/builder.py +527 -0
  46. galangal/prompts/defaults/benchmark.md +34 -0
  47. galangal/prompts/defaults/contract.md +35 -0
  48. galangal/prompts/defaults/design.md +54 -0
  49. galangal/prompts/defaults/dev.md +89 -0
  50. galangal/prompts/defaults/docs.md +104 -0
  51. galangal/prompts/defaults/migration.md +59 -0
  52. galangal/prompts/defaults/pm.md +110 -0
  53. galangal/prompts/defaults/pm_questions.md +53 -0
  54. galangal/prompts/defaults/preflight.md +32 -0
  55. galangal/prompts/defaults/qa.md +65 -0
  56. galangal/prompts/defaults/review.md +90 -0
  57. galangal/prompts/defaults/review_codex.md +99 -0
  58. galangal/prompts/defaults/security.md +84 -0
  59. galangal/prompts/defaults/test.md +91 -0
  60. galangal/results.py +176 -0
  61. galangal/ui/__init__.py +5 -0
  62. galangal/ui/console.py +126 -0
  63. galangal/ui/tui/__init__.py +56 -0
  64. galangal/ui/tui/adapters.py +168 -0
  65. galangal/ui/tui/app.py +902 -0
  66. galangal/ui/tui/entry.py +24 -0
  67. galangal/ui/tui/mixins.py +196 -0
  68. galangal/ui/tui/modals.py +339 -0
  69. galangal/ui/tui/styles/app.tcss +86 -0
  70. galangal/ui/tui/styles/modals.tcss +197 -0
  71. galangal/ui/tui/types.py +107 -0
  72. galangal/ui/tui/widgets.py +263 -0
  73. galangal/validation/__init__.py +5 -0
  74. galangal/validation/runner.py +1072 -0
  75. galangal_orchestrate-0.13.0.dist-info/METADATA +985 -0
  76. galangal_orchestrate-0.13.0.dist-info/RECORD +79 -0
  77. galangal_orchestrate-0.13.0.dist-info/WHEEL +4 -0
  78. galangal_orchestrate-0.13.0.dist-info/entry_points.txt +2 -0
  79. 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")