`worktree plan` sem argumento cria a sessão persistente sem driver (contrato do `new`); com bag auto-submete uma única `/plan-issue <bag>` e usa o mesmo texto como rótulo do branch, espelhando o `fix`. - purposeInvocation (lib/agent-session): ramo plan reusa a sanitização do fix e devolve null quando o bag fica vazio (driverless por desenho). - opencodeLaunchDirective emite --argument para fix||plan; cmdPlan repassa argument: bag; cmdStart isenta plan do aviso "sem comando". - next/new/fix, --stay e o comando /worktree intactos. - Specs repinados; textos vivos e changelog atualizados. Débito de risk map das libs + pin do wiring registrado em #1071.
11 KiB
Impl: OPS119 — worktree plan: abrir sem auto-enviar o /plan-issue (mensagem opcional na linha)
Status: em execução Atualizado em: 2026-09-16 Issue: #1066 Intenção: docs/plans/ops119-worktree-plan-sem-auto-envio-plan-issue.md Appetite restante: herdado (~0,5 dia eng — 2 libs puras + 1 script + 2 specs + textos)
Leitura da intenção
- Outcome:
worktree plan(sem argumento) abre a sessão persistente sem driver (nenhuma mensagem auto-submetida, mesmo contrato observável donew);worktree plan "<mensagem>"auto-submete uma/plan-issue <mensagem>e usa a mesma mensagem como bag do branch — simétrico afix <bag>.next/new/fix,--staye o comando/worktreeintactos. - O que NÃO negociar: a mensagem passa pela mesma sanitização do
fix(replace(/["\\]/g,'')+trim); bag vazio não gera driver;nextsegue/work-issue --issue <N>;newsegue driverless;fix <bag>segue/bug-fix <bag>;--staye/ouTEQO_WORKTREE_TERMINALausente continuam sem diretiva. Sem migration/access/Consent/UI (Impeccable A). - O que reavaliar (hipóteses da "Direção no codebase" que o explorador corrigiu):
- O plano de intenção não previu o aviso
[agent:session] purpose=… sem comando/issue válida(scripts/agent-session.mjs:536-539):plansem msg cairia noelse if (purpose !== 'new')e logaria um falso alarme. Entra no escopo (é a mesma aceitação observável: "abre sem driver", sem ruído). - O docblock de
purposeInvocation(scripts/lib/agent-session.mjs:164-181) e o USAGE dostart(:83, "model obrigatório next/plan/fix") ficam imprecisos — oplandeixa de exigir--modelquando não há driver. Ajuste de texto barato, feito na passagem. planBranchName/namespaceBranchName(scripts/lib/worktree.mjs:150-170,277-278) já resolvem o branch a partir do bag (fallbackplano,slugify) — nada a mudar ali; o bag vazio já cai no sequencialplans/plan-issue-<n>.
- O plano de intenção não previu o aviso
Abordagem recomendada
flowchart LR
A["worktree plan [bag]"] --> B["cmdPlan (scripts/worktree.mjs)"]
B -->|"argument: bag"| C["cmdNamespaceBranch"]
C --> D["printLaunchDirective"]
D --> E["opencodeLaunchDirective (scripts/lib/worktree.mjs)"]
E -->|"plan + bag sanitizado → --argument=«JSON»"| F["launch agent-session start --purpose=plan [--argument]"]
F --> G["cmdStart → purposeInvocation (scripts/lib/agent-session.mjs)"]
G -->|"bag válido → {command:'plan-issue', arguments}"| H["spawn driverArgs (uma /plan-issue <msg>)"]
G -->|"sem bag / bag vazio → null"| I["sessão criada, SEM driver; só attach"]
H --> J["attach TUI"]
I --> J
Opções consideradas: A | B | C
Recomendação: A — o dono da decisão "quando um purpose carrega skill" é purposeInvocation (scripts/lib/agent-session.mjs), e o ramo do plan é cópia exata do ramo fix (:192-197): sanitiza, vazio → null, senão { command, arguments }. O opencodeLaunchDirective passa a emitir --argument para fix || plan e cmdPlan passa argument: bag — o restante do caminho (cmdNamespaceBranch runner, printLaunchDirective, driverArgs, dispatch) não muda.
Rejeitadas: B — manter purposeInvocation devolvendo comando "bare" para plan e decidir o não-driver dentro do cmdStart (CLI): quebra a pureza do dono do ciclo (a lib passaria a mentir "sempre há comando"), e a regra ficaria em dois lugares quando um 5º purpose chegar. C — criar uma flag nova de driver (--driver/--no-driver) na diretiva: vocabulário novo sem necessidade, e destrói a simetria com fix, que já expressa "tem driver" pela presença do bag.
Componentes / mudanças
purposeInvocation(scripts/lib/agent-session.mjs:184-199): ramoplanantes doreturn { command, arguments: '' }final, espelhandofix—const sanitized = typeof argument === 'string' ? argument.replace(/["\\]/g, '').trim() : '';if (!sanitized) return null;return { command, arguments: sanitized }. Ponto de divergência deliberada dofix: nofixbag vazio ainda auto-submete/bug-fixbare; noplan(sem contexto) vazio → sem driver.- Docblock de
purposeInvocation(:176-181): descreverplansem-arg →null, com-arg →/plan-issue <msg>;fixinalterado. opencodeLaunchDirective(scripts/lib/worktree.mjs:202-232): condiçãoif (purpose === 'fix')→if (purpose === 'fix' || purpose === 'plan')(:227); comentário do:224-226passa a citar o par. O sufixo do issue segue só donext(:223).cmdPlan(scripts/worktree.mjs:755-764): acrescentaargument: bagaocmdNamespaceBranch(comocmdFix :810).cmdNamespaceBranch(:675-743),printLaunchDirective(:221-235) e o dispatch (:1037-1038) intocados.cmdStart(scripts/agent-session.mjs:536-539):else if (purpose !== 'new')→else if (purpose !== 'new' && purpose !== 'plan')+ comentário ("plansem bag é driverless por desenho").- Textos do CLI: header/USAGE de
scripts/agent-session.mjs(:9,:74-83—--modelobrigatório "quando há driver"); docblock/uso doplanemscripts/worktree.mjs:68-83,979-992;.agents/shell/worktree.sh:9-31,43-49;.opencode/commands/worktree.md:8;.agents/skills/worktree-next-issue/SKILL.md:23,30,34;AGENTS.md:75;docs/AGENT-OPS.md:65,70. - Changelog:
docs/changelog/2026-09-16-ops119.md(uma entrada). Não editar o HISTORY nem o agregado. - Migration: sem migration (nenhum schema/payload é tocado).
- Access / Consent: N/A.
- UI: N/A (Impeccable A — CLI/shell).
Dados → forma (se aplicável)
- N/A — sem superfície de dados. A "forma" é o contrato observável do launch:
plan≡newsem bag;plan <bag>≡fix <bag>com a skill trocada. Rejeitada a forma "prefill-draft" (upstream inexistente no opencode 1.18.31 — proposição~/Code/propositions/opencode/prefill-prompt-sem-submit.mdre-flagada fora do repo).
Fases verificáveis
- F1 — Sessão (owner) ~40% do appetite: ramo
planempurposeInvocation+ docblock; isenção do aviso emcmdStart. Prova: unit doagentSession.unit.spec.ts(plan sem arg →null; com arg →{command:'plan-issue', arguments}; bag vazio/só aspas →null;next/fix/new/unknown inalterados). - F2 — Launch ~20%:
opencodeLaunchDirective(fix || plan) +cmdPlan(argument: bag). Prova: unit doworktree.unit.spec.ts(plan sem arg = diretiva de hoje; plan com bag carrega--argument="…"; plan ignoraissueNumber;newsegue ignorandoargument). - F3 — Testes + textos ~30%: repin dos dois specs; varredura dos textos listados por "plan auto-submete
/plan-issue"; entrada de changelog. - F4 — Gates ~10%:
pnpm gate:fast(lint + typecheck + unit) verde; walkthrough manual (terminal comopencodeinstalado):worktree planabre sem mensagem eworktree plan "mensagem"abre com/plan-issue mensagem+ branchplans/plan-issue-mensagem; push viapnpm push. Este PR tocascripts/worktree.mjs(HIGH_RISK_EXACT) ⇒ CI = full de qualquer forma.
Rabbit holes / Não escopo (engenharia)
- Novo vocabulário de CLI (subcomandos,
--message, separar bag de mensagem): cortado — só o par sem-arg/arg, idêntico aofix. - Prefill-sem-submit no opencode: upstream; apenas re-flagar a proposição (fora do repo).
- Gateway
--prompt/--variant: intocados (OPS95/OPS110). kill/attach/resolveStartDecision/deriveSessionStatus: agnósticos ao driver (driverPid:null→idle); não mexer.HIGH_RISK_EXACTnão cobrescripts/lib/agent-session.mjsnemscripts/lib/worktree.mjs(scripts/lib/test-affected-core.mjs:25-53): gap pré-existente e independente desta entrega (o PR atual tocascripts/worktree.mjs, que já é high-risk). Não expandido aqui; registrado como Issue de follow-up #1071 (planodocs/plans/escala-dry-pos-ops119.md, com o pin do wiring docmdPlan).
Riscos e mitigação
- Regressão em
next/new/fixao mexer na condição da diretiva: a mudança é aditiva (|| purpose === 'plan') e os specs denew/fixficam verdes sem edição no comportamento deles; teste explícito travanewignorarargumentefixmanter--argument. - Divergência entre slug do branch e mensagem sanitizada: branch usa
slugify(existente), mensagem reusa a sanitização dofix; ambos idempotentes entre a camada diretiva e opurposeInvocation. - Falso alarme "sem driver" no
plan: coberto pela isenção docmdStart(F1) — é justamente o que a intenção exige ("mesmo contrato observável donew"). --modeldito obrigatório mas ausente no plan driverless: o aviso/erro real (cmdStart:476) só dispara quandoinvocationexiste; texto do USAGE ajustado em F3.- Texto/doc remanescente afirmando auto-envio sem-arg: varredura por
/plan-issuenos arquivos listados antes do commit (histórico congelado intocado:docs/plans/worktree-plan-abre-opencode-com-plan-issue*.md,docs/CHANGELOG-AGENTS-HISTORY.md:241).
Triage pós-simplify (capture-review-debts)
- S4 (pin do wiring
cmdPlan→cmdNamespaceBranch) + S6 (HIGH_RISK_EXACTdas libs do launch) — score 3, mesma superfície → registrado como Issue #1071 (docs/plans/escala-dry-pos-ops119.md;kind:chore, P3,depends: OPS119). - S1 (
return { command, arguments: '' }final depurposeInvocation) — descartado: fallback fail-safe para um purpose mapeado sem args. - S2 (sanitização
["\\]duplicada entre as duas libs) — defer, gatilho: 3º call site ou import cruzado das libs. - S3 (política driverless em
!==no CLI) — defer, gatilho: 3º purpose driverless. - S5 (doc drift de
opencodeLaunchDirective/cmdNamespaceBranch+ fixture fantasma dodriverArgs) — já resolvido no simplify.
Aceite de engenharia
- Aceite de produto da intenção coberto:
plansem arg =null/driverless;plan <msg>={command:'plan-issue', arguments:<sanitized>};next/new/fix/--stay//worktreeintactos. - Invariantes AGENTS/engineering-standards: reusa o dono (
purposeInvocation/caminho dofix), sem módulo/abstração nova; sempush:false/schema/migration; sem segredo. - Testes de domínio (unit) repinados onde o contrato muda:
tests/unit/agentSession.unit.spec.tsetests/unit/worktree.unit.spec.ts(plan sem bag →null; plan com bag →--argumentsanitizado;new/fix/nextinalterados). - Textos vivos atualizados; histórico congelado intocado;
docs/changelog/2026-09-16-ops119.mdcriado. pnpm gate:fastverde; cadeia bag→diretiva→purposeInvocationverificada por execução direta das libs (walkthrough do launch registrado no PR).
Self-score decision-quality
- Decisões caras com rejeitadas? Sim — D1 (dono da decisão), D2 (bag vazio →
null, divergência deliberada dofix), D4 (isenção do aviso), D6 (classifier deferido). - Cabe no appetite (~0,5 dia)? Sim — 2 libs puras + 1 script + 2 specs + textos.
- Rabbit holes nomeados? Sim — vocabulário novo, prefill upstream, classifier, kill/attach.
- Depth check reusa? Sim — ramo
fix,slugify, sanitização edriverArgsexistentes; zero abstração nova. - Intenção preservada? Sim — engenharia não reescreve outcome; só estende o caminho já provado do
fix.
Self-score: 5/5 (gate ≥4 atendido). Status inicial rascunho — a pausa do GATE de aprovação humana vale.