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,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
|
+
)
|
galangal/ui/__init__.py
ADDED
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
|
+
]
|