primocode 10.7.1 → 10.8.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/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # PrimoCode v10.7.1
1
+ # PrimoCode v10.8.0
2
2
 
3
3
  Agente de engenharia brasileiro para o terminal, no estilo Claude Code.
4
4
  Cria arquivos de verdade, roda comandos, **controla o navegador e o desktop** —
@@ -9,7 +9,7 @@ OpenRouter, e cada apelido (`top`/`main`/`fast`) é uma **corrente**: se um mode
9
9
  está no limite de pedidos, o próximo atende. Você não vê o erro, e não paga nada.
10
10
 
11
11
  ```
12
- ▐▛███▜▌ Primo Code v10.7.1
12
+ ▐▛███▜▌ Primo Code v10.8.0
13
13
  ▝▜█████▛▘ Bem-vindo de volta, Joel
14
14
  ▘▘ ▝▝ ~/primocode · /dir <pasta> muda
15
15
  ```
@@ -0,0 +1,98 @@
1
+ /**
2
+ * conferencia.js — a conferência do vídeo PRONTO: o arquivo, medido, e não
3
+ * o plano.
4
+ *
5
+ * "gostei, ficou bom. mas ainda não. tá quase lá" (Joel, 11/10)
6
+ *
7
+ * O revisor.js olha o plano antes do render. Este olha o .mp4 que saiu, e
8
+ * troca o "quase lá" no olho por número que se confere sem abrir o vídeo
9
+ * (a ideia veio do review.mjs do cth9191/animate, MIT):
10
+ *
11
+ * som volume integrado (LUFS), variação (LRA) e pico verdadeiro, pelo
12
+ * ebur128 do ffmpeg. Alvo: -14 LUFS (±2) e pico até -1 dBTP.
13
+ * parado trecho em que a imagem não muda por 2 s ou mais, pelo
14
+ * freezedetect, com o quadro reduzido a 320 px. Calibrado em 13/10
15
+ * em 5 vídeos: na resolução cheia o ruído do fundo esconde a tela
16
+ * parada; a 160 px o texto fica pequeno demais e a palavra que
17
+ * entra não conta como mudança. A 320 px o teste-10-5-motion-com-
18
+ * imagem dá 6,5 s a 8,3 s ("torrado" parado, a pílula vazia, e
19
+ * para quando "café" entra), e os vídeos 3D não dão nada.
20
+ *
21
+ * Só mede e avisa. Não conserta nada: quem decide se refaz é o agente.
22
+ * As duas leituras (lerSom, lerParados) são puras e testáveis sem vídeo.
23
+ */
24
+
25
+ 'use strict';
26
+
27
+ const video = require('./video.js');
28
+
29
+ const ALVO = { lufs: -14, folgaLufs: 2, picoMax: -1, paradoMin: 2 };
30
+ const r1 = (n) => Math.round(n * 10) / 10;
31
+
32
+ /* O ebur128 escreve uma linha a cada 100 ms e um resumo no fim. Só o resumo
33
+ vale: a última linha "I:" de antes dele é a média até ali, não do vídeo. */
34
+ function lerSom(texto) {
35
+ const t = String(texto || '');
36
+ const i = t.lastIndexOf('Summary:');
37
+ if (i < 0) return null;
38
+ const resumo = t.slice(i);
39
+ const num = (re) => { const m = resumo.match(re); return m ? Number(m[1]) : null; };
40
+ const lufs = num(/I:\s*(-?[\d.]+|-inf)\s*LUFS/);
41
+ if (lufs == null) return null;
42
+ return { lufs, lra: num(/LRA:\s*(-?[\d.]+)\s*LU\b/), pico: num(/Peak:\s*(-?[\d.]+)\s*dBFS/) };
43
+ }
44
+
45
+ /* O freezedetect marca o começo e o fim de cada trecho parado. Um começo
46
+ sem fim é tela parada até o último quadro. Dois trechos colados (o texto
47
+ some e o fundo fica) são, na tela, uma parada só. */
48
+ function lerParados(texto, duracao) {
49
+ const trechos = [];
50
+ const juntar = (de, ate) => {
51
+ const ultimo = trechos[trechos.length - 1];
52
+ if (ultimo && de - ultimo.ate < 0.2) ultimo.ate = r1(ate);
53
+ else trechos.push({ de: r1(de), ate: r1(ate) });
54
+ };
55
+ let de = null;
56
+ for (const linha of String(texto || '').split('\n')) {
57
+ const i = linha.match(/freeze_start:\s*([\d.]+)/);
58
+ if (i) { de = Number(i[1]); continue; }
59
+ const f = linha.match(/freeze_end:\s*([\d.]+)/);
60
+ if (f && de != null) { juntar(de, Number(f[1])); de = null; }
61
+ }
62
+ if (de != null && duracao > de) juntar(de, duracao);
63
+ return trechos;
64
+ }
65
+
66
+ /** As medidas viram avisos. Puro. */
67
+ function julgar({ som, parados = [] }, alvo = ALVO) {
68
+ const avisos = [];
69
+ if (som && Number.isFinite(som.lufs)) {
70
+ if (Math.abs(som.lufs - alvo.lufs) > alvo.folgaLufs) {
71
+ avisos.push(`volume em ${som.lufs} LUFS, longe do alvo de ${alvo.lufs} (${som.lufs < alvo.lufs ? 'baixo demais' : 'alto demais'})`);
72
+ }
73
+ if (som.pico != null && som.pico > alvo.picoMax) avisos.push(`pico de ${som.pico} dBTP: estoura no celular (o máximo é ${alvo.picoMax})`);
74
+ }
75
+ for (const p of parados) {
76
+ const dura = r1(p.ate - p.de);
77
+ if (dura >= alvo.paradoMin) avisos.push(`imagem parada por ${dura} s (de ${p.de} s a ${p.ate} s)`);
78
+ }
79
+ return avisos;
80
+ }
81
+
82
+ /** Mede o arquivo numa passada só do ffmpeg e devolve medidas e avisos. */
83
+ async function conferir(arquivo, { duracao = 0 } = {}) {
84
+ const c = video.conferir();
85
+ if (!c.ok) return c;
86
+ const r = await video.rodar(video.ffmpeg(), [
87
+ '-hide_banner', '-nostats', '-i', arquivo,
88
+ '-vf', 'scale=320:-2,freezedetect=n=0.002:d=1.5',
89
+ '-af', 'ebur128=peak=true',
90
+ '-f', 'null', '-',
91
+ ], { timeout: 10 * 60000 });
92
+ if (!r.ok) return { ok: false, error: `a conferência não leu o vídeo: ${r.erro.slice(-200)}` };
93
+ const som = lerSom(r.erro);
94
+ const parados = lerParados(r.erro, duracao);
95
+ return { ok: true, som, parados, avisos: julgar({ som, parados }) };
96
+ }
97
+
98
+ module.exports = { ALVO, lerSom, lerParados, julgar, conferir };
@@ -47,6 +47,7 @@ const assets = require('./assets.js');
47
47
  const video = require('./video.js');
