From 8e824417b5fa03022e619174a0de7f3f31785134 Mon Sep 17 00:00:00 2001 From: Joao Porta Date: Wed, 5 Aug 2026 11:02:35 -0300 Subject: [PATCH] dev flow --- hosts/jpporta-nixos/home.nix | 2 + .../home-manager/dev-orchestrator/default.nix | 16 + tooling/WORKFLOW.md | 233 +++++++++++++ tooling/dev-orchestrator | 306 ++++++++++++++++++ 4 files changed, 557 insertions(+) create mode 100644 modules/home-manager/dev-orchestrator/default.nix create mode 100644 tooling/WORKFLOW.md create mode 100755 tooling/dev-orchestrator diff --git a/hosts/jpporta-nixos/home.nix b/hosts/jpporta-nixos/home.nix index b0629f1..0864268 100644 --- a/hosts/jpporta-nixos/home.nix +++ b/hosts/jpporta-nixos/home.nix @@ -32,6 +32,7 @@ ../../modules/home-manager/pi ../../modules/home-manager/tmux ../../modules/home-manager/openspec + ../../modules/home-manager/dev-orchestrator ../../modules/home-manager/power-profiles ../../modules/home-manager/ntfy-notify ../../modules/home-manager/pinentry @@ -97,6 +98,7 @@ enable = true; }; openspec.enable = true; + dev-orchestrator.enable = true; power-profiles.enable = true; ntfy-notify = { enable = true; diff --git a/modules/home-manager/dev-orchestrator/default.nix b/modules/home-manager/dev-orchestrator/default.nix new file mode 100644 index 0000000..9228b27 --- /dev/null +++ b/modules/home-manager/dev-orchestrator/default.nix @@ -0,0 +1,16 @@ +{ lib, config, pkgs, ... }: +{ + options.custom = { + dev-orchestrator.enable = lib.mkEnableOption "enable dev-orchestrator — OpenSpec + OpenCode dev→QA loop with git worktrees"; + }; + + config = lib.mkIf config.custom.dev-orchestrator.enable { + home.packages = [ + (pkgs.writeShellApplication { + name = "dev-orchestrator"; + runtimeInputs = with pkgs; [ git ]; + text = builtins.readFile ../../../tooling/dev-orchestrator; + }) + ]; + }; +} diff --git a/tooling/WORKFLOW.md b/tooling/WORKFLOW.md new file mode 100644 index 0000000..c50a912 --- /dev/null +++ b/tooling/WORKFLOW.md @@ -0,0 +1,233 @@ +# Dev Orchestrator — AI Workflow Guide + +Toolchain de desenvolvimento com IA: **OpenCode** (agente) + **OpenSpec** (spec-driven dev) + **Dev Orchestrator** (loop dev→QA automático). + +--- + +## Ferramentas — Quando Usar Cada Uma + +### OpenCode — O Agente + +Executa tarefas de código. Use **diretamente** quando: + +- Explorar uma codebase nova: `opencode` (TUI interativo) +- Fazer uma mudança pontual: `opencode run "corrige typo no header"` +- Debugar um erro específico: `opencode run "por que esse teste falha?" --thinking` +- Revisar um PR manualmente: `opencode pr 42` + +Use via **orquestrador** quando: +- Implementar uma feature completa com spec (`dev-orchestrator build`) +- Precisar de QA automático depois do dev +- Quiser loop dev→QA sem babysitting + +```bash +# Modos do OpenCode +opencode # TUI interativo (exploração) +opencode run "tarefa" # one-shot (automação) +opencode run "..." --thinking # vê o raciocínio do modelo +opencode run "..." -f arquivo.ts # anexa contexto +``` + +### OpenSpec — O Spec Engine + +Gerencia specs como source of truth. Use **diretamente** quando: + +- Criar spec manualmente: `openspec new change minha-feature` +- Validar um spec existente: `openspec validate minha-feature --strict` +- Ver status de todos os changes: `openspec list --json` +- Arquivar spec concluído: `openspec archive minha-feature --yes` + +Use via **orquestrador** quando: +- Quiser que a IA preencha o spec automaticamente (`dev-orchestrator spec`) +- O archive deve acontecer automático pós-QA-pass (`dev-orchestrator build`) + +```bash +# Comandos OpenSpec que você mais usa +openspec list # o que está ativo? +openspec show minha-feature # ler um spec +openspec validate minha-feature # check pré-implementação +openspec status --change minha-feature # progresso dos artefatos +openspec instructions --change minha-feature # o que o agente deve fazer +``` + +### Dev Orchestrator — O Script + +Automatiza o loop completo. **Substitui** os comandos manuais acima no fluxo principal. + +``` +você tem ideia → spec → review → build → PR pronto + ↑ ↑ ↑ + orquestrador você automático +``` + +--- + +## Pré-requisitos + +```bash +npm install -g opencode-ai@latest +npm install -g @fission-ai/openspec@latest +opencode auth login # configura provider (OpenRouter, Anthropic, etc.) +``` + +--- + +## Comandos do Orquestrador + +| Comando | O que faz | +|---|---| +| `dev-orchestrator init` | Configura repo (OpenSpec + .gitignore) — 1x por projeto | +| `dev-orchestrator spec ` | Cria spec + worktree isolado + preenche spec via IA | +| `dev-orchestrator build ` | Loop dev→QA (3 tentativas), merge automático se passar | +| `dev-orchestrator status` | Dashboard de features em andamento | +| `dev-orchestrator clean ` | Remove worktree + branch (abortar feature) | + +--- + +## Fluxo Completo + +``` +┌──────────┐ ┌──────────────┐ ┌──────────────┐ ┌─────────────┐ +│ init │───→│ spec │───→│ [review] │───→│ build │ +│ (1x/proj)│ │ (orquestrador│ │ (você aprova) │ │ (dev→QA loop)│ +└──────────┘ │ preenche) │ └──────────────┘ └──────┬──────┘ + └──────────────┘ │ + ┌────────────────────┘ + │ QA PASS → merge + archive + │ QA FAIL → retry (3x max) + └──────────────────────── +``` + +### 1. Inicializar + +```bash +cd ~/projetos/meu-app +dev-orchestrator init +``` + +### 2. Especificar feature + +```bash +dev-orchestrator spec add-oauth +``` + +O que acontece internamente: +1. `openspec new change add-oauth --json` → cria templates +2. `git worktree add -b dev-flow/add-oauth ../.worktrees/add-oauth/ HEAD` +3. `npm install` dentro do worktree +4. `opencode run "preenche os specs lendo a codebase"` → orquestrador preenche spec.md, design.md, tasks.md + +### 3. Revisar os specs (MOMENTO CRÍTICO) + +```bash +# Ler o spec gerado +cat openspec/changes/add-oauth/spec.md +cat openspec/changes/add-oauth/design.md + +# Validar +openspec validate add-oauth --strict + +# Ajustar manualmente se quiser — edite os arquivos +vim openspec/changes/add-oauth/spec.md +``` + +> **Você é o gatekeeper aqui.** Se o spec não está certo, o build vai sair errado. +> Gaste tempo revisando os specs — é o investimento mais rentável do fluxo. + +### 4. Build (dev→QA loop) + +```bash +dev-orchestrator build add-oauth +``` + +Loop: +``` +Dev Phase ──→ OpenCode implementa + testa + commita + │ + ▼ +QA Phase ──→ OpenCode revisa: spec compliance, testes, regressões + │ + ├── PASS → merge no main, archive spec, remove worktree ✅ + │ + └── FAIL → volta pro Dev (até 3 tentativas) + falhou 3x → worktree mantido pra correção manual +``` + +### 5. Status dashboard + +```bash +dev-orchestrator status +``` + +--- + +## Quando Sair do Orquestrador + +O script cobre 90% dos casos. Saia dele quando: + +| Situação | O que fazer | +|---|---| +| Spec ficou ruim e quero reescrever do zero | `openspec new change X` manual, preenche na mão | +| QA rejeitou e quero corrigir eu mesmo | `cd ../.worktrees/feature/` → edita → commita → `dev-orchestrator build feature` | +| Feature complexa demais pra um spec só | `openspec new change feature-pt1`, `openspec new change feature-pt2` | +| Quero iterar rápido sem spec | `opencode` TUI direto (pula o orquestrador) | +| Bug fix trivial (1-2 linhas) | `opencode run "fix: ..."` direto, nem cria spec | + +--- + +## Paralelismo + +Rode múltiplos features ao mesmo tempo: + +```bash +# Terminal 1 +dev-orchestrator build feature-a + +# Terminal 2 +dev-orchestrator build feature-b +``` + +Cada um em seu worktree isolado. O merge no main serializa no final — se houver conflito, o script para e avisa. + +--- + +## Estrutura de Diretórios + +``` +~/projetos/meu-app/ +├── src/ +├── openspec/ +│ ├── specs/ # specs arquivados +│ └── 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/ + ├── node_modules/ + └── ... +``` + +--- + +## Opcional: Swarm Tools + .hive + +Kanban mais visual, sem servidor: + +```bash +npm install -g opencode-swarm-plugin +swarm setup +``` + +Dentro do OpenCode: +- `/swarm "tarefa"` — decompõe e spawna workers paralelos +- `/hive` — quadro kanban das tasks +- `/inbox` — mensagens entre agentes + +O `.hive/` é uma pasta git-tracked. Independe do `dev-orchestrator`. \ No newline at end of file diff --git a/tooling/dev-orchestrator b/tooling/dev-orchestrator new file mode 100755 index 0000000..e73a962 --- /dev/null +++ b/tooling/dev-orchestrator @@ -0,0 +1,306 @@ +#!/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 + +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" + if [ -d openspec ]; then + _ok "openspec already initialized" + else + openspec init --tools opencode --force + _ok "openspec initialized" + fi + + grep -qxF '.worktrees/' .gitignore 2>/dev/null || { + echo '.worktrees/' >> .gitignore + _ok "added .worktrees/ to .gitignore" + } + + echo "" + echo "Repo ready. Next: dev-orchestrator spec " +} + +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; branch="$(_wt_branch "$name")" + local wt; 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 + # shellcheck disable=SC1091 + (cd "$wt" && [ -d .venv ] || python3 -m venv .venv && . .venv/bin/activate && pip install -e . -q 2>&1 | tail -1) || true + fi + + echo "" + _ok "Spec '$name' scaffolded at openspec/changes/$name/" + echo " Worktree: $wt" + echo "" + echo " ── Next: explore in OpenCode ──" + echo " opencode" + echo " Then use OpenSpec native commands:" + echo " /opsx:explore — discuss the feature, refine requirements" + echo " /opsx:continue — fill spec artifacts one by one" + echo "" + echo " After specs are filled → dev-orchestrator build $name" +} + +cmd_fill() { + local name="$1" + _guard + + local wt; wt="$(_wt_path "$name")" + [ -d "openspec/changes/$name" ] || _die "spec not found: openspec/changes/$name. Run: dev-orchestrator spec $name" + + echo "── AI filling spec from conversation context ──" + 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' filled at openspec/changes/$name/" + echo " Review the spec, then: dev-orchestrator build $name" +} + +cmd_build() { + local name="$1" + _guard + + local wt; wt="$(_wt_path "$name")" + [ -d "$wt" ] || _die "worktree not found: $wt. Run: dev-orchestrator spec $name" + + local branch; 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: " \ + --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 ──" + 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 + name=$(basename "$wt") + local branch; 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="$(_wt_path "$name")" + local branch; branch="$(_wt_branch "$name")" + + git worktree remove "$wt" 2>/dev/null || true + git branch -D "$branch" 2>/dev/null || true + if [ ! -d "$wt" ]; then + _ok "cleaned $name" + else + _die "could not remove $wt" + fi +} + +# ── dispatch ───────────────────────────────────────────────────── + +case "${1:-}" in + init) + cmd_init + ;; + spec) + [ $# -ge 2 ] || _die "usage: dev-orchestrator spec " + cmd_spec "$2" + ;; + fill) + [ $# -ge 2 ] || _die "usage: dev-orchestrator fill " + cmd_fill "$2" + ;; + build) + [ $# -ge 2 ] || _die "usage: dev-orchestrator build " + cmd_build "$2" + ;; + status) + cmd_status + ;; + clean) + [ $# -ge 2 ] || _die "usage: dev-orchestrator clean " + 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 Scaffold OpenSpec + worktree (no fill)" + echo " fill AI fills spec after exploration" + echo " build Dev→QA loop in isolated worktree" + echo " status Kanban view of features in flight" + echo " clean Remove worktree + branch" + echo "" + echo "flow: init → spec → [explore in OpenCode] → fill → build → [QA loop]" + echo "" + echo "worktrees live at: ../.worktrees//" + echo "branches named: dev-flow/" + exit 1 + ;; +esac