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,33 @@
|
|
|
1
|
+
"""Core workflow components."""
|
|
2
|
+
|
|
3
|
+
from galangal.core.artifacts import artifact_exists, read_artifact, write_artifact
|
|
4
|
+
from galangal.core.state import (
|
|
5
|
+
MAX_ROLLBACKS_PER_STAGE,
|
|
6
|
+
ROLLBACK_TIME_WINDOW_HOURS,
|
|
7
|
+
STAGE_METADATA,
|
|
8
|
+
STAGE_ORDER,
|
|
9
|
+
RollbackEvent,
|
|
10
|
+
Stage,
|
|
11
|
+
StageMetadata,
|
|
12
|
+
TaskType,
|
|
13
|
+
WorkflowState,
|
|
14
|
+
)
|
|
15
|
+
from galangal.core.tasks import get_active_task, list_tasks, set_active_task
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"Stage",
|
|
19
|
+
"StageMetadata",
|
|
20
|
+
"STAGE_METADATA",
|
|
21
|
+
"TaskType",
|
|
22
|
+
"WorkflowState",
|
|
23
|
+
"STAGE_ORDER",
|
|
24
|
+
"RollbackEvent",
|
|
25
|
+
"MAX_ROLLBACKS_PER_STAGE",
|
|
26
|
+
"ROLLBACK_TIME_WINDOW_HOURS",
|
|
27
|
+
"artifact_exists",
|
|
28
|
+
"read_artifact",
|
|
29
|
+
"write_artifact",
|
|
30
|
+
"get_active_task",
|
|
31
|
+
"set_active_task",
|
|
32
|
+
"list_tasks",
|
|
33
|
+
]
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Artifact management - reading and writing task artifacts.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import subprocess
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from galangal.config.loader import get_project_root
|
|
10
|
+
from galangal.core.state import get_task_dir
|
|
11
|
+
from galangal.exceptions import TaskError
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def artifact_path(name: str, task_name: str | None = None) -> Path:
|
|
15
|
+
"""Get path to an artifact file."""
|
|
16
|
+
from galangal.core.tasks import get_active_task
|
|
17
|
+
|
|
18
|
+
if task_name is None:
|
|
19
|
+
task_name = get_active_task()
|
|
20
|
+
if task_name is None:
|
|
21
|
+
raise TaskError("No active task")
|
|
22
|
+
return get_task_dir(task_name) / name
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def artifact_exists(name: str, task_name: str | None = None) -> bool:
|
|
26
|
+
"""Check if an artifact exists."""
|
|
27
|
+
try:
|
|
28
|
+
return artifact_path(name, task_name).exists()
|
|
29
|
+
except TaskError:
|
|
30
|
+
return False
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def read_artifact(name: str, task_name: str | None = None) -> str | None:
|
|
34
|
+
"""Read an artifact file."""
|
|
35
|
+
try:
|
|
36
|
+
path = artifact_path(name, task_name)
|
|
37
|
+
if path.exists():
|
|
38
|
+
return path.read_text()
|
|
39
|
+
except TaskError:
|
|
40
|
+
pass
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def write_artifact(name: str, content: str, task_name: str | None = None) -> None:
|
|
45
|
+
"""Write an artifact file."""
|
|
46
|
+
path = artifact_path(name, task_name)
|
|
47
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
48
|
+
path.write_text(content)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def write_skip_artifact(stage: str, reason: str, task_name: str | None = None) -> None:
|
|
52
|
+
"""Write a standardized skip artifact for a stage.
|
|
53
|
+
|
|
54
|
+
Creates a {STAGE}_SKIP.md artifact with consistent formatting.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
stage: Stage name (e.g., "MIGRATION", "SECURITY").
|
|
58
|
+
reason: Reason for skipping the stage.
|
|
59
|
+
task_name: Task name, or None to use active task.
|
|
60
|
+
"""
|
|
61
|
+
from galangal.core.utils import now_iso
|
|
62
|
+
|
|
63
|
+
content = f"""# {stage.upper()} Stage Skipped
|
|
64
|
+
|
|
65
|
+
Date: {now_iso()}
|
|
66
|
+
Reason: {reason}
|
|
67
|
+
"""
|
|
68
|
+
write_artifact(f"{stage.upper()}_SKIP.md", content, task_name)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def parse_stage_plan(task_name: str | None = None) -> dict[str, dict] | None:
|
|
72
|
+
"""
|
|
73
|
+
Parse STAGE_PLAN.md artifact to extract stage recommendations.
|
|
74
|
+
|
|
75
|
+
The STAGE_PLAN.md file contains a markdown table with stage recommendations:
|
|
76
|
+
| Stage | Action | Reason |
|
|
77
|
+
|-------|--------|--------|
|
|
78
|
+
| MIGRATION | skip | No database changes |
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Dictionary mapping stage name to {"action": "skip"|"run", "reason": "..."},
|
|
82
|
+
or None if the artifact doesn't exist or can't be parsed.
|
|
83
|
+
"""
|
|
84
|
+
import re
|
|
85
|
+
|
|
86
|
+
content = read_artifact("STAGE_PLAN.md", task_name)
|
|
87
|
+
if not content:
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
stage_plan = {}
|
|
91
|
+
|
|
92
|
+
# Parse markdown table rows
|
|
93
|
+
# Match lines like: | MIGRATION | skip | No database changes |
|
|
94
|
+
table_row_pattern = re.compile(
|
|
95
|
+
r"^\|\s*(\w+)\s*\|\s*(skip|run)\s*\|\s*(.+?)\s*\|",
|
|
96
|
+
re.IGNORECASE | re.MULTILINE,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
for match in table_row_pattern.finditer(content):
|
|
100
|
+
stage_name = match.group(1).upper()
|
|
101
|
+
action = match.group(2).lower()
|
|
102
|
+
reason = match.group(3).strip()
|
|
103
|
+
|
|
104
|
+
# Only track plannable stages
|
|
105
|
+
if stage_name in {"MIGRATION", "CONTRACT", "BENCHMARK", "SECURITY"}:
|
|
106
|
+
stage_plan[stage_name] = {"action": action, "reason": reason}
|
|
107
|
+
|
|
108
|
+
return stage_plan if stage_plan else None
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def run_command(
|
|
112
|
+
cmd: list[str], cwd: Path | None = None, timeout: int = 300
|
|
113
|
+
) -> tuple[int, str, str]:
|
|
114
|
+
"""Run a command and return (exit_code, stdout, stderr).
|
|
115
|
+
|
|
116
|
+
Sets GIT_TERMINAL_PROMPT=0 to prevent git from hanging when
|
|
117
|
+
credentials are needed (since stdin is not available).
|
|
118
|
+
"""
|
|
119
|
+
# Disable git credential prompts to prevent hanging
|
|
120
|
+
env = os.environ.copy()
|
|
121
|
+
env["GIT_TERMINAL_PROMPT"] = "0"
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
result = subprocess.run(
|
|
125
|
+
cmd,
|
|
126
|
+
cwd=cwd or get_project_root(),
|
|
127
|
+
capture_output=True,
|
|
128
|
+
text=True,
|
|
129
|
+
timeout=timeout,
|
|
130
|
+
env=env,
|
|
131
|
+
)
|
|
132
|
+
return result.returncode, result.stdout, result.stderr
|
|
133
|
+
except subprocess.TimeoutExpired:
|
|
134
|
+
return -1, "", f"Command timed out after {timeout}s"
|
|
135
|
+
except Exception as e:
|
|
136
|
+
return -1, "", str(e)
|