48
48
  const batida = require('./batida.js');
49
49
  const revisor = require('./revisor.js');
50
+ const conferencia = require('./conferencia.js');
50
51
  const margem = require('./margem.js');
51
52
  const assinatura = require('./assinatura.js');
52
53
  const identidade = require('./identidade.js');
@@ -2106,6 +2107,12 @@ async function ferramentaRender(args, ctx = {}) {
2106
2107
  projeto.salvar(args.id, { renders: [...(p.renders || []), { quando: new Date().toISOString(), arquivo: saida, bytes }] });
2107
2108
  projeto.anotar(args.id, 'render', `${(bytes / 1048576).toFixed(1)} MB, ${conferida.duracao}s`);
2108
2109
 
2110
+ /* A CONFERÊNCIA (conferencia.js): o arquivo que saiu, medido. Som fora do
2111
+ alvo e imagem parada viram aviso; quem decide se refaz é o agente. */
2112
+ const conf = fs.existsSync(saida) ? await conferencia.conferir(saida, { duracao: conferida.duracao }) : { ok: false };
2113
+ const avisosDoArquivo = conf.ok ? conf.avisos : [];
2114
+ if (avisosDoArquivo.length) projeto.anotar(args.id, 'conferencia', avisosDoArquivo.join('; '));
2115
+
2109
2116
  // O vídeo pronto abre. É a única coisa que abre — a interface morreu, e
2110
2117
  // o que a pessoa pediu foi o vídeo.
2111
2118
  if (ctx.abrir) { try { ctx.abrir(saida); } catch { /* o caminho está na resposta */ } }
@@ -2114,8 +2121,9 @@ async function ferramentaRender(args, ctx = {}) {
2114
2121
  ok: true, arquivo: saida, bytes, segundos: conferida.duracao,
2115
2122
  faltaram: faltaram.length ? faltaram : undefined,
2116
2123
  revisao: revisao.consertos.length || revisao.avisos.length ? { consertado: revisao.consertos, avisos: revisao.avisos } : undefined,
2124
+ conferencia: conf.ok ? { som: conf.som, parados: conf.parados, avisos: conf.avisos } : undefined,
2117
2125
  aviso: 'ABERTO para o usuário assistir. Chame finish dizendo o que foi feito'
2118
- + (revisao.avisos.length ? ' — e cite os avisos da revisão que valem um novo plano.' : '.'),
2126
+ + (revisao.avisos.length || avisosDoArquivo.length ? ' — e cite os avisos da revisão e da conferência que valem um novo plano.' : '.'),
2119
2127
  };
2120
2128
  }
