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,20 @@
1
+ """
2
+ galangal list - List all tasks.
3
+ """
4
+
5
+ import argparse
6
+
7
+ from galangal.core.tasks import get_active_task, list_tasks
8
+ from galangal.ui.console import console, display_task_list
9
+
10
+
11
+ def cmd_list(args: argparse.Namespace) -> int:
12
+ """List all tasks."""
13
+ tasks = list_tasks()
14
+ active = get_active_task()
15
+
16
+ display_task_list(tasks, active)
17
+
18
+ if active:
19
+ console.print("\n[dim]→ = active task[/dim]")
20
+ return 0
@@ -0,0 +1,34 @@
1
+ """
2
+ galangal pause - Pause the active task.
3
+ """
4
+
5
+ import argparse
6
+
7
+ from galangal.core.state import Stage
8
+ from galangal.core.tasks import ensure_active_task_with_state
9
+ from galangal.ui.console import console, print_info
10
+
11
+
12
+ def cmd_pause(args: argparse.Namespace) -> int:
13
+ """Pause the active task for a break or shutdown."""
14
+ active, state = ensure_active_task_with_state()
15
+ if not active or not state:
16
+ return 1
17
+
18
+ if state.stage == Stage.COMPLETE:
19
+ print_info(f"Task '{active}' is already complete.")
20
+ console.print("Use 'complete' to create PR and move to done/.")
21
+ return 0
22
+
23
+ console.print("\n" + "=" * 60)
24
+ console.print("[yellow]⏸️ TASK PAUSED[/yellow]")
25
+ console.print("=" * 60)
26
+ console.print(f"\nTask: {state.task_name}")
27
+ console.print(f"Stage: {state.stage.value} (attempt {state.attempt})")
28
+ console.print(f"Type: {state.task_type.display_name()}")
29
+ console.print(f"Description: {state.task_description[:60]}...")
30
+ console.print("\nYour progress is saved. You can safely shut down now.")
31
+ console.print("\nTo resume later, run:")
32
+ console.print(" [cyan]galangal resume[/cyan]")
33
+ console.print("=" * 60)
34
+ return 0
@@ -0,0 +1,89 @@
1
+ """
2
+ galangal prompts - Manage stage prompts.
3
+ """
4
+
5
+ import argparse
6
+ import shutil
7
+ from pathlib import Path
8
+
9
+ from galangal.config.loader import get_prompts_dir
10
+ from galangal.core.state import parse_stage_arg
11
+ from galangal.prompts.builder import PromptBuilder
12
+ from galangal.ui.console import console, print_error, print_info, print_success
13
+
14
+
15
+ def cmd_prompts_export(args: argparse.Namespace) -> int:
16
+ """Export default prompts to .galangal/prompts/ for customization."""
17
+ prompts_dir = get_prompts_dir()
18
+ defaults_dir = Path(__file__).parent.parent / "prompts" / "defaults"
19
+
20
+ if not defaults_dir.exists():
21
+ print_error("Default prompts directory not found.")
22
+ return 1
23
+
24
+ prompts_dir.mkdir(parents=True, exist_ok=True)
25
+
26
+ exported = []
27
+ skipped = []
28
+
29
+ for prompt_file in defaults_dir.glob("*.md"):
30
+ dest = prompts_dir / prompt_file.name
31
+ if dest.exists():
32
+ skipped.append(prompt_file.name)
33
+ else:
34
+ shutil.copy(prompt_file, dest)
35
+ exported.append(prompt_file.name)
36
+
37
+ if exported:
38
+ print_success(f"Exported {len(exported)} prompts to .galangal/prompts/")
39
+ for name in exported:
40
+ console.print(f" [green]✓[/green] {name}")
41
+
42
+ if skipped:
43
+ print_info(f"Skipped {len(skipped)} existing prompts (won't overwrite)")
44
+ for name in skipped:
45
+ console.print(f" [dim]○[/dim] {name}")
46
+
47
+ console.print("\n[dim]Edit these files to customize prompts for your project.[/dim]")
48
+ console.print("[dim]Project prompts override package defaults.[/dim]")
49
+
50
+ return 0
51
+
52
+
53
+ def cmd_prompts_show(args: argparse.Namespace) -> int:
54
+ """Show the effective prompt for a stage."""
55
+ # Parse stage (COMPLETE excluded - it has no prompt)
56
+ stage = parse_stage_arg(args.stage, exclude_complete=True)
57
+ if stage is None:
58
+ return 1
59
+
60
+ builder = PromptBuilder()
61
+ prompt = builder.get_stage_prompt(stage)
62
+
63
+ # Determine source
64
+ prompts_dir = get_prompts_dir()
65
+ override_path = prompts_dir / f"{stage.value.lower()}.md"
66
+ source = "project override" if override_path.exists() else "package default"
67
+
68
+ console.print(f"\n[bold]Stage:[/bold] {stage.value}")
69
+ console.print(f"[bold]Source:[/bold] {source}")
70
+ console.print("[dim]" + "=" * 60 + "[/dim]")
71
+ console.print(prompt)
72
+ console.print("[dim]" + "=" * 60 + "[/dim]")
73
+
74
+ return 0
75
+
76
+
77
+ def cmd_prompts(args: argparse.Namespace) -> int:
78
+ """Prompts management command router."""
79
+ if hasattr(args, "prompts_command") and args.prompts_command:
80
+ if args.prompts_command == "export":
81
+ return cmd_prompts_export(args)
82
+ elif args.prompts_command == "show":
83
+ return cmd_prompts_show(args)
84
+
85
+ console.print("Usage: galangal prompts <command>")
86
+ console.print("\nCommands:")
87
+ console.print(" export Export default prompts for customization")
88
+ console.print(" show Show effective prompt for a stage")
89
+ return 1
@@ -0,0 +1,41 @@
1
+ """
2
+ galangal reset - Delete the active task.
3
+ """
4
+
5
+ import argparse
6
+ import shutil
7
+
8
+ from rich.prompt import Prompt
9
+
10
+ from galangal.core.state import get_task_dir
11
+ from galangal.core.tasks import clear_active_task, get_active_task
12
+ from galangal.ui.console import print_error, print_info, print_success
13
+
14
+
15
+ def cmd_reset(args: argparse.Namespace) -> int:
16
+ """Delete the active task."""
17
+ active = get_active_task()
18
+ if not active:
19
+ print_error("No active task.")
20
+ return 0
21
+
22
+ task_dir = get_task_dir(active)
23
+ if not task_dir.exists():
24
+ print_info("Task directory not found.")
25
+ clear_active_task()
26
+ return 0
27
+
28
+ if not args.force:
29
+ confirm = (
30
+ Prompt.ask(f"Delete task '{active}' and all its artifacts? [y/N]", default="n")
31
+ .strip()
32
+ .lower()
33
+ )
34
+ if confirm != "y":
35
+ print_info("Reset cancelled.")
36
+ return 1
37
+
38
+ shutil.rmtree(task_dir)
39
+ clear_active_task()
40
+ print_success(f"Task '{active}' deleted.")
41
+ return 0
@@ -0,0 +1,30 @@
1
+ """
2
+ galangal resume - Resume the active task.
3
+ """
4
+
5
+ import argparse
6
+
7
+ from galangal.core.tasks import ensure_active_task_with_state
8
+ from galangal.core.workflow import run_workflow
9
+ from galangal.ui.console import console
10
+
11
+
12
+ def cmd_resume(args: argparse.Namespace) -> int:
13
+ """Resume the active task."""
14
+ active, state = ensure_active_task_with_state(
15
+ no_task_msg="No active task. Use 'list' to see tasks, 'switch' to select one."
16
+ )
17
+ if not active or not state:
18
+ return 1
19
+
20
+ console.print(f"[bold]Resuming task:[/bold] {active}")
21
+ console.print(f"[dim]Stage:[/dim] {state.stage.value}")
22
+ console.print(f"[dim]Type:[/dim] {state.task_type.display_name()}")
23
+
24
+ # Pass skip_discovery flag via state attribute
25
+ if getattr(args, "skip_discovery", False):
26
+ state._skip_discovery = True
27
+ console.print("[dim]Discovery Q&A:[/dim] skipped")
28
+
29
+ run_workflow(state)
30
+ return 0
@@ -0,0 +1,62 @@
1
+ """
2
+ galangal skip-to - Jump to a specific stage for debugging.
3
+ """
4
+
5
+ import argparse
6
+
7
+ from rich.prompt import Prompt
8
+
9
+ from galangal.core.state import STAGE_ORDER, parse_stage_arg, save_state
10
+ from galangal.core.tasks import ensure_active_task_with_state
11
+ from galangal.core.workflow import run_workflow
12
+ from galangal.ui.console import console, print_info, print_success
13
+
14
+
15
+ def cmd_skip_to(args: argparse.Namespace) -> int:
16
+ """Jump to a specific stage (for debugging/re-running)."""
17
+ active, state = ensure_active_task_with_state()
18
+ if not active or not state:
19
+ return 1
20
+
21
+ # Parse target stage (COMPLETE not allowed - use 'complete' command instead)
22
+ target_stage = parse_stage_arg(args.stage, exclude_complete=True)
23
+ if target_stage is None:
24
+ return 1
25
+
26
+ current_stage = state.stage
27
+ current_idx = STAGE_ORDER.index(current_stage) if current_stage in STAGE_ORDER else -1
28
+ target_idx = STAGE_ORDER.index(target_stage)
29
+
30
+ # Warn if skipping backwards or forwards
31
+ if target_idx < current_idx:
32
+ console.print(
33
+ f"[yellow]⚠️ Going backwards: {current_stage.value} → {target_stage.value}[/yellow]"
34
+ )
35
+ elif target_idx > current_idx:
36
+ console.print(
37
+ f"[yellow]⚠️ Skipping forward: {current_stage.value} → {target_stage.value}[/yellow]"
38
+ )
39
+ else:
40
+ console.print(f"[dim]Re-running current stage: {target_stage.value}[/dim]")
41
+
42
+ if not args.force:
43
+ confirm = Prompt.ask(f"Jump to {target_stage.value}? [y/N]", default="n").strip().lower()
44
+ if confirm != "y":
45
+ print_info("Cancelled.")
46
+ return 0
47
+
48
+ # Update state
49
+ state.stage = target_stage
50
+ state.reset_attempts()
51
+ state.awaiting_approval = False
52
+ state.clarification_required = False
53
+ save_state(state)
54
+
55
+ print_success(f"Jumped to stage: {target_stage.value}")
56
+
57
+ # Optionally resume immediately
58
+ if args.resume:
59
+ console.print("\n[dim]Resuming workflow...[/dim]")
60
+ run_workflow(state)
61
+
62
+ return 0