galangal-orchestrate 0.2.11__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.

Potentially problematic release.


This version of galangal-orchestrate might be problematic. Click here for more details.

Files changed (49) hide show
  1. galangal/__init__.py +8 -0
  2. galangal/__main__.py +6 -0
  3. galangal/ai/__init__.py +6 -0
  4. galangal/ai/base.py +55 -0
  5. galangal/ai/claude.py +278 -0
  6. galangal/ai/gemini.py +38 -0
  7. galangal/cli.py +296 -0
  8. galangal/commands/__init__.py +42 -0
  9. galangal/commands/approve.py +187 -0
  10. galangal/commands/complete.py +268 -0
  11. galangal/commands/init.py +173 -0
  12. galangal/commands/list.py +20 -0
  13. galangal/commands/pause.py +40 -0
  14. galangal/commands/prompts.py +98 -0
  15. galangal/commands/reset.py +43 -0
  16. galangal/commands/resume.py +29 -0
  17. galangal/commands/skip.py +216 -0
  18. galangal/commands/start.py +144 -0
  19. galangal/commands/status.py +62 -0
  20. galangal/commands/switch.py +28 -0
  21. galangal/config/__init__.py +13 -0
  22. galangal/config/defaults.py +133 -0
  23. galangal/config/loader.py +113 -0
  24. galangal/config/schema.py +155 -0
  25. galangal/core/__init__.py +18 -0
  26. galangal/core/artifacts.py +66 -0
  27. galangal/core/state.py +248 -0
  28. galangal/core/tasks.py +170 -0
  29. galangal/core/workflow.py +835 -0
  30. galangal/prompts/__init__.py +5 -0
  31. galangal/prompts/builder.py +166 -0
  32. galangal/prompts/defaults/design.md +54 -0
  33. galangal/prompts/defaults/dev.md +39 -0
  34. galangal/prompts/defaults/docs.md +46 -0
  35. galangal/prompts/defaults/pm.md +75 -0
  36. galangal/prompts/defaults/qa.md +49 -0
  37. galangal/prompts/defaults/review.md +65 -0
  38. galangal/prompts/defaults/security.md +68 -0
  39. galangal/prompts/defaults/test.md +59 -0
  40. galangal/ui/__init__.py +5 -0
  41. galangal/ui/console.py +123 -0
  42. galangal/ui/tui.py +1065 -0
  43. galangal/validation/__init__.py +5 -0
  44. galangal/validation/runner.py +395 -0
  45. galangal_orchestrate-0.2.11.dist-info/METADATA +278 -0
  46. galangal_orchestrate-0.2.11.dist-info/RECORD +49 -0
  47. galangal_orchestrate-0.2.11.dist-info/WHEEL +4 -0
  48. galangal_orchestrate-0.2.11.dist-info/entry_points.txt +2 -0
  49. galangal_orchestrate-0.2.11.dist-info/licenses/LICENSE +674 -0