2121
2129
 
@@ -281,6 +281,11 @@ function leiaute(P) {
281
281
  ela ficava VAZIA até a voz chegar na frase (teste de 08/10). */
282
282
  const p0 = pr ? pr.palavras[0].em : b.de;
283
283
  const entT = [Math.max(ent[0], p0 - 0.4), Math.max(ent[1], p0 + 0.1)];
284
+ /* Mas a pílula de ANTES ficava: o texto dela saía e a forma esperava
285
+ vazia até a voz (0,6 s no café de 11/10, de 7,2 s a 7,9 s). Quando
286
+ a voz demora, a forma encolhe num ponto e cresce já com a frase. */
287
+ // Começa em b.de, quando o texto de antes já saiu (antes disso a forma laranja ficava atrás dele).
288
+ if (entT[0] >= b.de + 0.4) vai(b.de, b.de + 0.4, forma(tx, ty, 16 * u, 16 * u, { solid: 1, ac: 1 }));
284
289
  if (umaLinha) vai(entT[0], entT[1], forma(tx, ty, (t ? t.w : 200 * u) + 110 * u, h, { fill: 1, stroke: 0.35 }));
285
290
  else vai(entT[0], entT[1], forma(t.left - 40 * u, ty, 6 * u, t.h * 0.92, { solid: 1, ac: 1, r: 3 }));
286
291
  }
@@ -831,6 +831,15 @@ const ESP = {
831
831
  macaco: { forma: 4, corpo: [0.36, 0.42, 0.42], perna: 0.36, cab: 0.36, foc: [0.22, 0.14, 0.08], orelha: 'redonda', rabo: 'longo', cor: '#7a5232', cor2: '#e6c9a0', focCor: '#e6c9a0' },
832
832
  };
833
833
 
834
+ /** Onde a cabeça do quatro-patas fica no chão, a partir do centro do bicho:
835
+ * o corpo vira 0,55 de lado e a cabeça vai na frente dele (o Quadrupede).
836
+ * A câmera mirava o meio do corpo e o rosto saía cortado na borda (13/10). */
837
+ export function frenteDoBicho(especie) {
838
+ const e = ESP[especie] || ESP.cachorro;
839
+ if (e.forma === 'ave' || e.forma === 'pinguim' || e.forma === 'peixe' || e.forma === 'dino') return { giro: 0, dist: 0 };
840
+ return { giro: 0.55, dist: e.corpo[2] / 2 + e.cab * 0.25 + (e.pescoco || 0) * 0.25 };
841
+ }
842
+
834
843
  /** A altura da cabeça (para a câmera mirar no rosto do bicho). */
