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,84 @@
1
+ # Security Stage
2
+
3
+ You are a Security Engineer reviewing the implementation for vulnerabilities.
4
+
5
+ ## Scope
6
+
7
+ **DO:**
8
+ - Review code changes for security issues
9
+ - Run automated security scans (dependency audit, secret detection)
10
+ - Check for common vulnerabilities (OWASP Top 10)
11
+ - Document findings and waivers
12
+
13
+ **DO NOT:**
14
+ - Run the full test suite
15
+ - Make code changes (only document issues)
16
+ - Run linting or other QA checks
17
+
18
+ ## Documentation Configuration
19
+
20
+ Check the "Documentation Configuration" section in the context above for:
21
+ - **Security Audit Directory**: Where to store persistent security audit reports
22
+ - **Update Security Audit**: Whether to create/update security audit documentation (YES/NO)
23
+
24
+ If **Update Security Audit** is YES:
25
+ - Store persistent security audit reports in the configured security audit directory
26
+ - These reports should be tracked in version control
27
+ - Include security policy documentation and vulnerability tracking
28
+
29
+ If **Update Security Audit** is NO:
30
+ - Only create the SECURITY_CHECKLIST.md artifact (not persisted to repo)
31
+ - Skip creating files in the security audit directory
32
+
33
+ ## Output
34
+
35
+ Create `SECURITY_CHECKLIST.md` in the task's artifacts directory:
36
+
37
+ ```markdown
38
+ # Security Review: [Task Title]
39
+
40
+ ## Change Summary
41
+ - What does this change do?
42
+ - Does it handle user input? [Yes/No]
43
+ - Does it modify authentication/authorization? [Yes/No]
44
+ - Does it handle secrets or credentials? [Yes/No]
45
+
46
+ ## Automated Scan Results
47
+
48
+ ### Dependency Audit
49
+ - Status: [PASS/FAIL]
50
+ - Issues: [Count or None]
51
+
52
+ ### Secret Detection
53
+ - Status: [PASS/FAIL]
54
+ - Secrets Found: [Count or None]
55
+
56
+ ## Manual Review
57
+
58
+ ### Input Validation
59
+ - [ ] User input sanitized
60
+ - [ ] No injection vulnerabilities
61
+
62
+ ### Authentication & Authorization
63
+ - [ ] Proper auth required
64
+ - [ ] Authorization checks in place
65
+
66
+ ## Known Issues / Waivers
67
+ | Issue | Severity | Justification |
68
+ |-------|----------|---------------|
69
+ | [Issue] | [High/Med/Low] | [Why accepted] |
70
+
71
+ ## Recommendation
72
+
73
+ **APPROVED** - Safe to deploy
74
+ OR
75
+ **REJECTED** - Must fix: [list blocking issues]
76
+ ```
77
+
78
+ ## Process
79
+
80
+ 1. Review code changes for security implications
81
+ 2. Run available security scans
82
+ 3. Document any issues found
83
+ 4. If issues are pre-existing or waived, document the justification
84
+ 5. Provide final APPROVED or REJECTED recommendation
@@ -0,0 +1,91 @@
1
+ # TEST Stage - Test Implementation
2
+
3
+ You are a Test Engineer writing tests for the implemented feature.
4
+
5
+ ## Your Task
6
+
7
+ Create comprehensive tests that verify the implementation meets the acceptance criteria in SPEC.md.
8
+
9
+ ## CRITICAL: Do NOT Fix Implementation Bugs
10
+
11
+ **Your job is to WRITE TESTS and REPORT results, not to fix the implementation.**
12
+
13
+ If tests fail because the implementation is wrong:
14
+ 1. Document the failures clearly in TEST_PLAN.md
15
+ 2. Set **Status:** FAIL
16
+ 3. The workflow will automatically roll back to DEV with your failure report
17
+ 4. DO NOT attempt to modify the implementation code to make tests pass
18
+
19
+ If tests fail because your test code is wrong (e.g., wrong selector, typo):
20
+ - You may fix your test code
21
+ - But if after 2-3 attempts the test still fails, assume it's an implementation issue
22
+
23
+ ## Your Output
24
+
25
+ Create TEST_PLAN.md in the task's artifacts directory:
26
+
27
+ ```markdown
28
+ # Test Plan: [Task Title]
29
+
30
+ ## Test Coverage
31
+
32
+ ### Unit Tests
33
+ | Test | Description | File |
34
+ |------|-------------|------|
35
+ | test_xxx | Tests that... | path/to/test.py |
36
+
37
+ ### Integration Tests
38
+ | Test | Description | File |
39
+ |------|-------------|------|
40
+ | test_xxx | Tests that... | path/to/test.py |
41
+
42
+ ## Test Results
43
+
44
+ **Status:** PASS / FAIL
45
+
46
+ ### Summary
47
+ - Total tests: X
48
+ - Passed: X
49
+ - Failed: X
50
+
51
+ ### Failed Tests (if any)
52
+ | Test | Error | Likely Cause |
53
+ |------|-------|--------------|
54
+ | test_xxx | Expected X got Y | Implementation missing feature Z |
55
+
56
+ ### Details
57
+ [Output from test run]
58
+ ```
59
+
60
+ ## Process
61
+
62
+ 1. Read SPEC.md for acceptance criteria
63
+ 2. Read PLAN.md for what was implemented
64
+ 3. Analyze the implementation to understand what needs testing
65
+ 4. Write tests that verify:
66
+ - Core functionality works
67
+ - Edge cases are handled
68
+ - Error conditions are handled properly
69
+ 5. Run the newly created tests
70
+ 6. Document results in TEST_PLAN.md with accurate PASS/FAIL status
71
+
72
+ ## Important Rules
73
+
74
+ - Test the behavior, not the implementation details
75
+ - Include both happy path and error cases
76
+ - Follow existing test patterns in the codebase
77
+ - Tests should be deterministic (no flaky tests)
78
+ - **DO NOT modify implementation code** - only write/fix test code
79
+ - If tests fail due to implementation bugs, report FAIL status clearly
80
+ - After 2-3 failed attempts to fix a test, assume implementation is wrong and report FAIL
81
+
82
+ ## Non-Blocking Test Execution
83
+
84
+ **CRITICAL**: Tests must run non-interactively. Never use modes that wait for user input:
85
+
86
+ - **Playwright**: Use `--reporter=list` or set `PLAYWRIGHT_HTML_OPEN=never`
87
+ - **Jest/Vitest**: Never use `--watch` mode
88
+ - **Cypress**: Use `cypress run` (not `cypress open`)
89
+ - **Any framework**: Avoid watch mode, interactive mode, or GUI mode
90
+
91
+ If a test command hangs waiting for input, the workflow will stall. Always use CI-friendly, non-interactive test commands.
galangal/results.py ADDED
@@ -0,0 +1,176 @@
1
+ """
2
+ Structured result types for operations and stage execution.
3
+
4
+ This module provides type-safe result objects instead of tuple[bool, str]
5
+ with magic string prefixes like "PREFLIGHT_FAILED:" or "ROLLBACK:".
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass
11
+ from enum import Enum, auto
12
+ from typing import TYPE_CHECKING
13
+
14
+ if TYPE_CHECKING:
15
+ from galangal.core.state import Stage
16
+
17
+
18
+ class StageResultType(Enum):
19
+ """Types of outcomes from stage execution."""
20
+
21
+ SUCCESS = auto()
22
+ PREFLIGHT_FAILED = auto()
23
+ VALIDATION_FAILED = auto()
24
+ ROLLBACK_REQUIRED = auto()
25
+ CLARIFICATION_NEEDED = auto()
26
+ USER_DECISION_NEEDED = auto() # Decision file missing, user must confirm
27
+ PAUSED = auto()
28
+ TIMEOUT = auto()
29
+ MAX_TURNS = auto()
30
+ ERROR = auto()
31
+
32
+
33
+ @dataclass
34
+ class Result:
35
+ """Base result for simple operations."""
36
+
37
+ success: bool
38
+ message: str
39
+
40
+ def __bool__(self) -> bool:
41
+ """Allow using result directly in boolean context."""
42
+ return self.success
43
+
44
+
45
+ @dataclass
46
+ class StageResult(Result):
47
+ """Result from stage execution with structured outcome type.
48
+
49
+ Replaces the old pattern of encoding state in message strings like:
50
+ - "PREFLIGHT_FAILED:details"
51
+ - "ROLLBACK:DEV:reason"
52
+
53
+ Now the type field explicitly indicates the outcome, and structured
54
+ fields hold the relevant data.
55
+ """
56
+
57
+ type: StageResultType = StageResultType.SUCCESS
58
+ rollback_to: Stage | None = None
59
+ output: str | None = None
60
+ is_fast_track: bool = False # True for minor rollbacks (skip passed stages)
61
+
62
+ @classmethod
63
+ def create_success(cls, message: str = "", output: str | None = None) -> StageResult:
64
+ """Create a successful stage result."""
65
+ return cls(
66
+ success=True,
67
+ message=message,
68
+ type=StageResultType.SUCCESS,
69
+ output=output,
70
+ )
71
+
72
+ @classmethod
73
+ def preflight_failed(cls, message: str, details: str = "") -> StageResult:
74
+ """Create a preflight failure result."""
75
+ return cls(
76
+ success=False,
77
+ message=message,
78
+ type=StageResultType.PREFLIGHT_FAILED,
79
+ output=details,
80
+ )
81
+
82
+ @classmethod
83
+ def validation_failed(cls, message: str) -> StageResult:
84
+ """Create a validation failure result."""
85
+ return cls(
86
+ success=False,
87
+ message=message,
88
+ type=StageResultType.VALIDATION_FAILED,
89
+ )
90
+
91
+ @classmethod
92
+ def rollback_required(
93
+ cls,
94
+ message: str,
95
+ rollback_to: Stage,
96
+ output: str | None = None,
97
+ is_fast_track: bool = False,
98
+ ) -> StageResult:
99
+ """Create a rollback required result.
100
+
101
+ Args:
102
+ message: Description of why rollback is needed.
103
+ rollback_to: Stage to roll back to.
104
+ output: Optional detailed output.
105
+ is_fast_track: If True, this is a minor rollback that should
106
+ skip stages that already passed (REQUEST_MINOR_CHANGES).
107
+ """
108
+ return cls(
109
+ success=False,
110
+ message=message,
111
+ type=StageResultType.ROLLBACK_REQUIRED,
112
+ rollback_to=rollback_to,
113
+ output=output,
114
+ is_fast_track=is_fast_track,
115
+ )
116
+
117
+ @classmethod
118
+ def clarification_needed(cls, message: str = "") -> StageResult:
119
+ """Create a clarification needed result."""
120
+ return cls(
121
+ success=False,
122
+ message=message or "Clarification required. Please provide ANSWERS.md.",
123
+ type=StageResultType.CLARIFICATION_NEEDED,
124
+ )
125
+
126
+ @classmethod
127
+ def paused(cls, message: str = "User requested pause") -> StageResult:
128
+ """Create a paused result."""
129
+ return cls(
130
+ success=False,
131
+ message=message,
132
+ type=StageResultType.PAUSED,
133
+ )
134
+
135
+ @classmethod
136
+ def timeout(cls, timeout_seconds: int) -> StageResult:
137
+ """Create a timeout result."""
138
+ return cls(
139
+ success=False,
140
+ message=f"Timed out after {timeout_seconds}s",
141
+ type=StageResultType.TIMEOUT,
142
+ )
143
+
144
+ @classmethod
145
+ def max_turns(cls, output: str = "") -> StageResult:
146
+ """Create a max turns exceeded result."""
147
+ return cls(
148
+ success=False,
149
+ message="Max turns exceeded",
150
+ type=StageResultType.MAX_TURNS,
151
+ output=output,
152
+ )
153
+
154
+ @classmethod
155
+ def error(cls, message: str, output: str | None = None) -> StageResult:
156
+ """Create a general error result."""
157
+ return cls(
158
+ success=False,
159
+ message=message,
160
+ type=StageResultType.ERROR,
161
+ output=output,
162
+ )
163
+
164
+ @classmethod
165
+ def user_decision_needed(cls, message: str, artifact_content: str | None = None) -> StageResult:
166
+ """Create a result indicating user must confirm the stage decision.
167
+
168
+ Used when the AI-generated decision file is missing or unclear.
169
+ The user will be prompted to review the artifact and approve/reject.
170
+ """
171
+ return cls(
172
+ success=False,
173
+ message=message,
174
+ type=StageResultType.USER_DECISION_NEEDED,
175
+ output=artifact_content,
176
+ )
@@ -0,0 +1,5 @@
1
+ """UI components."""
2
+
3
+ from galangal.ui.console import console, print_error, print_info, print_success, print_warning
4
+
5
+ __all__ = ["console", "print_success", "print_error", "print_warning", "print_info"]
galangal/ui/console.py ADDED
@@ -0,0 +1,126 @@
1
+ """
2
+ Console output utilities using Rich.
3
+ """
4
+
5
+ from rich.console import Console
6
+ from rich.panel import Panel
7
+ from rich.table import Table
8
+
9
+ from galangal.core.state import TASK_TYPE_SKIP_STAGES, Stage, TaskType
10
+ from galangal.core.utils import debug_log
11
+
12
+ console = Console()
13
+
14
+
15
+ def print_success(message: str) -> None:
16
+ """Print a success message."""
17
+ debug_log("[SUCCESS]", content=message)
18
+ console.print(f"[green]✓ {message}[/green]")
19
+
20
+
21
+ def print_error(message: str) -> None:
22
+ """Print an error message."""
23
+ debug_log("[ERROR]", content=message)
24
+ console.print(f"[red]✗ {message}[/red]")
25
+
26
+
27
+ def print_warning(message: str) -> None:
28
+ """Print a warning message."""
29
+ debug_log("[WARNING]", content=message)
30
+ console.print(f"[yellow]⚠ {message}[/yellow]")
31
+
32
+
33
+ def print_info(message: str) -> None:
34
+ """Print an info message."""
35
+ debug_log("[INFO]", content=message)
36
+ console.print(f"[blue]ℹ {message}[/blue]")
37
+
38
+
39
+ def display_task_list(tasks: list[tuple[str, str, str, str]], active: str | None) -> None:
40
+ """Display a table of tasks."""
41
+ if not tasks:
42
+ print_info("No tasks found.")
43
+ return
44
+
45
+ table = Table(title="Tasks")
46
+ table.add_column("", style="cyan", width=2)
47
+ table.add_column("Name", style="bold")
48
+ table.add_column("Stage")
49
+ table.add_column("Type")
50
+ table.add_column("Description")
51
+
52
+ for name, stage, task_type, desc in tasks:
53
+ marker = "→" if name == active else ""
54
+ stage_style = "green" if stage == "COMPLETE" else "yellow"
55
+ table.add_row(
56
+ marker,
57
+ name,
58
+ f"[{stage_style}]{stage}[/{stage_style}]",
59
+ task_type,
60
+ desc,
61
+ )
62
+
63
+ console.print(table)
64
+
65
+
66
+ def display_status(
67
+ task_name: str,
68
+ stage: Stage,
69
+ task_type: TaskType,
70
+ attempt: int,
71
+ awaiting_approval: bool,
72
+ last_failure: str | None,
73
+ description: str,
74
+ artifacts: list[tuple[str, bool]],
75
+ ) -> None:
76
+ """Display detailed task status."""
77
+ status_color = "green" if stage == Stage.COMPLETE else "yellow"
78
+
79
+ console.print(Panel(f"[bold]{task_name}[/bold]", title="Task Status"))
80
+ console.print(f"[dim]Stage:[/dim] [{status_color}]{stage.value}[/{status_color}]")
81
+ console.print(f"[dim]Type:[/dim] {task_type.display_name()}")
82
+ console.print(f"[dim]Attempt:[/dim] {attempt}")
83
+ console.print(f"[dim]Description:[/dim] {description[:100]}...")
84
+
85
+ if awaiting_approval:
86
+ console.print("[yellow]⏳ Awaiting approval[/yellow]")
87
+
88
+ if last_failure:
89
+ console.print(f"[red]Last failure:[/red] {last_failure[:200]}...")
90
+
91
+ # Artifacts
92
+ console.print("\n[bold]Artifacts:[/bold]")
93
+ for name, exists in artifacts:
94
+ icon = "✓" if exists else "○"
95
+ color = "green" if exists else "dim"
96
+ console.print(f" [{color}]{icon} {name}[/{color}]")
97
+
98
+
99
+ def display_task_type_menu() -> None:
100
+ """Display the task type selection menu."""
101
+ console.print("\n[bold]Select task type:[/bold]")
102
+ for i, task_type in enumerate(TaskType, 1):
103
+ skipped = TASK_TYPE_SKIP_STAGES.get(task_type, set())
104
+ skip_info = f" [dim](skips: {', '.join(s.value for s in skipped)})[/dim]" if skipped else ""
105
+ console.print(f" [{i}] {task_type.display_name()} - {task_type.description()}{skip_info}")
106
+
107
+
108
+ def get_task_type_from_input(choice: str) -> TaskType | None:
109
+ """Convert user input to TaskType."""
110
+ task_types = list(TaskType)
111
+
112
+ # Try numeric selection
113
+ try:
114
+ idx = int(choice) - 1
115
+ if 0 <= idx < len(task_types):
116
+ return task_types[idx]
117
+ except ValueError:
118
+ pass
119
+
120
+ # Try name match
121
+ choice_lower = choice.lower().replace(" ", "_").replace("-", "_")
122
+ for tt in TaskType:
123
+ if tt.value == choice_lower or tt.display_name().lower() == choice.lower():
124
+ return tt
125
+
126
+ return None
@@ -0,0 +1,56 @@
1
+ """
2
+ Textual TUI for workflow execution display.
3
+
4
+ This package provides:
5
+ - WorkflowTUIApp: Main TUI application for workflow execution
6
+ - PromptType: Enum of prompt types the TUI can show
7
+ - StageUI: Interface for stage execution UI updates
8
+ - TUIAdapter: Adapter connecting ClaudeBackend to TUI
9
+ - run_stage_with_tui: Entry point for running a single stage with TUI
10
+ """
11
+
12
+ from galangal.ui.tui.adapters import PromptType, StageUI, TUIAdapter
13
+ from galangal.ui.tui.app import StageTUIApp, WorkflowTUIApp
14
+ from galangal.ui.tui.entry import run_stage_with_tui
15
+ from galangal.ui.tui.modals import MultilineInputModal, PromptModal, PromptOption, TextInputModal
16
+ from galangal.ui.tui.types import (
17
+ ActivityCategory,
18
+ ActivityEntry,
19
+ ActivityLevel,
20
+ export_activity_log,
21
+ )
22
+ from galangal.ui.tui.widgets import (
23
+ CurrentActionWidget,
24
+ ErrorPanelWidget,
25
+ FilesPanelWidget,
26
+ HeaderWidget,
27
+ StageProgressWidget,
28
+ )
29
+
30
+ __all__ = [
31
+ # Main app
32
+ "WorkflowTUIApp",
33
+ "StageTUIApp",
34
+ # Adapters and interfaces
35
+ "PromptType",
36
+ "StageUI",
37
+ "TUIAdapter",
38
+ # Entry points
39
+ "run_stage_with_tui",
40
+ # Activity log types
41
+ "ActivityEntry",
42
+ "ActivityLevel",
43
+ "ActivityCategory",
44
+ "export_activity_log",
45
+ # Widgets
46
+ "HeaderWidget",
47
+ "StageProgressWidget",
48
+ "CurrentActionWidget",
49
+ "ErrorPanelWidget",
50
+ "FilesPanelWidget",
51
+ # Modals
52
+ "PromptOption",
53
+ "PromptModal",
54
+ "TextInputModal",
55
+ "MultilineInputModal",
56
+ ]