@@ -0,0 +1,5 @@
1
+ """Validation system."""
2
+
3
+ from galangal.validation.runner import ValidationRunner, ValidationResult
4
+
5
+ __all__ = ["ValidationRunner", "ValidationResult"]
@@ -0,0 +1,395 @@
1
+ """
2
+ Config-driven validation runner.
3
+ """
4
+
5
+ import fnmatch
6
+ import subprocess
7
+ from dataclasses import dataclass
8
+ from pathlib import Path
9
+ from typing import Optional
10
+
11
+ from galangal.config.loader import get_project_root, get_config
12
+ from galangal.config.schema import StageValidation, ValidationCommand, PreflightCheck
13
+ from galangal.core.artifacts import artifact_exists, read_artifact, write_artifact
14
+
15
+
16
+ @dataclass
17
+ class ValidationResult:
18
+ """Result of a validation check."""
19
+
20
+ success: bool
21
+ message: str
22
+ output: Optional[str] = None
23
+ rollback_to: Optional[str] = None # Stage to rollback to on failure
24
+
25
+
26
+ class ValidationRunner:
27
+ """Run config-driven validation for stages."""
28
+
29
+ def __init__(self):
30
+ self.config = get_config()
31
+ self.project_root = get_project_root()
32
+
33
+ def validate_stage(
34
+ self,
35
+ stage: str,
36
+ task_name: str,
37
+ ) -> ValidationResult:
38
+ """Run validation for a stage based on config."""
39
+ stage_lower = stage.lower()
40
+
41
+ # Get stage validation config
42
+ validation_config = self.config.validation
43
+ stage_config: Optional[StageValidation] = getattr(
44
+ validation_config, stage_lower, None
45
+ )
46
+
47
+ if stage_config is None:
48
+ # No config for this stage - use defaults
49
+ return self._validate_with_defaults(stage, task_name)
50
+
51
+ # Check skip conditions
52
+ if stage_config.skip_if:
53
+ if self._should_skip(stage_config.skip_if, task_name):
54
+ self._write_skip_artifact(stage, task_name, "Condition met")
55
+ return ValidationResult(True, f"{stage} skipped (condition met)")
56
+
57
+ # SECURITY stage: if checklist says APPROVED, skip validation commands
58
+ # (the AI has already run scans and documented waivers)
59
+ if stage_lower == "security":
60
+ if artifact_exists("SECURITY_CHECKLIST.md", task_name):
61
+ checklist = read_artifact("SECURITY_CHECKLIST.md", task_name) or ""
62
+ if "APPROVED" in checklist.upper():
63
+ return ValidationResult(True, "Security review approved")
64
+ if "REJECTED" in checklist.upper() or "BLOCKED" in checklist.upper():
65
+ return ValidationResult(
66
+ False, "Security review found blocking issues", rollback_to="DEV"
67
+ )
68
+
69
+ # Run preflight checks (for PREFLIGHT stage)
70
+ if stage_config.checks:
71
+ result = self._run_preflight_checks(stage_config.checks, task_name)
72
+ if not result.success:
73
+ return result
74
+
75
+ # Run validation commands
76
+ for cmd_config in stage_config.commands:
77
+ result = self._run_command(cmd_config, task_name, stage_config.timeout)
78
+ if not result.success:
79
+ if cmd_config.optional:
80
+ continue
81
+ if cmd_config.allow_failure:
82
+ # Log but don't fail
83
+ continue
84
+ return result
85
+
86
+ # Check for pass/fail markers in artifacts (for AI-driven stages)
87
+ if stage_config.artifact and stage_config.pass_marker:
88
+ result = self._check_artifact_markers(stage_config, task_name)
89
+ if not result.success:
90
+ return result
91
+
92
+ # Check required artifacts
93
+ for artifact_name in stage_config.artifacts_required:
94
+ if not artifact_exists(artifact_name, task_name):
95
+ return ValidationResult(
96
+ False,
97
+ f"{artifact_name} not found",
98
+ rollback_to="DEV",
99
+ )
100
+
101
+ return ValidationResult(True, f"{stage} validation passed")
102
+
103
+ def _should_skip(self, skip_condition, task_name: str) -> bool:
104
+ """Check if skip condition is met."""
105
+ if skip_condition.no_files_match:
106
+ # Check if any files match the glob pattern in git diff
107
+ try:
108
+ result = subprocess.run(
109
+ ["git", "diff", "--name-only", "main...HEAD"],
110
+ cwd=self.project_root,
111
+ capture_output=True,
112
+ text=True,
113
+ timeout=10,
114
+ )
115
+ changed_files = result.stdout.strip().split("\n")
116
+ pattern = skip_condition.no_files_match
117
+
118
+ for f in changed_files:
119
+ if fnmatch.fnmatch(f, pattern):
120
+ return False # Found a match, don't skip
121
+
122
+ return True # No matches, skip
123
+ except Exception:
124
+ return False # On error, don't skip
125
+
126
+ return False
127
+
128
+ def _write_skip_artifact(self, stage: str, task_name: str, reason: str) -> None:
129
+ """Write a skip marker artifact."""
130
+ from datetime import datetime, timezone
131
+
132
+ content = f"""# {stage} Stage Skipped
133
+
134
+ Date: {datetime.now(timezone.utc).isoformat()}
135
+ Reason: {reason}
136
+ """
137
+ write_artifact(f"{stage.upper()}_SKIP.md", content, task_name)
138
+
139
+ def _run_preflight_checks(
140
+ self, checks: list[PreflightCheck], task_name: str
141
+ ) -> ValidationResult:
142
+ """Run preflight environment checks."""
143
+ from datetime import datetime, timezone
144
+
145
+ results: dict[str, dict] = {}
146
+ all_ok = True
147
+
148
+ for check in checks:
149
+ if check.path_exists:
150
+ path = self.project_root / check.path_exists
151
+ exists = path.exists()
152
+ results[check.name] = {"status": "OK" if exists else "Missing"}
153
+ if not exists and not check.warn_only:
154
+ all_ok = False
155
+
156
+ elif check.command:
157
+ try:
158
+ result = subprocess.run(
159
+ check.command,
160
+ shell=True,
161
+ cwd=self.project_root,
162
+ capture_output=True,
163
+ text=True,
164
+ timeout=30,
165
+ )
166
+ output = result.stdout.strip()
167
+
168
+ if check.expect_empty:
169
+ # Filter out task-related files for git status
170
+ if output:
171
+ filtered = self._filter_task_files(output, task_name)
172
+ ok = not filtered
173
+ else:
174
+ ok = True
175
+ else:
176
+ ok = result.returncode == 0
177
+
178
+ status = "OK" if ok else ("Warning" if check.warn_only else "Failed")
179
+ results[check.name] = {
180
+ "status": status,
181
+ "output": output[:200] if output else "",
182
+ }
183
+ if not ok and not check.warn_only:
184
+ all_ok = False
185
+
186
+ except Exception as e:
187
+ results[check.name] = {"status": "Error", "error": str(e)}
188
+ if not check.warn_only:
189
+ all_ok = False
190
+
191
+ # Generate report
192
+ status = "READY" if all_ok else "NOT_READY"
193
+ report = f"""# Preflight Report
194
+
195
+ ## Summary
196
+ - **Status:** {status}
197
+ - **Date:** {datetime.now(timezone.utc).isoformat()}
198
+
199
+ ## Checks
200
+ """
201
+ for name, result in results.items():
202
+ status_val = result.get("status", "Unknown")
203
+ if status_val == "OK":
204
+ status_icon = "✓"
205
+ elif status_val == "Warning":
206
+ status_icon = "⚠"
207
+ else:
208
+ status_icon = "✗"
209
+ report += f"\n### {status_icon} {name}\n"
210
+ report += f"- Status: {result.get('status', 'Unknown')}\n"
211
+ if result.get("output"):
212
+ report += f"- Output: {result['output']}\n"
213
+ if result.get("error"):
214
+ report += f"- Error: {result['error']}\n"
215
+
216
+ write_artifact("PREFLIGHT_REPORT.md", report, task_name)
217
+
218
+ if all_ok:
219
+ return ValidationResult(True, "Preflight checks passed", output=report)
220
+ return ValidationResult(
221
+ False,
222
+ "Preflight checks failed - fix environment issues",
223
+ output=report,
224
+ )
225
+
226
+ def _filter_task_files(self, git_status: str, task_name: str) -> str:
227
+ """Filter out task-related files from git status output."""
228
+ config = get_config()
229
+ tasks_dir = config.tasks_dir
230
+
231
+ filtered_lines = []
232
+ for line in git_status.split("\n"):
233
+ file_path = line[3:] if len(line) > 3 else line
234
+ # Skip task artifacts directory
235
+ if file_path.startswith(f"{tasks_dir}/"):
236
+ continue
237
+ filtered_lines.append(line)
238
+
239
+ return "\n".join(filtered_lines)
240
+
241
+ def _run_command(
242
+ self, cmd_config: ValidationCommand, task_name: str, default_timeout: int
243
+ ) -> ValidationResult:
244
+ """Run a single validation command."""
245
+ command = cmd_config.command.replace("{task_dir}", str(get_project_root() / get_config().tasks_dir / task_name))
246
+ timeout = cmd_config.timeout if cmd_config.timeout is not None else default_timeout
247
+
248
+ try:
249
+ result = subprocess.run(
250
+ command,
251
+ shell=True,
252
+ cwd=self.project_root,
253
+ capture_output=True,
254
+ text=True,
255
+ timeout=timeout,
256
+ )
257
+
258
+ if result.returncode == 0:
259
+ return ValidationResult(
260
+ True,
261
+ f"{cmd_config.name}: passed",
262
+ output=result.stdout,
263
+ )
264
+ else:
265
+ return ValidationResult(
266
+ False,
267
+ f"{cmd_config.name}: failed",
268
+ output=result.stdout + result.stderr,
269
+ rollback_to="DEV",
270
+ )
271
+
272
+ except subprocess.TimeoutExpired:
273
+ return ValidationResult(
274
+ False,
275
+ f"{cmd_config.name}: timed out",
276
+ rollback_to="DEV",
277
+ )
278
+ except Exception as e:
279
+ return ValidationResult(
280
+ False,
281
+ f"{cmd_config.name}: error - {e}",
282
+ rollback_to="DEV",
283
+ )
284
+
285
+ def _check_artifact_markers(
286
+ self, stage_config: StageValidation, task_name: str
287
+ ) -> ValidationResult:
288
+ """Check for pass/fail markers in an artifact."""
289
+ artifact_name = stage_config.artifact
290
+ if not artifact_name:
291
+ return ValidationResult(True, "No artifact to check")
292
+
293
+ content = read_artifact(artifact_name, task_name)
294
+ if not content:
295
+ return ValidationResult(
296
+ False,
297
+ f"{artifact_name} not found or empty",
298
+ rollback_to="DEV",
299
+ )
300
+
301
+ content_upper = content.upper()
302
+
303
+ if stage_config.pass_marker and stage_config.pass_marker in content_upper:
304
+ return ValidationResult(True, f"{artifact_name}: approved")
305
+
306
+ if stage_config.fail_marker and stage_config.fail_marker in content_upper:
307
+ return ValidationResult(
308
+ False,
309
+ f"{artifact_name}: changes requested",
310
+ rollback_to="DEV",
311
+ )
312
+
313
+ return ValidationResult(
314
+ False,
315
+ f"{artifact_name}: unclear result - must contain {stage_config.pass_marker} or {stage_config.fail_marker}",
316
+ )
317
+
318
+ def _validate_with_defaults(
319
+ self, stage: str, task_name: str
320
+ ) -> ValidationResult:
321
+ """Validate using default logic when no config is provided."""
322
+ stage_upper = stage.upper()
323
+
324
+ # PM stage - check for SPEC.md and PLAN.md
325
+ if stage_upper == "PM":
326
+ if not artifact_exists("SPEC.md", task_name):
327
+ return ValidationResult(False, "SPEC.md not found")
328
+ if not artifact_exists("PLAN.md", task_name):
329
+ return ValidationResult(False, "PLAN.md not found")
330
+ return ValidationResult(True, "PM stage validated")
331
+
332
+ # DESIGN stage - check for DESIGN.md or skip marker
333
+ if stage_upper == "DESIGN":
334
+ if artifact_exists("DESIGN_SKIP.md", task_name):
335
+ return ValidationResult(True, "Design skipped")
336
+ if not artifact_exists("DESIGN.md", task_name):
337
+ return ValidationResult(False, "DESIGN.md not found")
338
+ return ValidationResult(True, "Design stage validated")
339
+
340
+ # DEV stage - just check Claude completed
341
+ if stage_upper == "DEV":
342
+ return ValidationResult(True, "DEV stage completed - QA will validate")
343
+
344
+ # TEST stage - check for TEST_PLAN.md
345
+ if stage_upper == "TEST":
346
+ if not artifact_exists("TEST_PLAN.md", task_name):
347
+ return ValidationResult(False, "TEST_PLAN.md not found")
348
+ return ValidationResult(True, "Test stage validated")
349
+
350
+ # QA stage - check for QA_REPORT.md
351
+ if stage_upper == "QA":
352
+ if not artifact_exists("QA_REPORT.md", task_name):
353
+ return ValidationResult(False, "QA_REPORT.md not found")
354
+ report = read_artifact("QA_REPORT.md", task_name) or ""
355
+ if "PASS" in report.upper() and "FAIL" not in report.upper():
356
+ return ValidationResult(True, "QA passed")
357
+ return ValidationResult(False, "QA failed", rollback_to="DEV")
358
+
359
+ # SECURITY stage - check for SECURITY_CHECKLIST.md with APPROVED
360
+ if stage_upper == "SECURITY":
361
+ if artifact_exists("SECURITY_SKIP.md", task_name):
362
+ return ValidationResult(True, "Security skipped")
363
+ if not artifact_exists("SECURITY_CHECKLIST.md", task_name):
364
+ return ValidationResult(False, "SECURITY_CHECKLIST.md not found")
365
+ checklist = read_artifact("SECURITY_CHECKLIST.md", task_name) or ""
366
+ if "APPROVED" in checklist.upper():
367
+ return ValidationResult(True, "Security review approved")
368
+ if "REJECTED" in checklist.upper() or "BLOCKED" in checklist.upper():
369
+ return ValidationResult(
370
+ False, "Security review found blocking issues", rollback_to="DEV"
371
+ )
372
+ # Checklist exists but no clear marker - pass with warning
373
+ return ValidationResult(True, "Security checklist created")
374
+
375
+ # REVIEW stage - check for REVIEW_NOTES.md with APPROVE
376
+ if stage_upper == "REVIEW":
377
+ if not artifact_exists("REVIEW_NOTES.md", task_name):
378
+ return ValidationResult(False, "REVIEW_NOTES.md not found")
379
+ notes = read_artifact("REVIEW_NOTES.md", task_name) or ""
380
+ if "APPROVE" in notes.upper():
381
+ return ValidationResult(True, "Review approved")
382
+ if "REQUEST_CHANGES" in notes.upper():
383
+ return ValidationResult(
384
+ False, "Review requested changes", rollback_to="DEV"
385
+ )
386
+ return ValidationResult(False, "Review result unclear")
387
+
388
+ # DOCS stage - check for DOCS_REPORT.md
389
+ if stage_upper == "DOCS":
390
+ if not artifact_exists("DOCS_REPORT.md", task_name):
391
+ return ValidationResult(False, "DOCS_REPORT.md not found")
392
+ return ValidationResult(True, "Docs stage validated")
393
+
394
+ # Default: pass
395
+ return ValidationResult(True, f"{stage} completed")
@@ -0,0 +1,278 @@
1
+ Metadata-Version: 2.4
2
+ Name: galangal-orchestrate
3
+ Version: 0.2.11
4
+ Summary: AI-driven development workflow orchestrator
5
+ Project-URL: Homepage, https://github.com/Galangal-Media/galangal-orchestrate
6
+ Project-URL: Repository, https://github.com/Galangal-Media/galangal-orchestrate
7
+ Project-URL: Issues, https://github.com/Galangal-Media/galangal-orchestrate/issues
8
+ Author: Galangal Media
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: ai,claude,development,orchestrator,workflow
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Software Development
22
+ Classifier: Topic :: Software Development :: Quality Assurance
23
+ Requires-Python: >=3.10
24
+ Requires-Dist: pydantic>=2.0.0
25
+ Requires-Dist: pyyaml>=6.0
26
+ Requires-Dist: rich>=13.0.0
27
+ Requires-Dist: textual>=0.40.0
28
+ Provides-Extra: dev
29
+ Requires-Dist: mypy>=1.0.0; extra == 'dev'
30
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
31
+ Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
32
+ Requires-Dist: pytest>=7.0.0; extra == 'dev'
33
+ Requires-Dist: ruff>=0.1.0; extra == 'dev'
34
+ Description-Content-Type: text/markdown
35
+
36
+ # Galangal Orchestrate
37
+
38
+ AI-driven development workflow orchestrator. A deterministic workflow system that guides AI assistants through structured development stages.
39
+
40
+ **Note:** Currently designed for [Claude Code](https://docs.anthropic.com/en/docs/claude-code) with a Claude Pro or Max subscription. Support for other AI backends (Gemini, etc.) is planned for future releases.
41
+
42
+ ## Features
43
+
44
+ - **Structured Workflow**: PM → DESIGN → DEV → TEST → QA → SECURITY → REVIEW → DOCS
45
+ - **Multi-Framework Support**: Python, TypeScript, PHP, Go, Rust - configure multiple stacks per project
46
+ - **Config-Driven**: All validation, prompts, and behavior customizable via YAML
47
+ - **AI Backend Abstraction**: Built for Claude CLI, ready for Gemini and others
48
+ - **Approval Gates**: Human-in-the-loop for plans and designs
49
+ - **Automatic Rollback**: Failed stages roll back to appropriate fix points
50
+ - **TUI Progress Display**: Real-time progress visualization
51
+
52
+ ## Installation
53
+
54
+ ```bash
55
+ pip install galangal-orchestrate
56
+ ```
57
+
58
+ Or with pipx for isolated global install (recommended):
59
+
60
+ ```bash
61
+ pipx install galangal-orchestrate
62
+ ```
63
+
64
+ ### Updating
65
+
66
+ ```bash
67
+ # If installed with pip
68
+ pip install --upgrade galangal-orchestrate
69
+
70
+ # If installed with pipx
71
+ pipx upgrade galangal-orchestrate
72
+ ```
73
+
74
+ ## Quick Start
75
+
76
+ ```bash
77
+ # Initialize in your project
78
+ cd your-project
79
+ galangal init
80
+
81
+ # Start a new task
82
+ galangal start "Add user authentication feature"
83
+
84
+ # Resume after a break
85
+ galangal resume
86
+
87
+ # Check status
88
+ galangal status
89
+ ```
90
+
91
+ ## Workflow Stages
92
+
93
+ | Stage | Purpose | Artifacts |
94
+ |-------|---------|-----------|
95
+ | PM | Requirements & planning | SPEC.md, PLAN.md |
96
+ | DESIGN | Architecture design | DESIGN.md |
97
+ | PREFLIGHT | Environment checks | PREFLIGHT_REPORT.md |
98
+ | DEV | Implementation | (code changes) |
99
+ | MIGRATION* | DB migration validation | MIGRATION_REPORT.md |
100
+ | TEST | Test implementation | TEST_PLAN.md |
101
+ | CONTRACT* | API contract validation | CONTRACT_REPORT.md |
102
+ | QA | Quality assurance | QA_REPORT.md |
103
+ | BENCHMARK* | Performance validation | BENCHMARK_REPORT.md |
104
+ | SECURITY | Security review | SECURITY_CHECKLIST.md |
105
+ | REVIEW | Code review | REVIEW_NOTES.md |
106
+ | DOCS | Documentation updates | DOCS_REPORT.md |
107
+
108
+ *Conditional stages - auto-skipped if conditions not met
109
+
110
+ ## Task Types
111
+
112
+ Different task types skip certain stages:
113
+
114
+ | Type | Skips |
115
+ |------|-------|
116
+ | Feature | (full workflow) |
117
+ | Bug Fix | DESIGN, BENCHMARK |
118
+ | Refactor | DESIGN, MIGRATION, CONTRACT, BENCHMARK, SECURITY |
119
+ | Chore | DESIGN, MIGRATION, CONTRACT, BENCHMARK |
120
+ | Docs | Most stages |
121
+ | Hotfix | DESIGN, BENCHMARK |
122
+
123
+ ## Configuration
124
+
125
+ After `galangal init`, customize `.galangal/config.yaml`:
126
+
127
+ ```yaml
128
+ project:
129
+ name: "My Project"
130
+ stacks:
131
+ - language: python
132
+ framework: fastapi
133
+ root: backend/
134
+ - language: typescript
135
+ framework: vite
136
+ root: frontend/
137
+
138
+ stages:
139
+ skip:
140
+ - BENCHMARK
141
+ timeout: 14400
142
+ max_retries: 5
143
+
144
+ validation:
145
+ qa:
146
+ timeout: 3600
147
+ commands:
148
+ - name: "Lint"
149
+ command: "./scripts/lint.sh"
150
+ timeout: 600
151
+ - name: "Tests"
152
+ command: "pytest"
153
+
154
+ pr:
155
+ codex_review: true
156
+ base_branch: main
157
+
158
+ prompt_context: |
159
+ ## Project Patterns
160
+ - Use repository pattern for data access
161
+ - API responses use api_success() / api_error()
162
+ ```
163
+
164
+ ## Customizing Prompts
165
+
166
+ Galangal uses a layered prompt system:
167
+
168
+ 1. **Base prompts** - Generic, language-agnostic prompts built into the package
169
+ 2. **Project prompts** - Your customizations in `.galangal/prompts/`
170
+
171
+ ### Prompt Modes
172
+
173
+ Project prompts support two modes:
174
+
175
+ #### Supplement Mode (Recommended)
176
+
177
+ Add project-specific content that gets prepended to the base prompt. Include the `# BASE` marker where you want the base prompt inserted:
178
+
179
+ ```markdown
180
+ <!-- .galangal/prompts/dev.md -->
181
+
182
+ ## My Project CLI Scripts
183
+
184
+ Use these commands for testing:
185
+ - `./scripts/test.sh` - Run tests
186
+ - `./scripts/lint.sh` - Run linter
187
+
188
+ ## My Project Patterns
189
+
190
+ - Always use `api_success()` for responses
191
+ - Never use raw SQL queries
192
+
193
+ # BASE
194
+ ```
195
+
196
+ The `# BASE` marker tells galangal to insert the generic base prompt at that location. Your project-specific content appears first, followed by the standard instructions.
197
+
198
+ #### Override Mode
199
+
200
+ To completely replace a base prompt, simply omit the `# BASE` marker:
201
+
202
+ ```markdown
203
+ <!-- .galangal/prompts/preflight.md -->
204
+
205
+ # Custom Preflight
206
+
207
+ This completely replaces the default preflight prompt.
208
+
209
+ [Your custom instructions here...]
210
+ ```
211
+
212
+ ### Available Prompts
213
+
214
+ Create any of these files in `.galangal/prompts/` to customize:
215
+
216
+ | File | Stage |
217
+ |------|-------|
218
+ | `pm.md` | Requirements & planning |
219
+ | `design.md` | Architecture design |
220
+ | `preflight.md` | Environment checks |
221
+ | `dev.md` | Implementation |
222
+ | `test.md` | Test writing |
223
+ | `qa.md` | Quality assurance |
224
+ | `security.md` | Security review |
225
+ | `review.md` | Code review |
226
+ | `docs.md` | Documentation |
227
+
228
+ ### Config-Based Context
229
+
230
+ You can also inject context via `config.yaml` without creating prompt files:
231
+
232
+ ```yaml
233
+ # .galangal/config.yaml
234
+
235
+ # Injected into ALL stage prompts
236
+ prompt_context: |
237
+ ## Project Rules
238
+ - Use TypeScript strict mode
239
+ - All APIs must be documented
240
+
241
+ # Injected into specific stages only
242
+ stage_context:
243
+ dev: |
244
+ ## Dev Environment
245
+ - Run `npm run dev` for hot reload
246
+ test: |
247
+ ## Test Setup
248
+ - Use vitest for unit tests
249
+ ```
250
+
251
+ ## Commands
252
+
253
+ | Command | Description |
254
+ |---------|-------------|
255
+ | `galangal init` | Initialize in current project |
256
+ | `galangal start "desc"` | Start new task |
257
+ | `galangal list` | List all tasks |
258
+ | `galangal switch <name>` | Switch active task |
259
+ | `galangal status` | Show task status |
260
+ | `galangal resume` | Continue active task |
261
+ | `galangal pause` | Pause for break |
262
+ | `galangal approve` | Approve plan |
263
+ | `galangal approve-design` | Approve design |
264
+ | `galangal skip-design` | Skip design stage |
265
+ | `galangal skip-to <stage>` | Jump to stage |
266
+ | `galangal complete` | Finalize & create PR |
267
+ | `galangal reset` | Delete active task |
268
+
269
+ ## Requirements
270
+
271
+ - Python 3.10+
272
+ - [Claude Code](https://docs.anthropic.com/en/docs/claude-code) CLI installed (`claude` command available)
273
+ - Claude Pro or Max subscription
274
+ - Git
275
+
276
+ ## License
277
+
278
+ MIT License - see LICENSE file.