835
844
  export function alturaDoBicho(especie) {
836
845
  const e = ESP[especie] || ESP.cachorro;
@@ -30,7 +30,7 @@ import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPa
30
30
  import { BokehPass } from 'three/examples/jsm/postprocessing/BokehPass.js';
31
31
  import { OutputPass } from 'three/examples/jsm/postprocessing/OutputPass.js';
32
32
  import { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment.js';
33
- import { Mundo, Animal, Coisa, alturaDoBicho, useIcones } from './mundo.jsx';
33
+ import { Mundo, Animal, Coisa, alturaDoBicho, frenteDoBicho, useIcones } from './mundo.jsx';
34
34
 
35
35
  const clamp = (x, a = 0, b = 1) => Math.max(a, Math.min(b, x));
36
36
  const lerp = (a, b, k) => a + (b - a) * k;
@@ -267,7 +267,8 @@ function RostoPixel({ p, est }) {
267
267
  "olha como quero o 3D": o rosto da referência é LOW-POLY — uma esfera de
268
268
  poucas faces com sombreado chapado, nariz de pirâmide, sobrancelha grossa,
269
269
  o cabelo cacheado feito de dezenas de pedrinhas facetadas. O corpo segue
270
- de blocos. `cabeca: 'cubo'` volta ao rosto de pixel da 10.5. */
270
+ de blocos. Desde 13/10 só sai com `cabeca: 'facetada'`: o padrão voltou a
271
+ ser o cubo com rosto de pixel da 10.5 (partitura sem o campo também). */
271
272
  const GEO_FACETA = new Map();
272
273
  function faceta(det) {
273
274
  if (!GEO_FACETA.has(det)) GEO_FACETA.set(det, new THREE.IcosahedronGeometry(1, det));
@@ -432,7 +433,7 @@ function PessoaBloco({ p, est }) {
432
433
  <BracoBloco lado={-1} g={est.gesto[0]} cor={p.roupa} pele={p.pele} t={est.t} acao={a} k={ka} />
433
434
  <BracoBloco lado={1} g={est.gesto[1]} cor={p.roupa} pele={p.pele} t={est.t} acao={a} k={ka} />
434
435
  <group position={[0, 1.8 + resp, 0]} rotation={[cp, cy, cr]}>
435
- {p.cabeca === 'cubo' ? (
436
+ {p.cabeca !== 'facetada' ? (
436
437
  <>
437
438
  <Bloco s={[CAB, CAB, CAB]} c={p.pele} r={0.04} rough={0.6} />
438
439
  <RostoPixel p={p} est={est} />
@@ -623,13 +624,34 @@ function lugarDoFigurante(el, fig, j, vert = false) {
623
624
  return [lado * (borda + Math.floor(j / 2) * 0.9), 0, baixo ? 0.45 : -0.5];
624
625
  }
625
626
 
627
+ /* A CÂMERA NÃO ATRAVESSA NINGUÉM (13/10): no close de quem fala, outro do
628
+ elenco ficava entre a lente e o rosto, ou com a lente dentro dele (o close
629
+ do cachorro com o braço do outro cortando o quadro). `bate` diz se alguém
630
+ do elenco, um cilindro do chão até acima da cabeça, corta o caminho. */
631
+ function bate(el, quem, pos, alvo) {
632
+ const dx = alvo[0] - pos[0], dz = alvo[2] - pos[2], L2 = dx * dx + dz * dz || 1;
633
+ for (let j = 0; j < el.length; j++) {
634
+ if (j === quem) continue;
635
+ const [x, , z] = lugarDe(el, j);
636
+ const u = clamp(((x - pos[0]) * dx + (z - pos[2]) * dz) / L2);
637
+ const py = pos[1] + (alvo[1] - pos[1]) * u;
638
+ if (Math.hypot(pos[0] + dx * u - x, pos[2] + dz * u - z) < 0.55 && py < alturaDaCabeca(el[j]) + 0.3) return true;
639
+ }
640
+ return false;
641
+ }
642
+
626
643
  function cameraDe(pl, t, part, aspecto) {
627
644
  const vert = aspecto < 1;
628
645
  const el = part.elenco;
629
646
  const dois = el.length > 1;
630
647
  const quem = el[pl.alvo] ? pl.alvo : 0;
631
- const cabeca = (i) => { const p = lugarDe(el, i); return [p[0], alturaDaCabeca(el[i]), p[2]]; };
632
- const frente = (i) => [Math.sin(giroDe(el, i)), 0, Math.cos(giroDe(el, i))];
648
+ // O bicho de quatro patas tem a cabeça NA FRENTE do corpo, e o rosto virado para onde o corpo vira.
649
+ const doBicho = (i) => (el[i] && el[i].tipo === 'animal' ? frenteDoBicho(el[i].especie) : { giro: 0, dist: 0 });
650
+ const cabeca = (i) => {
651
+ const p = lugarDe(el, i), b = doBicho(i), g = giroDe(el, i) + b.giro;
652
+ return [p[0] + Math.sin(g) * b.dist, alturaDaCabeca(el[i]), p[2] + Math.cos(g) * b.dist];
653
+ };
654
+ const frente = (i) => { const g = giroDe(el, i) + doBicho(i).giro * 0.7; return [Math.sin(g), 0, Math.cos(g)]; };
633
655
  const k = clamp((t - pl.de) / Math.max(0.5, pl.ate - pl.de));
634
656
  const fovK = vert ? 1.45 : 1;
635
657
  let pos, alvo, fov;
@@ -658,11 +680,16 @@ function cameraDe(pl, t, part, aspecto) {
658
680
  const dist = (pl.tipo === 'detalhe' ? 1.25 : pl.tipo === 'close' ? 1.7 : 2.7) * (vert ? 1.25 : 1);
659
681
  fov = (pl.tipo === 'detalhe' ? 30 : pl.tipo === 'close' ? 32 : 34) * fovK;
660
682
  const h = cabeca(quem), f = frente(quem);
661
- const lado = pl.lado * 0.42;
662
- const dir = [f[0] * Math.cos(lado) - f[2] * Math.sin(lado), 0, f[0] * Math.sin(lado) + f[2] * Math.cos(lado)];
663
683
  const baixo = pl.tipo === 'medio' ? 0.05 : -0.32;
664
- pos = [h[0] + dir[0] * dist, h[1] + baixo, h[2] + dir[2] * dist];
665
684
  alvo = [h[0], h[1] + (pl.tipo === 'medio' ? -0.35 : -0.02), h[2]];
685
+ const de = (ld) => {
686
+ const lado = ld * 0.42;
687
+ const dir = [f[0] * Math.cos(lado) - f[2] * Math.sin(lado), 0, f[0] * Math.sin(lado) + f[2] * Math.cos(lado)];
688
+ return [h[0] + dir[0] * dist, h[1] + baixo, h[2] + dir[2] * dist];
689
+ };
690
+ // O lado pedido; se alguém corta, o outro lado, de frente, mais aberto; senão, a câmera sobe por cima.
691
+ pos = [pl.lado, -pl.lado, 0, 1.6, -1.6].map(de).find((q) => !bate(el, quem, q, alvo));
692
+ if (!pos) { const q = de(pl.lado); pos = [q[0], q[1] + 0.9, q[2]]; }
666
693
  }
667
694
  // No vertical a legenda está no alto: a câmera mira acima, e o rosto desce no quadro.
668
695
  if (vert) alvo = [alvo[0], alvo[1] + (pl.tipo === 'aberto' || pl.tipo === 'largo' ? 0.45 : pl.tipo === 'medio' ? 0.3 : pl.tipo === 'ombro' ? 0.22 : 0.17), alvo[2]];
@@ -154,8 +154,10 @@ function aparencia(nome, pedido = {}) {
154
154
  barba: !!pedido.barba && pedido.genero !== 'f', // só quando pedida: sorteada, ficava estranha em quem tem cabelo longo
155
155
  bigode: !!pedido.bigode && pedido.genero !== 'f',
156
156
  ...(pedido.genero ? { genero: pedido.genero } : {}),
157
- // A cabeça facetada (low-poly) é o padrão; 'cubo' é o rosto de pixel da 10.5.
158
- ...(pedido.cabeca === 'cubo' ? { cabeca: 'cubo' } : {}),
157
+ /* O cubo com rosto de pixel (o da 10.5) é o padrão de novo: o low-poly
158
+ da 10.6 ficou "feito com polígonos, bugado e horrível" (Joel, 13/10).
159
+ A facetada só sai pedida. */
160
+ cabeca: pedido.cabeca === 'facetada' ? 'facetada' : 'cubo',
159
161
  };
160
162
  }
161
163
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "primocode",
3
- "version": "10.7.1",
3
+ "version": "10.8.0",
4
4
  "description": "PrimoCode — agente de engenharia com IA e cursor próprio. Requer conta Conecta Primo AI (Premium ou Super). Cria arquivos, roda comandos, controla navegador e desktop: abre apps, clica em botões e ícones pelo nome, digita e usa atalhos.",
5
5
  "main": "bin/primocode.js",
6
6
  "bin": {
@@ -8,7 +8,7 @@
8
8
  },
9
9
  "scripts": {
10
10
  "start": "node bin/primocode.js",
11
- "test": "node test/tools.test.js && node test/ui.test.js && node test/entrada.test.js && node test/transparencia.test.js && node test/permissao.test.js && node test/compatibilidade.test.js && node test/skills.test.js && node test/skills-de-fora.test.js && node test/skills-novas.test.js && node test/comandos.test.js && node test/estudio.test.js && node test/voz.test.js && node test/modo.test.js && node test/pasta.test.js && node test/projeto.test.js && node test/nome-projeto.test.js && node test/api-propria.test.js && node test/claude-engine.test.js && node test/codex.test.js && node test/conferir.test.js && node test/catalogo.test.js && node test/desktop.test.js && node test/seta.test.js && node test/effort.test.js && node test/regressao.test.js && node test/memoria-conversa.test.js && node test/repeticao.test.js && node test/casar.test.js && node test/recado.test.js && node test/releitura.test.js && node test/snapshot.test.js && node test/video.test.js && node test/limpeza.test.js && node test/autonomia.test.js && node test/desfazer.test.js && node test/desfazer-git.test.js && node test/referencia.test.js && node test/parar.test.js && node test/continuar.test.js && node test/pipeline.test.js && node test/conta.test.mjs && node test/planos-tela.test.js && node test/nuvem.test.js && node test/prazo.test.js && node test/primeira-vez.test.js && node test/fala.test.js && node test/especialistas.test.js && node test/empurrao.test.js && node test/terminal-novo.test.js && node test/markdown.test.js && node test/coerencia.test.js && node test/contexto.test.js && node test/perigo.test.js && node test/rapidez.test.js && node test/mascotes.test.js && node test/comandos-extra.test.js && node test/offline.test.js && node test/edicao.test.js && node test/composicao.test.js && node test/acervo-site.test.js && node test/impacto.test.js && node test/cinema.test.js && node test/producao.test.js && node test/ende.test.js && node test/capa.test.js && node test/broll.test.js && node test/morfar.test.js && node test/motion.test.js && node test/motion-roteiro.test.js && node test/kit-motion.test.js && node test/gerativo.test.js && node test/gerativo-identidade.test.js && node test/gerativo-moldura.test.js && node test/gerativo-heroi.test.js && node test/gerativo-ambiente.test.js && node test/gerativo-variedade.test.js && node test/hibrido.test.js && node test/batida.test.js && node test/estudio-acervo.test.js && node test/estudio-diretor.test.js && node test/estudio-marca.test.js && node test/estudio-novo-projeto.test.js && node test/estudio-plano.test.js && node test/estudio-slides.test.js && node test/estudio-video.test.js && node test/revisor.test.js && node test/assinatura.test.js && node test/biblioteca.test.js && node test/mcp.test.js && node test/personas.test.js && node test/efeitos.test.js && node test/auto.test.js && node test/colar.test.js && node test/orquestra.test.js && node test/shell-limpo.test.js && node test/editar-remotion.test.js && node test/variacao3d.test.js && node test/portao-skill.test.js && node test/clones.test.js && node test/kokoro.test.js && node test/fluido.test.js && node test/zoom.test.js && node test/personagem.test.js && node test/tela.test.js && node test/margem.test.js && node test/travado.test.js && node test/leitura.test.js && node test/producao-provedores.test.js && node test/broll-frase.test.js && node test/vazamento.test.js && node test/colecao.test.js && node test/mundo.test.js && node test/mcp-procurar.test.js && node test/selecao.test.js && node test/nome-curto.test.js && node test/agents-md.test.js && node test/outra-pasta.test.js && node test/estudio-sem-id.test.js && node test/estudio-objeto-do-site.test.js && node test/cenarios.test.js && node test/movimentos.test.js && node test/gerativo-so-fala.test.js"
11
+ "test": "node test/tools.test.js && node test/ui.test.js && node test/entrada.test.js && node test/transparencia.test.js && node test/permissao.test.js && node test/compatibilidade.test.js && node test/skills.test.js && node test/skills-de-fora.test.js && node test/skills-novas.test.js && node test/comandos.test.js && node test/estudio.test.js && node test/voz.test.js && node test/modo.test.js && node test/pasta.test.js && node test/projeto.test.js && node test/nome-projeto.test.js && node test/api-propria.test.js && node test/claude-engine.test.js && node test/codex.test.js && node test/conferir.test.js && node test/catalogo.test.js && node test/desktop.test.js && node test/seta.test.js && node test/effort.test.js && node test/regressao.test.js && node test/memoria-conversa.test.js && node test/repeticao.test.js && node test/casar.test.js && node test/recado.test.js && node test/releitura.test.js && node test/snapshot.test.js && node test/video.test.js && node test/limpeza.test.js && node test/autonomia.test.js && node test/desfazer.test.js && node test/desfazer-git.test.js && node test/referencia.test.js && node test/parar.test.js && node test/continuar.test.js && node test/pipeline.test.js && node test/conta.test.mjs && node test/planos-tela.test.js && node test/nuvem.test.js && node test/prazo.test.js && node test/primeira-vez.test.js && node test/fala.test.js && node test/especialistas.test.js && node test/empurrao.test.js && node test/terminal-novo.test.js && node test/markdown.test.js && node test/coerencia.test.js && node test/contexto.test.js && node test/perigo.test.js && node test/rapidez.test.js && node test/mascotes.test.js && node test/comandos-extra.test.js && node test/offline.test.js && node test/edicao.test.js && node test/composicao.test.js && node test/acervo-site.test.js && node test/impacto.test.js && node test/cinema.test.js && node test/producao.test.js && node test/ende.test.js && node test/capa.test.js && node test/broll.test.js && node test/morfar.test.js && node test/motion.test.js && node test/motion-roteiro.test.js && node test/kit-motion.test.js && node test/gerativo.test.js && node test/gerativo-identidade.test.js && node test/gerativo-moldura.test.js && node test/gerativo-heroi.test.js && node test/gerativo-ambiente.test.js && node test/gerativo-variedade.test.js && node test/hibrido.test.js && node test/batida.test.js && node test/estudio-acervo.test.js && node test/estudio-diretor.test.js && node test/estudio-marca.test.js && node test/estudio-novo-projeto.test.js && node test/estudio-plano.test.js && node test/estudio-slides.test.js && node test/estudio-video.test.js && node test/revisor.test.js && node test/conferencia.test.js && node test/assinatura.test.js && node test/biblioteca.test.js && node test/mcp.test.js && node test/personas.test.js && node test/efeitos.test.js && node test/auto.test.js && node test/colar.test.js && node test/orquestra.test.js && node test/shell-limpo.test.js && node test/editar-remotion.test.js && node test/variacao3d.test.js && node test/portao-skill.test.js && node test/clones.test.js && node test/kokoro.test.js && node test/fluido.test.js && node test/zoom.test.js && node test/personagem.test.js && node test/tela.test.js && node test/margem.test.js && node test/travado.test.js && node test/leitura.test.js && node test/producao-provedores.test.js && node test/broll-frase.test.js && node test/vazamento.test.js && node test/colecao.test.js && node test/mundo.test.js && node test/mcp-procurar.test.js && node test/selecao.test.js && node test/nome-curto.test.js && node test/agents-md.test.js && node test/outra-pasta.test.js && node test/estudio-sem-id.test.js && node test/estudio-objeto-do-site.test.js && node test/cenarios.test.js && node test/movimentos.test.js && node test/gerativo-so-fala.test.js"
12
12
  },
13
13
  "engines": {
14
14
  "node": ">=18.17.0"