mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 15:13:07 +00:00
Merge pull request #579 from fsolla/OPS25-opencode-comandos-work-issue-e-plan-issue-atalho-diret
OPS25: comandos opencode /work-issue e /plan-issue (atalho direto às skills)
This commit is contained in:
commit
865c451f6f
5 changed files with 155 additions and 0 deletions
8
.opencode/commands/plan-issue.md
Normal file
8
.opencode/commands/plan-issue.md
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
description: Transforma ideias humanas em Issues GitHub + planos de intenção em docs/plans/ (com gate e registro)
|
||||
model: deepseek/deepseek-v4-flash
|
||||
---
|
||||
|
||||
Carregue a skill `plan-issue` (ferramenta de skills, nome exato) e siga o fluxo dela de ponta a ponta. A fonte canônica é `.agents/skills/plan-issue/SKILL.md` — não a transcreva nem recrie o fluxo.
|
||||
|
||||
$ARGUMENTS
|
||||
8
.opencode/commands/work-issue.md
Normal file
8
.opencode/commands/work-issue.md
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
description: Executa uma Issue claimada de ponta a ponta com supervisão humana (claim → plano de implementação → pausa → execução → simplify → PR)
|
||||
model: deepseek/deepseek-v4-flash
|
||||
---
|
||||
|
||||
Carregue a skill `work-issue` (ferramenta de skills, nome exato) e siga o fluxo dela de ponta a ponta. A fonte canônica é `.agents/skills/work-issue/SKILL.md` — não a transcreva nem recrie o fluxo.
|
||||
|
||||
$ARGUMENTS
|
||||
|
|
@ -6,6 +6,8 @@ Conteúdo movido do `AGENTS.md` em 2026-07-30 (fatiamento do paradigma de agente
|
|||
|
||||
**Recently resolved (2026-08-10):** **OPS24** (Issue #569) — `--go` virou o **padrão** do worktree CLI e `kill` volta ao main: `worktree next`/`plan` imprimem `cd <dir>` na última linha **por padrão** (antes exigiam `--go`); `--stay` suprime; `--go` explícito continua aceito como no-op (não quebra hábito/automação). `kill` troca a mensagem "Volte ao main: cd …" (que nunca casava com o sed `s/^cd //p` da função shell) pela **linha máquina `cd <main>` como última linha** — depois do drop dos DBs, que rodava antes — e **não aceita `--stay`** (ficar em cwd destruído é exatamente o bug corrigido; `--go` no kill é no-op). Vale nas duas superfícies que aplicam o cd: função `worktree()` de `.agents/shell/worktree.sh` (agora sempre captura/applica, `--stay` retorna cedo) e comando `/worktree` do opencode (agente aplica o `cd` da última linha sempre, exceto `--stay`). Docs: header/uso do script, AGENTS.md ("Per-worktree environments"), skills `worktree-next-issue`/`local-database`. Sem migration/dados/access; derivação pura (`worktree-env.mjs`/`worktree.mjs` libs) intocada. Intenção [`worktree-go-default-e-kill-volta-ao-main.md`](plans/worktree-go-default-e-kill-volta-ao-main.md); impl [`worktree-go-default-e-kill-volta-ao-main-impl.md`](plans/worktree-go-default-e-kill-volta-ao-main-impl.md).
|
||||
|
||||
**Recently resolved (2026-08-10):** **OPS25** (Issue #570) — atalhos diretos no TUI do opencode: `.opencode/commands/work-issue.md` e `plan-issue.md` (mecanismo nativo de commands, mesmo padrão de `issue.md`/`worktree.md`). O frontmatter leva `description` pt-BR (autocomplete do TUI) + `model: deepseek/deepseek-v4-flash` (pin do modelo de preferência, alinhado ao launch da OPS26 com `--model`); o corpo instrui carregar a skill de **mesmo nome** pela ferramenta de skills e segui-la ponta a ponta, apontando a fonte canônica `.agents/skills/<nome>/SKILL.md` — **sem transcrever** (anti-goal da intenção) — com `$ARGUMENTS` repassado no fim. **Guard de acoplamento** `tests/unit/opencodeCommands.unit.spec.ts`: comando↔skill presos por nome exato (renome de skill → build falha em vez de comando quebrado silencioso). Sem migration/access/Consent/UI. Intenção [`opencode-comandos-work-issue-e-plan-issue.md`](plans/opencode-comandos-work-issue-e-plan-issue.md); impl [`opencode-comandos-work-issue-e-plan-issue-impl.md`](plans/opencode-comandos-work-issue-e-plan-issue-impl.md).
|
||||
|
||||
**Recently resolved (2026-08-10):** **B192** (Issue #533) — o Sollinha encerra respostas factuais com **follow-ups sugeridos como chips** (2–3, no mesmo slot dos chips de abertura do B191, acima do input; tocar envia como mensagem do usuário; substituídos a cada resposta; somem durante o streaming; mobile limita a 2). **Uma única chamada ao modelo:** o prompt (`systemPrompt.ts`) instrui encerrar com o bloco estável `**Sugestões de continuação:**` + lista markdown (só perguntas respondíveis pelas próprias tools, leader sem sugestão eleitoral — B180; bloco = instrução de interface, nunca citar em prosa) e o cliente extrai. **Contrato em fonte única:** `src/lib/sollinhaFollowUpSuggestions.ts` — `SOLLINHA_FOLLOW_UP_MARKER` (interpolado no prompt) + `splitSollinhaFollowUpBlock` (última ocorrência vence; body sempre mascarado do marcador em diante; itens `-/*/1./1)` aceitos, bold e duplicados limpos, teto 3; **fail-closed: <2 itens ou linha não-lista → sem chips**, resposta normal). UI: strip no render de todo text part assistant **antes** do markdown/links (bloco nunca pisca como prosa; mensagens antigas restauradas via B188 continuam mascaradas e os chips renascem por derivação); slot B191 estendido (catálogo vazio → follow-ups); tipo compartilhado renomeado `SollinhaOpeningQuestion → SollinhaChatChip`. **Strip defensivo no servidor:** `route.ts` remove o bloco das mensagens assistant ao reconverter para o modelo (próximo turno sem eco nem custo de contexto). Unit (16 casos do split) + e2e novo `campaignAiChatFollowUps.e2e.spec.ts` (wire format v7 como o mock do B187; bolha sem o bloco, chips, substituição, fail-closed, cap mobile). Sem migration/access/Consent/schema/endpoint novo. Intenção [`sollinha-follow-ups-respostas.md`](plans/sollinha-follow-ups-respostas.md); impl [`sollinha-follow-ups-respostas-impl.md`](plans/sollinha-follow-ups-respostas-impl.md).
|
||||
|
||||
**Recently resolved (2026-08-10):** **OPS23** (Issue #566) — a Action OPS18 (`plan-issue-ready-on-main-merge.yml`) falhava **sempre** no primeiro disparo real (PR #563, run 31381977772): o job roda `actions/setup-node@v5` sem `pnpm/action-setup` e o script é plain Node — com `package-manager-cache: true` (default do v5), o setup-node resolve o `packageManager` do package.json (`pnpm@10.11.0`) e procura o executável pnpm → `Unable to locate executable file: pnpm`, promote nunca rodava. Correção: `package-manager-cache: false` no setup-node (precedente exato dos jobs plain-Node: cooldown do `ci.yml`, `vercel-promote.yml`, `archive-cursor-agent.yml`; `pnpm/action-setup@v6` rejeitado — só existe onde há `pnpm install`). Varredura dos 21 usos de setup-node: `plan-issue-ready` era o único com o bug latente. Validação funcional = próximo merge de PR com `Related #N` (trigger `closed+merged→main` só dispara em merge real). Impl [`ops23-action-promote-related-setup-node-impl.md`](plans/ops23-action-promote-related-setup-node-impl.md).
|
||||
|
|
|
|||
97
docs/plans/opencode-comandos-work-issue-e-plan-issue-impl.md
Normal file
97
docs/plans/opencode-comandos-work-issue-e-plan-issue-impl.md
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
# Impl: OpenCode: comandos `/work-issue` e `/plan-issue` (atalho direto para as skills)
|
||||
|
||||
Status: aprovado
|
||||
Atualizado em: 2026-08-10
|
||||
Issue: #570
|
||||
Intenção: docs/plans/opencode-comandos-work-issue-e-plan-issue.md
|
||||
Appetite restante: herdado (~0,5 dia eng) — não estoura
|
||||
|
||||
## Leitura da intenção
|
||||
|
||||
- **Outcome:** digitar `/work-issue` ou `/plan-issue` no TUI do opencode dispara, com zero fricção, a skill de mesmo nome — carregada pela ferramenta de skills, seguida ponta a ponta, com `$ARGUMENTS` repassado; os comandos aparecem no autocomplete.
|
||||
- **O que NÃO negociar:** o corpo do comando **não** transcreve a skill (fonte canônica é `.agents/skills/`); não existe terceiro fluxo de planejamento; só as duas skills pedidas ganham comando.
|
||||
- **O que reavaliar:** a hipótese de `model:` no frontmatter (ver decisão 1) e se o comando deve fixar `agent` (ver decisão 2) — ambas já direcionadas pela intenção, confirmadas contra a fonte oficial do opencode.
|
||||
|
||||
## Abordagem recomendada
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A[Usuário digita /work-issue ou /plan-issue] --> B[opencode expande .opencode/commands/<nome>.md]
|
||||
B --> C[Frontmatter: description p/ autocomplete + model deepseek/deepseek-v4-flash]
|
||||
C --> D[Corpo: instrução curta — carregar skill pelo nome exato via ferramenta de skills]
|
||||
D --> E[$ARGUMENTS repassados no fim]
|
||||
E --> F[Agente carrega .agents/skills/<nome>/SKILL.md e segue o fluxo ponta a ponta]
|
||||
```
|
||||
|
||||
**Opções consideradas:** A | B | C
|
||||
**Recomendação:** A — dois arquivos markdown em `.opencode/commands/` seguindo o mecanismo nativo de commands (mesmo precedente de `issue.md`/`worktree.md`), frontmatter `description` + `model`, corpo de instrução sem transcrição, `$ARGUMENTS` no fim. Porque é o único caminho que usa o mecanismo do produto (autocomplete no TUI vem do frontmatter `description`) sem criar infra paralela.
|
||||
|
||||
### Decisões de engenharia
|
||||
|
||||
**1. `model:` no frontmatter dos comandos?**
|
||||
|
||||
- Opções: A) `deepseek/deepseek-v4-flash` nos dois | B) herdar o modelo da sessão.
|
||||
- Recomendação: **A** — o OPS26 já lança o opencode com `--model deepseek/deepseek-v4-flash` e `/work-issue` como primeira mensagem; o atalho manual deve produzir a mesma sessão de modelo de preferência. `model` é frontmatter documentado do opencode (formato `provider/model`). O pool roda em Cursor, não no TUI do opencode — o pin não conflita com o `model:` da Issue.
|
||||
- Rejeitada: B porque a sessão herdaria o último modelo usado, e o launch automático e o atalho manual divergiriam.
|
||||
|
||||
**2. `agent` no frontmatter?**
|
||||
|
||||
- Opções: A) não fixar (usa o agente da sessão) | B) fixar `build`.
|
||||
- Recomendação: **A** — o fluxo de `work-issue` exige pausa para confirmação humana e deliberação (Plan mode), que vivem no agente da sessão; fixar `build` quebraria o gate humano e `plan-issue` tem gate próprio. `agent` é opcional e defaults para o agente da sessão.
|
||||
- Rejeitada: B porque o atalho não deve escolher o agente — a skill decide o modo.
|
||||
|
||||
**3. Como guardar o acoplamento nome-comando ↔ nome-skill?**
|
||||
|
||||
- Opções: A) teste unit leve que pina os dois nomes | B) confiar em convenção (nomes amarrados "na mão").
|
||||
- Recomendação: **A** — spec novo `tests/unit/opencodeCommands.unit.spec.ts` (sem DB, síncrono, barato): para cada comando novo, existe o arquivo, o frontmatter tem `description` e `model: deepseek/deepseek-v4-flash`, o corpo referencia o nome exato da skill **e** `.agents/skills/<nome>/SKILL.md` existe. Renomear uma skill → build falha → renomear o comando junto.
|
||||
- Rejeitada: B porque o risco da intenção ("renomeou a skill, o comando quebra silencioso") é exatamente o que um teste de 20 linhas elimina.
|
||||
|
||||
### Componentes / mudanças
|
||||
|
||||
- **`.opencode/commands/work-issue.md`** (novo): frontmatter `description` (pt-BR, autocomplete) + `model: deepseek/deepseek-v4-flash`; corpo instruindo carregar a skill `work-issue` pela ferramenta de skills e segui-la ponta a ponta; `$ARGUMENTS` no fim.
|
||||
- **`.opencode/commands/plan-issue.md`** (novo): idem, referenciando a skill `plan-issue`.
|
||||
- **`tests/unit/opencodeCommands.unit.spec.ts`** (novo): guard de acoplamento (decisão 3).
|
||||
- **Migration:** sem migration (nada de schema).
|
||||
- **Access / Consent:** N/A — arquivos de configuração do opencode.
|
||||
- **UI:** Impeccable A — N/A (sem UI de produto).
|
||||
|
||||
### Formato do comando (exemplo)
|
||||
|
||||
```markdown
|
||||
---
|
||||
description: <descrição curta em pt-BR para o autocomplete>
|
||||
model: deepseek/deepseek-v4-flash
|
||||
---
|
||||
|
||||
Carregue a skill `<nome>` (ferramenta de skills, nome exato) e siga o fluxo dela de ponta a ponta. A fonte canônica é `.agents/skills/<nome>/SKILL.md` — não a transcreva nem recrie o fluxo.
|
||||
|
||||
$ARGUMENTS
|
||||
```
|
||||
|
||||
## Fases verificáveis
|
||||
|
||||
1. **Comandos** — criar `.opencode/commands/work-issue.md` e `plan-issue.md` (frontmatter + corpo + `$ARGUMENTS`); sem tocar em `issue.md`/`worktree.md` nem em `opencode.json`.
|
||||
2. **Guard de acoplamento** — `tests/unit/opencodeCommands.unit.spec.ts` (frontmatter, nome da skill, `$ARGUMENTS`, existência da skill referenciada).
|
||||
3. **Gates** — `pnpm gate:fast` (inclui prettier — `.opencode/` não está no `.prettierignore` —, lint, tsc, testes); `pnpm push`; PR Ready `--base main` com `Closes #570` + auto-merge.
|
||||
|
||||
Verificação manual pós-merge (pelo humano, não em CI): `/work-issue` e `/plan-issue` no autocomplete do TUI.
|
||||
|
||||
## Rabbit holes / Não escopo (engenharia)
|
||||
|
||||
- Um comando por skill do repo (só as duas; o padrão fica provado pelo par).
|
||||
- Transcrever o corpo das skills dentro do comando (duplicação cara de manter).
|
||||
- Validar o TUI em CI (autocomplete é comportamento do produto, não testável headless aqui).
|
||||
- `subtask`/plugins/`opencode.json` — nada disso é necessário.
|
||||
|
||||
## Riscos e mitigação
|
||||
|
||||
- **Diferença de versão do opencode no parsing do frontmatter:** mitigado usando só chaves documentadas (`description`, `model`) — verificadas na fonte oficial (opencode.ai/docs/commands, atualizada 2026-08-09).
|
||||
- **Renome de skill quebra o comando silenciosamente:** mitigado pelo guard unit (decisão 3) — o build falha junto.
|
||||
- **`model:` no frontmatter sobrescreve o modelo da sessão:** é o comportamento desejado (OPS26 alinhado); documentado na decisão 1.
|
||||
- **Prettier rejeitar o frontmatter YAML:** mitigado escrevendo frontmatter mínimo no estilo dos arquivos existentes e rodando `pnpm format:check` na fase 3.
|
||||
|
||||
## Aceite de engenharia
|
||||
|
||||
- [x] Aceite de produto da intenção ainda coberto (atalho direto às duas skills, sem transcrição, `$ARGUMENTS` repassado, autocomplete)
|
||||
- [x] Invariantes AGENTS/engineering-standards (sem DB, sem schema, sem PII — arquivos de config)
|
||||
- [x] Testes previstos: `tests/unit/opencodeCommands.unit.spec.ts` (guard de acoplamento)
|
||||
40
tests/unit/opencodeCommands.unit.spec.ts
Normal file
40
tests/unit/opencodeCommands.unit.spec.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import { existsSync, readFileSync } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
// Programmatic guard for the OPS25 coupling: each `.opencode/commands/<name>.md`
|
||||
// must keep pointing at the skill of the same name (`.agents/skills/<name>/SKILL.md`).
|
||||
// The command body deliberately does NOT transcribe the skill — the canonical
|
||||
// source is the SKILL.md. If a skill is renamed, this spec fails the build so the
|
||||
// command is renamed together instead of breaking silently at the `/` prompt.
|
||||
|
||||
const repoRoot = process.cwd()
|
||||
|
||||
const commands = ['work-issue', 'plan-issue'] as const
|
||||
|
||||
describe('opencode commands couple to their skill by exact name', () => {
|
||||
it.each(commands)('command /%s references the existing skill of the same name', (name) => {
|
||||
const commandPath = resolve(repoRoot, `.opencode/commands/${name}.md`)
|
||||
const skillPath = resolve(repoRoot, `.agents/skills/${name}/SKILL.md`)
|
||||
|
||||
expect(existsSync(commandPath), 'command file must exist').toBe(true)
|
||||
expect(existsSync(skillPath), 'referenced skill must exist').toBe(true)
|
||||
|
||||
const content = readFileSync(commandPath, 'utf8')
|
||||
|
||||
expect(content, 'command must reference the skill by exact name').toContain(`\`${name}\``)
|
||||
expect(content, 'command must pass arguments through').toContain('$ARGUMENTS')
|
||||
expect(content, 'command must point at the canonical skill file').toContain(
|
||||
`.agents/skills/${name}/SKILL.md`,
|
||||
)
|
||||
|
||||
const frontmatter = content.match(/^---\n([\s\S]*?)\n---/)
|
||||
expect(frontmatter, 'frontmatter must be delimited by ---').not.toBeNull()
|
||||
expect(frontmatter![1], 'frontmatter must declare a TUI description').toMatch(
|
||||
/^description: .+$/m,
|
||||
)
|
||||
expect(frontmatter![1], 'frontmatter must pin the preference model').toMatch(
|
||||
/^model: deepseek\/deepseek-v4-flash$/m,
|
||||
)
|
||||
})
|
||||
})
|
||||
Loading…
Reference in a new issue