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,527 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Prompt building with project override support.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from galangal.config.loader import get_config, get_project_root, get_prompts_dir
|
|
9
|
+
from galangal.core.artifacts import artifact_exists, read_artifact
|
|
10
|
+
from galangal.core.state import Stage, WorkflowState
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class PromptBuilder:
|
|
14
|
+
"""
|
|
15
|
+
Build prompts for workflow stages with project override support.
|
|
16
|
+
|
|
17
|
+
This class constructs prompts by merging:
|
|
18
|
+
1. Base prompts from `galangal/prompts/defaults/` (built into package)
|
|
19
|
+
2. Project prompts from `.galangal/prompts/` (project-specific)
|
|
20
|
+
3. Task context (description, artifacts, state)
|
|
21
|
+
4. Config context (prompt_context, stage_context)
|
|
22
|
+
|
|
23
|
+
Project prompts can either:
|
|
24
|
+
- Supplement: Include `# BASE` marker where default prompt is inserted
|
|
25
|
+
- Override: No marker means complete replacement of base prompt
|
|
26
|
+
|
|
27
|
+
Example supplement prompt:
|
|
28
|
+
```markdown
|
|
29
|
+
# Project-Specific Instructions
|
|
30
|
+
Follow our coding style guide.
|
|
31
|
+
|
|
32
|
+
# BASE
|
|
33
|
+
|
|
34
|
+
# Additional Notes
|
|
35
|
+
Use our custom test framework.
|
|
36
|
+
```
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self) -> None:
|
|
40
|
+
self.config = get_config()
|
|
41
|
+
self.project_root = get_project_root()
|
|
42
|
+
self.override_dir = get_prompts_dir()
|
|
43
|
+
self.defaults_dir = Path(__file__).parent / "defaults"
|
|
44
|
+
|
|
45
|
+
def _merge_with_base(self, project_prompt: str, base_prompt: str) -> str:
|
|
46
|
+
"""Merge project prompt with base using # BASE marker.
|
|
47
|
+
|
|
48
|
+
If project_prompt contains '# BASE', splits around it and inserts
|
|
49
|
+
the base_prompt at that location. Content before the marker becomes
|
|
50
|
+
a header, content after becomes a footer.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
project_prompt: Project-specific prompt that may contain # BASE marker.
|
|
54
|
+
base_prompt: Default/base prompt to insert at marker location.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
Merged prompt with base inserted at marker, or project_prompt unchanged
|
|
58
|
+
if no marker present.
|
|
59
|
+
"""
|
|
60
|
+
if "# BASE" not in project_prompt:
|
|
61
|
+
return project_prompt
|
|
62
|
+
|
|
63
|
+
parts = project_prompt.split("# BASE", 1)
|
|
64
|
+
header = parts[0].rstrip()
|
|
65
|
+
footer = parts[1].lstrip() if len(parts) > 1 else ""
|
|
66
|
+
|
|
67
|
+
result_parts = []
|
|
68
|
+
if header:
|
|
69
|
+
result_parts.append(header)
|
|
70
|
+
if base_prompt:
|
|
71
|
+
result_parts.append(base_prompt)
|
|
72
|
+
if footer:
|
|
73
|
+
result_parts.append(footer)
|
|
74
|
+
|
|
75
|
+
return "\n\n".join(result_parts)
|
|
76
|
+
|
|
77
|
+
def get_prompt_by_name(self, name: str) -> str:
|
|
78
|
+
"""Get a prompt by filename (without .md extension).
|
|
79
|
+
|
|
80
|
+
Supports project override/supplement like get_stage_prompt.
|
|
81
|
+
Used for non-stage prompts like 'pm_questions'.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
name: Prompt name, e.g., 'pm_questions'
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
Prompt content with project overrides applied.
|
|
88
|
+
"""
|
|
89
|
+
# Get base prompt
|
|
90
|
+
default_path = self.defaults_dir / f"{name}.md"
|
|
91
|
+
base_prompt = ""
|
|
92
|
+
if default_path.exists():
|
|
93
|
+
base_prompt = default_path.read_text()
|
|
94
|
+
|
|
95
|
+
# Check for project prompt
|
|
96
|
+
project_path = self.override_dir / f"{name}.md"
|
|
97
|
+
if not project_path.exists():
|
|
98
|
+
return base_prompt or f"Execute {name}."
|
|
99
|
+
|
|
100
|
+
project_prompt = project_path.read_text()
|
|
101
|
+
|
|
102
|
+
# Merge with base (returns project_prompt unchanged if no # BASE marker)
|
|
103
|
+
merged = self._merge_with_base(project_prompt, base_prompt)
|
|
104
|
+
if merged != project_prompt:
|
|
105
|
+
return merged
|
|
106
|
+
|
|
107
|
+
# No marker = full override
|
|
108
|
+
return project_prompt
|
|
109
|
+
|
|
110
|
+
def build_discovery_prompt(
|
|
111
|
+
self, state: WorkflowState, qa_history: list[dict[str, Any]] | None = None
|
|
112
|
+
) -> str:
|
|
113
|
+
"""Build the prompt for PM discovery questions.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
state: Current workflow state with task info.
|
|
117
|
+
qa_history: Previous Q&A rounds, if any.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Complete prompt for generating discovery questions.
|
|
121
|
+
"""
|
|
122
|
+
base_prompt = self.get_prompt_by_name("pm_questions")
|
|
123
|
+
task_name = state.task_name
|
|
124
|
+
|
|
125
|
+
# Build context
|
|
126
|
+
context_parts = [
|
|
127
|
+
f"# Task: {task_name}",
|
|
128
|
+
f"# Task Type: {state.task_type.display_name()}",
|
|
129
|
+
f"# Brief\n{state.task_description}",
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
# Add screenshot context if available
|
|
133
|
+
context_parts.extend(self._get_screenshot_context(state))
|
|
134
|
+
|
|
135
|
+
# Add previous Q&A history
|
|
136
|
+
if qa_history:
|
|
137
|
+
qa_text = self._format_qa_history(qa_history)
|
|
138
|
+
context_parts.append(f"\n# Previous Q&A Rounds\n{qa_text}")
|
|
139
|
+
else:
|
|
140
|
+
context_parts.append("\n# Previous Q&A Rounds\nNone - this is the first round.")
|
|
141
|
+
|
|
142
|
+
# Add global prompt context from config
|
|
143
|
+
if self.config.prompt_context:
|
|
144
|
+
context_parts.append(f"\n# Project Context\n{self.config.prompt_context}")
|
|
145
|
+
|
|
146
|
+
context = "\n".join(context_parts)
|
|
147
|
+
return f"{context}\n\n---\n\n{base_prompt}"
|
|
148
|
+
|
|
149
|
+
def _get_screenshot_context(self, state: WorkflowState) -> list[str]:
|
|
150
|
+
"""
|
|
151
|
+
Get screenshot context for inclusion in prompts.
|
|
152
|
+
|
|
153
|
+
When screenshots are available from a GitHub issue, instructs the AI
|
|
154
|
+
to read them for visual context (bug reports, designs, etc.).
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
state: Workflow state containing screenshot paths.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
List of context strings to include in prompt.
|
|
161
|
+
"""
|
|
162
|
+
if not state.screenshots:
|
|
163
|
+
return []
|
|
164
|
+
|
|
165
|
+
parts = ["\n# Screenshots from GitHub Issue"]
|
|
166
|
+
parts.append(
|
|
167
|
+
"The following screenshots were attached to the GitHub issue. "
|
|
168
|
+
"Use the Read tool to view these images for visual context "
|
|
169
|
+
"(e.g., bug screenshots, design mockups, UI references):"
|
|
170
|
+
)
|
|
171
|
+
for i, path in enumerate(state.screenshots, 1):
|
|
172
|
+
parts.append(f" {i}. {path}")
|
|
173
|
+
|
|
174
|
+
return ["\n".join(parts)]
|
|
175
|
+
|
|
176
|
+
def _format_qa_history(self, qa_history: list[dict[str, Any]]) -> str:
|
|
177
|
+
"""Format Q&A history for prompt inclusion."""
|
|
178
|
+
parts = []
|
|
179
|
+
for i, round_data in enumerate(qa_history, 1):
|
|
180
|
+
parts.append(f"## Round {i}")
|
|
181
|
+
parts.append("### Questions")
|
|
182
|
+
for j, q in enumerate(round_data.get("questions", []), 1):
|
|
183
|
+
parts.append(f"{j}. {q}")
|
|
184
|
+
parts.append("### Answers")
|
|
185
|
+
for j, a in enumerate(round_data.get("answers", []), 1):
|
|
186
|
+
parts.append(f"{j}. {a}")
|
|
187
|
+
parts.append("")
|
|
188
|
+
return "\n".join(parts)
|
|
189
|
+
|
|
190
|
+
def get_stage_prompt(self, stage: Stage, backend_name: str | None = None) -> str:
|
|
191
|
+
"""Get the prompt for a stage, with project override/supplement support.
|
|
192
|
+
|
|
193
|
+
Project prompts in .galangal/prompts/ can either:
|
|
194
|
+
- Supplement the base: Include '# BASE' marker where base prompt should be inserted
|
|
195
|
+
- Override entirely: No marker = full replacement of base prompt
|
|
196
|
+
|
|
197
|
+
Backend-specific prompts are supported. If backend_name is provided,
|
|
198
|
+
the system will first look for {stage}_{backend}.md (e.g., review_codex.md)
|
|
199
|
+
before falling back to the generic {stage}.md prompt.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
stage: The workflow stage
|
|
203
|
+
backend_name: Optional backend name for backend-specific prompts
|
|
204
|
+
"""
|
|
205
|
+
stage_lower = stage.value.lower()
|
|
206
|
+
|
|
207
|
+
# For backend-specific prompts, try {stage}_{backend}.md first
|
|
208
|
+
prompt_names = [stage_lower]
|
|
209
|
+
if backend_name:
|
|
210
|
+
# Insert backend-specific name at the front
|
|
211
|
+
prompt_names.insert(0, f"{stage_lower}_{backend_name}")
|
|
212
|
+
|
|
213
|
+
base_prompt = ""
|
|
214
|
+
for prompt_name in prompt_names:
|
|
215
|
+
default_path = self.defaults_dir / f"{prompt_name}.md"
|
|
216
|
+
if default_path.exists():
|
|
217
|
+
base_prompt = default_path.read_text()
|
|
218
|
+
break
|
|
219
|
+
|
|
220
|
+
# Check for project prompts (also supports backend-specific)
|
|
221
|
+
for prompt_name in prompt_names:
|
|
222
|
+
project_path = self.override_dir / f"{prompt_name}.md"
|
|
223
|
+
if project_path.exists():
|
|
224
|
+
project_prompt = project_path.read_text()
|
|
225
|
+
|
|
226
|
+
# Merge with base (returns project_prompt unchanged if no # BASE marker)
|
|
227
|
+
merged = self._merge_with_base(project_prompt, base_prompt)
|
|
228
|
+
if merged != project_prompt:
|
|
229
|
+
return merged
|
|
230
|
+
|
|
231
|
+
# No marker = full override
|
|
232
|
+
return project_prompt
|
|
233
|
+
|
|
234
|
+
return base_prompt or f"Execute the {stage.value} stage for the task."
|
|
235
|
+
|
|
236
|
+
def build_full_prompt(
|
|
237
|
+
self, stage: Stage, state: WorkflowState, backend_name: str | None = None
|
|
238
|
+
) -> str:
|
|
239
|
+
"""
|
|
240
|
+
Build the complete prompt for a stage execution.
|
|
241
|
+
|
|
242
|
+
Assembles a full prompt by combining:
|
|
243
|
+
1. Task metadata (name, type, description, attempt)
|
|
244
|
+
2. Relevant artifacts (SPEC.md, PLAN.md, ROLLBACK.md, etc.)
|
|
245
|
+
3. Global prompt_context from config
|
|
246
|
+
4. Stage-specific stage_context from config
|
|
247
|
+
5. Documentation config (for DOCS and SECURITY stages)
|
|
248
|
+
6. The stage prompt (from get_stage_prompt)
|
|
249
|
+
|
|
250
|
+
The artifacts included vary by stage - later stages receive more
|
|
251
|
+
context from earlier artifacts.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
stage: The workflow stage to build prompt for.
|
|
255
|
+
state: Current workflow state with task info and history.
|
|
256
|
+
backend_name: Optional backend name for backend-specific prompts
|
|
257
|
+
(e.g., "codex" to use review_codex.md instead of review.md).
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
Complete prompt string ready for AI invocation.
|
|
261
|
+
"""
|
|
262
|
+
base_prompt = self.get_stage_prompt(stage, backend_name)
|
|
263
|
+
task_name = state.task_name
|
|
264
|
+
|
|
265
|
+
# Build context
|
|
266
|
+
context_parts = [
|
|
267
|
+
f"# Task: {task_name}",
|
|
268
|
+
f"# Task Type: {state.task_type.display_name()}",
|
|
269
|
+
f"# Description\n{state.task_description}",
|
|
270
|
+
f"\n# Current Stage: {stage.value}",
|
|
271
|
+
f"\n# Attempt: {state.attempt}",
|
|
272
|
+
f"\n# Artifacts Directory: {self.config.tasks_dir}/{task_name}/",
|
|
273
|
+
]
|
|
274
|
+
|
|
275
|
+
# Add screenshot context if available (especially useful for PM and early stages)
|
|
276
|
+
if stage in [Stage.PM, Stage.DESIGN, Stage.DEV]:
|
|
277
|
+
context_parts.extend(self._get_screenshot_context(state))
|
|
278
|
+
|
|
279
|
+
# Add failure context
|
|
280
|
+
if state.last_failure:
|
|
281
|
+
context_parts.append(f"\n# Previous Failure\n{state.last_failure}")
|
|
282
|
+
|
|
283
|
+
# Add relevant artifacts based on stage
|
|
284
|
+
context_parts.extend(self._get_artifact_context(stage, task_name))
|
|
285
|
+
|
|
286
|
+
# Add global prompt context from config
|
|
287
|
+
if self.config.prompt_context:
|
|
288
|
+
context_parts.append(f"\n# Project Context\n{self.config.prompt_context}")
|
|
289
|
+
|
|
290
|
+
# Add stage-specific context from config
|
|
291
|
+
stage_context = self.config.stage_context.get(stage.value, "")
|
|
292
|
+
if stage_context:
|
|
293
|
+
context_parts.append(f"\n# Stage Context\n{stage_context}")
|
|
294
|
+
|
|
295
|
+
# Add documentation config for DOCS and SECURITY stages
|
|
296
|
+
if stage in [Stage.DOCS, Stage.SECURITY]:
|
|
297
|
+
docs_config = self.config.docs
|
|
298
|
+
context_parts.append(f"""
|
|
299
|
+
# Documentation Configuration
|
|
300
|
+
## Paths
|
|
301
|
+
- Changelog Directory: {docs_config.changelog_dir}
|
|
302
|
+
- Security Audit Directory: {docs_config.security_audit}
|
|
303
|
+
- General Documentation: {docs_config.general}
|
|
304
|
+
|
|
305
|
+
## What to Update
|
|
306
|
+
- Update Changelog: {"YES" if docs_config.update_changelog else "NO - Skip changelog updates"}
|
|
307
|
+
- Update Security Audit: {"YES" if docs_config.update_security_audit else "NO - Skip security audit docs"}
|
|
308
|
+
- Update General Docs: {"YES" if docs_config.update_general_docs else "NO - Skip general documentation"}
|
|
309
|
+
|
|
310
|
+
Only update documentation types marked as YES above.""")
|
|
311
|
+
|
|
312
|
+
context = "\n".join(context_parts)
|
|
313
|
+
|
|
314
|
+
# Add decision file info at the END of the prompt for emphasis
|
|
315
|
+
# This ensures the AI sees the critical file creation requirement last
|
|
316
|
+
from galangal.core.state import get_decision_info_for_prompt
|
|
317
|
+
|
|
318
|
+
decision_info = get_decision_info_for_prompt(stage)
|
|
319
|
+
decision_suffix = f"\n\n---\n\n{decision_info}" if decision_info else ""
|
|
320
|
+
|
|
321
|
+
return f"{context}\n\n---\n\n{base_prompt}{decision_suffix}"
|
|
322
|
+
|
|
323
|
+
def _get_artifact_context(self, stage: Stage, task_name: str) -> list[str]:
|
|
324
|
+
"""
|
|
325
|
+
Get relevant artifact content for inclusion in the stage prompt.
|
|
326
|
+
|
|
327
|
+
Each stage receives context from earlier artifacts based on what it needs:
|
|
328
|
+
- DESIGN.md supersedes PLAN.md when present
|
|
329
|
+
- If DESIGN was skipped, PLAN.md is included instead
|
|
330
|
+
- Only includes artifacts that exist
|
|
331
|
+
|
|
332
|
+
Key inclusion rules:
|
|
333
|
+
- PM: DISCOVERY_LOG.md (Q&A to incorporate into SPEC)
|
|
334
|
+
- DESIGN: SPEC.md only (creates the authoritative implementation plan)
|
|
335
|
+
- DEV+: SPEC.md + DESIGN.md (or PLAN.md if design was skipped)
|
|
336
|
+
- DEV: + DEVELOPMENT.md (resume), ROLLBACK.md (issues to fix)
|
|
337
|
+
- TEST: + TEST_PLAN.md, ROLLBACK.md
|
|
338
|
+
- REVIEW: + QA_REPORT.md, SECURITY_CHECKLIST.md (verify addressed)
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
stage: Current stage to get context for.
|
|
342
|
+
task_name: Task name for artifact lookups.
|
|
343
|
+
|
|
344
|
+
Returns:
|
|
345
|
+
List of formatted artifact sections (e.g., "# SPEC.md\\n{content}").
|
|
346
|
+
"""
|
|
347
|
+
parts = []
|
|
348
|
+
|
|
349
|
+
# PM stage: only needs discovery Q&A to incorporate into SPEC
|
|
350
|
+
if stage == Stage.PM:
|
|
351
|
+
if artifact_exists("DISCOVERY_LOG.md", task_name):
|
|
352
|
+
parts.append(
|
|
353
|
+
f"\n# DISCOVERY_LOG.md (User Q&A - use these answers!)\n{read_artifact('DISCOVERY_LOG.md', task_name)}"
|
|
354
|
+
)
|
|
355
|
+
return parts
|
|
356
|
+
|
|
357
|
+
# All stages after PM need SPEC (core requirements)
|
|
358
|
+
if artifact_exists("SPEC.md", task_name):
|
|
359
|
+
parts.append(f"\n# SPEC.md\n{read_artifact('SPEC.md', task_name)}")
|
|
360
|
+
|
|
361
|
+
# Stages after DESIGN: include DESIGN.md if it exists, otherwise fall back to PLAN.md
|
|
362
|
+
# (DESIGN.md supersedes PLAN.md, but some task types skip DESIGN)
|
|
363
|
+
if stage not in [Stage.PM, Stage.DESIGN]:
|
|
364
|
+
if artifact_exists("DESIGN.md", task_name):
|
|
365
|
+
parts.append(f"\n# DESIGN.md\n{read_artifact('DESIGN.md', task_name)}")
|
|
366
|
+
elif artifact_exists("DESIGN_SKIP.md", task_name):
|
|
367
|
+
parts.append(
|
|
368
|
+
f"\n# Note: Design stage was skipped\n{read_artifact('DESIGN_SKIP.md', task_name)}"
|
|
369
|
+
)
|
|
370
|
+
# Include PLAN.md as the implementation guide when design was skipped
|
|
371
|
+
if artifact_exists("PLAN.md", task_name):
|
|
372
|
+
parts.append(f"\n# PLAN.md\n{read_artifact('PLAN.md', task_name)}")
|
|
373
|
+
|
|
374
|
+
# DEV stage: progress tracking and rollback issues
|
|
375
|
+
if stage == Stage.DEV:
|
|
376
|
+
if artifact_exists("DEVELOPMENT.md", task_name):
|
|
377
|
+
parts.append(
|
|
378
|
+
f"\n# DEVELOPMENT.md (Previous progress - continue from here)\n{read_artifact('DEVELOPMENT.md', task_name)}"
|
|
379
|
+
)
|
|
380
|
+
if artifact_exists("ROLLBACK.md", task_name):
|
|
381
|
+
parts.append(
|
|
382
|
+
f"\n# ROLLBACK.md (PRIORITY - Fix these issues first!)\n{read_artifact('ROLLBACK.md', task_name)}"
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
# TEST stage: test plan and rollback issues
|
|
386
|
+
if stage == Stage.TEST:
|
|
387
|
+
if artifact_exists("TEST_PLAN.md", task_name):
|
|
388
|
+
parts.append(f"\n# TEST_PLAN.md\n{read_artifact('TEST_PLAN.md', task_name)}")
|
|
389
|
+
if artifact_exists("ROLLBACK.md", task_name):
|
|
390
|
+
parts.append(
|
|
391
|
+
f"\n# ROLLBACK.md (Issues to address in tests)\n{read_artifact('ROLLBACK.md', task_name)}"
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
# CONTRACT stage: needs test plan for context
|
|
395
|
+
if stage == Stage.CONTRACT:
|
|
396
|
+
if artifact_exists("TEST_PLAN.md", task_name):
|
|
397
|
+
parts.append(f"\n# TEST_PLAN.md\n{read_artifact('TEST_PLAN.md', task_name)}")
|
|
398
|
+
|
|
399
|
+
# QA stage: include test summary and test gate results for context
|
|
400
|
+
if stage == Stage.QA:
|
|
401
|
+
if artifact_exists("TEST_SUMMARY.md", task_name):
|
|
402
|
+
parts.append(
|
|
403
|
+
f"\n# TEST_SUMMARY.md (Test results summary)\n{read_artifact('TEST_SUMMARY.md', task_name)}"
|
|
404
|
+
)
|
|
405
|
+
# Include TEST_GATE_RESULTS.md if test gate ran
|
|
406
|
+
if artifact_exists("TEST_GATE_RESULTS.md", task_name):
|
|
407
|
+
test_gate_content = read_artifact("TEST_GATE_RESULTS.md", task_name)
|
|
408
|
+
parts.append(
|
|
409
|
+
f"\n# TEST_GATE_RESULTS.md (Automated tests already verified)\n"
|
|
410
|
+
f"**IMPORTANT:** The following tests have already been run and passed in the TEST_GATE stage. "
|
|
411
|
+
f"Do NOT re-run these tests - focus on exploratory testing, edge cases, and code quality.\n\n"
|
|
412
|
+
f"{test_gate_content}"
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
# SECURITY stage: include test summary for coverage context
|
|
416
|
+
if stage == Stage.SECURITY:
|
|
417
|
+
if artifact_exists("TEST_SUMMARY.md", task_name):
|
|
418
|
+
parts.append(
|
|
419
|
+
f"\n# TEST_SUMMARY.md (Test results)\n{read_artifact('TEST_SUMMARY.md', task_name)}"
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
# REVIEW stage: needs QA and Security reports to verify they were addressed
|
|
423
|
+
if stage == Stage.REVIEW:
|
|
424
|
+
if artifact_exists("TEST_SUMMARY.md", task_name):
|
|
425
|
+
parts.append(
|
|
426
|
+
f"\n# TEST_SUMMARY.md (Test results)\n{read_artifact('TEST_SUMMARY.md', task_name)}"
|
|
427
|
+
)
|
|
428
|
+
if artifact_exists("QA_REPORT.md", task_name):
|
|
429
|
+
parts.append(f"\n# QA_REPORT.md\n{read_artifact('QA_REPORT.md', task_name)}")
|
|
430
|
+
if artifact_exists("SECURITY_CHECKLIST.md", task_name):
|
|
431
|
+
parts.append(
|
|
432
|
+
f"\n# SECURITY_CHECKLIST.md\n{read_artifact('SECURITY_CHECKLIST.md', task_name)}"
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
# DOCS stage: include test summary for documentation context
|
|
436
|
+
if stage == Stage.DOCS:
|
|
437
|
+
if artifact_exists("TEST_SUMMARY.md", task_name):
|
|
438
|
+
parts.append(
|
|
439
|
+
f"\n# TEST_SUMMARY.md (Test results)\n{read_artifact('TEST_SUMMARY.md', task_name)}"
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
return parts
|
|
443
|
+
|
|
444
|
+
def build_minimal_review_prompt(self, state: WorkflowState, backend_name: str) -> str:
|
|
445
|
+
"""
|
|
446
|
+
Build a minimal prompt for independent code review.
|
|
447
|
+
|
|
448
|
+
Used for secondary review backends (like Codex) that should give an
|
|
449
|
+
unbiased opinion without being influenced by Claude's interpretations.
|
|
450
|
+
|
|
451
|
+
Only includes:
|
|
452
|
+
- Original task description
|
|
453
|
+
- List of changed files with stats
|
|
454
|
+
- Instructions to read files as needed
|
|
455
|
+
|
|
456
|
+
Does NOT include:
|
|
457
|
+
- Full git diff (too large for big changes)
|
|
458
|
+
- SPEC.md (Claude's interpretation of requirements)
|
|
459
|
+
- QA_REPORT.md, SECURITY_CHECKLIST.md (previous findings)
|
|
460
|
+
- Any other artifacts from the workflow
|
|
461
|
+
|
|
462
|
+
Args:
|
|
463
|
+
state: Workflow state with task info
|
|
464
|
+
backend_name: Backend name for prompt selection (e.g., "codex")
|
|
465
|
+
|
|
466
|
+
Returns:
|
|
467
|
+
Minimal prompt for independent review
|
|
468
|
+
"""
|
|
469
|
+
import subprocess
|
|
470
|
+
|
|
471
|
+
base_branch = self.config.pr.base_branch
|
|
472
|
+
|
|
473
|
+
# Get the review prompt (may be backend-specific like review_codex.md)
|
|
474
|
+
review_prompt = self.get_stage_prompt(Stage.REVIEW, backend_name)
|
|
475
|
+
|
|
476
|
+
def run_git(*args: str) -> str:
|
|
477
|
+
"""Run a git command and return stdout."""
|
|
478
|
+
try:
|
|
479
|
+
result = subprocess.run(
|
|
480
|
+
["git", *args],
|
|
481
|
+
cwd=self.project_root,
|
|
482
|
+
capture_output=True,
|
|
483
|
+
text=True,
|
|
484
|
+
timeout=30,
|
|
485
|
+
)
|
|
486
|
+
return result.stdout.strip() if result.returncode == 0 else ""
|
|
487
|
+
except Exception:
|
|
488
|
+
return ""
|
|
489
|
+
|
|
490
|
+
# Get file names for the list
|
|
491
|
+
committed_files = run_git("diff", "--name-only", f"{base_branch}...HEAD")
|
|
492
|
+
staged_files = run_git("diff", "--name-only", "--cached")
|
|
493
|
+
unstaged_files = run_git("diff", "--name-only")
|
|
494
|
+
|
|
495
|
+
# Combine and deduplicate file names
|
|
496
|
+
all_files = set()
|
|
497
|
+
for files in [committed_files, staged_files, unstaged_files]:
|
|
498
|
+
if files:
|
|
499
|
+
all_files.update(files.split("\n"))
|
|
500
|
+
all_files.discard("")
|
|
501
|
+
|
|
502
|
+
files_list = (
|
|
503
|
+
"\n".join(f"- {f}" for f in sorted(all_files)) if all_files else "(No files changed)"
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
# Build minimal context - instruct to READ files, not dump diff
|
|
507
|
+
context = f"""# Independent Code Review
|
|
508
|
+
|
|
509
|
+
## Task
|
|
510
|
+
{state.task_description}
|
|
511
|
+
|
|
512
|
+
## Changed Files
|
|
513
|
+
The following files have been modified and need review:
|
|
514
|
+
|
|
515
|
+
{files_list}
|
|
516
|
+
|
|
517
|
+
## Instructions
|
|
518
|
+
1. Read each changed file listed above to understand the implementation
|
|
519
|
+
2. Use `git diff {base_branch}...HEAD -- <file>` to see specific changes if needed
|
|
520
|
+
3. Review for code quality, bugs, security issues, and best practices
|
|
521
|
+
4. Provide your independent assessment
|
|
522
|
+
|
|
523
|
+
---
|
|
524
|
+
|
|
525
|
+
{review_prompt}"""
|
|
526
|
+
|
|
527
|
+
return context
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Benchmark Stage
|
|
2
|
+
|
|
3
|
+
You are running performance benchmarks for this task. Focus ONLY on performance validation.
|
|
4
|
+
|
|
5
|
+
## Scope
|
|
6
|
+
|
|
7
|
+
**DO:**
|
|
8
|
+
- Run performance-specific benchmarks if configured
|
|
9
|
+
- Compare metrics against baseline if available
|
|
10
|
+
- Identify performance regressions
|
|
11
|
+
- Document benchmark results
|
|
12
|
+
|
|
13
|
+
**DO NOT:**
|
|
14
|
+
- Run the full test suite
|
|
15
|
+
- Make code changes
|
|
16
|
+
- Run linting or other QA checks
|
|
17
|
+
- Optimize code (just measure)
|
|
18
|
+
|
|
19
|
+
## Process
|
|
20
|
+
|
|
21
|
+
1. **Identify benchmarks** - Find relevant performance tests
|
|
22
|
+
2. **Run benchmarks** - Execute performance measurements
|
|
23
|
+
3. **Compare** - Check against baselines if available
|
|
24
|
+
4. **Document** - Create BENCHMARK_REPORT.md with results
|
|
25
|
+
|
|
26
|
+
## Output
|
|
27
|
+
|
|
28
|
+
Create `BENCHMARK_REPORT.md` in the task artifacts directory with:
|
|
29
|
+
- Benchmarks executed
|
|
30
|
+
- Performance metrics
|
|
31
|
+
- Comparison to baseline (if available)
|
|
32
|
+
- Any regressions identified
|
|
33
|
+
|
|
34
|
+
If no benchmarks are configured for this project, note that in the report and complete the stage.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Contract Stage
|
|
2
|
+
|
|
3
|
+
You are validating API contracts for this task. Focus ONLY on contract-related work.
|
|
4
|
+
|
|
5
|
+
## Scope
|
|
6
|
+
|
|
7
|
+
**DO:**
|
|
8
|
+
- Review API endpoint changes (new/modified routes)
|
|
9
|
+
- Validate request/response schemas match documentation
|
|
10
|
+
- Check OpenAPI/Swagger specs are updated if applicable
|
|
11
|
+
- Verify breaking changes are documented
|
|
12
|
+
- Run contract-specific tests if available
|
|
13
|
+
|
|
14
|
+
**DO NOT:**
|
|
15
|
+
- Run the full test suite
|
|
16
|
+
- Make code changes unrelated to API contracts
|
|
17
|
+
- Run linting or other QA checks
|
|
18
|
+
- Modify business logic
|
|
19
|
+
|
|
20
|
+
## Process
|
|
21
|
+
|
|
22
|
+
1. **Identify API changes** - Find new/modified endpoints in this task
|
|
23
|
+
2. **Review contracts** - Check request/response shapes are correct
|
|
24
|
+
3. **Validate specs** - Ensure OpenAPI/Swagger is updated if present
|
|
25
|
+
4. **Document** - Create CONTRACT_REPORT.md with findings
|
|
26
|
+
|
|
27
|
+
## Output
|
|
28
|
+
|
|
29
|
+
Create `CONTRACT_REPORT.md` in the task artifacts directory with:
|
|
30
|
+
- List of API endpoints reviewed
|
|
31
|
+
- Schema validation results
|
|
32
|
+
- Breaking changes identified (if any)
|
|
33
|
+
- Spec file updates needed
|
|
34
|
+
|
|
35
|
+
If no API changes exist for this task, note that in the report and complete the stage.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# DESIGN Stage - Architecture Design
|
|
2
|
+
|
|
3
|
+
You are a Software Architect designing the implementation for a feature. Create a detailed technical design document.
|
|
4
|
+
|
|
5
|
+
## Your Output
|
|
6
|
+
|
|
7
|
+
Create DESIGN.md in the task's artifacts directory with these sections:
|
|
8
|
+
|
|
9
|
+
```markdown
|
|
10
|
+
# Technical Design: [Task Title]
|
|
11
|
+
|
|
12
|
+
## Architecture Overview
|
|
13
|
+
[High-level description of the approach]
|
|
14
|
+
[How this fits into the existing system]
|
|
15
|
+
|
|
16
|
+
## Data Model
|
|
17
|
+
[Any new or modified data structures]
|
|
18
|
+
[Database schema changes if applicable]
|
|
19
|
+
|
|
20
|
+
## API Impact
|
|
21
|
+
[New or modified endpoints]
|
|
22
|
+
[Request/response formats]
|
|
23
|
+
|
|
24
|
+
## Sequence Diagram
|
|
25
|
+
[Use mermaid or text-based diagram showing the flow]
|
|
26
|
+
|
|
27
|
+
## Edge Cases
|
|
28
|
+
- [Edge case 1 and how it's handled]
|
|
29
|
+
- [Edge case 2 and how it's handled]
|
|
30
|
+
|
|
31
|
+
## Migration Plan
|
|
32
|
+
[How to deploy this without breaking existing functionality]
|
|
33
|
+
[Rollback strategy if needed]
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Process
|
|
37
|
+
|
|
38
|
+
1. Read SPEC.md and PLAN.md from context
|
|
39
|
+
2. Analyze the codebase to understand:
|
|
40
|
+
- Current architecture
|
|
41
|
+
- Integration points
|
|
42
|
+
- Patterns to follow
|
|
43
|
+
3. Design the solution considering:
|
|
44
|
+
- Scalability
|
|
45
|
+
- Maintainability
|
|
46
|
+
- Backward compatibility
|
|
47
|
+
4. Write DESIGN.md
|
|
48
|
+
|
|
49
|
+
## Important Rules
|
|
50
|
+
|
|
51
|
+
- Consider all edge cases
|
|
52
|
+
- Plan for failure scenarios
|
|
53
|
+
- Keep the design focused on the task scope
|
|
54
|
+
- Do NOT implement - only design
|