From 3bdef65d8789158770a0bac621f84b22fa6c51df Mon Sep 17 00:00:00 2001 From: rnascimento10 Date: Wed, 17 Jun 2026 16:47:09 -0300 Subject: [PATCH 01/54] chore(release): bump version to 0.5.3 for npm publish --- package.json | 2 +- packages/cli/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 315c62f..66e1d50 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "letra", - "version": "0.5.1", + "version": "0.5.3", "private": true, "description": "SDD-agnostic memory framework for AI coding agents", "type": "module", diff --git a/packages/cli/package.json b/packages/cli/package.json index 933a200..3d92c13 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@letra-ai/cli", - "version": "0.4.1", + "version": "0.5.3", "type": "module", "bin": { "letra": "dist/index.js" From cd5dbd77eb7416172c5a352569f43e37886d9859 Mon Sep 17 00:00:00 2001 From: rnascimento10 Date: Wed, 17 Jun 2026 17:49:34 -0300 Subject: [PATCH 02/54] docs(letra-dev-loop): add explicit publish-from-clean-main rule --- software-development/letra-dev-loop/SKILL.md | 39 ++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 software-development/letra-dev-loop/SKILL.md diff --git a/software-development/letra-dev-loop/SKILL.md b/software-development/letra-dev-loop/SKILL.md new file mode 100644 index 0000000..39fc86e --- /dev/null +++ b/software-development/letra-dev-loop/SKILL.md @@ -0,0 +1,39 @@ +# Letra Dev Loop + +Fluxo oficial do projeto letra. Siga na ordem: pulse -> context -> focus -> spec. + +## Pré-sessão +- Rode `letra pulse` +- Leia `AGENTS.md`, `context.md`, `focus.md`, `specs/` +- Não mova `focus.md` manualmente; use `letra focus` + +## Regras fixas +- Use `letra flow move ... --auto`; nunca use `--force` +- Registre logs com `letra log add ... --item ` +- Marque ACs com `letra ac done --spec ` +- Não transite estágios sem aprovação humana +- Só mova para Code após aprovação da spec +- Após criar a spec, faça release imediato do item e aguarde aprovação humana antes de Code +- Quando não houver mais estágios, mova para Done manualmente + +## Release / Publish (regra obrigatória) +- O publish **deve** ser feito a partir de um checkout limpo de `main` +- Passos: + 1. `git checkout main` + 2. `git clean -fd` (só remove arquivos não rastreados não-ignorados; preserva `.gitignore`) + 3. `git checkout .` (descarta alterações locais em arquivos tracked) + 4. `git pull origin main` + 5. Confirme `package.json` e `packages//package.json` com a versão da tag + 6. `npm -w packages/ run typecheck` (ex: `npm -w packages/cli run typecheck`) + 7. `npm -w packages/ run test` + 8. `npm publish --workspace packages/ --access public` +- Nunca publique a partir de uma working tree suja +- Se o npm rejeitar por versão existente, confira se já foi publicado antes e não repita publish +- Tag e publish devem usar a mesma versão; atualize package.json **antes** de publicar + +## Checklist de publish +- [ ] Checkout limpo de `main` +- [ ] Typecheck verde (somente workspaces com script) +- [ ] Testes verdes +- [ ] Pacote versionado corretamente +- [ ] Publicação confirmada no npm From 8f820d6baa9d05be92df5ce046ba1d5c7e12d86a Mon Sep 17 00:00:00 2001 From: rnascimento10 Date: Mon, 22 Jun 2026 11:52:52 -0300 Subject: [PATCH 03/54] feat: gate enforcement + empty state + ac CLI + harness loader MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - flow-move: human gates bloqueiam sempre; --force pula apenas ACs - flow-serve PATCH: valida gates humanos antes de atualizar stage - FlowView: carrega humanGateStages via /api/harness/templates - KanbanView: empty state com CTA 'Add Item' - ac.ts: done/list parsing com IDs implícitos - harness loader + parser customizado (sem dep externa yaml) - flow-init --quick usa template sdlc do harness automaticamente --- .hermes/instructions.md | 24 +- .letra/workflow.json | 511 +++--------------- packages/cli/src/adapters/hermes.ts | 56 ++ packages/cli/src/commands/ac.ts | 24 +- packages/cli/src/commands/flow-init.ts | 35 +- packages/cli/src/commands/flow-move.ts | 55 +- packages/cli/src/commands/flow-serve.ts | 94 +++- packages/cli/src/harness/loader.ts | 126 +++++ packages/cli/src/harness/parse.ts | 99 ++++ packages/cli/src/harness/types.ts | 64 +++ .../client/src/components/Flow/FlowView.tsx | 26 +- .../src/components/Kanban/KanbanView.tsx | 23 +- 12 files changed, 665 insertions(+), 472 deletions(-) create mode 100644 packages/cli/src/adapters/hermes.ts create mode 100644 packages/cli/src/harness/loader.ts create mode 100644 packages/cli/src/harness/parse.ts create mode 100644 packages/cli/src/harness/types.ts diff --git a/.hermes/instructions.md b/.hermes/instructions.md index b8ccbc9..76332a5 100644 --- a/.hermes/instructions.md +++ b/.hermes/instructions.md @@ -1,19 +1,15 @@ -# Gerado por letra flow move. Nao edite manualmente. - -### ⚠ ATENÇÃO: 2 problema(s) grave(s) detectado(s) pelo diagnóstico automático -Execute `letra health` para detalhes e `letra health ack ` para reconhecer. -# Letra Session — letra +# Letra Session — Letra Self-Management PASSO OBRIGATÓRIO #1: letra pulse — verificar estado do workspace PASSO OBRIGATÓRIO #2: Leia .letra/context.md — contexto completo do projeto PASSO OBRIGATÓRIO #3: Leia .letra/focus.md — foco e outcome da sessão -PASSO OBRIGATÓRIO #4: Leia .letra/specs/write-sync/spec.md — ACs do item +PASSO OBRIGATÓRIO #4: Leia .letra/specs/harness-loader-implementation/spec.md — ACs do item ## Foco Atual -Item: ITEM-1 · Branding: header Letra no flow serve + 3 opções de logo -Spec: write-sync -Estágio: Done +Item: item-1 · workspace-discovery +Spec: harness-loader-implementation +Estágio: Spec Review → Code ## Alertas @@ -21,35 +17,35 @@ Alerta · severidade baixa ID: hr-4652f8f0 O que: AC "flow diff" marcado [ ] mas implementado Onde: ac-stale - Desde: 17/06/2026, 14:13:59 + Desde: 22/06/2026, 11:10:40 Ação: `letra health ack hr-4652f8f0` Alerta · severidade baixa ID: hr-4103b765 O que: AC "flow visualize" marcado [ ] mas implementado Onde: ac-stale - Desde: 17/06/2026, 14:13:59 + Desde: 22/06/2026, 11:10:40 Ação: `letra health ack hr-4103b765` Alerta · severidade baixa ID: hr-dd77601 O que: AC "flow export" marcado [ ] mas implementado Onde: ac-stale - Desde: 17/06/2026, 14:13:59 + Desde: 22/06/2026, 11:10:40 Ação: `letra health ack hr-dd77601` Alerta · severidade baixa ID: hr-6380dc9e O que: AC "GET /" marcado [ ] mas implementado Onde: ac-stale - Desde: 17/06/2026, 14:13:59 + Desde: 22/06/2026, 11:10:40 Ação: `letra health ack hr-6380dc9e` Alerta · severidade baixa ID: hr-352219f2 O que: AC "letra sitrep" marcado [ ] mas implementado Onde: ac-stale - Desde: 17/06/2026, 14:13:59 + Desde: 22/06/2026, 11:10:40 Ação: `letra health ack hr-352219f2` e mais 3 alertas diff --git a/.letra/workflow.json b/.letra/workflow.json index 148def1..fdac41c 100644 --- a/.letra/workflow.json +++ b/.letra/workflow.json @@ -1,9 +1,10 @@ { - "version": "1.0", - "name": "letra", - "description": "SDD-agnostic memory framework for AI coding agents", - "createdAt": "2026-06-07T21:45:54.843Z", - "updatedAt": "2026-06-17T17:14:02.615Z", + "id": "wf_letra-100", + "templateRef": "sdlc", + "version": "0.1.0", + "name": "Letra Self-Management", + "createdAt": "2026-06-22T11:16:15Z", + "updatedAt": "2026-06-22T14:10:40.003Z", "stages": [ { "id": "backlog", @@ -12,472 +13,108 @@ "zone": "todo" }, { - "id": "design", - "name": "Design", + "id": "spec-draft", + "name": "Spec Draft", "order": 1, "zone": "doing" }, { - "id": "code", - "name": "Code", + "id": "spec-review", + "name": "Spec Review", "order": 2, "zone": "doing" }, { - "id": "review", - "name": "Review", + "id": "code", + "name": "Code", "order": 3, "zone": "doing" }, { - "id": "done", - "name": "Done", + "id": "code-review", + "name": "Code Review", "order": 4, - "zone": "done" - } - ], - "items": [ - { - "id": "ITEM-1", - "description": "Branding: header Letra no flow serve + 3 opções de logo", - "stage": "done", - "createdAt": "2026-06-07T21:45:59.038Z", - "spec": "flow-serve" - }, - { - "id": "ITEM-2", - "description": "adapter claude code", - "spec": "adapter-claude-code", - "stage": "done", - "createdAt": "2026-06-07T22:00:00.000Z" - }, - { - "id": "ITEM-3", - "description": "adapter codex cli", - "spec": "adapter-codex-cli", - "stage": "done", - "createdAt": "2026-06-07T22:00:00.000Z" - }, - { - "id": "ITEM-4", - "description": "adapter cursor", - "spec": "adapter-cursor", - "stage": "done", - "createdAt": "2026-06-07T22:00:00.000Z" - }, - { - "id": "ITEM-5", - "description": "adapter opencode", - "spec": "adapter-opencode", - "stage": "done", - "createdAt": "2026-06-07T22:00:00.000Z" - }, - { - "id": "ITEM-6", - "description": "adapter vscode", - "spec": "adapter-vscode", - "stage": "done", - "createdAt": "2026-06-07T22:00:00.000Z" - }, - { - "id": "ITEM-7", - "description": "adapter windsurf", - "spec": "adapter-windsurf", - "stage": "done", - "createdAt": "2026-06-07T22:00:00.000Z" - }, - { - "id": "ITEM-8", - "description": "ci cd pipeline", - "spec": "ci-cd-pipeline", - "stage": "done", - "createdAt": "2026-06-07T22:00:00.000Z" - }, - { - "id": "ITEM-9", - "description": "cli skeleton", - "spec": "cli-skeleton", - "stage": "done", - "createdAt": "2026-06-07T22:00:00.000Z" - }, - { - "id": "ITEM-10", - "description": "decision new", - "spec": "decision-new", - "stage": "done", - "createdAt": "2026-06-07T22:00:00.000Z" - }, - { - "id": "ITEM-11", - "description": "design system", - "spec": "design-system", - "stage": "done", - "createdAt": "2026-06-07T22:00:00.000Z" - }, - { - "id": "ITEM-12", - "description": "flow designer", - "spec": "flow-designer", - "stage": "done", - "createdAt": "2026-06-07T22:00:00.000Z" - }, - { - "id": "ITEM-13", - "description": "flow edit diff", - "spec": "flow-edit-diff", - "stage": "done", - "createdAt": "2026-06-07T22:00:00.000Z" - }, - { - "id": "ITEM-14", - "description": "flow export import", - "spec": "flow-export-import", - "stage": "done", - "createdAt": "2026-06-07T22:00:00.000Z" - }, - { - "id": "ITEM-15", - "description": "flow hub redesign", - "spec": "flow-hub-redesign", - "stage": "done", - "createdAt": "2026-06-07T22:00:00.000Z" - }, - { - "id": "ITEM-16", - "description": "flow import issues", - "spec": "flow-import-issues", - "stage": "backlog", - "createdAt": "2026-06-07T22:00:00.000Z" - }, - { - "id": "ITEM-17", - "description": "flow mvp", - "spec": "flow-mvp", - "stage": "done", - "createdAt": "2026-06-07T22:00:00.000Z" - }, - { - "id": "ITEM-18", - "description": "flow serve", - "spec": "flow-serve", - "stage": "done", - "createdAt": "2026-06-07T22:00:00.000Z" - }, - { - "id": "ITEM-19", - "description": "flow visualize", - "spec": "flow-visualize", - "stage": "done", - "createdAt": "2026-06-07T22:00:00.000Z" - }, - { - "id": "ITEM-20", - "description": "init interactive", - "spec": "init-interactive", - "stage": "done", - "createdAt": "2026-06-07T22:00:00.000Z" - }, - { - "id": "ITEM-21", - "description": "item 26 home rica", - "spec": "item-26-home-rica", - "stage": "done", - "createdAt": "2026-06-07T22:00:00.000Z" - }, - { - "id": "ITEM-22", - "description": "item 7 automacoes", - "spec": "item-7-automacoes", - "stage": "done", - "createdAt": "2026-06-07T22:00:00.000Z" - }, - { - "id": "ITEM-23", - "description": "session focus", - "spec": "session-focus", - "stage": "done", - "createdAt": "2026-06-07T22:00:00.000Z" - }, - { - "id": "ITEM-24", - "description": "spec templates domain", - "spec": "spec-templates-domain", - "stage": "done", - "createdAt": "2026-06-07T22:00:00.000Z" - }, - { - "id": "ITEM-25", - "description": "validate config", - "spec": "validate-config", - "stage": "done", - "createdAt": "2026-06-07T22:00:00.000Z" - }, - { - "id": "ITEM-26", - "description": "validate conflict", - "spec": "validate-conflict", - "stage": "done", - "createdAt": "2026-06-07T22:00:00.000Z" - }, - { - "id": "ITEM-27", - "description": "validate format pr", - "spec": "validate-format-pr", - "stage": "done", - "createdAt": "2026-06-07T22:00:00.000Z" - }, - { - "id": "ITEM-28", - "description": "validate intelligence", - "spec": "validate-intelligence", - "stage": "done", - "createdAt": "2026-06-07T22:00:00.000Z" - }, - { - "id": "ITEM-29", - "description": "validate watch", - "spec": "validate-watch", - "stage": "done", - "createdAt": "2026-06-07T22:00:00.000Z" - }, - { - "id": "ITEM-30", - "description": "self-diagnosis engine — detecta e corrige drifts entre specs, código e workflow", - "spec": "self-diagnosis-core", - "stage": "done", - "createdAt": "2026-06-14T14:56:00.000Z" - }, - { - "id": "ITEM-31", - "description": "diagnostics web — SSE + REST endpoints para diagnóstico no flow serve", - "spec": "diagnostics-web", - "stage": "done", - "createdAt": "2026-06-14T14:56:00.000Z" - }, - { - "id": "ITEM-32", - "description": "diagnostics UI — badge, sugestões, histórico de correções e undo", - "spec": "diagnostics-ui", - "stage": "done", - "createdAt": "2026-06-14T14:56:00.000Z" - }, - { - "id": "ITEM-33", - "description": "ruler header — DocumentView reutilizável com sticky header + régua de seções + fade no scroll", - "spec": "ruler-header", - "stage": "done", - "createdAt": "2026-06-14T15:33:00.000Z" - }, - { - "id": "ITEM-34", - "description": "write-sync: motor de sincronização de memória", - "stage": "done", - "createdAt": "2026-06-16T14:03:20.110Z", - "spec": "write-sync" - }, - { - "id": "ITEM-35", - "description": "web-health-alerts: alertas de saúde na web UI", - "stage": "design", - "createdAt": "2026-06-16T14:03:20.584Z", - "spec": "web-health-alerts" - }, - { - "id": "ITEM-36", - "description": "web-sync-button: botão de sincronização na web UI", - "stage": "design", - "createdAt": "2026-06-16T14:03:21.014Z", - "spec": "web-sync-button" + "zone": "doing" }, { - "id": "ITEM-37", - "description": "Item Claim — Soft Lock de Atividade para Agentes", - "stage": "done", - "createdAt": "2026-06-16T16:28:42.448Z", - "spec": "item-claim" + "id": "security", + "name": "Security", + "order": 5, + "zone": "doing" }, { - "id": "ITEM-38", - "description": "Kanban UX — Card Enriquecido + Modal Full-Screen", - "stage": "done", - "createdAt": "2026-06-16T16:51:39.911Z", - "spec": "kanban-ux" + "id": "ready-to-pr", + "name": "Ready to PR", + "order": 6, + "zone": "doing" }, { - "id": "ITEM-39", - "description": "Loop Gaps — Correções no Ciclo de Desenvolvimento", - "stage": "review", - "createdAt": "2026-06-16T17:22:03.596Z", - "spec": "loop-gaps" - }, + "id": "done", + "name": "Done", + "order": 7, + "zone": "done" + } + ], + "items": [ { - "id": "ITEM-40", - "description": "Arquitetura Domain-Agnostic — workspace isolado + abstração de domínio", - "stage": "backlog", - "createdAt": "2026-06-16T19:48:19.933Z", - "spec": "architecture-agnostic" + "id": "item-1", + "description": "workspace-discovery", + "stage": "spec-review", + "createdAt": "2026-06-21T00:00:00Z", + "spec": "workspace-discovery" }, { - "id": "ITEM-41", - "description": "Remover dependência de npm para publish", - "stage": "done", - "createdAt": "2026-06-16T20:48:46.071Z", - "spec": "npm-agnostic" + "id": "item-2", + "description": "sdlc-template-gates", + "stage": "spec-review", + "createdAt": "2026-06-21T00:01:00Z", + "spec": "sdlc-template-gates" }, { - "id": "ITEM-42", - "description": "Harness Optimization — Loop de Execução para Agentes", - "stage": "review", - "createdAt": "2026-06-17T13:03:29.031Z", - "spec": "harness-optimization" + "id": "item-3", + "description": "tui-onboarding", + "stage": "spec-review", + "createdAt": "2026-06-21T00:02:00Z", + "spec": "tui-onboarding" }, { - "id": "ITEM-43", - "description": "Implementar adapter Hermes Agent", - "stage": "review", - "createdAt": "2026-06-17T13:22:24.748Z", - "spec": "adapter-hermes" + "id": "item-4", + "description": "harness-loader-implementation", + "stage": "security", + "createdAt": "2026-06-21T00:03:00Z", + "spec": "harness-loader-implementation" }, { - "id": "ITEM-44", - "description": "Melhorias no harness e loop de execução — sincronia, testes, disciplina, animação real", - "stage": "review", - "createdAt": "2026-06-17T16:37:48.435Z", - "spec": "harness-loop-realtime" + "id": "item-5", + "description": "letra-init-command", + "stage": "spec-review", + "createdAt": "2026-06-21T00:04:00Z", + "spec": "letra-init-command" } ], + "tools": [ + "hermes", + "cursor", + "claude-code", + "windsurf", + "opencode", + "vscode" + ], "specLinks": { - "adapter-claude-code": { - "path": ".letra/specs/adapter-claude-code/spec.md" - }, - "adapter-codex-cli": { - "path": ".letra/specs/adapter-codex-cli/spec.md" - }, - "adapter-cursor": { - "path": ".letra/specs/adapter-cursor/spec.md" - }, - "adapter-opencode": { - "path": ".letra/specs/adapter-opencode/spec.md" - }, - "adapter-vscode": { - "path": ".letra/specs/adapter-vscode/spec.md" - }, - "adapter-windsurf": { - "path": ".letra/specs/adapter-windsurf/spec.md" - }, - "ci-cd-pipeline": { - "path": ".letra/specs/ci-cd-pipeline/spec.md" - }, - "cli-skeleton": { - "path": ".letra/specs/cli-skeleton/spec.md" + "workspace-discovery": { + "path": ".letra/specs/workspace-discovery/spec.md" }, - "decision-new": { - "path": ".letra/specs/decision-new/spec.md" + "sdlc-template-gates": { + "path": ".letra/specs/sdlc-template-gates/spec.md" }, - "design-system": { - "path": ".letra/specs/design-system/spec.md" + "tui-onboarding": { + "path": ".letra/specs/tui-onboarding/spec.md" }, - "flow-designer": { - "path": ".letra/specs/flow-designer/spec.md" + "harness-loader-implementation": { + "path": ".letra/specs/harness-loader-implementation/spec.md" }, - "flow-edit-diff": { - "path": ".letra/specs/flow-edit-diff/spec.md" - }, - "flow-export-import": { - "path": ".letra/specs/flow-export-import/spec.md" - }, - "flow-hub-redesign": { - "path": ".letra/specs/flow-hub-redesign/spec.md" - }, - "flow-import-issues": { - "path": ".letra/specs/flow-import-issues/spec.md" - }, - "flow-mvp": { - "path": ".letra/specs/flow-mvp/spec.md" - }, - "flow-serve": { - "path": ".letra/specs/flow-serve/spec.md" - }, - "flow-visualize": { - "path": ".letra/specs/flow-visualize/spec.md" - }, - "init-interactive": { - "path": ".letra/specs/init-interactive/spec.md" - }, - "item-26-home-rica": { - "path": ".letra/specs/item-26-home-rica/spec.md" - }, - "item-7-automacoes": { - "path": ".letra/specs/item-7-automacoes/spec.md" - }, - "session-focus": { - "path": ".letra/specs/session-focus/spec.md" - }, - "spec-templates-domain": { - "path": ".letra/specs/spec-templates-domain/spec.md" - }, - "validate-config": { - "path": ".letra/specs/validate-config/spec.md" - }, - "validate-conflict": { - "path": ".letra/specs/validate-conflict/spec.md" - }, - "validate-format-pr": { - "path": ".letra/specs/validate-format-pr/spec.md" - }, - "validate-intelligence": { - "path": ".letra/specs/validate-intelligence/spec.md" - }, - "validate-watch": { - "path": ".letra/specs/validate-watch/spec.md" - }, - "self-diagnosis-core": { - "path": ".letra/specs/self-diagnosis-core/spec.md" - }, - "diagnostics-web": { - "path": ".letra/specs/diagnostics-web/spec.md" - }, - "diagnostics-ui": { - "path": ".letra/specs/diagnostics-ui/spec.md" - }, - "ruler-header": { - "path": ".letra/specs/ruler-header/spec.md" - }, - "item-claim": { - "path": ".letra/specs/item-claim/spec.md" - }, - "kanban-ux": { - "path": ".letra/specs/kanban-ux/spec.md" - }, - "loop-gaps": { - "path": ".letra/specs/loop-gaps/spec.md" - }, - "write-sync": { - "path": ".letra/specs/write-sync/spec.md" - }, - "architecture-agnostic": { - "path": ".letra/specs/architecture-agnostic/spec.md" - }, - "web-health-alerts": { - "path": ".letra/specs/web-health-alerts/spec.md" - }, - "web-sync-button": { - "path": ".letra/specs/web-sync-button/spec.md" - }, - "harness-optimization": { - "path": ".letra/specs/harness-optimization/spec.md" - }, - "npm-agnostic": { - "path": ".letra/specs/npm-agnostic/spec.md" - }, - "harness-loop-realtime": { - "path": ".letra/specs/harness-loop-realtime/spec.md" + "letra-init-command": { + "path": ".letra/specs/letra-init-command/spec.md" } - }, - "tools": [ - "cursor", - "opencode", - "vscode", - "hermes" - ] + } } \ No newline at end of file diff --git a/packages/cli/src/adapters/hermes.ts b/packages/cli/src/adapters/hermes.ts new file mode 100644 index 0000000..4ad0a81 --- /dev/null +++ b/packages/cli/src/adapters/hermes.ts @@ -0,0 +1,56 @@ +import { loadWorkflow } from "../commands/flow-init.js"; +import { buildHarnessSnapshot } from "./builder.js"; +import { formatAdapterContent } from "./formatters.js"; + +export type HermesOptions = { + root: string; + source?: string; + displayName?: string; +}; + +export function buildHermesSnapshot(root: string) { + const workflow = loadWorkflow(root); + if (!workflow) { + return { + workflowName: "letra", + hasWorkflow: false, + items: [], + hasFocus: false, + primaryItemId: null, + focusSpec: null, + focusPath: null, + pendingACs: 0, + totalACs: 0, + lastSession: null, + alerts: undefined, + } as const; + } + + const activeItemId = workflow.primaryItemId || workflow.items[0]?.id; + const activeItem = workflow.items.find((item) => item.id === activeItemId) || null; + + return buildHarnessSnapshot(root, { + workflow: { + name: workflow.name, + stages: workflow.stages, + items: workflow.items, + }, + activeStageId: activeItem?.stage || workflow.stages[0]?.id, + primaryItemId: activeItemId, + }); +} + +export function formatHermesContent(snapshot: ReturnType) { + return formatAdapterContent(snapshot, "text", { + source: "hermes", + displayName: "Hermes Agent", + }); +} + +export function generateHermesAdapter(root: string): string | null { + const snapshot = buildHermesSnapshot(root); + if (!snapshot.hasWorkflow) { + return null; + } + return formatHermesContent(snapshot); +} diff --git a/packages/cli/src/commands/ac.ts b/packages/cli/src/commands/ac.ts index 73a3450..e87f3e2 100644 --- a/packages/cli/src/commands/ac.ts +++ b/packages/cli/src/commands/ac.ts @@ -8,9 +8,12 @@ import { validate } from "./validate.js"; import { generateAdapters } from "../adapters/generate.js"; export function findAcByPattern(lines: string[], acId: string): number | null { - const pattern = new RegExp(`^- \\[ \\] \\*\\*${escapeRegex(acId)}\\*\\*`); + const normalized = acId.trim(); for (let i = 0; i < lines.length; i++) { - if (pattern.test(lines[i].trim())) return i; + const line = lines[i].trim(); + if (!/^- \[ \]/.test(line)) continue; + const text = line.replace(/^- \[ \]/, "").trim(); + if (text.toLowerCase().includes(normalized.toLowerCase())) return i; } return null; } @@ -21,10 +24,21 @@ function escapeRegex(s: string): string { export function listPendingACs(lines: string[]): { lineIdx: number; id: string; text: string }[] { const result: { lineIdx: number; id: string; text: string }[] = []; - const pattern = /^- \[ \] \*\*(AC[\d.]+)\*\*: (.+)$/; + const explicit = /^- \[ \] \*\*(AC[\d.]+)\*\*: (.+)$/; + const plain = /^- \[ \] (.+)$/; for (let i = 0; i < lines.length; i++) { - const match = lines[i].trim().match(pattern); - if (match) result.push({ lineIdx: i, id: match[1], text: match[2] }); + const line = lines[i].trim(); + let match = line.match(explicit); + if (match) { + result.push({ lineIdx: i, id: match[1], text: match[2] }); + continue; + } + match = line.match(plain); + if (match) { + const text = match[1]; + const id = `AC-${i + 1}`; + result.push({ lineIdx: i, id, text }); + } } return result; } diff --git a/packages/cli/src/commands/flow-init.ts b/packages/cli/src/commands/flow-init.ts index 78552af..f5dcbf1 100644 --- a/packages/cli/src/commands/flow-init.ts +++ b/packages/cli/src/commands/flow-init.ts @@ -1,12 +1,14 @@ -import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs"; import { join, resolve } from "node:path"; import { createInterface } from "node:readline"; import chalk from "chalk"; import ora from "ora"; import { generateAdapters } from "../adapters/generate.js"; +import { generateHermesAdapter } from "../adapters/hermes.js"; import { logEntry } from "../session-log.js"; import { DiagnosticEngine } from "../diagnostics/engine.js"; import { loadHealthRecord, mergeScanResults, saveHealthRecord } from "../health-record.js"; +import { loadHarness, resolveHarnessRoot } from "../harness/loader.js"; export interface Stage { id: string; @@ -65,6 +67,10 @@ export interface Workflow { tools: string[]; webhooks?: WebhookConfig[]; primaryItemId?: string; + state?: { + currentStage?: string; + locked?: boolean; + }; } function askText(query: string, defaultValue: string): Promise { @@ -81,12 +87,12 @@ function askText(query: string, defaultValue: string): Promise { export function detectExistingTools(root: string): string[] { const tools: string[] = []; + if (existsSync(join(root, ".hermes", "instructions.md"))) tools.push("hermes"); if (existsSync(join(root, ".cursorrules"))) tools.push("cursor"); if (existsSync(join(root, "CLAUDE.md"))) tools.push("claude-code"); if (existsSync(join(root, ".windsurfrules"))) tools.push("windsurf"); if (existsSync(join(root, "AGENTS.md"))) tools.push("opencode"); if (existsSync(join(root, ".github", "copilot-instructions.md"))) tools.push("vscode"); - if (existsSync(join(root, ".hermes", "instructions.md"))) tools.push("hermes"); return tools; } @@ -95,7 +101,7 @@ export function detectProjectName(root: string): string { if (existsSync(pkgPath)) { try { const pkg = JSON.parse(readFileSync(pkgPath, "utf-8")); - if (pkg.name) return pkg.name.replace(/^@[^/]+\//, ""); + if (pkg.name) return pkg.name.replace(/^@[^/]+/, ""); } catch {} } return resolve(root).split(/[\\/]/).pop() || "meu-projeto"; @@ -255,6 +261,19 @@ export async function writeWorkflow(root: string, options: WriteWorkflowOptions) primaryItemId: primaryItemId || workflow.items[0]?.id, graveIssueCount: graveIssueCount > 0 ? graveIssueCount : undefined, }); + + // Hermes-specific writing when selected + if (workflow.tools.includes("hermes")) { + const hermesContent = generateHermesAdapter(root); + if (hermesContent) { + const hermesPath = join(root, ".hermes", "instructions.md"); + const hermesDir = join(hermesPath, ".."); + if (!existsSync(hermesDir)) mkdirSync(hermesDir, { recursive: true }); + writeFileSync(hermesPath, hermesContent, "utf-8"); + filesUpdated.push(".hermes/instructions.md"); + } + } + for (const tool of workflow.tools) { const target = ADAPTER_TARGETS[tool]; if (target) filesUpdated.push(target); @@ -297,7 +316,7 @@ export async function flowInit(root: string, options?: { quick?: boolean }): Pro const stages = stagesFromInput(stagesInput); const detected = detectExistingTools(root); - const defaultTools = detected.length > 0 ? detected.join(", ") : "cursor, opencode"; + const defaultTools = detected.length > 0 ? detected.join(", ") : "hermes, opencode"; const toolsInput = await askText("Tools (comma-separated)?", defaultTools); const tools = toolsInput .split(",") @@ -319,9 +338,13 @@ export async function flowInit(root: string, options?: { quick?: boolean }): Pro async function flowInitQuick(root: string): Promise { const defaultName = detectProjectName(root); - const defaultStagesList = "backlog, design, code, review, done"; + const harness = loadHarness(resolveHarnessRoot(root)); + const sdlcStages = harness?.flows?.sdlc?.stages; + const defaultStagesList = sdlcStages && sdlcStages.length > 0 + ? sdlcStages.map((s) => s.name).join(", ") + : "backlog, design, code, review, done"; const detected = detectExistingTools(root); - const defaultTools = detected.length > 0 ? detected.join(", ") : "cursor, opencode"; + const defaultTools = detected.length > 0 ? detected.join(", ") : "hermes, opencode"; const name = await askText("Workflow name?", defaultName); const stagesInput = await askText("Stages (comma-separated)?", defaultStagesList); diff --git a/packages/cli/src/commands/flow-move.ts b/packages/cli/src/commands/flow-move.ts index 0afde32..a898ea5 100644 --- a/packages/cli/src/commands/flow-move.ts +++ b/packages/cli/src/commands/flow-move.ts @@ -5,6 +5,7 @@ import { loadWorkflow, writeWorkflow } from "./flow-init.js"; import { writeFocusFile } from "../adapters/focus-sync.js"; import { logEntry } from "../session-log.js"; import { queryLog } from "../session-log.js"; +import { loadHarness, resolveHarnessRoot } from "../harness/loader.js"; function now(): string { return new Date().toISOString(); @@ -30,8 +31,8 @@ export async function flowMove(root: string, itemId: string, targetStageInput: s } const item = - workflow.items.find((i) => i.id === itemId) || - workflow.items.find((i) => i.description === itemId); + workflow.items.find((i) => i.id.toLowerCase() === itemId.toLowerCase()) || + workflow.items.find((i) => i.description.toLowerCase() === itemId.toLowerCase()); if (!item) { console.log(chalk.red(`Item "${itemId}" not found`)); process.exit(1); @@ -71,13 +72,59 @@ export async function flowMove(root: string, itemId: string, targetStageInput: s const acLogEntries = queryLog(root, { itemId, action: "ac_done", limit: 999 }); if (acLogEntries.length < doneACs.length) { console.log(chalk.yellow(`⚠ ${doneACs.length - acLogEntries.length} AC(s) marked [x] without "ac done" log entry.`)); - console.log(chalk.yellow(` Run \`letra ac done \` for each completed AC.`)); + console.log(chalk.yellow(` Run letra ac done for each completed AC.`)); } } } } } + // AC12: validate spec before moving (non-auto flows) + if (!options?.auto && !options?.force && item.spec) { + const specFile = join(root, ".letra", "specs", item.spec, "spec.md"); + if (existsSync(specFile)) { + const content = readFileSync(specFile, "utf-8"); + const pendingACs = content.match(/^- \[ \]/gm) || []; + if (pendingACs.length > 0) { + console.log(chalk.yellow(`⚠ Item ${itemId} has ${pendingACs.length} pending AC(s) in "${item.spec}"`)); + console.log(chalk.yellow(` Use --force to move anyway, or complete ACs first.`)); + return; + } + const doneACs = content.match(/^- \[[xX]\]/gm) || []; + if (doneACs.length > 0) { + const acLogEntries = queryLog(root, { itemId, action: "ac_done", limit: 999 }); + if (acLogEntries.length < doneACs.length) { + console.log(chalk.yellow(`⚠ ${doneACs.length - acLogEntries.length} AC(s) marked [x] without "ac done" log entry.`)); + console.log(chalk.yellow(` Run letra ac done for each completed AC.`)); + } + } + } + } + + // Gate enforcement via harness (humano sempre bloqueia; automated avisa) + { + const harness = loadHarness(resolveHarnessRoot(root)); + const template = harness?.flows?.sdlc; + if (template) { + const targetDef = template.stages.find((s) => s.id === targetStageInput); + if (targetDef?.gate) { + const gateId = targetDef.gate.replace(/^.*[\\/]/, "").replace(/\.yaml$/, ""); + const gate = harness?.gates?.[gateId]; + if (gate?.type === "human" && gate.blocking) { + const resolvedTarget = resolveStage(workflow, targetStageInput) || targetStageInput; + console.log(chalk.red(`Gate bloqueante: ${gate.name}`)); + console.log(chalk.yellow(` Aprovação humana necessária para entrar em "${resolvedTarget}".`)); + return; + } + if (gate?.type === "automated" && gate.blocking) { + console.log(chalk.yellow(`⛔ Gate automated bloqueante: ${gate.name}`)); + console.log(chalk.yellow(` Valide as condições antes de avançar.`)); + // TODO: implementar check automático + } + } + } + } + const targetStageId = resolveStage(workflow, targetStageInput); if (!targetStageId) { const valid = workflow.stages.map((s) => `${s.id} (${s.name})`).join(", "); @@ -146,6 +193,6 @@ export function flowMoveAction( if (options.auto) { flowMove(root, normalized, options.to || "", { auto: true, force: options.force }); } else { - flowMove(root, normalized, options.to || ""); + flowMove(root, normalized, options.to || "", { force: options.force }); } } diff --git a/packages/cli/src/commands/flow-serve.ts b/packages/cli/src/commands/flow-serve.ts index 03865e1..0c00065 100644 --- a/packages/cli/src/commands/flow-serve.ts +++ b/packages/cli/src/commands/flow-serve.ts @@ -6,6 +6,8 @@ import { dirname, extname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { loadWorkflow, writeWorkflow, detectProjectName } from "./flow-init.js"; import type { Item, Workflow } from "./flow-init.js"; +import { loadHarness, resolveHarnessRoot } from "../harness/loader"; +import type { FlowTemplate, HarnessManifest } from "../harness/types"; import { DiagnosticEngine } from "../diagnostics/engine.js"; import type { DiagnosticResult } from "../diagnostics/types.js"; import { validateSpecStructure } from "../validation/structure.js"; @@ -108,8 +110,19 @@ function createWorkflowFromTemplate( root: string, templateId: string, options?: { name?: string; tools?: string[] }, + harness?: HarnessManifest | null, ): Workflow { - const t = TEMPLATES[templateId]; + const t = harness?.flows?.[templateId] + ? { + name: harness.flows[templateId].name, + stages: harness.flows[templateId].stages.map((s) => ({ + id: s.id, + name: s.name, + order: s.order, + zone: s.zone, + })), + } + : TEMPLATES[templateId]; if (!t) { throw new Error( `Template "${templateId}" not found. Available: ${Object.keys(TEMPLATES).join(", ")}`, @@ -147,12 +160,15 @@ export class FlowServer { private port: number; private loadWorkflow; private engine: DiagnosticEngine; + private harness: HarnessManifest | null; constructor(root: string, port: number = DEFAULT_PORT) { this.root = root; this.port = port; this.loadWorkflow = () => loadWorkflow(root); this.engine = new DiagnosticEngine(root); + const harnessRoot = resolveHarnessRoot(root); + this.harness = loadHarness(harnessRoot); } private handleRequest = async (req: IncomingMessage, res: ServerResponse): Promise => { @@ -213,7 +229,7 @@ export class FlowServer { wf = createWorkflowFromTemplate(this.root, data.template, { name: data.name, tools: data.tools, - }); + }, this.harness); if (existing?.language && wf) { wf.language = existing.language; } @@ -231,6 +247,59 @@ export class FlowServer { return; } + if (path === "/api/harness/templates" && req.method === "GET") { + const templates = this.harness + ? Object.values(this.harness.flows).map((f) => { + const gateIds = new Set( + f.stages + .map((s) => s.gate) + .filter((g): g is string => !!g) + .map((g) => { + // suporta tanto id puro quanto caminho relativo "gates/id.yaml" + const base = g.replace(/^.*[\\/]/, "").replace(/\.ya?ml$/, ""); + return base; + }), + ); + const gates = gateIds.size + ? [...gateIds] + .map((id) => this.harness?.gates?.[id]) + .filter((g): g is NonNullable => !!g) + : []; + + const roleIds = new Set( + f.stages.flatMap((s) => (Array.isArray(s.agents) ? s.agents : [])), + ); + const roles = roleIds.size + ? [...roleIds] + .map((id) => this.harness?.roles?.[id]) + .filter((r): r is NonNullable => !!r) + : []; + + const policyRefs = new Set(); + for (const g of gates) { + if (g.policyRef) policyRefs.add(g.policyRef.replace(/^.*[\\/]/, "").replace(/\.json$/, "")); + } + const policies = [...policyRefs] + .map((ref) => this.harness?.policies?.[ref]) + .filter((p): p is NonNullable => !!p); + + return { + id: f.id, + version: f.version, + name: f.name, + description: f.description, + stages: f.stages, + gates, + roles, + policies, + }; + }) + : []; + res.writeHead(200, { "Content-Type": "application/json" }); + res.end(JSON.stringify(templates)); + return; + } + if (path === "/api/specs" && req.method === "GET") { try { const wf = this.loadWorkflow(); @@ -459,7 +528,26 @@ export class FlowServer { return; } const oldStage = item.stage; - if (data.stage !== undefined) item.stage = data.stage; + if (data.stage !== undefined && data.stage !== oldStage) { + // Gate enforcement via harness + const template = this.harness?.flows?.sdlc; + console.log("[PATCH] stage change", itemId, oldStage, "->", data.stage, "has_harness:", !!this.harness, "has_template:", !!template); + if (template) { + const targetStage = template.stages.find((s: { id?: string }) => s.id === data.stage); + console.log("[PATCH] targetStage gate:", targetStage?.gate); + if (targetStage?.gate) { + const gateId = targetStage.gate.replace(/^.*[\\/]/, "").replace(/\.yaml$/, ""); + const gate = this.harness?.gates?.[gateId]; + console.log("[PATCH] gateId:", gateId, "gate:", gate); + if (gate && gate.type === "human") { + res.writeHead(422, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ error: `Gate bloqueante: ${gate.name}. Aprovação humana necessária para entrar em "${targetStage.name}".` })); + return; + } + } + } + item.stage = data.stage; + } if (data.description !== undefined) item.description = data.description; if (data.tasks !== undefined) item.tasks = data.tasks; wf.updatedAt = new Date().toISOString(); diff --git a/packages/cli/src/harness/loader.ts b/packages/cli/src/harness/loader.ts new file mode 100644 index 0000000..7276eea --- /dev/null +++ b/packages/cli/src/harness/loader.ts @@ -0,0 +1,126 @@ +import { readdirSync, existsSync, readFileSync } from "node:fs"; +import { join } from "node:path"; +import { parseSimpleYaml } from "./parse"; +import type { HarnessManifest, StageDef } from "./types"; + +export function resolveHarnessRoot(cwd: string, version = "v0.1.0"): string { + const candidates = [ + join(cwd, ".letra", "harness", version), + join(cwd, "..", ".letra", "harness", version), + ]; + for (const p of candidates) { + if (existsSync(p)) return p; + } + return candidates[0]; +} + +function unwrapStages(value: unknown): any[] { + if (Array.isArray(value)) return value; + if (value && typeof value === "object" && Array.isArray((value as any).stages)) return (value as any).stages; + return []; +} + +export function loadHarness(root: string): HarnessManifest | null { + const harnessDir = root; + if (!existsSync(harnessDir)) return null; + + const flowsDir = join(harnessDir, "flows"); + const gatesDir = join(harnessDir, "gates"); + const rolesDir = join(harnessDir, "roles"); + const policiesDir = join(harnessDir, "policies"); + + if (!existsSync(flowsDir) || !existsSync(gatesDir) || !existsSync(rolesDir)) return null; + + const flows: Record = {}; + if (existsSync(flowsDir)) { + for (const file of readdirSync(flowsDir)) { + if (!file.endsWith(".yaml")) continue; + const raw = parseSimpleYaml(readFileSync(join(flowsDir, file), "utf-8")); + flows[String(raw.id)] = { + id: String(raw.id), + version: String(raw.version ?? "0.0.0"), + name: String(raw.name ?? raw.id), + description: String(raw.description ?? ""), + defaultPolicy: String(raw.defaultPolicy ?? ""), + stages: unwrapStages(raw.stages).map((s: any) => ({ + id: String(s.id ?? ""), + name: String(s.name ?? s.id ?? ""), + order: typeof s.order === "number" ? s.order : Number(s.order ?? 0), + zone: ["todo", "doing", "done"].includes(s.zone) + ? (s.zone as StageDef["zone"]) + : undefined, + description: String(s.description ?? ""), + agents: Array.isArray(s.agents) + ? s.agents.map((a: any) => String(a)) + : typeof s.agents === "string" + ? s.agents.split(",").map((a: string) => a.trim()).filter(Boolean) + : [], + gate: typeof s.gate === "string" && s.gate.trim() ? s.gate.trim() : null, + })), + }; + } + } + + const gates: Record = {}; + if (existsSync(gatesDir)) { + for (const file of readdirSync(gatesDir)) { + if (!file.endsWith(".yaml")) continue; + const raw = parseSimpleYaml(readFileSync(join(gatesDir, file), "utf-8")); + gates[String(raw.id)] = { + id: String(raw.id), + name: String(raw.name ?? raw.id), + type: ["human", "automated", "external"].includes(raw.type as string) + ? (raw.type as HarnessManifest["gates"][string]["type"]) + : "automated", + blocking: raw.blocking === true, + policyRef: (raw.policyRef as string | undefined) ?? undefined, + description: String(raw.description ?? ""), + }; + } + } + + const roles: Record = {}; + if (existsSync(rolesDir)) { + for (const file of readdirSync(rolesDir)) { + if (!file.endsWith(".yaml")) continue; + const raw = parseSimpleYaml(readFileSync(join(rolesDir, file), "utf-8")); + const capabilities = Array.isArray(raw.capabilities) + ? raw.capabilities.map(String) + : typeof raw.capabilities === "string" + ? raw.capabilities.split(",").map((a: string) => a.trim()).filter(Boolean) + : []; + roles[String(raw.id)] = { + id: String(raw.id), + label: String(raw.label ?? raw.id), + description: String(raw.description ?? ""), + allowedStages: Array.isArray(raw.allowedStages) + ? raw.allowedStages.map(String) + : typeof raw.allowedStages === "string" + ? raw.allowedStages.split(",").map((a: string) => a.trim()).filter(Boolean) + : [], + capabilities, + }; + } + } + + const policies: Record = {}; + if (existsSync(policiesDir)) { + for (const file of readdirSync(policiesDir)) { + if (!file.endsWith(".json")) continue; + try { + const p = JSON.parse(readFileSync(join(policiesDir, file), "utf-8")); + policies[String(p.id)] = p as HarnessManifest["policies"][string]; + } catch { + // ignore malformed policy + } + } + } + + return { + version: "0.1.0", + flows, + gates, + roles, + policies, + }; +} diff --git a/packages/cli/src/harness/parse.ts b/packages/cli/src/harness/parse.ts new file mode 100644 index 0000000..69f1497 --- /dev/null +++ b/packages/cli/src/harness/parse.ts @@ -0,0 +1,99 @@ +export function parseSimpleYaml(text: string): Record { + type Frame = { obj: Record; indent: number; containerKey?: string }; + const root: Record = {}; + const stack: Frame[] = [{ obj: root, indent: -1 }]; + + const lines = text.split(/\r?\n/); + for (const raw of lines) { + const trimmed = raw.trim(); + if (!trimmed || trimmed.startsWith("#")) continue; + + const indent = raw.length - trimmed.length; + + while (stack.length > 1 && stack[stack.length - 1].indent >= indent) { + stack.pop(); + } + + const parent = stack[stack.length - 1].obj; + + // list item + const listMatch = trimmed.match(/^- \s*(.*)/); + if (listMatch) { + const content = listMatch[1].trim(); + const containerKey = stack[stack.length - 1].containerKey; + if (!containerKey) continue; + + const arr: unknown[] = Array.isArray(parent[containerKey]) ? (parent[containerKey] as unknown[]) : []; + parent[containerKey] = arr; + + const kv = content.match(/^([A-Za-z0-9_-]+)\s*:\s*(.*)$/); + if (kv) { + const obj: Record = {}; + obj[kv[1]] = parseValue(kv[2]); + arr.push(obj); + stack.push({ obj, indent, containerKey }); + } else { + arr.push(parseValue(content)); + } + continue; + } + + // key: value + const kvMatch = trimmed.match(/^([A-Za-z0-9_-]+)\s*:\s*(.*)$/); + if (kvMatch) { + const key = kvMatch[1]; + const val = kvMatch[2].trim(); + + if (!val) { + const obj: Record = {}; + parent[key] = obj; + stack.push({ obj, indent, containerKey: key }); + } else { + parent[key] = parseValue(val); + stack[stack.length - 1].containerKey = key; + } + continue; + } + } + + return normalize(root); +} + +function normalize(value: unknown): unknown { + if (Array.isArray(value)) return value.map(normalize); + if (value && typeof value === "object") { + const obj = value as Record; + for (const [k, v] of Object.entries(obj)) { + obj[k] = normalize(v) as Record[string]; + } + unwrapSingleKeyArrays(obj); + return obj; + } + return value; +} + +function unwrapSingleKeyArrays(obj: Record) { + for (const key of Object.keys(obj)) { + const value = obj[key]; + if (value && typeof value === "object" && !Array.isArray(value)) { + const inner = value as Record; + const entries = Object.entries(inner); + if (entries.length === 1 && entries[0][0] === key && Array.isArray(entries[0][1])) { + obj[key] = entries[0][1]; + } + } + } +} + +function parseValue(raw: string): unknown { + if (raw === "true") return true; + if (raw === "false") return false; + if (raw === "null" || raw === "~") return null; + if (/^-?\d+$/.test(raw)) return parseInt(raw, 10); + if (/^-?\d+\.\d+$/.test(raw)) return parseFloat(raw); + if ((raw.startsWith('"') && raw.endsWith('"')) || (raw.startsWith("'") && raw.endsWith("'"))) return raw.slice(1, -1); + if ((raw.startsWith("[") && raw.endsWith("]")) || (raw.startsWith("{") && raw.endsWith("}"))) { + try { return JSON.parse(raw); } catch { /* fallthrough */ } + } + return raw; +} diff --git a/packages/cli/src/harness/types.ts b/packages/cli/src/harness/types.ts new file mode 100644 index 0000000..5eda482 --- /dev/null +++ b/packages/cli/src/harness/types.ts @@ -0,0 +1,64 @@ +export interface AgentCapability { + id: string; + label: string; + description: string; + allowedStages: string[]; + capabilities: string[]; +} + +export interface Gate { + id: string; + name: string; + type: "human" | "automated" | "external"; + blocking: boolean; + policyRef?: string; + description: string; +} + +export interface StageDef { + id: string; + name: string; + order: number; + zone?: "todo" | "doing" | "done"; + description: string; + agents: string[]; + gate: string | null; +} + +export interface FlowTemplate { + id: string; + version: string; + name: string; + description: string; + defaultPolicy: string; + stages: StageDef[]; +} + +export interface Policy { + id: string; + version: string; + review: { + minReviewers: number; + requireHuman: boolean; + allowAgentToComment: boolean; + allowAgentToApprove: boolean; + }; + security: { + blockOnCritical: boolean; + blockOnHigh: boolean; + allowAgentToOverride: boolean; + }; + pr: { + requireDescription: boolean; + requireChecksPassing: boolean; + allowAgentToMerge: boolean; + }; +} + +export interface HarnessManifest { + version: string; + flows: Record; + gates: Record; + roles: Record; + policies: Record; +} diff --git a/packages/client/src/components/Flow/FlowView.tsx b/packages/client/src/components/Flow/FlowView.tsx index 2dde35f..6ae5582 100644 --- a/packages/client/src/components/Flow/FlowView.tsx +++ b/packages/client/src/components/Flow/FlowView.tsx @@ -43,6 +43,7 @@ export default function FlowView({ workflow, specRefreshKey, onItemMoved, onTabC pendingChecks: boolean[]; } | null>(null); const [dragStageIdx, setDragStageIdx] = useState(null); + const [humanGateStages, setHumanGateStages] = useState>(new Set()); const loadSpecs = useCallback(() => { fetch("/api/specs") @@ -62,6 +63,26 @@ export default function FlowView({ workflow, specRefreshKey, onItemMoved, onTabC useEffect(() => { setEditingWebhooks(workflow.webhooks ?? []); }, [workflow.webhooks]); + useEffect(() => { + fetch("/api/harness/templates") + .then((r) => r.json()) + .then((templates) => { + const sdlc = templates.find((t: any) => t.id === "sdlc"); + if (!sdlc) return; + const stages = new Set(); + for (const stage of sdlc.stages ?? []) { + const gate = stage.gate + ? String(stage.gate).replace(/^.*[\\/]/, "").replace(/\.ya?ml$/, "") + : null; + if (gate) { + const gateData = sdlc.gates?.find((g: any) => g.id === gate); + if (gateData?.type === "human") stages.add(stage.id); + } + } + setHumanGateStages(stages); + }) + .catch(() => {}); + }, [workflow.stages]); const selectedItem = selectedItemId ? workflow.items.find((it) => it.id === selectedItemId) @@ -81,7 +102,9 @@ export default function FlowView({ workflow, specRefreshKey, onItemMoved, onTabC function allowMoveToStage(item: Workflow["items"][0], targetStageId: string): boolean { const srcStage = workflow.stages.find((s) => s.id === item.stage); if (!srcStage || !srcStage.allow || srcStage.allow.length === 0) return true; - return srcStage.allow.includes(targetStageId); + if (!srcStage.allow.includes(targetStageId)) return false; + if (humanGateStages.has(targetStageId)) return false; + return true; } function getValidateChecks(item: Workflow["items"][0]): string[] { @@ -750,6 +773,7 @@ export default function FlowView({ workflow, specRefreshKey, onItemMoved, onTabC onDropItem={handleDropItem} allowMoveToStage={allowMoveToStage} specRefreshKey={specRefreshKey} + onAddItem={() => setShowAddDialog(true)} /> )} diff --git a/packages/client/src/components/Kanban/KanbanView.tsx b/packages/client/src/components/Kanban/KanbanView.tsx index 6edf3d5..1cd54c7 100644 --- a/packages/client/src/components/Kanban/KanbanView.tsx +++ b/packages/client/src/components/Kanban/KanbanView.tsx @@ -14,6 +14,7 @@ interface Props { onDropItem?: (itemId: string, targetStageId: string) => void; allowMoveToStage?: (item: Workflow["items"][0], targetStageId: string) => boolean; specRefreshKey?: number; + onAddItem?: () => void; } function daysSince(dateStr: string): number { @@ -69,6 +70,7 @@ export default function KanbanView({ onDropItem, allowMoveToStage, specRefreshKey = 0, + onAddItem, }: Props) { const [dragOver, setDragOver] = useState(null); const [draggingId, setDraggingId] = useState(null); @@ -166,8 +168,25 @@ export default function KanbanView({ } return ( -
- {workflow.stages.map((stage) => { + workflow.items.length === 0 ? ( +
+
+

+ Nenhum item no board. +

+

+ Adicione seu primeiro item via letra flow backlog add <desc> +

+ {onAddItem && ( + + )} +
+
+ ) : ( +
+ {workflow.stages.map((stage) => { const stageItems = workflow.items.filter((it) => it.stage === stage.id); const isOver = dragOver === stage.id; const isOverDenied = From 8b6a501d9c79f31184466b15c927b9b286573d3c Mon Sep 17 00:00:00 2001 From: rnascimento10 Date: Mon, 22 Jun 2026 12:23:42 -0300 Subject: [PATCH 04/54] feat(kanban): empty state + gate enforcement visual MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - KanbanView: empty state com CTA 'Add Item' quando sem itens - KanbanView: Button import + prop onAddItem - FlowView: integração do CTA com showAddDialog existente - Build limpo após correção de JSX e imports --- packages/client/src/components/Kanban/KanbanView.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/client/src/components/Kanban/KanbanView.tsx b/packages/client/src/components/Kanban/KanbanView.tsx index 1cd54c7..478ea2b 100644 --- a/packages/client/src/components/Kanban/KanbanView.tsx +++ b/packages/client/src/components/Kanban/KanbanView.tsx @@ -1,7 +1,7 @@ import { useCallback, useEffect, useRef, useState } from "react"; import type { ResolvedSpec, Workflow } from "@letra/types"; import { Card, CardContent } from "@letra/ui"; -import { Icon, Progress } from "@letra/ui"; +import { Icon, Progress, Button } from "@letra/ui"; import { cn } from "../../lib/utils"; import { MarchingBorder } from "./MarchingBorder"; import type { Item } from "@letra/types"; @@ -186,7 +186,7 @@ export default function KanbanView({
) : (
- {workflow.stages.map((stage) => { + {workflow.stages.map((stage) => { const stageItems = workflow.items.filter((it) => it.stage === stage.id); const isOver = dragOver === stage.id; const isOverDenied = @@ -430,5 +430,6 @@ export default function KanbanView({ ); })}
- ); + ) + ); } From df7057eb19f5cd76e4c3124a0cc78d752b129f00 Mon Sep 17 00:00:00 2001 From: rnascimento10 Date: Thu, 2 Jul 2026 12:31:17 -0300 Subject: [PATCH 05/54] feat: harness viewer cascade (L1-L4) + sheet popover bg fix + audit log improvements --- .agents/skills/shadcn/SKILL.md | 267 + .agents/skills/shadcn/agents/openai.yml | 5 + .agents/skills/shadcn/assets/shadcn-small.png | Bin 0 -> 1049 bytes .agents/skills/shadcn/assets/shadcn.png | Bin 0 -> 3852 bytes .agents/skills/shadcn/cli.md | 290 + .agents/skills/shadcn/customization.md | 209 + .agents/skills/shadcn/evals/evals.json | 47 + .agents/skills/shadcn/mcp.md | 105 + .agents/skills/shadcn/registry.md | 277 + .agents/skills/shadcn/rules/base-vs-radix.md | 306 + .agents/skills/shadcn/rules/composition.md | 195 + .agents/skills/shadcn/rules/forms.md | 192 + .agents/skills/shadcn/rules/icons.md | 101 + .agents/skills/shadcn/rules/styling.md | 162 + .cursorrules | 44 +- .github/copilot-instructions.md | 136 +- .hermes/instructions.md | 132 +- .letra/constitution.md | 143 +- .letra/constraints.md | 18 +- .letra/context.md | 90 +- .../flow-engine-architecture-synthesis.md | 1241 ++ .../decisions/product-rebrand-architecture.md | 839 ++ .letra/decisions/workspace-centric-model.md | 294 + .../workspace-runtime-governance-model.md | 171 + .letra/docs/operational-rules.md | 197 + .letra/docs/tdd-workflow.md | 294 + .letra/focus.md | 8 +- .letra/harness/v0.1.0/flows/sdlc.yaml | 122 + .../harness/v0.1.0/gates/all-acs-passing.yaml | 8 + .../harness/v0.1.0/gates/has-spec-file.yaml | 7 + .../v0.1.0/gates/human-approved-code.yaml | 8 + .../v0.1.0/gates/human-approved-spec.yaml | 8 + .../harness/v0.1.0/gates/security-clear.yaml | 8 + .../harness/v0.1.0/policies/sdlc-default.json | 20 + .letra/harness/v0.1.0/roles/analyst.yaml | 9 + .letra/harness/v0.1.0/roles/builder.yaml | 9 + .letra/harness/v0.1.0/roles/pr-bot.yaml | 8 + .letra/harness/v0.1.0/roles/reviewer.yaml | 10 + .letra/harness/v0.1.0/roles/security.yaml | 8 + .letra/harness/v0.1.1/flows/flow-main.yaml | 61 + .letra/harness/v0.1.1/flows/sdlc.yaml | 122 + .../harness/v0.1.1/gates/all-acs-passing.yaml | 8 + .../harness/v0.1.1/gates/has-spec-file.yaml | 7 + .../v0.1.1/gates/human-approved-code.yaml | 8 + .../v0.1.1/gates/human-approved-spec.yaml | 8 + .../harness/v0.1.1/gates/security-clear.yaml | 8 + .../harness/v0.1.1/policies/sdlc-default.json | 20 + .letra/harness/v0.1.1/roles/analyst.yaml | 9 + .letra/harness/v0.1.1/roles/builder.yaml | 9 + .letra/harness/v0.1.1/roles/pr-bot.yaml | 8 + .letra/harness/v0.1.1/roles/reviewer.yaml | 10 + .letra/harness/v0.1.1/roles/security.yaml | 8 + .letra/harness/v0.1.2/flows/flow-main.yaml | 86 + .letra/harness/v0.1.2/flows/sdlc.yaml | 122 + .../harness/v0.1.2/gates/all-acs-passing.yaml | 8 + .../harness/v0.1.2/gates/has-spec-file.yaml | 7 + .../v0.1.2/gates/human-approved-code.yaml | 8 + .../v0.1.2/gates/human-approved-spec.yaml | 8 + .../harness/v0.1.2/gates/security-clear.yaml | 8 + .../harness/v0.1.2/policies/sdlc-default.json | 20 + .letra/harness/v0.1.2/roles/analyst.yaml | 9 + .letra/harness/v0.1.2/roles/builder.yaml | 9 + .letra/harness/v0.1.2/roles/pr-bot.yaml | 8 + .letra/harness/v0.1.2/roles/reviewer.yaml | 10 + .letra/harness/v0.1.2/roles/security.yaml | 8 + .letra/phase-prompt.md | 7 + .letra/reports/amber-rebrand-accessibility.md | 48 + .letra/reports/shadcn-first-accessibility.md | 79 + .../1781634095715_dead-icons_file-text.json | 1 - ...1781634095720_dead-icons_chevron-down.json | 1 - .../1781634095723_dead-icons_settings.json | 1 - .../1781634095726_dead-icons_info.json | 1 - .../1781634095742_dead-icons_specs.json | 1 - ...1781634095744_dead-icons_alert-circle.json | 1 - .../1781634095748_dead-icons_star.json | 1 - ...1781634095751_dead-icons_check-circle.json | 1 - .../1781634095754_dead-icons_check.json | 1 - .../1781634095756_dead-icons_grid.json | 1 - .../1781634095758_dead-icons_plus.json | 1 - ...81634095761_dead-icons_alert-triangle.json | 1 - ...5783_ac-stale_flow-mvp_flow-visualize.json | 1 - ..._ac-stale_situation-room_letra-sitrep.json | 1 + ...96954_ac-stale_stage-discovery_--auto.json | 1 + ..._ac-stale_workspace-pulse_letra-pulse.json | 1 + ...782127039508_missing-spec-link_item-1.json | 1 + ...782127039518_missing-spec-link_item-2.json | 1 + ...782127039527_missing-spec-link_item-3.json | 1 + ...899_ac-stale_flow-edit-diff_flow-diff.json | 1 + ...9903_ac-stale_flow-mvp_flow-visualize.json | 1 + ...8419906_ac-stale_flow-mvp_flow-export.json | 1 + ..._ac-stale_situation-room_letra-sitrep.json | 1 + ...19916_ac-stale_stage-discovery_--auto.json | 1 + ..._ac-stale_workspace-pulse_letra-pulse.json | 1 + .../1782304017668_harness-stale_all.json | 1 + .letra/specs/activity-context/acceptance.md | 14 + .../architecture-current-state.md | 859 ++ .letra/specs/activity-context/spec.md | 49 + .letra/specs/adapter-alerts/acceptance.md | 12 + .letra/specs/adapter-alerts/spec.md | 108 +- .../specs/adapter-claude-code/acceptance.md | 4 +- .letra/specs/adapter-claude-code/spec.md | 4 +- .letra/specs/adapter-codex-cli/acceptance.md | 2 +- .letra/specs/adapter-codex-cli/spec.md | 3 +- .letra/specs/adapter-cursor/acceptance.md | 3 +- .letra/specs/adapter-cursor/spec.md | 4 +- .letra/specs/adapter-hermes/acceptance.md | 42 +- .letra/specs/adapter-hermes/spec.md | 4 +- .letra/specs/adapter-opencode/acceptance.md | 4 +- .letra/specs/adapter-opencode/spec.md | 4 +- .letra/specs/adapter-vscode/acceptance.md | 3 +- .letra/specs/adapter-vscode/spec.md | 4 +- .letra/specs/adapter-windsurf/acceptance.md | 3 +- .letra/specs/adapter-windsurf/spec.md | 4 +- .../acceptance.md | 7 + .../agent-direction-improvements/spec.md | 30 + .letra/specs/amber-rebrand/acceptance.md | 7 + .letra/specs/amber-rebrand/spec.md | 56 + .../specs/architecture-agnostic/acceptance.md | 22 + .letra/specs/architecture-agnostic/spec.md | 382 +- .../architecture-convergence/acceptance.md | 14 + .../phase-0-design.md | 400 + .../phase-1-design.md | 272 + .../phase-2-design.md | 186 + .../phase-3-design.md | 294 + .../phase-4-design.md | 192 + .../specs/architecture-convergence/roadmap.md | 174 + .letra/specs/architecture-convergence/spec.md | 67 + .../architecture-convergence/write-policy.md | 189 + .letra/specs/ci-cd-pipeline/acceptance.md | 6 +- .letra/specs/ci-cd-pipeline/spec.md | 4 +- .letra/specs/cleanup-phases-example/spec.md | 28 + .letra/specs/cli-skeleton/acceptance.md | 4 +- .letra/specs/cli-skeleton/spec.md | 4 +- .letra/specs/command-menu/acceptance.md | 11 + .letra/specs/command-menu/spec.md | 97 +- .letra/specs/completion-signal/acceptance.md | 12 + .letra/specs/completion-signal/spec.md | 115 +- .letra/specs/context-scope/acceptance.md | 7 + .letra/specs/context-scope/spec.md | 26 + .letra/specs/context-sync/acceptance.md | 12 + .letra/specs/context-sync/spec.md | 52 +- .letra/specs/cross-spec-dep/acceptance.md | 6 + .letra/specs/cross-spec-dep/spec.md | 22 +- .letra/specs/decision-new/acceptance.md | 2 +- .letra/specs/decision-new/spec.md | 4 +- .letra/specs/decision-protocol/acceptance.md | 10 + .letra/specs/decision-protocol/spec.md | 129 +- .letra/specs/design-system/acceptance.md | 11 + .letra/specs/design-system/spec.md | 230 +- .../specs/diagnostics-adapter/acceptance.md | 11 + .letra/specs/diagnostics-adapter/spec.md | 51 +- .letra/specs/diagnostics-hub/acceptance.md | 6 + .letra/specs/diagnostics-hub/spec.md | 22 + .letra/specs/diagnostics-state/acceptance.md | 12 + .letra/specs/diagnostics-state/spec.md | 38 +- .letra/specs/diagnostics-ui/acceptance.md | 13 + .letra/specs/diagnostics-ui/spec.md | 49 +- .letra/specs/diagnostics-web/acceptance.md | 10 + .letra/specs/diagnostics-web/spec.md | 39 +- .letra/specs/document-editor/acceptance.md | 7 + .letra/specs/document-editor/spec.md | 30 + .letra/specs/feature-cycle/acceptance.md | 14 + .letra/specs/feature-cycle/spec.md | 293 +- .letra/specs/fix-dead-icons/acceptance.md | 6 + .letra/specs/fix-dead-icons/spec.md | 14 +- .letra/specs/fix-ttl/acceptance.md | 5 + .letra/specs/fix-ttl/spec.md | 14 +- .letra/specs/flow-designer/acceptance.md | 10 + .letra/specs/flow-designer/spec.md | 18 +- .letra/specs/flow-edit-diff/acceptance.md | 14 +- .letra/specs/flow-edit-diff/spec.md | 9 +- .letra/specs/flow-export-import/acceptance.md | 13 +- .letra/specs/flow-export-import/spec.md | 9 +- .letra/specs/flow-hub-redesign/acceptance.md | 10 + .letra/specs/flow-hub-redesign/spec.md | 4 +- .letra/specs/flow-import-issues/acceptance.md | 12 + .letra/specs/flow-import-issues/spec.md | 45 +- .letra/specs/flow-mvp/acceptance.md | 13 + .letra/specs/flow-mvp/spec.md | 2 + .letra/specs/flow-phases-engine/spec.md | 33 + .letra/specs/flow-serve/acceptance.md | 14 + .letra/specs/flow-serve/spec.md | 29 +- .letra/specs/flow-visualize/acceptance.md | 15 +- .letra/specs/flow-visualize/spec.md | 9 +- .letra/specs/fullscreen-modal/acceptance.md | 7 + .letra/specs/fullscreen-modal/spec.md | 26 + .letra/specs/handoff-rules/acceptance.md | 14 + .letra/specs/handoff-rules/spec.md | 206 +- .../specs/harness-centralized/acceptance.md | 3 + .letra/specs/harness-centralized/spec.md | 43 + .letra/specs/harness-layer/acceptance.md | 44 +- .letra/specs/harness-layer/spec.md | 146 +- .../harness-loader-implementation/spec.md | 28 + .../specs/harness-loop-realtime/acceptance.md | 93 +- .letra/specs/harness-loop-realtime/spec.md | 46 +- .letra/specs/harness-meta-test/acceptance.md | 8 + .letra/specs/harness-meta-test/spec.md | 23 +- .../specs/harness-optimization/acceptance.md | 82 + .letra/specs/harness-optimization/spec.md | 481 +- .letra/specs/harness-viewer/acceptance.md | 6 + .letra/specs/harness-viewer/spec.md | 22 + .letra/specs/health-record/acceptance.md | 19 + .letra/specs/health-record/spec.md | 111 +- .../specs/incremental-progress/acceptance.md | 10 + .letra/specs/incremental-progress/spec.md | 128 +- .letra/specs/init-interactive/acceptance.md | 2 +- .letra/specs/init-interactive/spec.md | 4 +- .letra/specs/item-26-home-rica/acceptance.md | 10 + .letra/specs/item-26-home-rica/spec.md | 16 +- .letra/specs/item-7-automacoes/acceptance.md | 9 + .letra/specs/item-7-automacoes/spec.md | 18 +- .letra/specs/item-claim/acceptance.md | 96 + .letra/specs/item-claim/spec.md | 37 +- .letra/specs/kanban-sticky/acceptance.md | 7 + .letra/specs/kanban-sticky/spec.md | 25 + .letra/specs/kanban-ux/acceptance.md | 97 + .letra/specs/kanban-ux/spec.md | 58 +- .letra/specs/letra-init-command/spec.md | 28 + .letra/specs/log-search/acceptance.md | 7 + .letra/specs/log-search/spec.md | 28 + .letra/specs/logo-swap/acceptance.md | 7 + .letra/specs/logo-swap/spec.md | 24 + .letra/specs/loop-gaps/acceptance.md | 33 + .letra/specs/loop-gaps/spec.md | 16 +- .letra/specs/markdown-view/acceptance.md | 3 + .letra/specs/markdown-view/spec.md | 57 +- .letra/specs/menu-consolidation/acceptance.md | 10 + .letra/specs/menu-consolidation/spec.md | 35 + .letra/specs/multi-folder/spec.md | 45 + .letra/specs/npm-agnostic/acceptance.md | 54 + .letra/specs/npm-agnostic/spec.md | 94 +- .letra/specs/operational-audit/acceptance.md | 12 + .letra/specs/operational-audit/design.md | 160 + .letra/specs/operational-audit/spec.md | 53 + .letra/specs/phase-action-executor/spec.md | 36 + .letra/specs/phase-auto-loop/spec.md | 36 + .letra/specs/phase-engine-tests/spec.md | 39 + .letra/specs/phase-ui-indicator/spec.md | 34 + .letra/specs/pitagoras-ux/acceptance.md | 8 + .letra/specs/pitagoras-ux/spec.md | 57 + .letra/specs/product-metrics/acceptance.md | 7 + .letra/specs/product-metrics/spec.md | 26 + .letra/specs/product-rebrand/acceptance.md | 45 + .letra/specs/product-rebrand/planning.md | 186 + .letra/specs/product-rebrand/spec.md | 76 + .letra/specs/ruler-header/acceptance.md | 23 +- .letra/specs/ruler-header/spec.md | 108 +- .../specs/sdlc-template-gates/acceptance.md | 7 + .letra/specs/sdlc-template-gates/spec.md | 31 + .../specs/self-diagnosis-core/acceptance.md | 14 + .letra/specs/self-diagnosis-core/spec.md | 35 +- .letra/specs/session-focus/acceptance.md | 4 +- .letra/specs/session-focus/spec.md | 4 +- .letra/specs/session-kickoff/acceptance.md | 11 + .letra/specs/session-kickoff/spec.md | 96 +- .letra/specs/session-log/acceptance.md | 17 + .letra/specs/session-log/spec.md | 161 +- .letra/specs/setup-flow/acceptance.md | 7 + .letra/specs/setup-flow/spec.md | 33 + .letra/specs/shadcn-first/design.md | 184 + .letra/specs/shadcn-first/spec.md | 71 + .letra/specs/situation-room/acceptance.md | 15 + .letra/specs/situation-room/spec.md | 129 +- .letra/specs/snapshot-bloat/acceptance.md | 6 + .letra/specs/snapshot-bloat/spec.md | 24 +- .letra/specs/spec-code-drift/acceptance.md | 6 + .letra/specs/spec-code-drift/spec.md | 22 +- .letra/specs/spec-frontmatter/acceptance.md | 7 + .letra/specs/spec-frontmatter/spec.md | 26 + .../specs/spec-templates-domain/acceptance.md | 2 +- .letra/specs/spec-templates-domain/spec.md | 4 +- .letra/specs/stage-discovery/acceptance.md | 12 + .letra/specs/stage-discovery/spec.md | 135 +- .letra/specs/tool-adapters/acceptance.md | 6 + .letra/specs/tool-adapters/spec.md | 22 + .letra/specs/tui-onboarding/acceptance.md | 7 + .letra/specs/tui-onboarding/spec.md | 30 + .letra/specs/validate-ac-signal/acceptance.md | 15 +- .letra/specs/validate-ac-signal/spec.md | 72 +- .letra/specs/validate-config/acceptance.md | 2 +- .letra/specs/validate-config/spec.md | 4 +- .letra/specs/validate-conflict/acceptance.md | 4 +- .letra/specs/validate-conflict/spec.md | 4 +- .letra/specs/validate-format-pr/acceptance.md | 2 +- .letra/specs/validate-format-pr/spec.md | 4 +- .../specs/validate-intelligence/acceptance.md | 4 +- .letra/specs/validate-intelligence/spec.md | 4 +- .letra/specs/validate-watch/acceptance.md | 2 +- .letra/specs/validate-watch/spec.md | 4 +- .../validation-consolidation/acceptance.md | 33 +- .letra/specs/validation-consolidation/spec.md | 89 +- .letra/specs/web-health-alerts/acceptance.md | 13 + .letra/specs/web-health-alerts/spec.md | 64 +- .letra/specs/web-sync-button/acceptance.md | 12 + .letra/specs/web-sync-button/spec.md | 67 +- .../specs/workspace-discovery/acceptance.md | 6 + .letra/specs/workspace-discovery/spec.md | 29 + .letra/specs/workspace-gate/acceptance.md | 7 + .letra/specs/workspace-gate/spec.md | 39 + .letra/specs/workspace-pulse/acceptance.md | 19 + .letra/specs/workspace-pulse/spec.md | 146 +- .../acceptance.md | 11 + .../workspace-runtime-governance/design.md | 492 + .../pglite-schema.md | 482 + .../phase-1-implementation-plan.md | 505 + .../workspace-runtime-governance/spec.md | 69 + .../write-gateway.md | 436 + .letra/specs/write-sync/acceptance.md | 56 + .letra/specs/write-sync/spec.md | 82 +- .letra/workflow.json | 620 +- .letra/workflow.json.bak.2 | 33 + .letra/workflows/wf_letra-100.json | 32 + .vscode/launch.json | 14 + .windsurfrules | 120 +- AGENTS.md | 136 +- CLAUDE.md | 123 +- MANUAL_TEST_ROTEIRO.md | 171 + README.md | 6 +- RELEASE_NOTES_v0.4.0.md | 4 +- brand/brand-book.md | 125 + brand/colors.md | 66 + brand/iconography.md | 41 + brand/logo-guidelines.md | 106 + brand/logos/svg/app-icon.svg | 28 + brand/logos/svg/favicon.svg | 16 + brand/logos/svg/letra-dark.svg | 31 + brand/logos/svg/letra-horizontal.svg | 20 + brand/logos/svg/letra-light.svg | 21 + brand/logos/svg/letra-monochrome.svg | 10 + brand/logos/svg/letra-symbol.svg | 27 + brand/logos/svg/letra-vertical.svg | 20 + brand/motion.md | 49 + brand/typography.md | 50 + brand/voice.md | 59 + debug-gate.ts | 7 + debug-harness-tsx.ts | 17 + debug-harness.ts | 14 + debug-hermes.ts | 21 + debug-move.ts | 10 + debug-move2.ts | 30 + debug-server.ts | 23 + debug-workflow.ts | 5 + debug-write-hermes.ts | 10 + design-system/audit.md | 175 + design-system/journeys.md | 289 + design-system/screens/context-view.md | 23 + design-system/screens/dashboard-view.md | 28 + .../screens/diagnostics-indicator.md | 26 + design-system/screens/execution-view.md | 32 + design-system/screens/flow-view.md | 32 + design-system/screens/header-view.md | 29 + design-system/screens/home-view.md | 26 + design-system/screens/item-detail-modal.md | 26 + design-system/screens/kanban-view.md | 24 + design-system/screens/logs-view.md | 31 + design-system/screens/nav-tabs.md | 25 + design-system/screens/setup-wizard.md | 32 + design-system/screens/sidebar-view.md | 35 + design-system/screens/specs-view.md | 25 + design-system/screens/workspaces-view.md | 32 + design-system/states.md | 135 + design-system/tokens/design-tokens.css | 58 + design-system/tokens/scale.md | 175 + design-system/tokens/semantic-map.md | 193 + design-system/tokens/shadcn-theme.css | 44 + design-system/tokens/tailwind-preset.js | 46 + e2e/smoke.spec.ts | 26 + opencode.json | 14 + package-lock.json | 10438 ++++++++++++---- package.json | 2 + packages/cli/.letra/health-record.json | 5 + packages/cli/package.json | 5 +- .../cli/src/activity-context/builder.test.ts | 573 + packages/cli/src/activity-context/builder.ts | 139 + .../cli/src/activity-context/compatibility.ts | 216 + packages/cli/src/activity-context/index.ts | 4 + .../cli/src/activity-context/intent.test.ts | 110 + packages/cli/src/activity-context/intent.ts | 250 + packages/cli/src/activity-context/sources.ts | 138 + packages/cli/src/activity-context/types.ts | 50 + packages/cli/src/adapters/builder.ts | 89 +- packages/cli/src/adapters/formatters.ts | 39 + packages/cli/src/adapters/generate.test.ts | 195 + packages/cli/src/adapters/hermes.ts | 21 +- packages/cli/src/adapters/types.ts | 32 + .../cli/src/commands/activity-context.test.ts | 93 + packages/cli/src/commands/activity-context.ts | 85 + packages/cli/src/commands/decision.ts | 4 +- packages/cli/src/commands/flow-autopilot.ts | 37 + packages/cli/src/commands/flow-bind.test.ts | 109 + packages/cli/src/commands/flow-bind.ts | 72 + packages/cli/src/commands/flow-board.test.ts | 29 +- packages/cli/src/commands/flow-board.ts | 49 +- .../commands/flow-init.template-stage.test.ts | 40 + packages/cli/src/commands/flow-init.test.ts | 13 + packages/cli/src/commands/flow-init.ts | 151 +- packages/cli/src/commands/flow-move.test.ts | 66 + packages/cli/src/commands/flow-move.ts | 70 +- packages/cli/src/commands/flow-phase-run.ts | 67 + packages/cli/src/commands/flow-phases.ts | 76 + .../commands/flow-serve.integration.test.ts | 151 + packages/cli/src/commands/flow-serve.ts | 1387 +- packages/cli/src/commands/flow.ts | 50 +- packages/cli/src/commands/focus.ts | 10 +- packages/cli/src/commands/init.ts | 42 +- packages/cli/src/commands/pulse.ts | 57 +- packages/cli/src/commands/push.ts | 95 + packages/cli/src/commands/spec.ts | 102 + packages/cli/src/commands/status.ts | 50 + .../src/diagnostics/detectors/ac-false-pos.ts | 1 + .../cli/src/diagnostics/detectors/ac-stale.ts | 1 + .../diagnostics/detectors/spec-code-drift.ts | 36 +- .../src/diagnostics/detectors/stage-drift.ts | 5 +- packages/cli/src/diagnostics/engine.ts | 7 +- .../cli/src/diagnostics/shared/file-search.ts | 23 +- .../cli/src/flow-definition/resolve.test.ts | 259 + packages/cli/src/flow-definition/resolve.ts | 331 + packages/cli/src/flow-definition/types.ts | 22 + .../src/flow-serve/automation-runtime.test.ts | 41 + .../cli/src/flow-serve/automation-runtime.ts | 127 + packages/cli/src/flow-serve/client-assets.ts | 57 + packages/cli/src/flow-serve/context.test.ts | 26 + packages/cli/src/flow-serve/context.ts | 54 + packages/cli/src/flow-serve/diagnostics.ts | 37 + packages/cli/src/flow-serve/events.ts | 58 + packages/cli/src/flow-serve/http.test.ts | 55 + packages/cli/src/flow-serve/http.ts | 63 + .../src/flow-serve/request-context.test.ts | 26 + .../cli/src/flow-serve/request-context.ts | 34 + packages/cli/src/flow-serve/router.test.ts | 26 + packages/cli/src/flow-serve/router.ts | 18 + .../src/flow-serve/routes/context-routes.ts | 190 + .../routes/diagnostics-routes.test.ts | 47 + .../flow-serve/routes/diagnostics-routes.ts | 161 + .../src/flow-serve/routes/item-routes.test.ts | 109 + .../cli/src/flow-serve/routes/item-routes.ts | 280 + .../src/flow-serve/routes/spec-routes.test.ts | 80 + .../cli/src/flow-serve/routes/spec-routes.ts | 131 + .../src/flow-serve/routes/workflow-routes.ts | 125 + .../src/flow-serve/routes/workspace-routes.ts | 159 + packages/cli/src/flow-serve/specs.ts | 71 + .../cli/src/flow-serve/system-actions.test.ts | 50 + packages/cli/src/flow-serve/system-actions.ts | 106 + packages/cli/src/flow-serve/workspace.ts | 142 + .../cli/src/harness/activity-hints.test.ts | 20 + .../harness/default/v0.1.0/flows/sdlc.yaml | 179 + .../default/v0.1.0/gates/all-acs-passing.yaml | 8 + .../default/v0.1.0/gates/has-spec-file.yaml | 7 + .../v0.1.0/gates/human-approved-code.yaml | 8 + .../v0.1.0/gates/human-approved-spec.yaml | 8 + .../default/v0.1.0/gates/security-clear.yaml | 8 + .../default/v0.1.0/policies/sdlc-default.json | 20 + .../harness/default/v0.1.0/roles/analyst.yaml | 9 + .../harness/default/v0.1.0/roles/builder.yaml | 9 + .../harness/default/v0.1.0/roles/pr-bot.yaml | 8 + .../default/v0.1.0/roles/reviewer.yaml | 10 + .../default/v0.1.0/roles/security.yaml | 8 + .../default/v0.1.1/flows/flow-main.yaml | 61 + .../harness/default/v0.1.1/flows/sdlc.yaml | 179 + .../default/v0.1.1/gates/all-acs-passing.yaml | 8 + .../default/v0.1.1/gates/has-spec-file.yaml | 7 + .../v0.1.1/gates/human-approved-code.yaml | 8 + .../v0.1.1/gates/human-approved-spec.yaml | 8 + .../default/v0.1.1/gates/security-clear.yaml | 8 + .../default/v0.1.1/policies/sdlc-default.json | 20 + .../harness/default/v0.1.1/roles/analyst.yaml | 9 + .../harness/default/v0.1.1/roles/builder.yaml | 9 + .../harness/default/v0.1.1/roles/pr-bot.yaml | 8 + .../default/v0.1.1/roles/reviewer.yaml | 10 + .../default/v0.1.1/roles/security.yaml | 8 + .../default/v0.1.2/flows/flow-main.yaml | 86 + .../harness/default/v0.1.2/flows/sdlc.yaml | 179 + .../default/v0.1.2/gates/all-acs-passing.yaml | 8 + .../default/v0.1.2/gates/has-spec-file.yaml | 7 + .../v0.1.2/gates/human-approved-code.yaml | 8 + .../v0.1.2/gates/human-approved-spec.yaml | 8 + .../default/v0.1.2/gates/security-clear.yaml | 8 + .../default/v0.1.2/policies/sdlc-default.json | 20 + .../harness/default/v0.1.2/roles/analyst.yaml | 9 + .../harness/default/v0.1.2/roles/builder.yaml | 9 + .../harness/default/v0.1.2/roles/pr-bot.yaml | 8 + .../default/v0.1.2/roles/reviewer.yaml | 10 + .../default/v0.1.2/roles/security.yaml | 8 + packages/cli/src/harness/loader.ts | 219 +- packages/cli/src/harness/parse.ts | 2 +- packages/cli/src/harness/types.ts | 82 + packages/cli/src/index.ts | 73 +- .../src/operational-audit/normalizer.test.ts | 206 + .../cli/src/operational-audit/normalizer.ts | 181 + packages/cli/src/phases/autopilot.test.ts | 273 + packages/cli/src/phases/autopilot.ts | 104 + packages/cli/src/phases/engine.test.ts | 326 + packages/cli/src/phases/engine.ts | 128 + packages/cli/src/phases/runner.test.ts | 165 + packages/cli/src/phases/runner.ts | 120 + packages/cli/src/session-log.test.ts | 79 +- packages/cli/src/session-log.ts | 100 +- packages/cli/src/tui/init-wizard.tsx | 202 + packages/cli/src/workspace/index.ts | 175 + packages/cli/src/workspace/resolver.ts | 138 + packages/client/components.json | 25 + packages/client/index.html | 11 +- packages/client/package.json | 26 +- packages/client/public/favicon.svg | 18 +- packages/client/src/App.tsx | 576 +- .../src/components/Context/ContextView.tsx | 60 +- .../components/Dashboard/DashboardView.tsx | 3 +- .../src/components/Dashboard/GateCard.tsx | 150 + .../src/components/Dashboard/MetricCards.tsx | 52 + .../components/Dashboard/PipelineStatus.tsx | 134 + .../Diagnostics/DiagnosticsIndicator.tsx | 144 +- .../components/Diagnostics/UndoHistory.tsx | 146 +- .../components/Execution/AgentDetail.test.tsx | 65 + .../src/components/Execution/AgentDetail.tsx | 189 + .../components/Execution/ExecutionView.tsx | 272 + .../src/components/Flow/ActivityTimeline.tsx | 168 + .../Flow/FlowDefinitionWarnings.test.tsx | 41 + .../Flow/FlowDefinitionWarnings.tsx | 36 + .../client/src/components/Flow/FlowView.tsx | 807 +- .../src/components/Flow/ItemDetailModal.tsx | 410 +- .../src/components/Flow/KanbanBoard.tsx | 346 + .../src/components/Harness/HarnessViewer.tsx | 368 + .../client/src/components/Header/Header.tsx | 210 +- .../src/components/Header/LogoDiamond.tsx | 33 + .../client/src/components/Home/HomeView.tsx | 1393 ++- .../src/components/Kanban/KanbanView.tsx | 107 +- .../src/components/Kanban/MarchingBorder.tsx | 2 +- .../src/components/Kanban/PhaseBadge.tsx | 13 + .../src/components/Logs/AuditLogView.tsx | 618 + .../client/src/components/NavTabs/NavTabs.tsx | 42 +- .../SetupWizard/InlineSetupWizard.tsx | 1035 +- .../SetupWizard/PersonalizationWizard.tsx | 18 +- .../components/SetupWizard/SetupWizard.tsx | 92 +- .../src/components/SidePanel/SidePanel.tsx | 31 +- .../client/src/components/Sidebar/Sidebar.tsx | 257 + .../client/src/components/Specs/SpecsView.tsx | 160 +- .../client/src/components/Toast/Toast.tsx | 64 - .../client/src/components/ValidatingBar.tsx | 19 + .../Workspaces/WorkspaceSetupFlow.tsx | 535 + .../components/Workspaces/WorkspacesView.tsx | 157 + .../src/components/ui/Accessibility.test.tsx | 102 + .../src/components/ui/DocumentEditor.test.tsx | 94 + .../src/components/ui/DocumentEditor.tsx | 220 + .../client/src/components/ui/accordion.tsx | 72 + packages/client/src/components/ui/badge.tsx | 44 - packages/client/src/components/ui/button.tsx | 92 +- packages/client/src/components/ui/card.tsx | 57 - .../client/src/components/ui/checkbox.tsx | 34 - .../client/src/components/ui/collapsible.tsx | 21 + packages/client/src/components/ui/dialog.tsx | 222 - packages/client/src/components/ui/icon.tsx | 127 - packages/client/src/components/ui/input.tsx | 36 +- packages/client/src/components/ui/label.tsx | 18 + .../client/src/components/ui/radio-group.tsx | 36 + .../client/src/components/ui/separator.tsx | 23 + packages/client/src/components/ui/sheet.tsx | 136 + packages/client/src/components/ui/sidebar.tsx | 723 ++ .../client/src/components/ui/skeleton.tsx | 30 +- packages/client/src/components/ui/switch.tsx | 32 + packages/client/src/components/ui/table.tsx | 114 + .../client/src/components/ui/textarea.tsx | 20 - packages/client/src/components/ui/toggle.tsx | 45 + packages/client/src/components/ui/tooltip.tsx | 66 + packages/client/src/hooks/use-mobile.ts | 19 + packages/client/src/index.css | 336 +- packages/client/src/lib/active-flow.test.ts | 160 + packages/client/src/lib/active-flow.ts | 191 + packages/client/src/lib/utils.ts | 16 +- packages/client/src/test/setup.ts | 6 + packages/client/tsconfig.json | 3 +- packages/client/vite.config.ts | 14 + packages/client/vitest.config.ts | 19 + packages/types/src/index.ts | 142 + packages/ui/package.json | 10 +- packages/ui/src/accordion.tsx | 69 + packages/ui/src/alert.tsx | 10 +- packages/ui/src/avatar.tsx | 81 +- packages/ui/src/button.tsx | 27 +- packages/ui/src/collapsible.tsx | 15 + packages/ui/src/command.tsx | 130 + packages/ui/src/dialog.tsx | 70 +- packages/ui/src/drawer.tsx | 73 + packages/ui/src/dropdown-menu.tsx | 158 + packages/ui/src/error-banner.tsx | 69 + packages/ui/src/icon.tsx | 51 +- packages/ui/src/index.css | 118 +- packages/ui/src/index.ts | 26 +- packages/ui/src/input.tsx | 42 +- packages/ui/src/label.tsx | 17 + packages/ui/src/navigation-menu.tsx | 97 + packages/ui/src/popover.tsx | 98 + packages/ui/src/radio-group.tsx | 35 + packages/ui/src/scroll-area.tsx | 79 + packages/ui/src/select.tsx | 189 + packages/ui/src/separator.tsx | 27 + packages/ui/src/sheet.tsx | 171 + packages/ui/src/skeleton.tsx | 63 + packages/ui/src/switch.tsx | 27 + packages/ui/src/table.tsx | 80 + packages/ui/src/tabs.tsx | 4 +- packages/ui/src/textarea.tsx | 41 +- packages/ui/src/utils.ts | 7 +- playwright.config.ts | 21 + scripts/build-cli.mjs | 10 +- sketches/001-tui-dense/index.html | 306 + sketches/002-web-calm/index.html | 337 + sketches/003-dashboard-tui/index.html | 405 + sketches/004-flow-start/index.html | 171 + sketches/005-review-tui/index.html | 285 + sketches/er-onboarding-v2/index.html | 452 + sketches/er-onboarding/index.html | 414 + sketches/onboarding-linear/index.html | 356 + sketches/onboarding-web-vercel/index.html | 362 + sketches/references/README.md | 94 + ...omposer_2026-06-21_18-26-40-295_9c95ad.png | Bin 0 -> 1377431 bytes ...omposer_2026-06-21_18-28-29-021_d95096.png | Bin 0 -> 1332780 bytes sketches/sdlc-flow/index.html | 359 + skills-lock.json | 11 + software-development/letra-dev-loop/SKILL.md | 8 + test-harness.ts | 21 + test-parse.ts | 6 + 621 files changed, 49674 insertions(+), 12362 deletions(-) create mode 100644 .agents/skills/shadcn/SKILL.md create mode 100644 .agents/skills/shadcn/agents/openai.yml create mode 100644 .agents/skills/shadcn/assets/shadcn-small.png create mode 100644 .agents/skills/shadcn/assets/shadcn.png create mode 100644 .agents/skills/shadcn/cli.md create mode 100644 .agents/skills/shadcn/customization.md create mode 100644 .agents/skills/shadcn/evals/evals.json create mode 100644 .agents/skills/shadcn/mcp.md create mode 100644 .agents/skills/shadcn/registry.md create mode 100644 .agents/skills/shadcn/rules/base-vs-radix.md create mode 100644 .agents/skills/shadcn/rules/composition.md create mode 100644 .agents/skills/shadcn/rules/forms.md create mode 100644 .agents/skills/shadcn/rules/icons.md create mode 100644 .agents/skills/shadcn/rules/styling.md create mode 100644 .letra/decisions/flow-engine-architecture-synthesis.md create mode 100644 .letra/decisions/product-rebrand-architecture.md create mode 100644 .letra/decisions/workspace-centric-model.md create mode 100644 .letra/decisions/workspace-runtime-governance-model.md create mode 100644 .letra/docs/operational-rules.md create mode 100644 .letra/docs/tdd-workflow.md create mode 100644 .letra/harness/v0.1.0/flows/sdlc.yaml create mode 100644 .letra/harness/v0.1.0/gates/all-acs-passing.yaml create mode 100644 .letra/harness/v0.1.0/gates/has-spec-file.yaml create mode 100644 .letra/harness/v0.1.0/gates/human-approved-code.yaml create mode 100644 .letra/harness/v0.1.0/gates/human-approved-spec.yaml create mode 100644 .letra/harness/v0.1.0/gates/security-clear.yaml create mode 100644 .letra/harness/v0.1.0/policies/sdlc-default.json create mode 100644 .letra/harness/v0.1.0/roles/analyst.yaml create mode 100644 .letra/harness/v0.1.0/roles/builder.yaml create mode 100644 .letra/harness/v0.1.0/roles/pr-bot.yaml create mode 100644 .letra/harness/v0.1.0/roles/reviewer.yaml create mode 100644 .letra/harness/v0.1.0/roles/security.yaml create mode 100644 .letra/harness/v0.1.1/flows/flow-main.yaml create mode 100644 .letra/harness/v0.1.1/flows/sdlc.yaml create mode 100644 .letra/harness/v0.1.1/gates/all-acs-passing.yaml create mode 100644 .letra/harness/v0.1.1/gates/has-spec-file.yaml create mode 100644 .letra/harness/v0.1.1/gates/human-approved-code.yaml create mode 100644 .letra/harness/v0.1.1/gates/human-approved-spec.yaml create mode 100644 .letra/harness/v0.1.1/gates/security-clear.yaml create mode 100644 .letra/harness/v0.1.1/policies/sdlc-default.json create mode 100644 .letra/harness/v0.1.1/roles/analyst.yaml create mode 100644 .letra/harness/v0.1.1/roles/builder.yaml create mode 100644 .letra/harness/v0.1.1/roles/pr-bot.yaml create mode 100644 .letra/harness/v0.1.1/roles/reviewer.yaml create mode 100644 .letra/harness/v0.1.1/roles/security.yaml create mode 100644 .letra/harness/v0.1.2/flows/flow-main.yaml create mode 100644 .letra/harness/v0.1.2/flows/sdlc.yaml create mode 100644 .letra/harness/v0.1.2/gates/all-acs-passing.yaml create mode 100644 .letra/harness/v0.1.2/gates/has-spec-file.yaml create mode 100644 .letra/harness/v0.1.2/gates/human-approved-code.yaml create mode 100644 .letra/harness/v0.1.2/gates/human-approved-spec.yaml create mode 100644 .letra/harness/v0.1.2/gates/security-clear.yaml create mode 100644 .letra/harness/v0.1.2/policies/sdlc-default.json create mode 100644 .letra/harness/v0.1.2/roles/analyst.yaml create mode 100644 .letra/harness/v0.1.2/roles/builder.yaml create mode 100644 .letra/harness/v0.1.2/roles/pr-bot.yaml create mode 100644 .letra/harness/v0.1.2/roles/reviewer.yaml create mode 100644 .letra/harness/v0.1.2/roles/security.yaml create mode 100644 .letra/phase-prompt.md create mode 100644 .letra/reports/amber-rebrand-accessibility.md create mode 100644 .letra/reports/shadcn-first-accessibility.md delete mode 100644 .letra/snapshots/1781634095715_dead-icons_file-text.json delete mode 100644 .letra/snapshots/1781634095720_dead-icons_chevron-down.json delete mode 100644 .letra/snapshots/1781634095723_dead-icons_settings.json delete mode 100644 .letra/snapshots/1781634095726_dead-icons_info.json delete mode 100644 .letra/snapshots/1781634095742_dead-icons_specs.json delete mode 100644 .letra/snapshots/1781634095744_dead-icons_alert-circle.json delete mode 100644 .letra/snapshots/1781634095748_dead-icons_star.json delete mode 100644 .letra/snapshots/1781634095751_dead-icons_check-circle.json delete mode 100644 .letra/snapshots/1781634095754_dead-icons_check.json delete mode 100644 .letra/snapshots/1781634095756_dead-icons_grid.json delete mode 100644 .letra/snapshots/1781634095758_dead-icons_plus.json delete mode 100644 .letra/snapshots/1781634095761_dead-icons_alert-triangle.json delete mode 100644 .letra/snapshots/1781634095783_ac-stale_flow-mvp_flow-visualize.json create mode 100644 .letra/snapshots/1782118696950_ac-stale_situation-room_letra-sitrep.json create mode 100644 .letra/snapshots/1782118696954_ac-stale_stage-discovery_--auto.json create mode 100644 .letra/snapshots/1782118696957_ac-stale_workspace-pulse_letra-pulse.json create mode 100644 .letra/snapshots/1782127039508_missing-spec-link_item-1.json create mode 100644 .letra/snapshots/1782127039518_missing-spec-link_item-2.json create mode 100644 .letra/snapshots/1782127039527_missing-spec-link_item-3.json create mode 100644 .letra/snapshots/1782128419899_ac-stale_flow-edit-diff_flow-diff.json create mode 100644 .letra/snapshots/1782128419903_ac-stale_flow-mvp_flow-visualize.json create mode 100644 .letra/snapshots/1782128419906_ac-stale_flow-mvp_flow-export.json create mode 100644 .letra/snapshots/1782128419912_ac-stale_situation-room_letra-sitrep.json create mode 100644 .letra/snapshots/1782128419916_ac-stale_stage-discovery_--auto.json create mode 100644 .letra/snapshots/1782128419922_ac-stale_workspace-pulse_letra-pulse.json create mode 100644 .letra/snapshots/1782304017668_harness-stale_all.json create mode 100644 .letra/specs/activity-context/acceptance.md create mode 100644 .letra/specs/activity-context/architecture-current-state.md create mode 100644 .letra/specs/activity-context/spec.md create mode 100644 .letra/specs/adapter-alerts/acceptance.md create mode 100644 .letra/specs/agent-direction-improvements/acceptance.md create mode 100644 .letra/specs/agent-direction-improvements/spec.md create mode 100644 .letra/specs/amber-rebrand/acceptance.md create mode 100644 .letra/specs/amber-rebrand/spec.md create mode 100644 .letra/specs/architecture-agnostic/acceptance.md create mode 100644 .letra/specs/architecture-convergence/acceptance.md create mode 100644 .letra/specs/architecture-convergence/phase-0-design.md create mode 100644 .letra/specs/architecture-convergence/phase-1-design.md create mode 100644 .letra/specs/architecture-convergence/phase-2-design.md create mode 100644 .letra/specs/architecture-convergence/phase-3-design.md create mode 100644 .letra/specs/architecture-convergence/phase-4-design.md create mode 100644 .letra/specs/architecture-convergence/roadmap.md create mode 100644 .letra/specs/architecture-convergence/spec.md create mode 100644 .letra/specs/architecture-convergence/write-policy.md create mode 100644 .letra/specs/cleanup-phases-example/spec.md create mode 100644 .letra/specs/command-menu/acceptance.md create mode 100644 .letra/specs/completion-signal/acceptance.md create mode 100644 .letra/specs/context-scope/acceptance.md create mode 100644 .letra/specs/context-scope/spec.md create mode 100644 .letra/specs/context-sync/acceptance.md create mode 100644 .letra/specs/cross-spec-dep/acceptance.md create mode 100644 .letra/specs/decision-protocol/acceptance.md create mode 100644 .letra/specs/design-system/acceptance.md create mode 100644 .letra/specs/diagnostics-adapter/acceptance.md create mode 100644 .letra/specs/diagnostics-hub/acceptance.md create mode 100644 .letra/specs/diagnostics-hub/spec.md create mode 100644 .letra/specs/diagnostics-state/acceptance.md create mode 100644 .letra/specs/diagnostics-ui/acceptance.md create mode 100644 .letra/specs/diagnostics-web/acceptance.md create mode 100644 .letra/specs/document-editor/acceptance.md create mode 100644 .letra/specs/document-editor/spec.md create mode 100644 .letra/specs/feature-cycle/acceptance.md create mode 100644 .letra/specs/fix-dead-icons/acceptance.md create mode 100644 .letra/specs/fix-ttl/acceptance.md create mode 100644 .letra/specs/flow-designer/acceptance.md create mode 100644 .letra/specs/flow-hub-redesign/acceptance.md create mode 100644 .letra/specs/flow-import-issues/acceptance.md create mode 100644 .letra/specs/flow-mvp/acceptance.md create mode 100644 .letra/specs/flow-phases-engine/spec.md create mode 100644 .letra/specs/flow-serve/acceptance.md create mode 100644 .letra/specs/fullscreen-modal/acceptance.md create mode 100644 .letra/specs/fullscreen-modal/spec.md create mode 100644 .letra/specs/handoff-rules/acceptance.md create mode 100644 .letra/specs/harness-centralized/acceptance.md create mode 100644 .letra/specs/harness-centralized/spec.md create mode 100644 .letra/specs/harness-loader-implementation/spec.md create mode 100644 .letra/specs/harness-meta-test/acceptance.md create mode 100644 .letra/specs/harness-optimization/acceptance.md create mode 100644 .letra/specs/harness-viewer/acceptance.md create mode 100644 .letra/specs/harness-viewer/spec.md create mode 100644 .letra/specs/health-record/acceptance.md create mode 100644 .letra/specs/incremental-progress/acceptance.md create mode 100644 .letra/specs/item-26-home-rica/acceptance.md create mode 100644 .letra/specs/item-7-automacoes/acceptance.md create mode 100644 .letra/specs/item-claim/acceptance.md create mode 100644 .letra/specs/kanban-sticky/acceptance.md create mode 100644 .letra/specs/kanban-sticky/spec.md create mode 100644 .letra/specs/kanban-ux/acceptance.md create mode 100644 .letra/specs/letra-init-command/spec.md create mode 100644 .letra/specs/log-search/acceptance.md create mode 100644 .letra/specs/log-search/spec.md create mode 100644 .letra/specs/logo-swap/acceptance.md create mode 100644 .letra/specs/logo-swap/spec.md create mode 100644 .letra/specs/loop-gaps/acceptance.md create mode 100644 .letra/specs/markdown-view/acceptance.md create mode 100644 .letra/specs/menu-consolidation/acceptance.md create mode 100644 .letra/specs/menu-consolidation/spec.md create mode 100644 .letra/specs/multi-folder/spec.md create mode 100644 .letra/specs/npm-agnostic/acceptance.md create mode 100644 .letra/specs/operational-audit/acceptance.md create mode 100644 .letra/specs/operational-audit/design.md create mode 100644 .letra/specs/operational-audit/spec.md create mode 100644 .letra/specs/phase-action-executor/spec.md create mode 100644 .letra/specs/phase-auto-loop/spec.md create mode 100644 .letra/specs/phase-engine-tests/spec.md create mode 100644 .letra/specs/phase-ui-indicator/spec.md create mode 100644 .letra/specs/pitagoras-ux/acceptance.md create mode 100644 .letra/specs/pitagoras-ux/spec.md create mode 100644 .letra/specs/product-metrics/acceptance.md create mode 100644 .letra/specs/product-metrics/spec.md create mode 100644 .letra/specs/product-rebrand/acceptance.md create mode 100644 .letra/specs/product-rebrand/planning.md create mode 100644 .letra/specs/product-rebrand/spec.md create mode 100644 .letra/specs/sdlc-template-gates/acceptance.md create mode 100644 .letra/specs/sdlc-template-gates/spec.md create mode 100644 .letra/specs/self-diagnosis-core/acceptance.md create mode 100644 .letra/specs/session-kickoff/acceptance.md create mode 100644 .letra/specs/session-log/acceptance.md create mode 100644 .letra/specs/setup-flow/acceptance.md create mode 100644 .letra/specs/setup-flow/spec.md create mode 100644 .letra/specs/shadcn-first/design.md create mode 100644 .letra/specs/shadcn-first/spec.md create mode 100644 .letra/specs/situation-room/acceptance.md create mode 100644 .letra/specs/snapshot-bloat/acceptance.md create mode 100644 .letra/specs/spec-code-drift/acceptance.md create mode 100644 .letra/specs/spec-frontmatter/acceptance.md create mode 100644 .letra/specs/spec-frontmatter/spec.md create mode 100644 .letra/specs/stage-discovery/acceptance.md create mode 100644 .letra/specs/tool-adapters/acceptance.md create mode 100644 .letra/specs/tool-adapters/spec.md create mode 100644 .letra/specs/tui-onboarding/acceptance.md create mode 100644 .letra/specs/tui-onboarding/spec.md create mode 100644 .letra/specs/web-health-alerts/acceptance.md create mode 100644 .letra/specs/web-sync-button/acceptance.md create mode 100644 .letra/specs/workspace-discovery/acceptance.md create mode 100644 .letra/specs/workspace-discovery/spec.md create mode 100644 .letra/specs/workspace-gate/acceptance.md create mode 100644 .letra/specs/workspace-gate/spec.md create mode 100644 .letra/specs/workspace-pulse/acceptance.md create mode 100644 .letra/specs/workspace-runtime-governance/acceptance.md create mode 100644 .letra/specs/workspace-runtime-governance/design.md create mode 100644 .letra/specs/workspace-runtime-governance/pglite-schema.md create mode 100644 .letra/specs/workspace-runtime-governance/phase-1-implementation-plan.md create mode 100644 .letra/specs/workspace-runtime-governance/spec.md create mode 100644 .letra/specs/workspace-runtime-governance/write-gateway.md create mode 100644 .letra/specs/write-sync/acceptance.md create mode 100644 .letra/workflow.json.bak.2 create mode 100644 .letra/workflows/wf_letra-100.json create mode 100644 .vscode/launch.json create mode 100644 MANUAL_TEST_ROTEIRO.md create mode 100644 brand/brand-book.md create mode 100644 brand/colors.md create mode 100644 brand/iconography.md create mode 100644 brand/logo-guidelines.md create mode 100644 brand/logos/svg/app-icon.svg create mode 100644 brand/logos/svg/favicon.svg create mode 100644 brand/logos/svg/letra-dark.svg create mode 100644 brand/logos/svg/letra-horizontal.svg create mode 100644 brand/logos/svg/letra-light.svg create mode 100644 brand/logos/svg/letra-monochrome.svg create mode 100644 brand/logos/svg/letra-symbol.svg create mode 100644 brand/logos/svg/letra-vertical.svg create mode 100644 brand/motion.md create mode 100644 brand/typography.md create mode 100644 brand/voice.md create mode 100644 debug-gate.ts create mode 100644 debug-harness-tsx.ts create mode 100644 debug-harness.ts create mode 100644 debug-hermes.ts create mode 100644 debug-move.ts create mode 100644 debug-move2.ts create mode 100644 debug-server.ts create mode 100644 debug-workflow.ts create mode 100644 debug-write-hermes.ts create mode 100644 design-system/audit.md create mode 100644 design-system/journeys.md create mode 100644 design-system/screens/context-view.md create mode 100644 design-system/screens/dashboard-view.md create mode 100644 design-system/screens/diagnostics-indicator.md create mode 100644 design-system/screens/execution-view.md create mode 100644 design-system/screens/flow-view.md create mode 100644 design-system/screens/header-view.md create mode 100644 design-system/screens/home-view.md create mode 100644 design-system/screens/item-detail-modal.md create mode 100644 design-system/screens/kanban-view.md create mode 100644 design-system/screens/logs-view.md create mode 100644 design-system/screens/nav-tabs.md create mode 100644 design-system/screens/setup-wizard.md create mode 100644 design-system/screens/sidebar-view.md create mode 100644 design-system/screens/specs-view.md create mode 100644 design-system/screens/workspaces-view.md create mode 100644 design-system/states.md create mode 100644 design-system/tokens/design-tokens.css create mode 100644 design-system/tokens/scale.md create mode 100644 design-system/tokens/semantic-map.md create mode 100644 design-system/tokens/shadcn-theme.css create mode 100644 design-system/tokens/tailwind-preset.js create mode 100644 e2e/smoke.spec.ts create mode 100644 opencode.json create mode 100644 packages/cli/.letra/health-record.json create mode 100644 packages/cli/src/activity-context/builder.test.ts create mode 100644 packages/cli/src/activity-context/builder.ts create mode 100644 packages/cli/src/activity-context/compatibility.ts create mode 100644 packages/cli/src/activity-context/index.ts create mode 100644 packages/cli/src/activity-context/intent.test.ts create mode 100644 packages/cli/src/activity-context/intent.ts create mode 100644 packages/cli/src/activity-context/sources.ts create mode 100644 packages/cli/src/activity-context/types.ts create mode 100644 packages/cli/src/commands/activity-context.test.ts create mode 100644 packages/cli/src/commands/activity-context.ts create mode 100644 packages/cli/src/commands/flow-autopilot.ts create mode 100644 packages/cli/src/commands/flow-bind.test.ts create mode 100644 packages/cli/src/commands/flow-bind.ts create mode 100644 packages/cli/src/commands/flow-init.template-stage.test.ts create mode 100644 packages/cli/src/commands/flow-phase-run.ts create mode 100644 packages/cli/src/commands/flow-phases.ts create mode 100644 packages/cli/src/commands/push.ts create mode 100644 packages/cli/src/commands/status.ts create mode 100644 packages/cli/src/flow-definition/resolve.test.ts create mode 100644 packages/cli/src/flow-definition/resolve.ts create mode 100644 packages/cli/src/flow-definition/types.ts create mode 100644 packages/cli/src/flow-serve/automation-runtime.test.ts create mode 100644 packages/cli/src/flow-serve/automation-runtime.ts create mode 100644 packages/cli/src/flow-serve/client-assets.ts create mode 100644 packages/cli/src/flow-serve/context.test.ts create mode 100644 packages/cli/src/flow-serve/context.ts create mode 100644 packages/cli/src/flow-serve/diagnostics.ts create mode 100644 packages/cli/src/flow-serve/events.ts create mode 100644 packages/cli/src/flow-serve/http.test.ts create mode 100644 packages/cli/src/flow-serve/http.ts create mode 100644 packages/cli/src/flow-serve/request-context.test.ts create mode 100644 packages/cli/src/flow-serve/request-context.ts create mode 100644 packages/cli/src/flow-serve/router.test.ts create mode 100644 packages/cli/src/flow-serve/router.ts create mode 100644 packages/cli/src/flow-serve/routes/context-routes.ts create mode 100644 packages/cli/src/flow-serve/routes/diagnostics-routes.test.ts create mode 100644 packages/cli/src/flow-serve/routes/diagnostics-routes.ts create mode 100644 packages/cli/src/flow-serve/routes/item-routes.test.ts create mode 100644 packages/cli/src/flow-serve/routes/item-routes.ts create mode 100644 packages/cli/src/flow-serve/routes/spec-routes.test.ts create mode 100644 packages/cli/src/flow-serve/routes/spec-routes.ts create mode 100644 packages/cli/src/flow-serve/routes/workflow-routes.ts create mode 100644 packages/cli/src/flow-serve/routes/workspace-routes.ts create mode 100644 packages/cli/src/flow-serve/specs.ts create mode 100644 packages/cli/src/flow-serve/system-actions.test.ts create mode 100644 packages/cli/src/flow-serve/system-actions.ts create mode 100644 packages/cli/src/flow-serve/workspace.ts create mode 100644 packages/cli/src/harness/activity-hints.test.ts create mode 100644 packages/cli/src/harness/default/v0.1.0/flows/sdlc.yaml create mode 100644 packages/cli/src/harness/default/v0.1.0/gates/all-acs-passing.yaml create mode 100644 packages/cli/src/harness/default/v0.1.0/gates/has-spec-file.yaml create mode 100644 packages/cli/src/harness/default/v0.1.0/gates/human-approved-code.yaml create mode 100644 packages/cli/src/harness/default/v0.1.0/gates/human-approved-spec.yaml create mode 100644 packages/cli/src/harness/default/v0.1.0/gates/security-clear.yaml create mode 100644 packages/cli/src/harness/default/v0.1.0/policies/sdlc-default.json create mode 100644 packages/cli/src/harness/default/v0.1.0/roles/analyst.yaml create mode 100644 packages/cli/src/harness/default/v0.1.0/roles/builder.yaml create mode 100644 packages/cli/src/harness/default/v0.1.0/roles/pr-bot.yaml create mode 100644 packages/cli/src/harness/default/v0.1.0/roles/reviewer.yaml create mode 100644 packages/cli/src/harness/default/v0.1.0/roles/security.yaml create mode 100644 packages/cli/src/harness/default/v0.1.1/flows/flow-main.yaml create mode 100644 packages/cli/src/harness/default/v0.1.1/flows/sdlc.yaml create mode 100644 packages/cli/src/harness/default/v0.1.1/gates/all-acs-passing.yaml create mode 100644 packages/cli/src/harness/default/v0.1.1/gates/has-spec-file.yaml create mode 100644 packages/cli/src/harness/default/v0.1.1/gates/human-approved-code.yaml create mode 100644 packages/cli/src/harness/default/v0.1.1/gates/human-approved-spec.yaml create mode 100644 packages/cli/src/harness/default/v0.1.1/gates/security-clear.yaml create mode 100644 packages/cli/src/harness/default/v0.1.1/policies/sdlc-default.json create mode 100644 packages/cli/src/harness/default/v0.1.1/roles/analyst.yaml create mode 100644 packages/cli/src/harness/default/v0.1.1/roles/builder.yaml create mode 100644 packages/cli/src/harness/default/v0.1.1/roles/pr-bot.yaml create mode 100644 packages/cli/src/harness/default/v0.1.1/roles/reviewer.yaml create mode 100644 packages/cli/src/harness/default/v0.1.1/roles/security.yaml create mode 100644 packages/cli/src/harness/default/v0.1.2/flows/flow-main.yaml create mode 100644 packages/cli/src/harness/default/v0.1.2/flows/sdlc.yaml create mode 100644 packages/cli/src/harness/default/v0.1.2/gates/all-acs-passing.yaml create mode 100644 packages/cli/src/harness/default/v0.1.2/gates/has-spec-file.yaml create mode 100644 packages/cli/src/harness/default/v0.1.2/gates/human-approved-code.yaml create mode 100644 packages/cli/src/harness/default/v0.1.2/gates/human-approved-spec.yaml create mode 100644 packages/cli/src/harness/default/v0.1.2/gates/security-clear.yaml create mode 100644 packages/cli/src/harness/default/v0.1.2/policies/sdlc-default.json create mode 100644 packages/cli/src/harness/default/v0.1.2/roles/analyst.yaml create mode 100644 packages/cli/src/harness/default/v0.1.2/roles/builder.yaml create mode 100644 packages/cli/src/harness/default/v0.1.2/roles/pr-bot.yaml create mode 100644 packages/cli/src/harness/default/v0.1.2/roles/reviewer.yaml create mode 100644 packages/cli/src/harness/default/v0.1.2/roles/security.yaml create mode 100644 packages/cli/src/operational-audit/normalizer.test.ts create mode 100644 packages/cli/src/operational-audit/normalizer.ts create mode 100644 packages/cli/src/phases/autopilot.test.ts create mode 100644 packages/cli/src/phases/autopilot.ts create mode 100644 packages/cli/src/phases/engine.test.ts create mode 100644 packages/cli/src/phases/engine.ts create mode 100644 packages/cli/src/phases/runner.test.ts create mode 100644 packages/cli/src/phases/runner.ts create mode 100644 packages/cli/src/tui/init-wizard.tsx create mode 100644 packages/cli/src/workspace/index.ts create mode 100644 packages/cli/src/workspace/resolver.ts create mode 100644 packages/client/components.json create mode 100644 packages/client/src/components/Dashboard/GateCard.tsx create mode 100644 packages/client/src/components/Dashboard/MetricCards.tsx create mode 100644 packages/client/src/components/Dashboard/PipelineStatus.tsx create mode 100644 packages/client/src/components/Execution/AgentDetail.test.tsx create mode 100644 packages/client/src/components/Execution/AgentDetail.tsx create mode 100644 packages/client/src/components/Execution/ExecutionView.tsx create mode 100644 packages/client/src/components/Flow/ActivityTimeline.tsx create mode 100644 packages/client/src/components/Flow/FlowDefinitionWarnings.test.tsx create mode 100644 packages/client/src/components/Flow/FlowDefinitionWarnings.tsx create mode 100644 packages/client/src/components/Flow/KanbanBoard.tsx create mode 100644 packages/client/src/components/Harness/HarnessViewer.tsx create mode 100644 packages/client/src/components/Header/LogoDiamond.tsx create mode 100644 packages/client/src/components/Kanban/PhaseBadge.tsx create mode 100644 packages/client/src/components/Logs/AuditLogView.tsx create mode 100644 packages/client/src/components/Sidebar/Sidebar.tsx delete mode 100644 packages/client/src/components/Toast/Toast.tsx create mode 100644 packages/client/src/components/ValidatingBar.tsx create mode 100644 packages/client/src/components/Workspaces/WorkspaceSetupFlow.tsx create mode 100644 packages/client/src/components/Workspaces/WorkspacesView.tsx create mode 100644 packages/client/src/components/ui/Accessibility.test.tsx create mode 100644 packages/client/src/components/ui/DocumentEditor.test.tsx create mode 100644 packages/client/src/components/ui/DocumentEditor.tsx create mode 100644 packages/client/src/components/ui/accordion.tsx delete mode 100644 packages/client/src/components/ui/badge.tsx delete mode 100644 packages/client/src/components/ui/card.tsx delete mode 100644 packages/client/src/components/ui/checkbox.tsx create mode 100644 packages/client/src/components/ui/collapsible.tsx delete mode 100644 packages/client/src/components/ui/dialog.tsx delete mode 100644 packages/client/src/components/ui/icon.tsx create mode 100644 packages/client/src/components/ui/label.tsx create mode 100644 packages/client/src/components/ui/radio-group.tsx create mode 100644 packages/client/src/components/ui/separator.tsx create mode 100644 packages/client/src/components/ui/sheet.tsx create mode 100644 packages/client/src/components/ui/sidebar.tsx create mode 100644 packages/client/src/components/ui/switch.tsx create mode 100644 packages/client/src/components/ui/table.tsx delete mode 100644 packages/client/src/components/ui/textarea.tsx create mode 100644 packages/client/src/components/ui/toggle.tsx create mode 100644 packages/client/src/components/ui/tooltip.tsx create mode 100644 packages/client/src/hooks/use-mobile.ts create mode 100644 packages/client/src/lib/active-flow.test.ts create mode 100644 packages/client/src/lib/active-flow.ts create mode 100644 packages/client/src/test/setup.ts create mode 100644 packages/client/vitest.config.ts create mode 100644 packages/ui/src/accordion.tsx create mode 100644 packages/ui/src/collapsible.tsx create mode 100644 packages/ui/src/command.tsx create mode 100644 packages/ui/src/drawer.tsx create mode 100644 packages/ui/src/dropdown-menu.tsx create mode 100644 packages/ui/src/error-banner.tsx create mode 100644 packages/ui/src/label.tsx create mode 100644 packages/ui/src/navigation-menu.tsx create mode 100644 packages/ui/src/popover.tsx create mode 100644 packages/ui/src/radio-group.tsx create mode 100644 packages/ui/src/scroll-area.tsx create mode 100644 packages/ui/src/select.tsx create mode 100644 packages/ui/src/separator.tsx create mode 100644 packages/ui/src/sheet.tsx create mode 100644 packages/ui/src/switch.tsx create mode 100644 packages/ui/src/table.tsx create mode 100644 playwright.config.ts create mode 100644 sketches/001-tui-dense/index.html create mode 100644 sketches/002-web-calm/index.html create mode 100644 sketches/003-dashboard-tui/index.html create mode 100644 sketches/004-flow-start/index.html create mode 100644 sketches/005-review-tui/index.html create mode 100644 sketches/er-onboarding-v2/index.html create mode 100644 sketches/er-onboarding/index.html create mode 100644 sketches/onboarding-linear/index.html create mode 100644 sketches/onboarding-web-vercel/index.html create mode 100644 sketches/references/README.md create mode 100644 sketches/references/composer_2026-06-21_18-26-40-295_9c95ad.png create mode 100644 sketches/references/composer_2026-06-21_18-28-29-021_d95096.png create mode 100644 sketches/sdlc-flow/index.html create mode 100644 skills-lock.json create mode 100644 test-harness.ts create mode 100644 test-parse.ts diff --git a/.agents/skills/shadcn/SKILL.md b/.agents/skills/shadcn/SKILL.md new file mode 100644 index 0000000..dcdb754 --- /dev/null +++ b/.agents/skills/shadcn/SKILL.md @@ -0,0 +1,267 @@ +--- +name: shadcn +description: Manages shadcn components and projects — adding, searching, fixing, debugging, styling, and composing UI. Provides project context, component docs, and usage examples. Applies when working with shadcn/ui, component registries, presets, --preset codes, or any project with a components.json file. Also triggers for "shadcn init", "create an app with --preset", or "switch to --preset". +user-invocable: false +allowed-tools: Bash(npx shadcn@latest *), Bash(pnpm dlx shadcn@latest *), Bash(bunx --bun shadcn@latest *) +--- + +# shadcn/ui + +A framework for building ui, components and design systems. Components are added as source code to the user's project via the CLI. + +> **IMPORTANT:** Run all CLI commands using the project's package runner: `npx shadcn@latest`, `pnpm dlx shadcn@latest`, or `bunx --bun shadcn@latest` — based on the project's `packageManager`. Examples below use `npx shadcn@latest` but substitute the correct runner for the project. + +## Current Project Context + +```json +!`npx shadcn@latest info --json` +``` + +The JSON above contains the project config and installed components. Use `npx shadcn@latest docs ` to get documentation and example URLs for any component. + +## Principles + +1. **Use existing components first.** Use `npx shadcn@latest search` to check registries before writing custom UI. Check community registries too. +2. **Compose, don't reinvent.** Settings page = Tabs + Card + form controls. Dashboard = Sidebar + Card + Chart + Table. +3. **Use built-in variants before custom styles.** `variant="outline"`, `size="sm"`, etc. +4. **Use semantic colors.** `bg-primary`, `text-muted-foreground` — never raw values like `bg-blue-500`. + +## Critical Rules + +These rules are **always enforced**. Each links to a file with Incorrect/Correct code pairs. + +### Styling & Tailwind → [styling.md](./rules/styling.md) + +- **`className` for layout, not styling.** Never override component colors or typography. +- **No `space-x-*` or `space-y-*`.** Use `flex` with `gap-*`. For vertical stacks, `flex flex-col gap-*`. +- **Use `size-*` when width and height are equal.** `size-10` not `w-10 h-10`. +- **Use `truncate` shorthand.** Not `overflow-hidden text-ellipsis whitespace-nowrap`. +- **No manual `dark:` color overrides.** Use semantic tokens (`bg-background`, `text-muted-foreground`). +- **Use `cn()` for conditional classes.** Don't write manual template literal ternaries. +- **No manual `z-index` on overlay components.** Dialog, Sheet, Popover, etc. handle their own stacking. + +### Forms & Inputs → [forms.md](./rules/forms.md) + +- **Forms use `FieldGroup` + `Field`.** Never use raw `div` with `space-y-*` or `grid gap-*` for form layout. +- **`InputGroup` uses `InputGroupInput`/`InputGroupTextarea`.** Never raw `Input`/`Textarea` inside `InputGroup`. +- **Buttons inside inputs use `InputGroup` + `InputGroupAddon`.** +- **Option sets (2–7 choices) use `ToggleGroup`.** Don't loop `Button` with manual active state. +- **`FieldSet` + `FieldLegend` for grouping related checkboxes/radios.** Don't use a `div` with a heading. +- **Field validation uses `data-invalid` + `aria-invalid`.** `data-invalid` on `Field`, `aria-invalid` on the control. For disabled: `data-disabled` on `Field`, `disabled` on the control. + +### Component Structure → [composition.md](./rules/composition.md) + +- **Items always inside their Group.** `SelectItem` → `SelectGroup`. `DropdownMenuItem` → `DropdownMenuGroup`. `CommandItem` → `CommandGroup`. +- **Use `asChild` (radix) or `render` (base) for custom triggers.** Check `base` field from `npx shadcn@latest info`. → [base-vs-radix.md](./rules/base-vs-radix.md) +- **Dialog, Sheet, and Drawer always need a Title.** `DialogTitle`, `SheetTitle`, `DrawerTitle` required for accessibility. Use `className="sr-only"` if visually hidden. +- **Use full Card composition.** `CardHeader`/`CardTitle`/`CardDescription`/`CardContent`/`CardFooter`. Don't dump everything in `CardContent`. +- **Button has no `isPending`/`isLoading`.** Compose with `Spinner` + `data-icon` + `disabled`. +- **`TabsTrigger` must be inside `TabsList`.** Never render triggers directly in `Tabs`. +- **`Avatar` always needs `AvatarFallback`.** For when the image fails to load. + +### Use Components, Not Custom Markup → [composition.md](./rules/composition.md) + +- **Use existing components before custom markup.** Check if a component exists before writing a styled `div`. +- **Callouts use `Alert`.** Don't build custom styled divs. +- **Empty states use `Empty`.** Don't build custom empty state markup. +- **Toast via `sonner`.** Use `toast()` from `sonner`. +- **Use `Separator`** instead of `
` or `
`. +- **Use `Skeleton`** for loading placeholders. No custom `animate-pulse` divs. +- **Use `Badge`** instead of custom styled spans. + +### Icons → [icons.md](./rules/icons.md) + +- **Icons in `Button` use `data-icon`.** `data-icon="inline-start"` or `data-icon="inline-end"` on the icon. +- **No sizing classes on icons inside components.** Components handle icon sizing via CSS. No `size-4` or `w-4 h-4`. +- **Pass icons as objects, not string keys.** `icon={CheckIcon}`, not a string lookup. + +### CLI + +- **Never decode preset codes or build preset URLs manually.** Use `npx shadcn@latest preset decode `, `preset url `, or `preset open `. For project-aware preset detection, use `npx shadcn@latest preset resolve`. +- **Apply preset codes directly with the CLI.** Use `npx shadcn@latest apply ` for existing projects, or `npx shadcn@latest init --preset ` when initializing. + +## Key Patterns + +These are the most common patterns that differentiate correct shadcn/ui code. For edge cases, see the linked rule files above. + +```tsx +// Form layout: FieldGroup + Field, not div + Label. + + + Email + + + + +// Validation: data-invalid on Field, aria-invalid on the control. + + Email + + Invalid email. + + +// Icons in buttons: data-icon, no sizing classes. + + +// Spacing: gap-*, not space-y-*. +
// correct +
// wrong + +// Equal dimensions: size-*, not w-* h-*. + // correct + // wrong + +// Status colors: Badge variants or semantic tokens, not raw colors. ++20.1% // correct ++20.1% // wrong +``` + +## Component Selection + +| Need | Use | +| -------------------------- | --------------------------------------------------------------------------------------------------- | +| Button/action | `Button` with appropriate variant | +| Form inputs | `Input`, `Select`, `Combobox`, `Switch`, `Checkbox`, `RadioGroup`, `Textarea`, `InputOTP`, `Slider` | +| Toggle between 2–5 options | `ToggleGroup` + `ToggleGroupItem` | +| Data display | `Table`, `Card`, `Badge`, `Avatar` | +| Navigation | `Sidebar`, `NavigationMenu`, `Breadcrumb`, `Tabs`, `Pagination` | +| Overlays | `Dialog` (modal), `Sheet` (side panel), `Drawer` (bottom sheet), `AlertDialog` (confirmation) | +| Feedback | `sonner` (toast), `Alert`, `Progress`, `Skeleton`, `Spinner` | +| Command palette | `Command` inside `Dialog` | +| Charts | `Chart` (wraps Recharts) | +| Layout | `Card`, `Separator`, `Resizable`, `ScrollArea`, `Accordion`, `Collapsible` | +| Empty states | `Empty` | +| Menus | `DropdownMenu`, `ContextMenu`, `Menubar` | +| Tooltips/info | `Tooltip`, `HoverCard`, `Popover` | + +## Key Fields + +The injected project context contains these key fields: + +- **`aliases`** → use the actual alias prefix for imports (e.g. `@/`, `~/`), never hardcode. +- **`isRSC`** → when `true`, components using `useState`, `useEffect`, event handlers, or browser APIs need `"use client"` at the top of the file. Always reference this field when advising on the directive. +- **`tailwindVersion`** → `"v4"` uses `@theme inline` blocks; `"v3"` uses `tailwind.config.js`. +- **`tailwindCssFile`** → the global CSS file where custom CSS variables are defined. Always edit this file, never create a new one. +- **`style`** → component visual treatment (e.g. `nova`, `vega`). +- **`base`** → primitive library (`radix` or `base`). Affects component APIs and available props. +- **`iconLibrary`** → determines icon imports. Use `lucide-react` for `lucide`, `@tabler/icons-react` for `tabler`, etc. Never assume `lucide-react`. +- **`resolvedPaths`** → exact file-system destinations for components, utils, hooks, etc. +- **`framework`** → routing and file conventions (e.g. Next.js App Router vs Vite SPA). +- **`packageManager`** → use this for any non-shadcn dependency installs (e.g. `pnpm add date-fns` vs `npm install date-fns`). +- **`preset`** → resolved preset code and values for the current project. Use `npx shadcn@latest preset resolve --json` when you only need preset information. + +See [cli.md — `info` command](./cli.md) for the full field reference. + +## Component Docs, Examples, and Usage + +Run `npx shadcn@latest docs ` to get the URLs for a component's documentation, examples, and API reference. Fetch these URLs to get the actual content. + +```bash +npx shadcn@latest docs button dialog select +``` + +**When creating, fixing, debugging, or using a component, always run `npx shadcn@latest docs` and fetch the URLs first.** This ensures you're working with the correct API and usage patterns rather than guessing. + +## Workflow + +1. **Get project context** — already injected above. Run `npx shadcn@latest info` again if you need to refresh. +2. **Check installed components first** — before running `add`, always check the `components` list from project context or list the `resolvedPaths.ui` directory. Don't import components that haven't been added, and don't re-add ones already installed. +3. **Find components** — `npx shadcn@latest search`. +4. **Get docs and examples** — run `npx shadcn@latest docs ` to get URLs, then fetch them. Use `npx shadcn@latest view` to browse registry items you haven't installed. To preview changes to installed components, use `npx shadcn@latest add --diff`. +5. **Install or update** — `npx shadcn@latest add`. When updating existing components, use `--dry-run` and `--diff` to preview changes first (see [Updating Components](#updating-components) below). +6. **Fix imports in third-party components** — After adding components from community registries (e.g. `@bundui`, `@magicui`), check the added non-UI files for hardcoded import paths like `@/components/ui/...`. These won't match the project's actual aliases. Use `npx shadcn@latest info` to get the correct `ui` alias (e.g. `@workspace/ui/components`) and rewrite the imports accordingly. The CLI rewrites imports for its own UI files, but third-party registry components may use default paths that don't match the project. +7. **Review added components** — After adding a component or block from any registry, **always read the added files and verify they are correct**. Check for missing sub-components (e.g. `SelectItem` without `SelectGroup`), missing imports, incorrect composition, or violations of the [Critical Rules](#critical-rules). Also replace any icon imports with the project's `iconLibrary` from the project context (e.g. if the registry item uses `lucide-react` but the project uses `hugeicons`, swap the imports and icon names accordingly). Fix all issues before moving on. +8. **Registry must be explicit** — When the user asks to add a block or component, **do not guess the registry**. If no registry is specified (e.g. user says "add a login block" without specifying `@shadcn`, `@tailark`, `owner/repo`, etc.), ask which registry to use. Never default to a registry on behalf of the user. +9. **Switching presets** — Ask the user first: **overwrite**, **partial**, **merge**, or **skip**? + - **Inspect current preset**: `npx shadcn@latest preset resolve`. Use `--json` when you need structured values. + - **Inspect incoming preset**: `npx shadcn@latest preset decode `. Use `preset url ` or `preset open ` to share or open the preset builder. + - **Overwrite**: `npx shadcn@latest apply `. Overwrites detected components, fonts, and CSS variables. + - **Partial**: `npx shadcn@latest apply --only theme,font`. Updates only the selected preset parts without reinstalling UI components. Supported values are `theme` and `font`; comma-separated combinations are allowed. `icon` is intentionally not supported, because icon changes may require full component reinstall and transforms. + - **Merge**: `npx shadcn@latest init --preset --force --no-reinstall`, then run `npx shadcn@latest info` to list installed components, then for each installed component use `--dry-run` and `--diff` to [smart merge](#updating-components) it individually. + - **Skip**: `npx shadcn@latest init --preset --force --no-reinstall`. Only updates config and CSS, leaves components as-is. + - **Important**: Always run preset commands inside the user's project directory. `apply` only works in an existing project with a `components.json` file. The CLI automatically preserves the current base (`base` vs `radix`) from `components.json`. If you must use a scratch/temp directory (e.g. for `--dry-run` comparisons), pass `--base ` explicitly — preset codes do not encode the base. + +## Updating Components + +When the user asks to update a component from upstream while keeping their local changes, use `--dry-run` and `--diff` to intelligently merge. **NEVER fetch raw files from GitHub manually — always use the CLI.** + +1. Run `npx shadcn@latest add --dry-run` to see all files that would be affected. +2. For each file, run `npx shadcn@latest add --diff ` to see what changed upstream vs local. +3. Decide per file based on the diff: + - No local changes → safe to overwrite. + - Has local changes → read the local file, analyze the diff, and apply upstream updates while preserving local modifications. + - User says "just update everything" → use `--overwrite`, but confirm first. +4. **Never use `--overwrite` without the user's explicit approval.** + +## Quick Reference + +```bash +# Create a new project. +npx shadcn@latest init --name my-app --preset base-nova +npx shadcn@latest init --name my-app --preset a2r6bw --template vite + +# Create a monorepo project. +npx shadcn@latest init --name my-app --preset base-nova --monorepo +npx shadcn@latest init --name my-app --preset base-nova --template next --monorepo + +# Initialize existing project. +npx shadcn@latest init --preset base-nova +npx shadcn@latest init --defaults # shortcut: --template=next --preset=nova (base style implied) + +# Apply a preset to an existing project. +npx shadcn@latest apply a2r6bw +npx shadcn@latest apply a2r6bw --only theme +npx shadcn@latest apply a2r6bw --only font +npx shadcn@latest apply a2r6bw --only theme,font + +# Inspect preset codes and project preset state. +npx shadcn@latest preset decode a2r6bw +npx shadcn@latest preset url a2r6bw +npx shadcn@latest preset open a2r6bw +npx shadcn@latest preset resolve +npx shadcn@latest preset resolve --json + +# Add components. +npx shadcn@latest add button card dialog +npx shadcn@latest add @magicui/shimmer-button +npx shadcn@latest add owner/repo/item +npx shadcn@latest add --all + +# Preview changes before adding/updating. +npx shadcn@latest add button --dry-run +npx shadcn@latest add button --diff button.tsx +npx shadcn@latest add @acme/form --view button.tsx +npx shadcn@latest add owner/repo/item --dry-run + +# Search registries. +npx shadcn@latest search @shadcn -q "sidebar" +npx shadcn@latest search @tailark -q "stats" +npx shadcn@latest search owner/repo -q "login" +npx shadcn@latest search # all configured registries +npx shadcn@latest search @shadcn -q "menu" -t ui # filter by item type + +# Get component docs and example URLs. +npx shadcn@latest docs button dialog select + +# View registry item details (for items not yet installed). +npx shadcn@latest view @shadcn/button +npx shadcn@latest view owner/repo/item +``` + +**Named presets:** `nova`, `vega`, `maia`, `lyra`, `mira`, `luma` +**Templates:** `next`, `vite`, `start`, `react-router`, `astro` (all support `--monorepo`) and `laravel` (not supported for monorepo) +**Preset codes:** Version-prefixed base62 strings (e.g. `a2r6bw` or `b0`), from [ui.shadcn.com](https://ui.shadcn.com). + +## Detailed References + +- [rules/forms.md](./rules/forms.md) — FieldGroup, Field, InputGroup, ToggleGroup, FieldSet, validation states +- [rules/composition.md](./rules/composition.md) — Groups, overlays, Card, Tabs, Avatar, Alert, Empty, Toast, Separator, Skeleton, Badge, Button loading +- [rules/icons.md](./rules/icons.md) — data-icon, icon sizing, passing icons as objects +- [rules/styling.md](./rules/styling.md) — Semantic colors, variants, className, spacing, size, truncate, dark mode, cn(), z-index +- [rules/base-vs-radix.md](./rules/base-vs-radix.md) — asChild vs render, Select, ToggleGroup, Slider, Accordion +- [cli.md](./cli.md) — Commands, flags, presets, templates +- [registry.md](./registry.md) — Authoring source registries, `include`, item definitions, dependencies, GitHub registry rules +- [customization.md](./customization.md) — Theming, CSS variables, extending components diff --git a/.agents/skills/shadcn/agents/openai.yml b/.agents/skills/shadcn/agents/openai.yml new file mode 100644 index 0000000..ab636da --- /dev/null +++ b/.agents/skills/shadcn/agents/openai.yml @@ -0,0 +1,5 @@ +interface: + display_name: "shadcn/ui" + short_description: "Manages shadcn/ui components — adding, searching, fixing, debugging, styling, and composing UI." + icon_small: "./assets/shadcn-small.png" + icon_large: "./assets/shadcn.png" diff --git a/.agents/skills/shadcn/assets/shadcn-small.png b/.agents/skills/shadcn/assets/shadcn-small.png new file mode 100644 index 0000000000000000000000000000000000000000..547154b97f2298335159c23eec1dac0d147a1246 GIT binary patch literal 1049 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Ea{HEjtmSN`?>!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBDAAG{;hE;^%b*2hb1<+n z3NbJPS&Tr)z$nE4G7ZRL@M4sPvx68lplX;H7}_%#SfFa6fHVkr05M1pgl1mAh%j*h z6I`{x0%imor0saV%ts)_S>O>_%)r1c48n{Iv*t(uO^eJ7i71Ki^|4CM&(%vz$xlkv ztH>0+>{umUo3Q%e#RDspr3imfVamB1>jfNYSkzLEl1NlCV?QiN}Sf^&XRs)CuG zfu4bq9hZWFf=y9MnpKdC8&o@xXRDM^Qc_^0uU}qXu2*iXmtT~wZ)j<0sc&GUZ)Btk zRH0j3nOBlnp_^B%3^4>|j!SBBa#3bMNoIbY0?6FNr2NtnTO}osMQ{LdXGvxn!lt}p zsJDO~)CbAv8|oS8!_5Y2wE>A*`4?rT0&NDFZ)a!&R*518wZ}#uWI2*!AU*|)0=;U- zWup%dHajlKxQFb(K%V94;uvBfm>Yb$>yU#$kCbgiC(9RwrlwA(rsW(``(6H=zq@!* zUB_~Trk_lTdxPh$yxDTsVY&L;b+6{tRm^+d+gl^OQcCIBok=%y)Vhx@dR1ceL3r{0 z9=2nMXx8DxEkhL{v?(>~8{qDBkfB!u-iDB`| z-5d%Ae82YI%n8Zg9$mRN?o-|V)V*=5b-R>a>}P2{=pcK^B(&B-W{C#Z!Q%b*ueK>n zK6ymYLRneebCKDSzcqHEHFopo-mkH{zIkt2Z0^086Tko7%N)NxoZYSDM1jSUwPpuS zSeq*wzrC2D(tq4pZ#s9QP@Cd8Q^wW3*2cL_519?*;#4`B;+ei#o;C{Kkamm3Jooa; zE$3K-+c(6ShTjelW_B*W5o zcS6z34IR5C-SIF8m(lui^IOy!-~We?b1?PSGniCYR=ZyR^%<1lJzf1=);T3K0RU@N BSy=!8 literal 0 HcmV?d00001 diff --git a/.agents/skills/shadcn/assets/shadcn.png b/.agents/skills/shadcn/assets/shadcn.png new file mode 100644 index 0000000000000000000000000000000000000000..b7b6814acc25073e5f48099b1fd3f70c47bfb1c3 GIT binary patch literal 3852 zcmY*c2{@Ep*nY=Y1|x&SAZEs{m}!)<%wS9yONAB@M)rMZgo-eODf<#aqEe(qiK0X# zjeV<7Sz@e_J-dH=Uw_~KpZ7ZNbD!tj&;6X|T<3bv^oi2VQ{31CN!jQ|snzFQu@Q;0Du?| zfb$Q>hHZD6F}v@?{A7+^3dW)SVQ8`OjvI|a z{6nF7;ZWA*ClCgH0WJs)HH;bth37*c5IOtVpZ7;)e9lMRL5Xd-wU$;iu|t*(dB|ufCq)@;pdCk(RDoU7f8jS zP&Fw%scw{13LdO|DW$q z9v$>f^8d4#e=GgR%Ptkqr-S~xZFoMo{SXfTK;ub7{gc641VugDjPn7=AJBO!*mUKucc{7ey~li!vybaIkF z8OboYVX%y|2%P`k*m&i}s=eW8)0q&YJ<-thLrc)HZ?6uc7Fd!$(lWSvH;?Y^+|N2q z(lz!>Ip|<{_a4CnLN%GIxW|*Sl|;4c3?=Q>z5fWEEtp(&BzeAccRS{zf9!64+SAA{ zhe+CT7U8Ox1UUnilJqweOM{d}_w6-|yz}dA=-R7yHrxs|(MLR_`U=AC%0V#PpIuxs z`__xodV3Bl2ayP^m8isMu4iD<`=4UURtHBPsaLp81r-f3$(4vZbRPd}*>wTo@@?zp zjl1KkifF5p%-V<}MTk3`()wm7qcMd8<14WE>Y_YwjB;{Zt!@vJbH&pu!Ye!p{1kAj zlRg2pkl)WBbyj>3e<$)@mP?WW}W-c;_(UQ(}=MJLED9F^U*P1KWxu z1h@&s1?M0%ax_P5Qb>kkPKEH=OKGZn z&-@5MD^|kk?Hzkfz8h$8#{$$OjAyr!+uGtp>ns1^oS7Nlv6k$^tzTdIb%jUtI8F3t zL;LuqJ7-@4UI|Q)HN=Ap;dAmuZY4c&XphavC{kl z)mMY6L)S-94;V|YT3TA7U^;uvS0W!$W`JIb@kNAm0@IKVWJ@R_jgX6wlUd5V%pHIn;=Z~px z-B_MFLx+ldzTQUp5C zkXaznhH18TytzOgD%$iMnLiAtjvnHLkM;?P*3ZFb#cbY&ZF*|R;4V4$85DZMQ#T@| zxuMMPg?cl|hNJ$2S&%fLErMQSzCnzc_QGh3x@I8V&Omf&=_n+EaC}tft^`)^W5-=1 z0bbJ#wK8+=zNaz-V{U?NtJp&%IDd9-xIut--L}@gU{E>B&ym$vT2@wEZq- zzDqmDf)Cr-aZ-5KIYF!Elcq_X%?qXsg}Yhp=^bebcZ-Ucd$p+US;P}(@VrR4_fy6!qx?X)%SIi+LRsDR` zQ8+xRd{In4WLlVjoy5ElKt%?Ykr)cH*G}0^tE=$(#0W?l)_9MV89|sem>Y}t&?fGt zo3o0sdsG9?JhC1-KM}e*&zeb*H$7JoD&0z#=2sH725JmGV`WoVb86exi5p^Lm{B5BlZRd6(bKH`QmJ*u8#(j6IH1 zFf;Li5iqsV=V^irlzzwVJ$xXwz?I8y`i+t-g0?n77Ie}-fBp1{)&Dm1Yk^Fb=7rI4 zAD)SQRv9=Xr%G{qI!)}>rf;lzG))%89I~zF)R~&_Ur)%0>4+7bxbBknHT`^+jNFe%p=t<=q}3W1Y-u+ zi-cN{V#+HsEEB8UQsUt1uE991MvN%&V?(0Mcb~82H!ori&rLm~Xyf>i#u_t=rzHA3 zu?NRljnv7YU;T&V#(OVE$+=^@kaXvs%C3f78E(k$IyZ`)N$NYtLMfphBkV|oSI}7N zEpFrKnA*jL!k5P(%)+Sl^e>hI@Y}jqmvb~@RD`9$!9Jpu-Wf2Cq?hfV{ghuo&AG>9 zcvC>&o43J*WfyXuRWV)Y0~9;i68pPyv&~-%Ysp}xCY)|oF5?x{w~aS zfx&5VFU)gcnMk*q%=jkB9w_qQ)x=v#1Q^_3`uyEfKiD4y$vaOR4n4IpvQmyNXK%#R zU|XV~4Bq=Kr7Zp3#hdF5_MgA#yL-jJ4E5*j2?mhMq17{mD#6f?!#-b>@HA&~uECub zF2reQA9f>OHLzqeZhi$%7fW!n^Y3_Y{C0vfQs0?a(Pcz7Opuz;(9n>$6)c9L`((v~ z@R1+M2kRN}mDuKCk=od(UwR?y%hUbEv$dC0BD&Ir;03Mf&|A#ZFtn&*mnkH8>2w0+imbwLLU77q!A=W ztt$OR$b#$dkIAwM*ZWG{5*zUPkBN$42Kxe)AYe9DV+?(A^_|IoJLzRGKlbpb%n=PVmB`nxX< z)_}Tpg&IoX;4<0!ksDY9f>*dl`c}zSl!(+ujG#<&yu3AEKR)zVKTaK88*+ma)}3)G zx460(7TNMTz(t&DUv)`7lj(r=`!?>~Q}XW3w-sipV!{Trr8`zsvu9NEK9)zO0>v3D zOs=r|`OyjAS{Ea@UIRrr2Y7A?5+5}R4RpF)ee5^F-dhvP6MKyO`kNB4jGC{-$x}`x zXmpAxVV=WR{>!zDxoq7#DN+_BWG_PRr?$Sa(XT@zUL`HuM{I4bZSuo97aF&7tL@4P zO59)SaLA2ZMU3dO5>=P)GiqcIBUy4A2h_IC-7+IROG<+Qqczgo-JyHw-!h2%h8#$y zr;y4>z2J1R4+`7o>#OWA2iiS1m8)q6A}u3d7VBk **IMPORTANT:** Always run commands using the project's package runner: `npx shadcn@latest`, `pnpm dlx shadcn@latest`, or `bunx --bun shadcn@latest`. Check `packageManager` from project context to choose the right one. Examples below use `npx shadcn@latest` but substitute the correct runner for the project. + +> **IMPORTANT:** Only use the flags documented below. Do not invent or guess flags — if a flag isn't listed here, it doesn't exist. The CLI auto-detects the package manager from the project's lockfile; there is no `--package-manager` flag. + +## Contents + +- Commands: init, apply, add (dry-run, smart merge), search, view, docs, info, build +- Templates: next, vite, start, react-router, astro +- Presets: named, code, URL formats and fields +- Switching presets + +--- + +## Commands + +### `init` — Initialize or create a project + +```bash +npx shadcn@latest init [components...] [options] +``` + +Initializes shadcn/ui in an existing project or creates a new project (when `--name` is provided). Optionally installs components in the same step. + +| Flag | Short | Description | Default | +| ----------------------- | ----- | --------------------------------------------------------- | ------- | +| `--template