Clique nas respostas; o prompt se monta ao lado
Quem vai pedir um plano a um agente não quer escrever XML na mão nem lembrar de todas as seções que costumam faltar. Aqui as perguntas são o formulário, o prompt é a saída, e o arquivo continua sendo um só — sem build, sem servidor, sem dependência nova.
< ou & no alvo ou no
contexto, o prompt deixa de ser XML bem formado. Prefira escrever em prosa nesses dois campos.
Prompt de planejamento
Responda à esquerda; o prompt à direita é remontado a cada clique e fica pronto para colar no agente. As respostas ficam salvas neste navegador — voltar ao arquivo devolve a última combinação. Restaurar padrões volta ao estado de fábrica.
<task type="planning">
<objective>
Trocar o parser de configuração de YAML para TOML em toda a aplicação
</objective>
<context>
Monorepo Node 20 com quatro pacotes; a configuração é lida em onze lugares e dois deles rodam em CI.
</context>
<depth>
<level name="padrao">Etapas na ordem de execução, com os arquivos que cada uma toca e o risco que ela carrega.</level>
<budget max-lines="120">Detalhe o que muda uma decisão e resuma o resto.</budget>
</depth>
<deliverable>
<format name="markdown-checklist">Markdown com uma checklist em ordem de execução. Cada item começa com um verbo e cabe em uma linha.</format>
</deliverable>
<plan-sections>
<section id="riscos">O que pode dar errado, o sinal que denuncia cada caso e o que fazer quando ele aparecer.</section>
<section id="criterios-de-aceite">Como saber que terminou: um critério verificável por etapa, sem adjetivo.</section>
<section id="plano-de-testes">O que testar em cada etapa e o comando exato que roda o teste.</section>
</plan-sections>
<constraints>
<rule>Não introduza dependência nova. Se achar inevitável, pare e justifique em vez de assumir.</rule>
<rule>Nenhuma mudança que quebre a API pública ou o formato de dados já gravado em disco.</rule>
</constraints>
<protocol>
<step n="1">Antes de propor, liste os arquivos que o plano vai tocar e cite de cada um o trecho que sustenta a proposta.</step>
<step n="2">Não escreva código de produção nesta etapa. O entregável é o plano.</step>
<step n="3">Onde houver mais de um caminho, mostre as opções e o critério que decide entre elas — não escolha em silêncio.</step>
<step n="4">Marque explicitamente o que você NÃO verificou e o que depende de informação que só eu tenho.</step>
</protocol>
</task>
Seis perguntas, 38 linhas de prompt. O limite prático é doze perguntas: acima disso ninguém responde, e o construtor vira um formulário de imposto de renda.
Levar o construtor para um documento seu
São três blocos, e eles já estão marcados por comentário no fonte deste arquivo. Copie
nesta ordem — a spec e a casca dentro do .tab-pane, o runtime
no fim do <body>, depois do runtime do template.
| Bloco | Vai onde | Precisa mexer? |
|---|---|---|
pb:spec |
dentro do painel da aba | sim — é a sua spec: perguntas, fragmentos e o esqueleto do prompt |
pb:shell |
logo abaixo, no mesmo painel | quase nada — só o id e o data-pb-spec |
pb:runtime |
último <script> do arquivo |
não — copie inteiro, como o runtime do template |
1. A spec
Um bloco inerte com a declaração inteira. Cada pergunta vira um <fieldset>;
o corpo de cada <option> é o pedaço de prompt que ela injeta.
<script type="application/xml" id="pb-spec-plano">
<prompt-builder id="plano" lang="xml" title="Monte o prompt que pede o plano">
<question id="profundidade" type="radio" label="Profundidade do plano">
<option value="padrao" label="Padrão" default="true"><![CDATA[<level name="padrao">Etapas na ordem de execução.</level>]]></option>
<option value="exaustivo" label="Exaustivo"><![CDATA[<level name="exaustivo">Leia o código antes de propor.</level>]]></option>
</question>
<template><![CDATA[
<task type="planning">
<depth>
{{profundidade}}
</depth>
</task>
]]></template>
</prompt-builder>
</script>
2. A casca
O autor escreve só isto; o runtime preenche o [data-pb-form]. O que não pode
faltar: data-pb-spec apontando para o id do bloco acima, o
<pre data-live> em volta da saída, e o <noscript>.
<div class="row g-4 prompt-builder" id="pb-plano" data-pb-spec="pb-spec-plano">
<div class="col-lg-5">
<div data-pb-form><noscript>…aviso…</noscript></div>
</div>
<div class="col-lg-7">
<div class="position-sticky" style="top: 5rem">
<button type="button" class="btn btn-primary" data-pb-copy>Copiar prompt</button>
<button type="button" class="btn btn-outline-secondary" data-pb-reset>Restaurar padrões</button>
<span data-pb-status aria-live="polite"></span>
<pre data-live><code class="language-xml" data-pb-output>…prompt padrão, escapado…</code></pre>
</div>
</div>
</div>
<code> no próprio HTML. Assim impressão, PDF, leitor sem JavaScript e
view-source continuam entregando algo utilizável — o JavaScript só atualiza o que
já estava certo.
3. As duas linhas do template.html
O runtime "congelado" do template muda em exatamente dois pontos. Nada mais.
a) o laço que guarda a fonte crua passa a pular blocos vivos:
document.querySelectorAll('pre > code').forEach(function (code) {
+ if (code.closest('[data-live]')) return; // texto muda em runtime: leia na hora do clique
sources.set(code, code.textContent.replace(/\n$/, ''));
});
Sem essa linha o cache é tirado no load e nunca mais atualizado: o botãozinho
de hover do bloco copiaria o prompt inicial para sempre, mesmo depois de vinte
cliques. O sintoma é cruel porque a tela mostra o texto novo e a área de transferência traz o velho.
b) a função de copiar vira reutilizável:
+ // Exposto para quem estende o documento (ex.: o construtor de prompt) reusar o
+ // mesmo caminho de cópia em vez de duplicar o fallback de file://.
+ window.__explainerCopy = copyText;
O botão grande do construtor usa esse gancho quando ele existe e cai num
execCommand('copy') próprio quando não existe — então o bloco
pb:runtime também funciona colado num HTML que não veio desta skill.
Antes de entregar
- Rode
check-doc.mjs: ele confere a casca, a spec e cada{{marcador}}. - Clique em todas as opções e olhe o prompt: fragmento multi-linha desalinhado aparece na hora.
- Desmarque uma pergunta inteira de
checkbox— o elemento vazio que sobra ainda precisa ser XML válido. - Copie e cole num agente de verdade. Prompt que ninguém testou é prompt que não existe.
Quatro elementos, e acabou
Não há condicional, laço nem aninhamento. Fragmento é a única forma de condicionar —
se um pedaço do prompt só faz sentido em certo caso, ele é o corpo de uma <option>.
Essa limitação é deliberada: um mini-motor de template dentro do documento seria mais código
do que o construtor inteiro.
<prompt-builder> — a raiz
| Atributo | Obrig. | Valor |
|---|---|---|
id | sim | [a-z][a-z0-9-]*. Prefixa todo id gerado e é a chave do localStorage |
lang | não | linguagem do bloco de saída para o destaque. Padrão xml |
title | não | título exibido acima do formulário |
<question> — uma por fieldset
| Atributo | Obrig. | Valor |
|---|---|---|
id | sim | [a-z][a-z0-9-]*, único no construtor. É o nome usado em {{id}} |
type | sim | radio · checkbox · text · textarea |
label | sim | vira a <legend> do <fieldset> |
help | não | uma linha de ajuda sob o rótulo |
join | não | só em checkbox: newline (padrão) · blank-line · comma · space |
placeholder | não | só em text/textarea |
default | não | só em text/textarea: o valor inicial |
<option> — o fragmento vive no corpo
value e label são obrigatórios; default="true" marca a
inicial — exatamente uma em radio, zero ou mais em
checkbox. O corpo, sempre em CDATA, é o que entra no prompt.
Corpo vazio significa "o fragmento é o próprio value".
<template> — exatamente um
O esqueleto, também em CDATA, com os marcadores {{id-da-pergunta}}.
A indentação comum é removida na leitura, então dá para indentar o CDATA dentro
da spec sem sujar a saída.
As duas regras de substituição que importam
Indentação preservada. Marcador sozinho na linha aplica a própria indentação a todas as linhas do fragmento. Sem isso, fragmento de duas linhas sai torto:
<depth>
{{profundidade}}
</depth>
<depth>
<level name="padrao">Etapas na ordem.</level>
<budget max-lines="120">Resuma o resto.</budget>
</depth>
Linha vazia some. Marcador sozinho cuja resposta é vazia — nenhum
checkbox marcado, campo de texto em branco — leva a linha inteira embora,
sem deixar buraco:
<constraints>
</constraints>
<constraints>
</constraints>
Um {{x}} sem pergunta x fica literal no texto — e o
check-doc.mjs reprova o arquivo. É de propósito: marcador órfão significa
pergunta renomeada e esquecida.
Como cada tipo vira fragmento
| Tipo | O que {{id}} devolve |
|---|---|
radio | o corpo da opção escolhida |
checkbox |
os corpos das marcadas, na ordem da spec (não na ordem do clique), unidos pelo join:
newline = \n · blank-line = \n\n ·
comma = vírgula e espaço · space = um espaço |
text / textarea |
o valor digitado, com as pontas aparadas e sem escape — ver o aviso no topo |
Cinco armadilhas do runtime
Ele devolve um documento bem-sucedido contendo um elemento
<parsererror>. Um try/catch em volta não pega nada, e o
construtor abriria vazio sem uma linha de erro em lugar nenhum.
const doc = new DOMParser().parseFromString(xml, 'application/xml');
const bad = doc.querySelector('parsererror'); // sem namespace: casa em qualquer um
if (bad) return { error: bad.textContent.trim().split('\n')[0] };
<task type="planning"> em
innerHTML faz o navegador criar um elemento e sumir com a tag da tela.
E, como parte do texto vem de campo digitado, seria injeção de HTML de brinde.
É textContent, sempre.
Da versão 11.9.0 em diante ele grava data-highlighted="yes" e sai fora na
chamada seguinte, escrevendo um aviso no console. O segundo clique deixaria o prompt
sem cor nenhuma. A ordem é sempre esta:
code.textContent = prompt; // textContent, nunca innerHTML
delete code.dataset.highlighted; // sem isto, o segundo clique sai sem cor
if (window.hljs) hljs.highlightElement(code);
Vale para a remontagem. Na primeira, não: o hljs.highlightAll()
do runtime do documento ainda está agendado para o DOMContentLoaded, e
destacar antes dele faria o aviso aparecer em toda carga. Por isso a montagem inicial
escreve o texto e deixa o destaque para o highlightAll — a menos que o
documento já esteja pronto, quando não há mais ninguém para destacar.
<script type="application/xml"> o conteúdo é texto cru até a
primeira sequência de fechamento — inclusive dentro de CDATA, porque quem
corta é o parser de HTML, que não sabe o que é CDATA. Um prompt que ensina a
escrever <script> precisa quebrar a sequência com
<\/script>. Em compensação, o < comum não precisa de
escape nenhum ali dentro: é essa a razão de a spec ser um bloco de script e não um
<template> de HTML.
file:// o simples acesso lança
SecurityError — não só a escrita, a leitura também. Sem
try/catch nos dois lados, o handler morre antes de montar o prompt e o
construtor fica mudo. A persistência é conveniência; nada pode depender dela.
Acessibilidade: onde o aria-live vai
No [data-pb-status], com uma frase curta do tipo
"prompt atualizado · 38 linhas" — nunca no bloco de código. Um
aria-live no <pre> faria o leitor de tela reler as 38 linhas a
cada clique, o que transforma o construtor em algo inutilizável exatamente para quem mais
depende de um resumo. Cada pergunta é um <fieldset> com
<legend>, e todo controle tem <label for> casado — nos
campos de texto o rótulo é visually-hidden, porque a <legend>
já diz a mesma coisa na tela.