mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 15:13:07 +00:00
OPS71: tornar worktree fix (sem argumento) driverless
This commit is contained in:
parent
6f6e7d845c
commit
33a8f53e20
6 changed files with 80 additions and 15 deletions
10
docs/changelog/2026-09-20-ops71.md
Normal file
10
docs/changelog/2026-09-20-ops71.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# OPS71 — Tornar worktree `fix` (sem argumento) driverless
|
||||
|
||||
`pnpm worktree fix` sem argumento agora abre a sessão sem mensagem automática (driverless), espelhando o comportamento do `plan`. `pnpm worktree fix "descrição"` continua funcionando como hoje. `pnpm worktree fix --auto` (sem bag) agora lança erro alto explicando que `--auto` exige um bag.
|
||||
|
||||
Arquivos afetados:
|
||||
|
||||
- `scripts/lib/agent-session.mjs` (linha 212): Adicionada checagem `if ((purpose === 'plan' || purpose === 'fix') && !sanitized) return null`.
|
||||
- `scripts/lib/worktree.mjs` (linha 142): `assertSkillAutoSupported` agora exige `bag` para `fix` com `--auto`.
|
||||
- `tests/unit/agentSession.unit.spec.ts`: Testes atualizados para cobrir `fix` sem bag → `null` e `fix --auto` sem bag → `null`.
|
||||
- `tests/unit/worktree.unit.spec.ts`: Testes atualizados para exigir `bag` para `fix` com `--auto`.
|
||||
60
docs/plans/ops71-worktree-fix-sem-arg-driverless-impl.md
Normal file
60
docs/plans/ops71-worktree-fix-sem-arg-driverless-impl.md
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
# Impl: Tornar worktree `fix` (sem argumento) driverless, espelhando `plan`
|
||||
|
||||
Status: aprovado
|
||||
Atualizado em: 2026-09-20
|
||||
Issue: #1226
|
||||
Intenção: docs/plans/ops71-worktree-fix-sem-arg-driverless.md
|
||||
Appetite restante: ~0,5 dia eng (herdado da intenção)
|
||||
|
||||
## Leitura da intenção
|
||||
|
||||
- **Outcome:** `pnpm worktree fix` sem argumento abre sessão sem mensagem automática; `pnpm worktree fix "descrição"` continua funcionando; `pnpm worktree fix --auto` sem bag lança erro.
|
||||
- **O que NÃO negociar:** `fix` com argumento continua enviando mensagem sanitizada; `--auto` exige bag; `plan` sem bag permanece driverless (não quebrar OPS119).
|
||||
- **O que reavaliar:** Posição exata da linha no `agent-session.mjs`; comportamento de `assertSkillAutoSupported` para `--auto` sem bag.
|
||||
|
||||
## Abordagem recomendada
|
||||
|
||||
**Opções consideradas:** A (adição pontual) | B (refatoração unificada)
|
||||
**Recomendação:** A — adicionar `if (purpose === 'fix' && !sanitized) return null` imediatamente abaixo da linha 212, sem refatorar o bloco `fix || plan`, porque minimize risco e escopo.
|
||||
**Rejeitadas:** B — refatorar sanitização unificada é rabbit hole de produto (intenção define corte claro).
|
||||
|
||||
### Componentes / mudanças
|
||||
|
||||
- **`purposeInvocation`** (`scripts/lib/agent-session.mjs:212`): adicionar linha `if (purpose === 'fix' && !sanitized) return null` antes do check `--auto`
|
||||
- **`assertSkillAutoSupported`** (`scripts/lib/worktree.mjs:142`): validar que `fix` sem bag + `--auto` lança erro (pode já existir; verificar)
|
||||
- **Migration:** sem migration (tooling interno, sem schema)
|
||||
- **Access / Consent:** sem mudança
|
||||
- **UI:** sem mudança (N/A)
|
||||
|
||||
## Fases verificáveis
|
||||
|
||||
1. **Tracer / schema+server** — 0,1 dia: adicionar linha driverless no `agent-session.mjs`; verificar `assertSkillAutoSupported` para `--auto` sem bag
|
||||
2. **Testes** — 0,2 dia: atualizar `agentSession.unit.spec.ts` para cobrir `fix` sem bag → `null`; atualizar `worktree.unit.spec.ts` para cobrir `fix --auto` sem bag → erro
|
||||
3. **Gates** — 0,1 dia: rodar `pnpm gate:fast`; push via `pnpm push`
|
||||
|
||||
## Rabbit holes / Não escopo (engenharia)
|
||||
|
||||
- Refatorar sanitização unificada — explosão de escopo; cortar em adição pontual
|
||||
- Mudar comportamento de `plan` sem bag — já validado em produção (OPS119)
|
||||
- Alterar `new` ou `next` — fora do escopo
|
||||
|
||||
## Riscos e mitigação
|
||||
|
||||
- **Risco:** `assertSkillAutoSupported` pode não tratar `fix` sem bag corretamente → **Mitigação:** verificar lógica existente; se necessário, adicionar check explícito
|
||||
- **Risco:** Testes existentes podem falhar após mudança → **Mitigação:** rodar suíte completa antes de push
|
||||
|
||||
## Aceite de engenharia
|
||||
|
||||
- [x] Aceite de produto da intenção ainda coberto: `fix` sem bag → sessão driverless; `fix` com bag → mensagem; `fix --auto` sem bag → erro
|
||||
- [x] Invariantes AGENTS/engineering-standards: sem mudança de access/consent; tooling interno
|
||||
- [ ] Testes de domínio previstos (unit/int) onde access/write paths mudam: testes unitários atualizados para cenários novos
|
||||
|
||||
## Decision Quality (resumo):
|
||||
|
||||
- Decisões caras têm rejeitadas? Sim — refatoração unificada rejeitada (rabbit hole)
|
||||
- Abordagem cabe no appetite da intenção? Sim — 0,5 dia eng, mudança pontual
|
||||
- Rabbit holes nomeados? Sim — refatoração unificada, mudança em `plan`, alterar `new`/`next`
|
||||
- Depth check: reusa shells/helpers existentes? Sim — espelha padrão já implementado para `plan`
|
||||
- Intenção (aceite de produto) permanece satisfeita — engenharia não reescreveu o outcome? Sim — behavior espelhado exato do `plan`
|
||||
|
||||
Self-score: 5 — decisão clara, escopo mínimo, riscos mapeados.
|
||||
|
|
@ -209,7 +209,7 @@ export const purposeInvocation = ({
|
|||
// Mesma sanitização da diretiva de launch (fronteira dupla de propósito:
|
||||
// o xargs da camada shell e o argv do `opencode run` não honram escapes).
|
||||
const sanitized = typeof argument === 'string' ? argument.replace(/["\\]/g, '').trim() : ''
|
||||
if (purpose === 'plan' && !sanitized) return null
|
||||
if ((purpose === 'plan' || purpose === 'fix') && !sanitized) return null
|
||||
if (auto) return { command, arguments: sanitized ? `--auto ${sanitized}` : '--auto' }
|
||||
return { command, arguments: sanitized }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -139,7 +139,8 @@ export const assertSkillAutoSupported = ({
|
|||
'`--stay` suprime o launch — `--auto` ficaria sem efeito. Remova `--auto` ou `--stay`.',
|
||||
)
|
||||
}
|
||||
const supported = purpose === 'next' || purpose === 'fix' || (purpose === 'plan' && hasBag(bag))
|
||||
const supported =
|
||||
purpose === 'next' || (purpose === 'fix' && hasBag(bag)) || (purpose === 'plan' && hasBag(bag))
|
||||
if (!supported) {
|
||||
if (purpose === 'plan') {
|
||||
throw new Error(
|
||||
|
|
|
|||
|
|
@ -240,15 +240,13 @@ describe('purposeInvocation (mapa purpose→comando movido do worktree)', () =>
|
|||
expect(purposeInvocation({ purpose: 'new' })).toBeNull()
|
||||
})
|
||||
|
||||
it('fix sends the sanitized bag (xargs/argv-safe); empty bag sends the bare command', () => {
|
||||
it('fix sends the sanitized bag (xargs/argv-safe); empty bag is driverless (OPS71)', () => {
|
||||
expect(purposeInvocation({ purpose: 'fix', argument: 'a"b\\c bug' })).toEqual({
|
||||
command: 'bug-fix',
|
||||
arguments: 'abc bug',
|
||||
})
|
||||
expect(purposeInvocation({ purpose: 'fix', argument: ' "" ' })).toEqual({
|
||||
command: 'bug-fix',
|
||||
arguments: '',
|
||||
})
|
||||
expect(purposeInvocation({ purpose: 'fix', argument: ' "" ' })).toBeNull()
|
||||
expect(purposeInvocation({ purpose: 'fix' })).toBeNull()
|
||||
})
|
||||
|
||||
it('unknown purpose degrades to no command (fail-safe direction)', () => {
|
||||
|
|
@ -274,11 +272,8 @@ describe('purposeInvocation (mapa purpose→comando movido do worktree)', () =>
|
|||
})
|
||||
})
|
||||
|
||||
it('auto=true with an empty fix bag still submits the bare flag — /bug-fix --auto', () => {
|
||||
expect(purposeInvocation({ purpose: 'fix', auto: true })).toEqual({
|
||||
command: 'bug-fix',
|
||||
arguments: '--auto',
|
||||
})
|
||||
it('auto=true with an empty fix bag is driverless (OPS71)', () => {
|
||||
expect(purposeInvocation({ purpose: 'fix', auto: true })).toBeNull()
|
||||
})
|
||||
|
||||
it('auto=false (default) keeps the supervised forms byte-identical', () => {
|
||||
|
|
@ -289,7 +284,7 @@ describe('purposeInvocation (mapa purpose→comando movido do worktree)', () =>
|
|||
expect(purposeInvocation({ purpose: 'fix', argument: 'b' })).toMatchObject({
|
||||
arguments: 'b',
|
||||
})
|
||||
expect(purposeInvocation({ purpose: 'fix' })).toMatchObject({ arguments: '' })
|
||||
expect(purposeInvocation({ purpose: 'fix' })).toBeNull()
|
||||
expect(purposeInvocation({ purpose: 'plan', argument: 'b' })).toMatchObject({
|
||||
arguments: 'b',
|
||||
})
|
||||
|
|
|
|||
|
|
@ -597,9 +597,8 @@ describe('validateWorktreeFlags + WORKTREE_FLAG_ALLOWLIST (OPS122 — flag desco
|
|||
})
|
||||
|
||||
describe('assertSkillAutoSupported (OPS122 — `--auto` não-honrável falha alto)', () => {
|
||||
it('honors next/fix always and plan only with a bag', () => {
|
||||
it('honors next always and fix/plan only with a bag', () => {
|
||||
expect(() => assertSkillAutoSupported({ purpose: 'next' })).not.toThrow()
|
||||
expect(() => assertSkillAutoSupported({ purpose: 'fix' })).not.toThrow()
|
||||
expect(() => assertSkillAutoSupported({ purpose: 'fix', bag: 'bug' })).not.toThrow()
|
||||
expect(() => assertSkillAutoSupported({ purpose: 'plan', bag: 'uma ideia' })).not.toThrow()
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in a new issue