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
galangal/cli.py ADDED
@@ -0,0 +1,371 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Galangal Orchestrate - AI-Driven Development Workflow CLI
4
+
5
+ Usage:
6
+ galangal init - Initialize in current project
7
+ galangal start "task description" - Start new task
8
+ galangal start "desc" --name my-task - Start with explicit name
9
+ galangal list - List all tasks
10
+ galangal switch <task-name> - Switch active task
11
+ galangal status - Show active task status
12
+ galangal resume - Continue active task
13
+ galangal pause - Pause task for break/shutdown
14
+ galangal reset - Delete active task
15
+ galangal complete - Move task to done/, create PR
16
+ galangal prompts export - Export default prompts for customization
17
+
18
+ Debug mode:
19
+ galangal --debug <command> - Enable debug logging to logs/galangal_debug.log
20
+ GALANGAL_DEBUG=1 galangal <command> - Alternative via environment variable
21
+ """
22
+
23
+ import argparse
24
+ import os
25
+ import sys
26
+
27
+
28
+ def _setup_debug_mode() -> None:
29
+ """Enable debug mode by setting environment variable and configuring logging."""
30
+ os.environ["GALANGAL_DEBUG"] = "1"
31
+
32
+ from galangal.config.loader import get_project_root
33
+
34
+ # Create logs directory in project root (not cwd)
35
+ logs_dir = get_project_root() / "logs"
36
+ logs_dir.mkdir(exist_ok=True)
37
+
38
+ # Write initial debug log entry immediately so file is always created
39
+ # Do this BEFORE configure_logging to ensure we have a log even if that fails
40
+ from galangal.core.utils import debug_log, reset_debug_state
41
+
42
+ reset_debug_state() # Clear any cached state
43
+ debug_log("Debug mode enabled", command=" ".join(sys.argv))
44
+
45
+ # Also enable structured logging to file
46
+ try:
47
+ from galangal.logging import configure_logging
48
+
49
+ configure_logging(
50
+ level="debug",
51
+ log_file=logs_dir / "galangal.jsonl",
52
+ json_format=True,
53
+ console_output=False, # Don't spam console, just log to file
54
+ )
55
+ except Exception as e:
56
+ debug_log("Failed to configure structured logging", error=str(e))
57
+
58
+
59
+ def _build_epilog() -> str:
60
+ """Build CLI epilog from canonical sources in state.py."""
61
+ from galangal.core.state import TaskType, get_workflow_diagram
62
+
63
+ # Build task types section from TaskType enum
64
+ task_lines = []
65
+ for i, tt in enumerate(TaskType, start=1):
66
+ task_lines.append(f" [{i}] {tt.display_name():10} - {tt.short_description()}")
67
+
68
+ task_types_section = "\n".join(task_lines)
69
+
70
+ # Build workflow diagram from STAGE_ORDER
71
+ workflow = get_workflow_diagram().replace("→", "->")
72
+
73
+ return f"""
74
+ Debug mode:
75
+ galangal --debug start "task" Enable verbose logging to logs/galangal_debug.log
76
+ GALANGAL_DEBUG=1 galangal ... Alternative via environment variable
77
+
78
+ Examples:
79
+ galangal init
80
+ galangal start "Add user authentication"
81
+ galangal start "Add auth" --name add-auth-feature
82
+ galangal list
83
+ galangal switch add-auth-feature
84
+ galangal status
85
+ galangal resume
86
+ galangal pause
87
+ galangal skip-to DEV
88
+ galangal skip-to TEST --resume
89
+ galangal complete
90
+ galangal reset
91
+ galangal prompts export
92
+
93
+ Task Types:
94
+ At task start, you'll select from:
95
+ {task_types_section}
96
+
97
+ Workflow:
98
+ {workflow}
99
+
100
+ * = Conditional stages (auto-skipped if condition not met)
101
+
102
+ Tip: Press Ctrl+C during execution to pause gracefully.
103
+ """
104
+
105
+
106
+ def main() -> int:
107
+ parser = argparse.ArgumentParser(
108
+ description="Galangal Orchestrate - AI-Driven Development Workflow",
109
+ formatter_class=argparse.RawDescriptionHelpFormatter,
110
+ epilog=_build_epilog(),
111
+ )
112
+
113
+ # Global --debug flag (before subparsers)
114
+ parser.add_argument(
115
+ "--debug",
116
+ "-d",
117
+ action="store_true",
118
+ help="Enable debug logging to logs/galangal_debug.log and logs/galangal.jsonl",
119
+ )
120
+
121
+ subparsers = parser.add_subparsers(dest="command", required=True)
122
+
123
+ # init
124
+ init_parser = subparsers.add_parser("init", help="Initialize galangal in current project")
125
+ init_parser.add_argument(
126
+ "--quick",
127
+ "-q",
128
+ action="store_true",
129
+ help="Quick init without interactive wizard (for CI/automation)",
130
+ )
131
+ init_parser.set_defaults(func=_cmd_init)
132
+
133
+ # start
134
+ start_parser = subparsers.add_parser("start", help="Start new task")
135
+ start_parser.add_argument(
136
+ "description", nargs="*", help="Task description (prompted if not provided)"
137
+ )
138
+ start_parser.add_argument("--name", "-n", help="Task name (auto-generated if not provided)")
139
+ start_parser.add_argument(
140
+ "--type",
141
+ "-t",
142
+ choices=[
143
+ "feature",
144
+ "bugfix",
145
+ "refactor",
146
+ "chore",
147
+ "docs",
148
+ "hotfix",
149
+ "1",
150
+ "2",
151
+ "3",
152
+ "4",
153
+ "5",
154
+ "6",
155
+ ],
156
+ help="Task type (skip interactive selection)",
157
+ )
158
+ start_parser.add_argument(
159
+ "--skip-discovery",
160
+ action="store_true",
161
+ help="Skip the discovery Q&A phase and go straight to spec generation",
162
+ )
163
+ start_parser.add_argument(
164
+ "--issue", "-i", type=int, help="Create task from GitHub issue number"
165
+ )
166
+ start_parser.set_defaults(func=_cmd_start)
167
+
168
+ # list
169
+ list_parser = subparsers.add_parser("list", help="List all tasks")
170
+ list_parser.set_defaults(func=_cmd_list)
171
+
172
+ # switch
173
+ switch_parser = subparsers.add_parser("switch", help="Switch active task")
174
+ switch_parser.add_argument("task_name", help="Task name to switch to")
175
+ switch_parser.set_defaults(func=_cmd_switch)
176
+
177
+ # resume
178
+ resume_parser = subparsers.add_parser("resume", help="Resume active task")
179
+ resume_parser.add_argument(
180
+ "--skip-discovery",
181
+ action="store_true",
182
+ help="Skip remaining discovery Q&A and go straight to spec generation",
183
+ )
184
+ resume_parser.set_defaults(func=_cmd_resume)
185
+
186
+ # pause
187
+ pause_parser = subparsers.add_parser("pause", help="Pause task for break/shutdown")
188
+ pause_parser.set_defaults(func=_cmd_pause)
189
+
190
+ # status
191
+ status_parser = subparsers.add_parser("status", help="Show active task status")
192
+ status_parser.set_defaults(func=_cmd_status)
193
+
194
+ # skip-to
195
+ skip_to_parser = subparsers.add_parser(
196
+ "skip-to", help="Jump to a specific stage (for debugging/re-running)"
197
+ )
198
+ skip_to_parser.add_argument("stage", help="Target stage (e.g., DEV, TEST, SECURITY)")
199
+ skip_to_parser.add_argument("--force", "-f", action="store_true", help="Skip confirmation")
200
+ skip_to_parser.add_argument(
201
+ "--resume", "-r", action="store_true", help="Resume workflow immediately after jumping"
202
+ )
203
+ skip_to_parser.set_defaults(func=_cmd_skip_to)
204
+
205
+ # reset
206
+ reset_parser = subparsers.add_parser("reset", help="Delete active task")
207
+ reset_parser.add_argument("--force", "-f", action="store_true", help="Skip confirmation")
208
+ reset_parser.set_defaults(func=_cmd_reset)
209
+
210
+ # complete
211
+ complete_parser = subparsers.add_parser(
212
+ "complete", help="Move completed task to done/, create PR"
213
+ )
214
+ complete_parser.add_argument(
215
+ "--force", "-f", action="store_true", help="Continue on commit errors"
216
+ )
217
+ complete_parser.set_defaults(func=_cmd_complete)
218
+
219
+ # prompts
220
+ prompts_parser = subparsers.add_parser("prompts", help="Manage prompts")
221
+ prompts_subparsers = prompts_parser.add_subparsers(dest="prompts_command")
222
+ prompts_export = prompts_subparsers.add_parser(
223
+ "export", help="Export default prompts for customization"
224
+ )
225
+ prompts_export.set_defaults(func=_cmd_prompts_export)
226
+ prompts_show = prompts_subparsers.add_parser("show", help="Show effective prompt for a stage")
227
+ prompts_show.add_argument("stage", help="Stage name (e.g., pm, dev, test)")
228
+ prompts_show.set_defaults(func=_cmd_prompts_show)
229
+
230
+ # github
231
+ github_parser = subparsers.add_parser("github", help="GitHub integration")
232
+ github_subparsers = github_parser.add_subparsers(dest="github_command")
233
+ github_setup = github_subparsers.add_parser(
234
+ "setup", help="Set up GitHub integration (create labels, verify gh CLI)"
235
+ )
236
+ github_setup.add_argument(
237
+ "--help-install", action="store_true", help="Show detailed gh CLI installation instructions"
238
+ )
239
+ github_setup.set_defaults(func=_cmd_github_setup)
240
+ github_check = github_subparsers.add_parser(
241
+ "check", help="Check GitHub CLI installation and authentication"
242
+ )
243
+ github_check.set_defaults(func=_cmd_github_check)
244
+ github_issues = github_subparsers.add_parser("issues", help="List issues with galangal label")
245
+ github_issues.add_argument(
246
+ "--label", "-l", default="galangal", help="Label to filter by (default: galangal)"
247
+ )
248
+ github_issues.add_argument(
249
+ "--limit", "-n", type=int, default=50, help="Maximum number of issues to list"
250
+ )
251
+ github_issues.set_defaults(func=_cmd_github_issues)
252
+ github_run = github_subparsers.add_parser(
253
+ "run", help="Process all galangal-labeled issues (headless mode)"
254
+ )
255
+ github_run.add_argument(
256
+ "--label", "-l", default="galangal", help="Label to filter by (default: galangal)"
257
+ )
258
+ github_run.add_argument(
259
+ "--dry-run", action="store_true", help="List issues without processing them"
260
+ )
261
+ github_run.set_defaults(func=_cmd_github_run)
262
+
263
+ args = parser.parse_args()
264
+
265
+ # Enable debug mode if requested
266
+ if args.debug:
267
+ _setup_debug_mode()
268
+
269
+ result: int = args.func(args)
270
+ return result
271
+
272
+
273
+ # Command wrappers that import lazily to speed up CLI startup
274
+ def _cmd_init(args: argparse.Namespace) -> int:
275
+ from galangal.commands.init import cmd_init
276
+
277
+ return cmd_init(args)
278
+
279
+
280
+ def _cmd_start(args: argparse.Namespace) -> int:
281
+ from galangal.commands.start import cmd_start
282
+
283
+ return cmd_start(args)
284
+
285
+
286
+ def _cmd_list(args: argparse.Namespace) -> int:
287
+ from galangal.commands.list import cmd_list
288
+
289
+ return cmd_list(args)
290
+
291
+
292
+ def _cmd_switch(args: argparse.Namespace) -> int:
293
+ from galangal.commands.switch import cmd_switch
294
+
295
+ return cmd_switch(args)
296
+
297
+
298
+ def _cmd_resume(args: argparse.Namespace) -> int:
299
+ from galangal.commands.resume import cmd_resume
300
+
301
+ return cmd_resume(args)
302
+
303
+
304
+ def _cmd_pause(args: argparse.Namespace) -> int:
305
+ from galangal.commands.pause import cmd_pause
306
+
307
+ return cmd_pause(args)
308
+
309
+
310
+ def _cmd_status(args: argparse.Namespace) -> int:
311
+ from galangal.commands.status import cmd_status
312
+
313
+ return cmd_status(args)
314
+
315
+
316
+ def _cmd_skip_to(args: argparse.Namespace) -> int:
317
+ from galangal.commands.skip import cmd_skip_to
318
+
319
+ return cmd_skip_to(args)
320
+
321
+
322
+ def _cmd_reset(args: argparse.Namespace) -> int:
323
+ from galangal.commands.reset import cmd_reset
324
+
325
+ return cmd_reset(args)
326
+
327
+
328
+ def _cmd_complete(args: argparse.Namespace) -> int:
329
+ from galangal.commands.complete import cmd_complete
330
+
331
+ return cmd_complete(args)
332
+
333
+
334
+ def _cmd_prompts_export(args: argparse.Namespace) -> int:
335
+ from galangal.commands.prompts import cmd_prompts_export
336
+
337
+ return cmd_prompts_export(args)
338
+
339
+
340
+ def _cmd_prompts_show(args: argparse.Namespace) -> int:
341
+ from galangal.commands.prompts import cmd_prompts_show
342
+
343
+ return cmd_prompts_show(args)
344
+
345
+
346
+ def _cmd_github_setup(args: argparse.Namespace) -> int:
347
+ from galangal.commands.github import cmd_github_setup
348
+
349
+ return cmd_github_setup(args)
350
+
351
+
352
+ def _cmd_github_check(args: argparse.Namespace) -> int:
353
+ from galangal.commands.github import cmd_github_check
354
+
355
+ return cmd_github_check(args)
356
+
357
+
358
+ def _cmd_github_issues(args: argparse.Namespace) -> int:
359
+ from galangal.commands.github import cmd_github_issues
360
+
361
+ return cmd_github_issues(args)
362
+
363
+
364
+ def _cmd_github_run(args: argparse.Namespace) -> int:
365
+ from galangal.commands.github import cmd_github_run
366
+
367
+ return cmd_github_run(args)
368
+
369
+
370
+ if __name__ == "__main__":
371
+ sys.exit(main())
@@ -0,0 +1,27 @@
1
+ """CLI commands."""
2
+
3
+ from galangal.commands.complete import cmd_complete
4
+ from galangal.commands.init import cmd_init
5
+ from galangal.commands.list import cmd_list
6
+ from galangal.commands.pause import cmd_pause
7
+ from galangal.commands.prompts import cmd_prompts
8
+ from galangal.commands.reset import cmd_reset
9
+ from galangal.commands.resume import cmd_resume
10
+ from galangal.commands.skip import cmd_skip_to
11
+ from galangal.commands.start import cmd_start
12
+ from galangal.commands.status import cmd_status
13
+ from galangal.commands.switch import cmd_switch
14
+
15
+ __all__ = [
16
+ "cmd_init",
17
+ "cmd_start",
18
+ "cmd_resume",
19
+ "cmd_status",
20
+ "cmd_list",
21
+ "cmd_switch",
22
+ "cmd_pause",
23
+ "cmd_skip_to",
24
+ "cmd_reset",
25
+ "cmd_complete",
26
+ "cmd_prompts",
27
+ ]