feat(tooling): dev-orchestrator — OpenSpec + OpenCode dev→QA loop #5

Open
jpporta wants to merge 5 commits from feat/dev-orchestrator into main
2 changed files with 469 additions and 0 deletions
Showing only changes of commit 6d861958ce - Show all commits
+192
View File
@@ -0,0 +1,192 @@
# Dev Orchestrator — AI Workflow Guide
Workflow de desenvolvimento com IA usando **OpenCode** + **OpenSpec** + **git worktrees**, com orquestração automática do loop dev→QA.
```
Você (review specs) → Orquestrador (specs) → Dev Agent (implementa) → QA Agent (revisa)
↑ │
└─────────────────── FAIL ──────────────────────────┘
PASS → merge → archive
```
---
## Pré-requisitos
```bash
# Instalar as 2 dependências
npm install -g opencode-ai@latest
npm install -g @fission-ai/openspec@latest
# Configurar provedor (OpenRouter, Anthropic, etc.)
opencode auth login
```
---
## Comandos
| Comando | O que faz |
|---|---|
| `dev-orchestrator init` | Configura o repo com OpenSpec (roda 1x por projeto) |
| `dev-orchestrator spec <nome>` | Cria spec OpenSpec + worktree isolado + preenche spec via IA |
| `dev-orchestrator build <nome>` | Roda dev→QA loop no worktree, merge automático no main se QA passar |
| `dev-orchestrator status` | Visão kanban dos features ativos |
| `dev-orchestrator clean <nome>` | Remove worktree + branch (para features abandonadas) |
---
## Fluxo Completo
### 1. Inicializar o projeto
```bash
cd ~/projetos/meu-app
dev-orchestrator init
```
Isso cria a pasta `openspec/`, configura o OpenCode, e adiciona `.worktrees/` ao `.gitignore`.
### 2. Criar spec de uma feature
```bash
dev-orchestrator spec add-oauth-login
```
O que acontece:
1. Cria `openspec/changes/add-oauth-login/` com os templates
2. Cria um **git worktree** isolado em `../.worktrees/add-oauth-login/` no branch `dev-flow/add-oauth-login`
3. Roda `npm install` (ou `pip install`) dentro do worktree
4. Chama o **orquestrador** (OpenCode) que lê o código, preenche os specs, valida
### 3. Revisar os specs
```bash
# Ver arquivos gerados
ls openspec/changes/add-oauth-login/
cat openspec/changes/add-oauth-login/spec.md
# Validar
openspec validate add-oauth-login --strict
```
**Aqui você está no controle.** Revise os specs, ajuste o que quiser.
Só prossiga quando estiver satisfeito.
### 4. Disparar o build
```bash
dev-orchestrator build add-oauth-login
```
O que acontece:
```
── Dev Phase (attempt 1/3) ──
→ OpenCode implementa no worktree
→ Roda testes
── QA Review ──
→ OpenCode revisa: spec compliance, testes, regressões, edge cases
PASS → merge no main, archive spec, remove worktree
FAIL → volta pro dev (até 3 tentativas)
```
Se o QA falhar 3 vezes, o worktree é mantido pra você inspecionar e corrigir manualmente.
### 5. Ver status de tudo
```bash
dev-orchestrator status
```
Exemplo de output:
```
# FEATURE COMMITS SPEC WORKTREE
─── ───────────────────────────── ───────── ───────── ──────────────
1. add-oauth-login 3 2/4 ../.worktrees/add-oauth-login/
2. fix-payment-timeout 1 active ../.worktrees/fix-payment-timeout/
```
---
## Estrutura de diretórios
```
~/projetos/meu-app/
├── src/ # código principal
├── openspec/
│ ├── specs/ # specs arquivados (source of truth)
│ └── changes/ # specs ativos
│ └── add-oauth/
│ ├── spec.md
│ ├── design.md
│ └── tasks.md
├── .gitignore # inclui .worktrees/
└── ...
../.worktrees/
└── add-oauth/ # worktree isolado
├── .git # branch: dev-flow/add-oauth
├── src/ # cópia do código
├── node_modules/ # instalado localmente
└── ...
```
---
## FAQ
### Posso rodar múltiplos features em paralelo?
Sim. Cada `dev-orchestrator spec` cria um worktree isolado. Rode `build` em terminais separados:
```bash
# Terminal 1
dev-orchestrator build add-oauth
# Terminal 2
dev-orchestrator build fix-payments
```
Worktrees em branches diferentes = zero conflito. O merge no main é o único ponto de serialização.
### O QA falhou e eu quero corrigir manualmente
```bash
cd ../.worktrees/fix-payments
# ... faz as correções ...
git add -A && git commit -m "manual fix"
dev-orchestrator build fix-payments # re-dispara do QA
```
### Como limpar uma feature abandonada?
```bash
dev-orchestrator clean fix-abandonada
```
Remove o worktree e o branch. O spec em `openspec/changes/` fica — delete manualmente se quiser.
### Preciso commitar algo no meio do build?
O Dev Agent já commita a cada tentativa. Se quiser checkpoints extras, faça manualmente no worktree — o script não interfere.
### O que acontece com `node_modules`?
Cada worktree tem o seu próprio `node_modules`, instalado durante o `spec`. Sem symlinks, sem compartilhamento — isolamento total. Custa espaço em disco, mas evita qualquer heisenbug de dependência.
---
## Opcional: Kanban com .hive (Swarm Tools)
Se quiser tracking mais visual, instale o Swarm Tools (leve, sem daemon, sem binário grande):
```bash
npm install -g opencode-swarm-plugin
swarm setup
```
Depois, dentro do OpenCode, use `/hive` pra ver tasks e `/swarm "tarefa"` pra decompor em paralelo. O `.hive/` é uma pasta git-tracked com markdown — nada de banco ou servidor.
O script `dev-orchestrator` funciona com ou sem Swarm Tools. São independentes.
+277
View File
@@ -0,0 +1,277 @@
#!/usr/bin/env bash
# dev-orchestrator — OpenSpec + OpenCode dev→QA loop with git worktrees
# Zero dependencies beyond git, opencode, openspec. No heavy binaries.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO="${DEV_FLOW_REPO:-$(pwd)}"
WORKTREE_ROOT="$REPO/../.worktrees"
# ── helpers ──────────────────────────────────────────────────────
_wt_path() { echo "$WORKTREE_ROOT/$1"; }
_wt_branch(){ echo "dev-flow/$1"; }
_die() { echo "✗ $*" >&2; exit 1; }
_ok() { echo "✓ $*"; }
# Ensure we're inside a git repo with openspec
_guard() {
cd "$REPO"
git rev-parse --show-toplevel >/dev/null 2>&1 || _die "not a git repo: $REPO"
[ -d openspec ] || _die "openspec not initialized. Run: dev-orchestrator init"
}
# ── commands ─────────────────────────────────────────────────────
cmd_init() {
cd "$REPO"
git rev-parse --show-toplevel >/dev/null 2>&1 || _die "not a git repo"
mkdir -p "$WORKTREE_ROOT"
[ -d openspec ] && _ok "openspec already initialized" || {
openspec init --tools opencode --force
_ok "openspec initialized"
}
grep -qxF '.worktrees/' .gitignore 2>/dev/null || {
echo '.worktrees/' >> .gitignore
_ok "added .worktrees/ to .gitignore"
}
echo ""
echo "Repo ready. Next: dev-orchestrator spec <feature-name>"
}
cmd_spec() {
local name="$1"
_guard
# ── validate name (kebab-case) ──
[[ "$name" =~ ^[a-z0-9]+(-[a-z0-9]+)*$ ]] || _die "name must be kebab-case: add-oauth, fix-login"
# ── create OpenSpec change ──
echo "── Creating OpenSpec change: $name ──"
openspec new change "$name" --json 2>/dev/null || {
_die "openspec new change failed. Already exists? Run: dev-orchestrator status"
}
# ── create worktree ──
local branch="$(_wt_branch "$name")"
local wt="$(_wt_path "$name")"
echo "── Creating worktree: $wt [$branch] ──"
git worktree add -b "$branch" "$wt" HEAD
# ── install deps in worktree (fully isolated, no symlinks) ──
if [ -f "$wt/package.json" ]; then
echo "── Installing dependencies ──"
(cd "$wt" && npm install --silent 2>&1 | tail -1) || true
fi
if [ -f "$wt/pyproject.toml" ] || [ -f "$wt/setup.py" ]; then
(cd "$wt" && [ -d .venv ] || python3 -m venv .venv && . .venv/bin/activate && pip install -e . -q 2>&1 | tail -1) || true
fi
# ── orchestrator fills the spec ──
echo "── Orchestrator filling spec ──"
opencode run \
"You are filling an OpenSpec change for feature: $name.
STEPS:
1. Read the codebase structure first (scan src/, lib/, or equivalent).
2. Read openspec/changes/$name/ — note the template files created.
3. Read openspec/specs/ for existing specs to avoid conflicts.
4. For each template file in the change directory, fill it with:
- Clear requirements and acceptance criteria
- Files that need to change
- Dependencies on other modules
- Test plan
5. Run: openspec validate $name --strict
6. Fix any validation errors, re-run until clean.
7. Output a summary: spec files created, key decisions, estimated scope." \
--workdir "$REPO"
echo ""
_ok "Spec '$name' ready at openspec/changes/$name/"
echo " Worktree: $wt"
echo " Review the spec, then: dev-orchestrator build $name"
}
cmd_build() {
local name="$1"
_guard
local wt="$(_wt_path "$name")"
[ -d "$wt" ] || _die "worktree not found: $wt. Run: dev-orchestrator spec $name"
local branch="$(_wt_branch "$name")"
local max_retries=3
for attempt in $(seq 1 $max_retries); do
echo ""
echo "══════════════════════════════════════════════════"
echo " $name — Dev Phase (attempt $attempt/$max_retries)"
echo "══════════════════════════════════════════════════"
opencode run \
"IMPLEMENT the spec at openspec/changes/$name/.
RULES:
- Read openspec instructions --change $name first
- Implement ALL requirements from the spec
- Write tests for every new code path
- Run the test suite and ensure it passes
- If tests fail, fix them before considering work done
- Commit with message: '$name: implement feature (attempt $attempt)'
- Do NOT modify openspec/ files — only source code and tests" \
--workdir "$wt" || {
echo "⚠ Dev phase had errors, proceeding to QA anyway..."
}
# ── QA phase ──
echo ""
echo "──────────────────────────────────────────────────"
echo " $name — QA Review"
echo "──────────────────────────────────────────────────"
local qa_file="/tmp/dev-orchestrator-qa-$$.txt"
opencode run \
"QA REVIEW for $name.
CHECKLIST (answer each with PASS or FAIL):
1. SPEC COMPLIANCE — Does the code implement everything in openspec/changes/$name/?
2. TESTS — Do all tests pass? Run them now.
3. REGRESSIONS — Does any existing test break? Check git diff vs main.
4. EDGE CASES — Are errors handled? Null/empty inputs? Timeouts?
5. CODE QUALITY — Clear naming? No debug leftovers? No commented-out code?
OUTPUT FORMAT (exactly these 2 lines, nothing else):
VERDICT: PASS|FAIL
REASON: <one-sentence summary>" \
--workdir "$wt" > "$qa_file" 2>&1
local verdict
verdict=$(grep '^VERDICT:' "$qa_file" | head -1 | awk -F': ' '{print $2}')
local reason
reason=$(grep '^REASON:' "$qa_file" | head -1 | cut -d' ' -f2-)
if [ "$verdict" = "PASS" ]; then
echo ""
_ok "QA PASSED — $reason"
# ── archive + merge ──
cd "$REPO"
echo "── Archiving spec ──"
openspec archive "$name" --yes 2>/dev/null || true
echo "── Merging to main ──"
local current_branch=$(git branch --show-current)
git merge "$branch" -m "dev-flow: merge $name" 2>/dev/null || {
echo "⚠ Merge conflict. Resolve manually in $wt then run:"
echo " cd $wt && git checkout main && git merge $branch"
return 1
}
echo "── Cleaning up worktree ──"
git worktree remove "$wt" 2>/dev/null || true
git branch -d "$branch" 2>/dev/null || true
echo ""
_ok "$name — BUILD COMPLETE ✓"
rm "$qa_file"
return 0
fi
echo ""
echo "✗ QA FAILED — ${reason:-see $qa_file}"
rm "$qa_file"
if [ "$attempt" -eq "$max_retries" ]; then
_die "$name FAILED after $max_retries attempts. Worktree kept at $wt for manual fix."
fi
echo "↻ Sending back to dev with QA feedback..."
done
}
cmd_status() {
_guard 2>/dev/null || true
echo ""
printf "%-4s %-30s %-10s %-10s %-15s\n" "#" "FEATURE" "COMMITS" "SPEC" "WORKTREE"
printf "%-4s %-30s %-10s %-10s %-15s\n" "───" "──────────────────────────────" "──────────" "──────────" "──────────────"
local n=0
for wt in "$WORKTREE_ROOT"/*/; do
[ -d "$wt" ] || continue
local name=$(basename "$wt")
local branch="$(_wt_branch "$name")"
local commits="?"
[ -d "$wt/.git" ] && commits=$(cd "$wt" && git rev-list --count "$branch" -- 2>/dev/null || echo "?")
commits="${commits:-0}"
# spec status
local spec="?"
[ -d "$REPO/openspec/changes/$name" ] && {
spec=$(cd "$REPO" && openspec status --change "$name" --json 2>/dev/null | \
python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('artifacts_completed','?'),'/',d.get('artifacts_total','?'),sep='')" 2>/dev/null || echo "active")
}
n=$((n+1))
printf "%-4s %-30s %-10s %-10s %-15s\n" \
"$n." "$name" "$commits" "${spec:-active}" "$wt"
done
if [ "$n" -eq 0 ]; then
echo " (no active features)"
fi
echo ""
}
cmd_clean() {
local name="$1"
cd "$REPO"
local wt="$(_wt_path "$name")"
local branch="$(_wt_branch "$name")"
git worktree remove "$wt" 2>/dev/null || true
git branch -D "$branch" 2>/dev/null || true
[ ! -d "$wt" ] && _ok "cleaned $name" || _die "could not remove $wt"
}
# ── dispatch ─────────────────────────────────────────────────────
case "${1:-}" in
init)
cmd_init
;;
spec)
[ $# -ge 2 ] || _die "usage: dev-orchestrator spec <feature-name>"
cmd_spec "$2"
;;
build)
[ $# -ge 2 ] || _die "usage: dev-orchestrator build <feature-name>"
cmd_build "$2"
;;
status)
cmd_status
;;
clean)
[ $# -ge 2 ] || _die "usage: dev-orchestrator clean <feature-name>"
cmd_clean "$2"
;;
*)
echo "dev-orchestrator — OpenSpec + OpenCode dev→QA loop"
echo ""
echo "commands:"
echo " init Set up repo (run once per project)"
echo " spec <feature-name> Create OpenSpec + worktree + fill spec"
echo " build <feature-name> Dev→QA loop in isolated worktree"
echo " status Kanban view of features in flight"
echo " clean <feature-name> Remove worktree + branch"
echo ""
echo "flow: init → spec → [review] → build → [loop until QA passes]"
echo ""
echo "worktrees live at: ../.worktrees/<feature>/"
echo "branches named: dev-flow/<feature>"
exit 1
;;
esac