Documents the full process: adding slash commands, bot collaborator access, Gitea webhook configuration, infrastructure setup, env vars, and how workspaces work. Covers common gotchas (OAuth token priority, org vs repo permissions, lightweight vs heavy stages). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
246 lines
10 KiB
Markdown
246 lines
10 KiB
Markdown
# SDLC Orchestrator
|
|
|
|
Lightweight webhook-driven orchestrator for the Gitea + Coder + Claude Code SDLC pipeline. Receives Gitea webhooks, creates ephemeral Coder workspaces running Claude Code, and cleans up when done.
|
|
|
|
## Architecture
|
|
|
|
```
|
|
Gitea Webhook → Orchestrator → Coder API (create workspace)
|
|
→ Gitea API (post status comment)
|
|
|
|
Workspace runs Claude Code → reads issue → does work → posts results to Gitea
|
|
→ POST /webhook/task-complete/:name (callback)
|
|
|
|
Orchestrator receives callback → deletes workspace via Coder API
|
|
```
|
|
|
|
## Adding a New Project
|
|
|
|
To add a new Gitea repo to the SDLC pipeline:
|
|
|
|
### 1. Add Claude Code slash commands to the repo
|
|
|
|
Create `.claude/commands/` in the repo with command files for each SDLC stage:
|
|
|
|
```
|
|
.claude/commands/
|
|
├── analyse.md # Business analyst — requirements & acceptance criteria
|
|
├── architect.md # Systems architect — design doc & spec
|
|
├── develop.md # Developer — implement & create PR
|
|
├── review-code.md # Reviewer — approve or request changes
|
|
├── test.md # QA — test report & additional tests
|
|
├── fix-bug.md # Bug fix — skip analyse/architect
|
|
├── rework-pr.md # Address review feedback
|
|
├── release.md # Tag RC or production release
|
|
├── devops.md # Migration & deployment
|
|
└── maintenance.md # Weekly maintenance tasks
|
|
```
|
|
|
|
Each file uses this format:
|
|
|
|
```markdown
|
|
---
|
|
description: What this command does
|
|
allowed-tools: Read, Bash, Glob, Grep
|
|
---
|
|
|
|
You are a **Role Name**. Your job is to...
|
|
|
|
## Process
|
|
|
|
1. **Read the issue**:
|
|
```bash
|
|
curl -s "https://gitea.samson.media/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}/issues/$ARGUMENTS" \
|
|
-H "Authorization: token ${GITEA_TOKEN}" | jq '{title, body, labels: [.labels[].name]}'
|
|
```
|
|
|
|
2. **Do your work...**
|
|
|
|
3. **Post a comment**:
|
|
```bash
|
|
curl -X POST "https://gitea.samson.media/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}/issues/$ARGUMENTS/comments" \
|
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"body": "..."}'
|
|
```
|
|
```
|
|
|
|
`$ARGUMENTS` is replaced with the issue/PR number at runtime.
|
|
|
|
### 2. Add bot users as collaborators
|
|
|
|
Add both `claude-dev` and `claude-review` as **direct collaborators** with **Write** access on the repo. Org membership alone is not sufficient.
|
|
|
|
### 3. Configure Gitea webhooks
|
|
|
|
In the repo's **Settings → Webhooks**, create these webhooks:
|
|
|
|
| # | URL | Events |
|
|
|---|-----|--------|
|
|
| 1 | `https://sdlc.samson.media/webhook/gitea-issue-triage` | Issues: `opened` |
|
|
| 2 | `https://sdlc.samson.media/webhook/gitea-issue-label` | Issues: `labeled` |
|
|
| 3 | `https://sdlc.samson.media/webhook/gitea-issue-comment` | Issue Comment: `created`, `edited` |
|
|
| 4 | `https://sdlc.samson.media/webhook/gitea-pr-review` | Pull Request: `opened`, `synchronized` |
|
|
| 5 | `https://sdlc.samson.media/webhook/gitea-pr-review-rework` | Pull Request Review: `submitted` |
|
|
| 6 | `https://sdlc.samson.media/webhook/gitea-release` | Issues: `opened`, `labeled` |
|
|
|
|
All webhooks use:
|
|
- Content type: `application/json`
|
|
- Method: `POST`
|
|
|
|
### 4. (Optional) Add to maintenance cron
|
|
|
|
To include the repo in weekly maintenance, add it to the `MAINTENANCE_REPOS` env var:
|
|
|
|
```
|
|
MAINTENANCE_REPOS=org1/repo1=https://gitea.samson.media/org1/repo1.git,org2/repo2=https://gitea.samson.media/org2/repo2.git
|
|
```
|
|
|
|
### 5. Test it
|
|
|
|
Create an issue in the repo. The orchestrator will:
|
|
1. Receive the `issues/opened` webhook
|
|
2. Create a Coder workspace running `/analyse`
|
|
3. Claude reads the issue, posts clarifying questions as a comment
|
|
4. Workspace is deleted automatically when done
|
|
|
|
## SDLC Flow
|
|
|
|
| Stage | Trigger | Task Type | Bot Account |
|
|
|-------|---------|-----------|-------------|
|
|
| Analyse | Issue opened / comment | `analyse` | claude-dev |
|
|
| Architect | Label: `ready-for-architecture` | `architect` | claude-dev |
|
|
| Develop | Label: `ready-for-development` | `develop` | claude-dev |
|
|
| Review | PR opened/updated | `review-code` | claude-review |
|
|
| Rework | Review: changes requested | `rework-pr` | claude-dev |
|
|
| Test | Review: approved | `test` | claude-review |
|
|
| Deploy | Label: `ready-for-deployment` | `devops` | claude-dev |
|
|
| Fix Bug | Issue opened with `bug` label | `fix-bug` | claude-dev |
|
|
|
|
## Infrastructure Setup
|
|
|
|
### Prerequisites
|
|
|
|
- k3s cluster with Traefik ingress and cert-manager
|
|
- Coder instance (e.g. `coder.samson.media`)
|
|
- Gitea instance (e.g. `gitea.samson.media`)
|
|
- Docker registry (e.g. `registry.samson.media`)
|
|
- Claude Code Max subscription (for OAuth token)
|
|
|
|
### Deploy the Orchestrator
|
|
|
|
1. **Build the workspace image** (pre-installed Node.js, Claude Code, Playwright deps):
|
|
```bash
|
|
docker build -f coder/workspace.Dockerfile -t registry.samson.media/coder-workspace:latest .
|
|
docker push registry.samson.media/coder-workspace:latest
|
|
```
|
|
|
|
2. **Push the Coder template**:
|
|
```bash
|
|
coder templates push cloudflare-worker --directory coder/cloudflare-worker --yes
|
|
```
|
|
Note the template ID from the Coder dashboard.
|
|
|
|
3. **Create Gitea bot accounts**:
|
|
- `claude-dev` — used for most stages (analyse, develop, etc.)
|
|
- `claude-review` — used for review and test stages
|
|
- Generate API tokens for each
|
|
|
|
4. **Generate Claude Code OAuth token** (uses Max subscription instead of API credits):
|
|
```bash
|
|
claude setup-token
|
|
```
|
|
This opens a browser for auth and outputs a long-lived token (`sk-ant-oat01-...`).
|
|
|
|
5. **Create k8s secrets**:
|
|
```bash
|
|
kubectl create namespace sdlc-orchestrator
|
|
kubectl create secret generic orchestrator-secrets -n sdlc-orchestrator \
|
|
--from-literal=CODER_URL=https://coder.samson.media \
|
|
--from-literal=CODER_TOKEN=<coder-session-token> \
|
|
--from-literal=CODER_TEMPLATE_ID=<template-id> \
|
|
--from-literal=GITEA_DEV_TOKEN=<claude-dev-token> \
|
|
--from-literal=GITEA_REVIEW_TOKEN=<claude-review-token> \
|
|
--from-literal=CLAUDE_OAUTH_TOKEN=<oauth-token-from-step-4> \
|
|
--from-literal=CALLBACK_URL=https://sdlc.samson.media \
|
|
--from-literal=GITEA_URL=https://gitea.samson.media \
|
|
--from-literal=BOT_DEV_USERNAME=claude-dev \
|
|
--from-literal=BOT_REVIEW_USERNAME=claude-review
|
|
```
|
|
|
|
6. **Deploy**:
|
|
```bash
|
|
# Tag to trigger CI build
|
|
git tag -a v1.0.0 -m "Initial release"
|
|
git push origin main --tags
|
|
|
|
# Or deploy manually
|
|
docker build -t registry.samson.media/sdlc-orchestrator:latest .
|
|
docker push registry.samson.media/sdlc-orchestrator:latest
|
|
kubectl apply -f k8s/deployment.yaml
|
|
```
|
|
|
|
### Environment Variables
|
|
|
|
| Variable | Required | Description |
|
|
|----------|----------|-------------|
|
|
| `PORT` | No | HTTP port (default: 3000) |
|
|
| `CODER_URL` | Yes | Coder API base URL |
|
|
| `CODER_TOKEN` | Yes | Coder session token |
|
|
| `CODER_TEMPLATE_ID` | Yes | Coder workspace template ID |
|
|
| `GITEA_DEV_TOKEN` | Yes | Gitea API token for claude-dev |
|
|
| `GITEA_REVIEW_TOKEN` | Yes | Gitea API token for claude-review |
|
|
| `CLAUDE_OAUTH_TOKEN` | Yes | Claude Code Max OAuth token |
|
|
| `CALLBACK_URL` | Yes | Public URL of this service (e.g. `https://sdlc.samson.media`) |
|
|
| `GITEA_URL` | No | Gitea base URL (default: `https://gitea.samson.media`) |
|
|
| `BOT_DEV_USERNAME` | No | Dev bot username (default: `claude-dev`) |
|
|
| `BOT_REVIEW_USERNAME` | No | Review bot username (default: `claude-review`) |
|
|
| `QUEUE_CONCURRENCY` | No | Max concurrent workspaces (default: 2) |
|
|
| `MAINTENANCE_REPOS` | No | Comma-separated `org/repo=clone_url` for weekly maintenance |
|
|
|
|
> **Important**: Do NOT set `ANTHROPIC_API_KEY` in the workspace — it takes priority over the OAuth token and will use pay-per-use credits instead of your Max subscription.
|
|
|
|
## Webhook Endpoints
|
|
|
|
| Endpoint | Purpose |
|
|
|----------|---------|
|
|
| `GET /health` | Health check |
|
|
| `GET /queue` | Queue status (pending, running, active workspaces) |
|
|
| `POST /webhook/gitea-issue-triage` | Route new issues to analyse or fix-bug |
|
|
| `POST /webhook/gitea-issue-label` | Stage transitions via labels |
|
|
| `POST /webhook/gitea-issue-comment` | Re-trigger analyst on new/edited comments |
|
|
| `POST /webhook/gitea-pr-review` | Trigger code review on PR open/update |
|
|
| `POST /webhook/gitea-pr-review-rework` | Route review outcomes to rework or test |
|
|
| `POST /webhook/gitea-release` | Handle release workflow |
|
|
| `POST /webhook/task-complete/:name` | Callback from workspaces when done |
|
|
|
|
## How Workspaces Work
|
|
|
|
Workspaces are **ephemeral** (emptyDir, no persistent storage):
|
|
|
|
1. Orchestrator creates workspace via Coder API with parameters (issue number, task type, tokens, callback URL)
|
|
2. Startup script clones the repo and checks out `develop`
|
|
3. For heavy stages (develop, test, review, rework, fix-bug, devops): installs deps, builds frontend, installs Playwright
|
|
4. For lightweight stages (analyse, architect, release, maintenance): skips build
|
|
5. Reads `.claude/commands/<task_type>.md`, strips YAML frontmatter, substitutes `$ARGUMENTS` with issue number
|
|
6. Runs `claude -p --dangerously-skip-permissions --verbose "<prompt>"`
|
|
7. On completion (success or failure via ERR/EXIT trap): POSTs to callback URL
|
|
8. Orchestrator receives callback, deletes workspace via Coder API
|
|
|
|
## Resilience
|
|
|
|
- **Deduplication**: Tasks keyed by `{taskType}-{repo}-{issue}`. Duplicate webhooks are dropped.
|
|
- **Startup reconciliation**: On boot, queries Coder for running workspaces and re-adopts them into the queue.
|
|
- **409 handling**: If workspace already exists — adopts if running, deletes and retries if stopped/failed.
|
|
- **Callback trap**: Startup script uses `trap` to always fire the callback, even on clone failure or other errors.
|
|
- **TTL safety net**: Coder template has 1-hour auto-stop as a backstop for missed callbacks.
|
|
|
|
## CI/CD
|
|
|
|
| Workflow | Trigger | Output |
|
|
|----------|---------|--------|
|
|
| `publish.yml` | `v*` tags | `registry.samson.media/sdlc-orchestrator:<version>` + `:latest` |
|
|
| `publish-workspace.yml` | Changes to `coder/workspace.Dockerfile` on main | `registry.samson.media/coder-workspace:latest` |
|
|
|
|
Fleet GitOps (in `samson-media/devops` repo) watches `registry.samson.media/sdlc-orchestrator:latest` for deployment.
|