dd-harness 0.37.0 → 0.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/pergunta.js CHANGED
@@ -18,8 +18,21 @@ export function fechaPerguntas() {
18
18
  rl?.close();
19
19
  rl = null;
20
20
  }
21
- /** Numera as opcoes, pede um numero, devolve o item escolhido. Sem loop de retry: erra, recomeca o comando — start nao e ambiente para validar input ao infinito. */
22
- export async function escolha(titulo, itens) {
21
+ /**
22
+ * Escolha de uma opcao: setas e Enter quando ha terminal, numeracao quando nao ha.
23
+ *
24
+ * As setas exigem raw mode, que so existe em TTY — e `start` tambem roda com stdin
25
+ * redirecionado (script, CI, `echo | dd-harness start`). Ali `setRawMode` nem existe, e
26
+ * chama-lo lanca. Por isso a numeracao continua inteira como caminho alternativo: nao e
27
+ * legado, e o modo que funciona quando nao ha quem pressione tecla.
28
+ */
29
+ export async function escolha(titulo, itens, inicial = 0) {
30
+ if (!itens.length)
31
+ throw new Error("escolha: nenhuma opção para escolher.");
32
+ const porTecla = Boolean(process.stdin.isTTY) && typeof process.stdin.setRawMode === "function";
33
+ return porTecla ? porSetas(titulo, itens, inicial) : porNumero(titulo, itens);
34
+ }
35
+ async function porNumero(titulo, itens) {
23
36
  console.log(titulo);
24
37
  itens.forEach((item, i) => console.log(` ${i + 1}) ${item.rotulo}`));
25
38
  const resposta = await pergunta("> ");
@@ -29,3 +42,59 @@ export async function escolha(titulo, itens) {
29
42
  throw new Error(`escolha um número entre 1 e ${itens.length}.`);
30
43
  return escolhido.valor;
31
44
  }
45
+ function porSetas(titulo, itens, inicial) {
46
+ return new Promise((resolve, reject) => {
47
+ // A interface de linha e a das setas disputam o mesmo stdin: com as duas ativas, cada
48
+ // tecla chega duas vezes. Esta fecha e a proxima `pergunta` abre outra.
49
+ fechaPerguntas();
50
+ let atual = Math.min(Math.max(inicial, 0), itens.length - 1);
51
+ let primeiraPintura = true;
52
+ const pinta = () => {
53
+ // Sobe e limpa as linhas da pintura anterior; na primeira vez nao ha o que apagar.
54
+ if (!primeiraPintura)
55
+ process.stdout.write(`\x1b[${itens.length}A\x1b[0J`);
56
+ primeiraPintura = false;
57
+ for (const [i, item] of itens.entries()) {
58
+ const alvo = i === atual;
59
+ // Negrito no selecionado, cinza no resto: a seta sozinha se perde quando a lista
60
+ // passa de tres ou quatro itens.
61
+ process.stdout.write(alvo ? `\x1b[1m❯ ${item.rotulo}\x1b[0m\n` : `\x1b[2m ${item.rotulo}\x1b[0m\n`);
62
+ }
63
+ };
64
+ const encerra = () => {
65
+ process.stdin.setRawMode(false);
66
+ process.stdin.pause();
67
+ process.stdin.off("data", aoTeclar);
68
+ };
69
+ const aoTeclar = (dado) => {
70
+ const tecla = dado.toString();
71
+ // Ctrl+C precisa continuar matando o processo: em raw mode o terminal nao o trata
72
+ // mais sozinho, e sem isto o wizard ficaria impossivel de abortar.
73
+ if (tecla === "\x03") {
74
+ encerra();
75
+ process.stdout.write("\n");
76
+ process.exit(130);
77
+ }
78
+ if (tecla === "\x1b[A" || tecla === "k") {
79
+ atual = (atual - 1 + itens.length) % itens.length;
80
+ pinta();
81
+ }
82
+ else if (tecla === "\x1b[B" || tecla === "j") {
83
+ atual = (atual + 1) % itens.length;
84
+ pinta();
85
+ }
86
+ else if (tecla === "\r" || tecla === "\n") {
87
+ encerra();
88
+ const escolhido = itens[atual];
89
+ if (!escolhido)
90
+ return reject(new Error("escolha: índice fora da lista."));
91
+ resolve(escolhido.valor);
92
+ }
93
+ };
94
+ console.log(titulo);
95
+ pinta();
96
+ process.stdin.setRawMode(true);
97
+ process.stdin.resume();
98
+ process.stdin.on("data", aoTeclar);
99
+ });
100
+ }
@@ -1,12 +1,12 @@
1
1
  /** Regra do produto, entregue junto da política sem reescrever o artefato no serviço. */
2
- export const REGRA_DE_COMMITS = `## Identificação do agente nos commits
3
-
4
- Todo commit feito por agente começa com [IDENTIFICAÇÃO] - tipo: descrição.
5
- Exemplos: [CODEX] - feat: nova funcionalidade; [CLAUDE] - fix: corrige a busca;
6
- [GEMINI] - docs: atualiza o guia. Outro agente usa seu nome em maiúsculas entre colchetes.
7
- Identifique o agente/modelo que efetivamente realizou o commit, não apenas o editor:
8
- Claude no Antigravity continua [CLAUDE]; Gemini usa [GEMINI]; Codex usa [CODEX].
9
- Não invente a identidade. A regra não concede autorização para commitar, não altera
2
+ export const REGRA_DE_COMMITS = `## Identificação do agente nos commits
3
+
4
+ Todo commit feito por agente começa com [IDENTIFICAÇÃO] - tipo: descrição.
5
+ Exemplos: [CODEX] - feat: nova funcionalidade; [CLAUDE] - fix: corrige a busca;
6
+ [GEMINI] - docs: atualiza o guia. Outro agente usa seu nome em maiúsculas entre colchetes.
7
+ Identifique o agente/modelo que efetivamente realizou o commit, não apenas o editor:
8
+ Claude no Antigravity continua [CLAUDE]; Gemini usa [GEMINI]; Codex usa [CODEX].
9
+ Não invente a identidade. A regra não concede autorização para commitar, não altera
10
10
  o autor do Git e não pede reescrita de commits antigos.`;
11
11
  export function politicaParaSessao(conteudo) {
12
12
  if (!conteudo.trim() || conteudo.includes(REGRA_DE_COMMITS))