primocode 10.19.0 → 11.0.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.
Files changed (116) hide show
  1. package/LICENSES/opencode-MIT.txt +24 -0
  2. package/LICENSES/pptxgenjs-MIT.txt +31 -0
  3. package/README.md +16 -3
  4. package/agentes/acessibilidade.md +10 -0
  5. package/agentes/ambiente.md +10 -0
  6. package/agentes/api.md +10 -0
  7. package/agentes/backend.md +10 -0
  8. package/agentes/copy.md +10 -0
  9. package/agentes/css-layout.md +10 -0
  10. package/agentes/dados.md +10 -0
  11. package/agentes/depuracao.md +10 -0
  12. package/agentes/design.md +10 -0
  13. package/agentes/docs.md +10 -0
  14. package/agentes/frontend.md +10 -0
  15. package/agentes/geral/capacidades/arquivos.json +132 -0
  16. package/agentes/geral/capacidades/codigo.json +132 -0
  17. package/agentes/geral/capacidades/dados.json +126 -0
  18. package/agentes/geral/capacidades/depuracao.json +126 -0
  19. package/agentes/geral/capacidades/escrita.json +126 -0
  20. package/agentes/geral/capacidades/git.json +126 -0
  21. package/agentes/geral/capacidades/pesquisa.json +126 -0
  22. package/agentes/geral/capacidades/seguranca.json +132 -0
  23. package/agentes/geral/capacidades/shell.json +132 -0
  24. package/agentes/geral/capacidades/web.json +126 -0
  25. package/agentes/git-pr.md +10 -0
  26. package/agentes/indice.md +86 -0
  27. package/agentes/nextjs.md +10 -0
  28. package/agentes/node.md +10 -0
  29. package/agentes/performance.md +10 -0
  30. package/agentes/pesquisa.md +10 -0
  31. package/agentes/python.md +10 -0
  32. package/agentes/react.md +10 -0
  33. package/agentes/refatoracao.md +10 -0
  34. package/agentes/revisor.md +10 -0
  35. package/agentes/seguranca.md +10 -0
  36. package/agentes/sql.md +10 -0
  37. package/agentes/testes.md +10 -0
  38. package/agentes/typescript.md +10 -0
  39. package/agentes/ux.md +10 -0
  40. package/bin/primocode.js +65 -8
  41. package/editor/helpers/camera_viva.py +28 -2
  42. package/lib/act.js +79 -10
  43. package/lib/agentes/index.js +146 -0
  44. package/lib/agentes/nucleo.js +18 -0
  45. package/lib/catalogo.js +12 -2
  46. package/lib/cli/review.js +64 -0
  47. package/lib/cli/subcomandos.js +34 -0
  48. package/lib/cli/tentativas.js +107 -0
  49. package/lib/cli/util.js +51 -0
  50. package/lib/comandos-extra.js +4 -2
  51. package/lib/editor-pro-maquina.js +165 -0
  52. package/lib/editor-pro.js +10 -7
  53. package/lib/entregaveis/cli.js +26 -0
  54. package/lib/entregaveis/especialistas.js +50 -0
  55. package/lib/entregaveis/nucleo.js +111 -0
  56. package/lib/entregaveis/slides.js +221 -0
  57. package/lib/estudio/materiais.js +172 -0
  58. package/lib/fundo.js +34 -1
  59. package/lib/kits.js +41 -2
  60. package/lib/nomes.js +6 -0
  61. package/lib/placar.js +173 -0
  62. package/lib/plugin/autoteste.js +99 -0
  63. package/lib/plugin/edicao-visual.js +133 -0
  64. package/lib/plugin/pesquisar.js +92 -0
  65. package/lib/plugin/preview-cli.js +38 -0
  66. package/lib/plugin/preview.js +89 -0
  67. package/lib/plugin/tunel.js +27 -0
  68. package/lib/review.js +134 -0
  69. package/lib/session/compactacao.js +124 -0
  70. package/lib/statusline.js +52 -0
  71. package/lib/tool/diagnostico.js +100 -0
  72. package/lib/tool/perguntar.js +60 -0
  73. package/lib/tool/reparar-entrada.js +66 -0
  74. package/lib/tool/web.js +102 -0
  75. package/lib/tools.js +20 -1
  76. package/lib/ui.js +1 -1
  77. package/materiais/saas-linear/base.ts +84 -0
  78. package/materiais/saas-linear/camera/Camera.tsx +38 -0
  79. package/materiais/saas-linear/fontes/OFL-Inter.txt +93 -0
  80. package/materiais/saas-linear/fontes/OFL-Newsreader.txt +93 -0
  81. package/materiais/saas-linear/fontes/inter-400.woff2 +0 -0
  82. package/materiais/saas-linear/fontes/inter-600.woff2 +0 -0
  83. package/materiais/saas-linear/fontes/newsreader-400.woff2 +0 -0
  84. package/materiais/saas-linear/formas/Agenda.tsx +46 -0
  85. package/materiais/saas-linear/formas/CaixaPrompt.tsx +41 -0
  86. package/materiais/saas-linear/formas/CartoesIntegracao.tsx +57 -0
  87. package/materiais/saas-linear/formas/Cursor.tsx +40 -0
  88. package/materiais/saas-linear/formas/Menu.tsx +46 -0
  89. package/materiais/saas-linear/formas/Monitor.tsx +28 -0
  90. package/materiais/saas-linear/paleta.json +18 -0
  91. package/materiais/saas-linear/pecas.json +590 -0
  92. package/materiais/saas-linear/regras.md +40 -0
  93. package/materiais/saas-linear/sons/Som.tsx +39 -0
  94. package/materiais/saas-linear/sons/assinatura.mp3 +0 -0
  95. package/materiais/saas-linear/sons/clique.mp3 +0 -0
  96. package/materiais/saas-linear/sons/impacto.mp3 +0 -0
  97. package/materiais/saas-linear/sons/pop-cartao.mp3 +0 -0
  98. package/materiais/saas-linear/sons/pop-palavra.mp3 +0 -0
  99. package/materiais/saas-linear/sons/subida.mp3 +0 -0
  100. package/materiais/saas-linear/sons/swoosh.mp3 +0 -0
  101. package/materiais/saas-linear/sons/trilha.mp3 +0 -0
  102. package/materiais/saas-linear/sons/whoosh-entra.mp3 +0 -0
  103. package/materiais/saas-linear/sons/whoosh-sai.mp3 +0 -0
  104. package/materiais/saas-linear/texturas/Fundo.tsx +18 -0
  105. package/materiais/saas-linear/texturas/Particulas.tsx +22 -0
  106. package/materiais/saas-linear/tipografia/FraseSerifa.tsx +25 -0
  107. package/materiais/saas-linear/tipografia/Marca.tsx +20 -0
  108. package/materiais/saas-linear/tipografia/MarcaFinal.tsx +22 -0
  109. package/materiais/saas-linear/tipografia/Saudacao.tsx +27 -0
  110. package/materiais/saas-linear/transicoes/Cena.tsx +38 -0
  111. package/materiais/saas-linear/transicoes/CorteImpacto.tsx +16 -0
  112. package/package.json +3 -1
  113. package/skills/deck-conferido/SKILL.md +29 -0
  114. package/vendor/pptxgenjs/carregar.js +12 -0
  115. package/vendor/pptxgenjs/jszip.min.js +13 -0
  116. package/vendor/pptxgenjs/pptxgen.cjs.js +7370 -0
package/bin/primocode.js CHANGED
@@ -193,12 +193,16 @@ const SLASH_COMANDOS = [
193
193
  ['/howto', 'escreve no PRIMO.md como abrir e testar este projeto'],
194
194
  ['/save', 'guarda o projeto no GitHub (privado) e mostra o link · também vale /guardar'],
195
195
  ['/secrets', 'as chaves que o projeto usa (só os nomes) · /secrets add NOME guarda uma, escondida, no .env · também vale /segredos'],
196
+ ['/testar-app', 'sobe o app, abre, clica nos fluxos e conserta os erros (até 2 rodadas)'],
197
+ ['/pesquisar', 'relatório com fontes: /pesquisar <tema> grava em pesquisas/'],
198
+ ['/placar', 'qual modelo grátis acerta mais em cada tipo de tarefa, aprendido aqui · /placar zerar apaga'],
196
199
  ['/schedule', 'um pedido que roda sozinho: /schedule "toda segunda às 9h" <pedido> · sem nada, lista · /schedule off 1 tira · também vale /agendar'],
197
200
  ['/lock', 'arquivos que o agente só pode ler · /lock login.js trava · /lock off login.js solta · sem nada, lista'],
198
201
  ['/rule', 'uma regra que o PrimoCode segue sempre, mesmo depois de resumir a conversa'],
199
202
  ['/port', 'o app não abre? diz qual porta responde e o endereço certo'],
200
203
  ['/map', 'o que tem no projeto, em português, e o que está faltando · /map tree: as pastas'],
201
204
  ['/explain', 'o que o último pedido mudou, em até 5 linhas · /explain commit: uma palavra · /explain projeto: o projeto todo'],
205
+ ['/review', 'revisa o que mudou em duas passadas: acha e depois confere cada achado · /review rápido: uma passada só'],
202
206
  ['/plan', 'mostra 3 a 5 passos e só começa quando você aperta Enter (ou muda um passo) · /plan <pedido>'],
203
207
  ['/changes', 'o que mudou no projeto, em português (arquivos e linhas)'],
204
208
  ['/timeline', 'abre a história do projeto numa página'],
@@ -501,11 +505,9 @@ function barraDeStatus() {
501
505
  conselho e vira o que fazer. */
502
506
  const direita = [];
503
507
  const uso = memoria.usoDoContexto(state.memoria, state.history);
504
- if (uso.pct >= 5) {
505
- direita.push(uso.pct >= 100 ? c.err(`${uso.pct}% ctx`)
506
- : uso.pct >= 70 ? c.warn(`${uso.pct}% ctx`)
507
- : c.dim(`${uso.pct}% ctx`));
508
- }
508
+ // Modelo que respondeu · contexto · tokens de hoje (os mesmos do /usage) — lib/statusline.js.
509
+ const SL = require('../lib/statusline.js');
510
+ direita.push(...SL.partes({ modelo: SL.modelo(), ctxPct: uso.pct, tokensHoje: SL.tokensDeHoje(), c }));
509
511
  direita.push(state.trabalhando && rl && rl.ocupado
510
512
  ? c.warn('trabalhando') + c.dim(' · ') + c.white('esc esc') + c.dim(' para')
511
513
  : c.white('tab') + c.dim(' modos ') + c.white('ctrl+p') + c.dim(' comandos'));
@@ -1266,6 +1268,11 @@ async function sendPrompt(userText, { digitado = false } = {}) {
1266
1268
 
1267
1269
  /* Marca no arquivo de hoje o que ESTA conversa já gastou (lib/limite.js): é o que o /usage soma. */
1268
1270
  const IDENTIDADE_DA_SESSAO = `${process.pid}-${Date.now()}`;
1271
+ /* Tarefa em segundo plano que acabou sozinha: uma linha na tela (o modelo recebe o aviso na próxima volta). */
1272
+ require('../lib/fundo.js').aoTerminar((a) => {
1273
+ if (state.semTerminal) return;
1274
+ process.stdout.write('\n ' + (a.ok ? c.ok('✔') : c.err('✘')) + ' ' + c.muted(`tarefa ${a.id} terminou (código ${a.codigo}): ${a.comando.slice(0, 60)}`) + '\n');
1275
+ });
1269
1276
  function guardarUsoDeHoje() {
1270
1277
  try {
1271
1278
  const { total } = extra.resumoCusto(contadorDaSessao);
@@ -1397,6 +1404,7 @@ async function enviarPedido(userText) {
1397
1404
  // No pedido único (`primo "…"`) não há ninguém para responder:
1398
1405
  // sem `autorizar`, o que pede licença é recusado (lib/perigo.js).
1399
1406
  autorizar: state.semTerminal ? undefined : autorizarControle,
1407
+ escolher: state.semTerminal ? undefined : (q, o, op) => require('../lib/tool/perguntar').escolherNoTerminal(ask, c, q, o, op),
1400
1408
  // A memória do AGENTS.md só vai se o resumo ainda não a carrega.
1401
1409
  memoriaDaPasta: !memoria.jaDobrou(state.memoria, plan.dir),
1402
1410
  mudarPasta: state.semTerminal ? null : mudarPastaPeloAgente,
@@ -1718,9 +1726,10 @@ async function comprimirConversa(forcado = false, { silencioso = false } = {}) {
1718
1726
  if (!memoria.jaDobrou(state.memoria, dir) && !(state.memoria.semente && state.memoria.semente.pasta === dir)) {
1719
1727
  state.memoria = memoria.semear(state.memoria, dir, agentsMd.ler(dir));
1720
1728
  }
1729
+ // O mesmo `resumir` da compactação automática do agente (lib/session/compactacao.js): resumo + arquivos mexidos + to-dos.
1721
1730
  const nova = await memoria.atualizar(state.memoria, conversa, ({ resumo, mensagens }) =>
1722
- api.requestResumo(state.config.server, { resumo, mensagens, token: state.config.token })
1723
- .then((r) => r.resumo));
1731
+ require('../lib/session/compactacao.js').resumirComFatos({ resumo, mensagens },
1732
+ (p) => api.requestResumo(state.config.server, { ...p, token: state.config.token }).then((r) => r.resumo)));
1724
1733
  // /clear ou /chats trocaram a conversa enquanto o resumo era feito:
1725
1734
  // o resumo é da conversa velha e não pode voltar por cima da nova.
1726
1735
  if (state.history !== conversa) { sp.stop(); return; }
@@ -2506,6 +2515,7 @@ async function handleSlash(text) {
2506
2515
  const copiaDoBanco = BK.desde(projectDir(), alvo[alvo.length - 1].quando).slice(-1)[0];
2507
2516
  if (copiaDoBanco) console.log(c.muted(' banco ') + `volta pra cópia de ${hora(copiaDoBanco.criado)} (${copiaDoBanco.arquivos.join(', ')})`);
2508
2517
  if (!(await confirm('Desfazer?'))) { console.log(c.muted('Cancelado.')); break; }
2518
+ try { require('../lib/placar.js').desfez(); } catch { /* placar é conveniência */ } // desfez logo depois do pedido: conta como perda do modelo
2509
2519
  for (const f of checkpoint.desfazer(projectDir(), n)) {
2510
2520
  const rotulo = f.acao === 'falhou' ? c.err(` falhou: ${f.erro} `) : c.ok(` ${f.acao} `);
2511
2521
  console.log(rotulo + shortPath(f.caminho));
@@ -2690,6 +2700,26 @@ async function handleSlash(text) {
2690
2700
  break;
2691
2701
  }
2692
2702
  /* /explain: sem nada, o último pedido em 5 linhas; com uma palavra, uma linha; "projeto" é o comando de fábrica. */
2703
+ /* /review: duas passadas (lib/review.js) — achar e depois conferir cada achado. /review rápido = o comando de fábrica de uma passada. */
2704
+ case 'review': {
2705
+ if (/^(r[aá]pido|simples)\b/i.test(rest)) {
2706
+ const fabrica = comandosDaPessoa().find((x) => x.nome === 'review');
2707
+ if (fabrica) { await sendPrompt(comandosLib.expandir(fabrica, rest.replace(/^\S+\s*/, ''))); break; }
2708
+ }
2709
+ const R = require('../lib/review.js');
2710
+ const chamar = async (prompt) => {
2711
+ let resposta = '';
2712
+ await api.requestChat(state.config.server, { prompt, history: [], model: state.config.model, token: state.config.token },
2713
+ (evt) => { if (evt.delta) resposta += evt.delta; });
2714
+ return resposta;
2715
+ };
2716
+ const sp = new Spinner('revisando o que mudou (passada 1: achar)').start();
2717
+ let r;
2718
+ try { r = await R.revisar({ dir: projectDir(), foco: rest, chamar }); } catch (e) { r = { ok: false, erro: String((e && e.message) || e) }; }
2719
+ sp.stop();
2720
+ console.log('\n' + R.formatar(r).split('\n').map((l) => ' ' + l).join('\n') + '\n');
2721
+ break;
2722
+ }
2693
2723
  case 'explain': {
2694
2724
  const E = require('../lib/explicar.js');
2695
2725
  const m = E.modo(rest);
@@ -2795,6 +2825,25 @@ async function handleSlash(text) {
2795
2825
  console.log(c.muted(' /secrets add NOME guarda uma. Isto é uma chave, não mostre pra ninguém.'));
2796
2826
  break;
2797
2827
  }
2828
+ case 'testar-app': {
2829
+ const A = require('../lib/plugin/autoteste.js');
2830
+ const r = await A.testar({ dir: projectDir(), log: (l) => console.log(c.muted(' ' + l)), consertar: (erros, prints) => sendPrompt(A.pedidoDeConserto(erros, prints)) });
2831
+ console.log(r.ok ? c.ok(' O app passou no teste.') : c.warn(' ' + r.erro));
2832
+ break;
2833
+ }
2834
+ /* /placar: qual modelo grátis acerta mais em cada tipo de tarefa, neste computador (lib/placar.js). */
2835
+ case 'placar': {
2836
+ const P = require('../lib/placar.js');
2837
+ if (/^(zerar|limpar|reset)$/i.test(rest.trim())) { P.zerar(); console.log(c.ok(' Placar zerado. Ele começa a aprender de novo.')); break; }
2838
+ console.log(P.texto(P.ler()).split('\n').map((l) => ' ' + l.replace(/^ {2}/, '')).join('\n'));
2839
+ break;
2840
+ }
2841
+ case 'pesquisar': {
2842
+ if (!rest) { console.log(c.muted(' Uso: /pesquisar <tema>')); break; }
2843
+ const r = await require('../lib/plugin/pesquisar.js').pesquisar({ tema: rest, dir: projectDir(), log: (l) => console.log(c.muted(' ' + l)) });
2844
+ console.log(r.ok ? c.ok(` Relatório: ${r.arquivo}`) : c.warn(' ' + r.erro));
2845
+ break;
2846
+ }
2798
2847
  /* /schedule: pedido que roda sozinho no Mac, pelo launchd (lib/agendar.js). */
2799
2848
  case 'schedule': {
2800
2849
  const A = require('../lib/agendar.js');
@@ -3050,7 +3099,7 @@ async function handleSlash(text) {
3050
3099
  console.log(c.dim('\n Em tokens (a unidade que o modelo conta):'));
3051
3100
  for (const l of linhas) {
3052
3101
  console.log(` ${c.white(ui.padRight(l.provedor, 22))}${c.muted(ui.padRight(l.pedidos + (l.pedidos === 1 ? ' pedido' : ' pedidos'), 12))}`
3053
- + `${c.dim('enviou ')}${c.white(extra.fmtNumero(l.enviados))}${c.dim(' · recebeu ')}${c.white(extra.fmtNumero(l.recebidos))}${c.dim(' tokens' + (l.exato ? '' : ' (estimado)'))}`);
3102
+ + `${c.dim('enviou ')}${c.white(extra.fmtNumero(l.enviados))}${c.dim(' · recebeu ')}${c.white(extra.fmtNumero(l.recebidos))}${c.dim(' tokens' + (l.exato ? '' : ' (estimado)'))}${l.emCache ? c.dim(' · ') + c.ok(extra.fmtNumero(l.emCache) + ' do cache') : ''}`);
3054
3103
  }
3055
3104
  if (linhas.length > 1) {
3056
3105
  console.log(` ${c.white(ui.padRight('Total', 22))}${c.muted(ui.padRight(total.pedidos + ' pedidos', 12))}`
@@ -3659,6 +3708,14 @@ function conferirServidor() {
3659
3708
  }
3660
3709
 
3661
3710
  async function main() {
3711
+ /* `primocode autoteste|preview|review|pesquisar`: entradas sem sessão (lib/cli/subcomandos.js). */
3712
+ if (require('../lib/cli/subcomandos.js').NOMES.includes(process.argv[2])) {
3713
+ process.exit(await require('../lib/cli/subcomandos.js').main(process.argv[2], process.argv.slice(3)));
3714
+ }
3715
+ /* `primocode --tentativas N "pedido"`: best of N em worktrees separados (lib/cli/tentativas.js). */
3716
+ if (process.argv.slice(2).some((a) => a === '--tentativas' || a.startsWith('--tentativas='))) {
3717
+ process.exit(await require('../lib/cli/tentativas.js').main(process.argv.slice(2)));
3718
+ }
3662
3719
  /* `primocode video <op>`: o editor de vídeo por linha do tempo
3663
3720
  (lib/video/). Sai ANTES de tudo — sem sessão, sem pasta padrão —
3664
3721
  porque quem chama é o agente pelo run_shell, na pasta da gravação. */
@@ -40,6 +40,9 @@ from pathlib import Path
40
40
  MARCA = "camera_viva"
41
41
  ZOOM_A, ZOOM_B = 1.0, 1.12
42
42
  PUNCH_A, PUNCH_B = 1.25, 1.3
43
+ # (maquina) o enquadramento não alterna mais 1.0/1.12 em tique-taque: gira por uma escala de quatro degraus e a
44
+ # escolha vem do texto da própria frase (mesmo roteiro, mesma escolha; outro roteiro, outro ritmo), nunca repetindo.
45
+ ZOOMS_MAQUINA = (1.0, 1.08, 1.12, 1.18)
43
46
  MIN_GAP_S = 2.5
44
47
  LONGA_S = 4.5 # a sentence longer than this is split in two
45
48
  CURTA_S = 1.2 # a sentence shorter than this joins the next one
@@ -157,6 +160,24 @@ def energia(db, w: dict) -> float:
157
160
  return float(np.percentile(seg, 90)) if len(seg) else -99.0
158
161
 
159
162
 
163
+ def escolher_base(t: float, ultima: float) -> float:
164
+ """Escala de base do trecho que começa em `t`: gira pela escala, nunca repete a anterior e nunca salta mais
165
+ de dois degraus (para não parecer um tranco)."""
166
+ k = int(round(t * 1000)) * 2654435761 % (2 ** 32)
167
+ opcoes = [z for z in ZOOMS_MAQUINA if z != ultima]
168
+ if ultima in ZOOMS_MAQUINA:
169
+ i = ZOOMS_MAQUINA.index(ultima)
170
+ perto = [z for z in opcoes if abs(ZOOMS_MAQUINA.index(z) - i) <= 2]
171
+ opcoes = perto or opcoes
172
+ return opcoes[k % len(opcoes)]
173
+
174
+
175
+ def punch_para(base: float, motivo: str) -> float:
176
+ """O aperto na palavra de ênfase: número aperta mais que negação, que aperta mais que a palavra forte."""
177
+ extra = {"número": 0.30, "negação": 0.24}.get(motivo, 0.18)
178
+ return round(max(PUNCH_A if base <= ZOOM_A else PUNCH_B, base + extra), 3)
179
+
180
+
160
181
  def enfase(ws: list[dict], f: list[int], db) -> tuple[int, str] | None:
161
182
  """(index, reason) of the emphasis word of a sentence: number, negation, or the loudest content word."""
162
183
  en = {i: energia(db, ws[i]) for i in f}
@@ -343,7 +364,12 @@ def plano(cfg: dict, ws: list[dict], cortes: list[float], vdur: float, db=None,
343
364
  for a, b in zip(pontos, pontos[1:]):
344
365
  titulo = sob and any(x - 0.01 <= (a + b) / 2 <= y + 0.01 for x, y in titulos) and \
345
366
  not any(x < b - 0.05 and a + 0.05 < y for x, y in sobre)
346
- base = ZOOM_B if titulo else (ZOOM_A if sob or ultima_base == ZOOM_B else ZOOM_B)
367
+ if titulo:
368
+ base = ZOOM_B
369
+ elif sob:
370
+ base = ZOOM_A
371
+ else:
372
+ base = escolher_base(a, ultima_base)
347
373
  ultima_base = base
348
374
  bloco = dict(cena="tela_cheia", de=round(a, 3), ate=round(b, 3), zoom=base, auto=MARCA)
349
375
  if titulo:
@@ -359,7 +385,7 @@ def plano(cfg: dict, ws: list[dict], cortes: list[float], vdur: float, db=None,
359
385
  t1 = round(min(ws[i]["end"] + PUNCH_FICA_S, b - 0.05), 3)
360
386
  if any(x < t1 and t0 - 0.1 < y for x, y in ocupado):
361
387
  continue
362
- bloco["zooms"] = [dict(em=round(t0, 3), ate=t1, escala=PUNCH_A if base == ZOOM_A else PUNCH_B,
388
+ bloco["zooms"] = [dict(em=round(t0, 3), ate=t1, escala=punch_para(base, motivo),
363
389
  som=False, auto=MARCA)]
364
390
  punches.append(dict(em=round(t0, 2), palavra=ws[i]["text"], motivo=motivo))
365
391
  ultimo_punch = t0
package/lib/act.js CHANGED
@@ -58,7 +58,7 @@ const MAX_PROFUNDIDADE = 2; // principal + 1 nível de subagente
58
58
  // As leituras que, repetidas sem mudança no meio, devolvem o guardado.
59
59
  const RELEITURA = new Set(['read_file', 'list_dir']);
60
60
  /* O que só lê: pode rodar junto com as outras leituras da mesma rodada. */
61
- const SO_LEITURA = new Set(['read_file', 'list_dir', 'glob', 'grep', 'pesquisar_referencia', 'estudio_site', 'mcp_procurar']);
61
+ const SO_LEITURA = new Set(['read_file', 'list_dir', 'glob', 'grep', 'pesquisar_referencia', 'buscar_web', 'ler_url', 'estudio_site', 'mcp_procurar']);
62
62
  const ehLeitura = (tc) => SO_LEITURA.has(tc.name)
63
63
  || (tc.name === 'run_shell' && /^\s*primocode\s+(skill|mcp\s+list|video\s+(help|get_timeline|get_transcript))\b/.test(String((tc.arguments && tc.arguments.command) || '')));
64
64
 
@@ -768,6 +768,7 @@ const RITMO = '[ritmo]\n'
768
768
  + '- O resultado de write_file/edit_file é a verdade. Não releia um arquivo que você acabou de escrever para conferir (nem com cat/sed/head): a conferência é do PrimoCode.\n'
769
769
  + '- Várias ações independentes vão na MESMA resposta (ex.: escrever index.html, style.css e script.js juntos).\n'
770
770
  + '- Leia um arquivo inteiro uma vez, não em pedaços pequenos; não leia de novo o que não mudou.\n'
771
+ + '- PERGUNTAR (ferramenta perguntar) só quando está BLOQUEADO: duas saídas razoáveis e errar custa caro. Pergunta de educação gasta a vez da pessoa; escolha e siga.\n'
771
772
  + '- Não narre o próximo passo: faça. Termine com finish("o que foi feito").\n'
772
773
  + '- VOCÊ DECIDE O CAMINHO. Pedido simples: faça direto. Pedido grande com partes independentes: write_todos com as partes, '
773
774
  + 'depois spawn_agent — um por parte, até 8 NA MESMA resposta (rodam juntos), cada um com o especialista e tudo que ele precisa saber. '
@@ -824,6 +825,8 @@ function buildMemoryContext(projectDir, projectName, pedido = '', { memoriaDaPas
824
825
  const lidos = preLeitura.bloco(preLeitura.escolher(projectDir, arquivos, pedido));
825
826
  if (lidos) parts.push(lidos);
826
827
  parts.push(RITMO);
828
+ // Mastermind + Geral (lib/agentes): o núcleo sempre ligado; o resto o modelo busca (agentes_buscar, capacidade_buscar).
829
+ try { parts.push(require('./agentes').nucleo()); } catch { /* sem núcleo, segue com o ritmo */ }
827
830
  // Os servidores MCP configurados, numa linha (lib/mcp.js).
828
831
  try { const m = require('./mcp.js').dica(projectDir); if (m) parts.push(m); } catch { /* sem mcp */ }
829
832
  // A equipe calada (lib/personas.js): o olhar do especialista que o pedido pede.
@@ -832,9 +835,48 @@ function buildMemoryContext(projectDir, projectName, pedido = '', { memoriaDaPas
832
835
  return parts.join('\n\n');
833
836
  }
834
837
 
835
- async function runAct(userPrompt, opts) {
838
+ /* O PLACAR (lib/placar.js): o PrimoCode aprende qual modelo grátis acerta mais em cada tipo de tarefa.
839
+ Aqui ele (1) classifica o pedido, (2) sem modelo fixado, manda ao servidor o melhor modelo do apelido como
840
+ DICA (`preferido`), e (3) no fim anota o desfecho. Só no agente principal e no servidor de verdade
841
+ (motor injetado = teste = sem placar). Qualquer falha aqui é ignorada: o placar nunca atrapalha a tarefa. */
842
+ let _vivos = { em: 0, dados: null };
843
+ async function modelosVivos(server, token, alias) {
844
+ if (!_vivos.dados || Date.now() - _vivos.em > 10 * 60 * 1000) {
845
+ const h = await Promise.race([api.getHealth(server, token), new Promise((_, rej) => setTimeout(() => rej(new Error('lento')), 2500))]);
846
+ _vivos = { em: Date.now(), dados: (h && (h.correntes_ativas || h.correntes)) || {} };
847
+ }
848
+ return _vivos.dados[alias] || [];
849
+ }
850
+
851
+ async function runAct(userPrompt, opts = {}) {
852
+ if (opts.subagent || (opts.profundidade || 0) > 0 || (opts.motor && opts.motor !== api) || opts.sinais || process.env.PRIMOCODE_PLACAR === 'off') {
853
+ return runActInterno(userPrompt, opts);
854
+ }
855
+ const P = require('./placar.js');
856
+ const tipo = P.classificar(userPrompt);
857
+ const sinais = { modelo: null, testes: null, diagSujo: false };
858
+ let preferido = null;
859
+ const fixado = /[\/:]/.test(String(opts.model || '')); // /model <id>: o placar não mexe
860
+ if (!fixado) {
861
+ try {
862
+ const alias = ['fast', 'main', 'top'].includes(opts.model) ? opts.model : 'main';
863
+ const dados = P.ler();
864
+ const temDado = Object.values(dados.tipos[tipo] || {}).some((c) => (c.ganhos || 0) + (c.perdas || 0) >= P.MIN_AMOSTRAS);
865
+ if (temDado) {
866
+ const e = P.escolher(dados, tipo, await modelosVivos(opts.server, opts.token, alias));
867
+ if (e) preferido = e.modelo;
868
+ }
869
+ } catch { /* sem dica: o servidor decide */ }
870
+ }
871
+ const r = await runActInterno(userPrompt, { ...opts, sinais, preferido });
872
+ try { P.fechar(tipo, sinais, r && r.concluiu); } catch { /* anotar é conveniência */ }
873
+ return r;
874
+ }
875
+
876
+ async function runActInterno(userPrompt, opts) {
836
877
  const {
837
878
  server, model, token, history, projectDir, projectName,
879
+ sinais = null, preferido = null, // o placar (lib/placar.js): onde anotar o que aconteceu, e a dica de modelo
838
880
  ensureWorkDir, profundidade = 0, subagent = false, effort,
839
881
  /* A memória do AGENTS.md entra no contexto? O bin diz não quando o
840
882
  resumo da conversa já a dobrou (lib/memoria-conversa.js). */
@@ -849,6 +891,8 @@ async function runAct(userPrompt, opts) {
849
891
  quem sabe lembrar é a SESSÃO — um subagente não pode começar do
850
892
  zero e pedir de novo o que já foi autorizado. */
851
893
  autorizar = null, controle = null,
894
+ /* Quem mostra a pergunta com opções da ferramenta `perguntar` (terminal). Sem ele: 1ª opção + log. */
895
+ escolher = null,
852
896
  /* O modo do /permissoes (auto · perguntar · livre). Vem da config pela
853
897
  CLI e desce para os subagentes pelo ctx: eles recusam o perigoso,
854
898
  mas só sabem que é "livre" se alguém disser. */
@@ -972,6 +1016,7 @@ async function runAct(userPrompt, opts) {
972
1016
  try { r = await C.rodar(como.comando, pastaAtual, { timeout: C.PRAZO_SOZINHO }); } catch { r = null; } finally { sp.stop(); }
973
1017
  tempos.somar('rodar os testes', Date.now() - t);
974
1018
  if (!r || r.estourou) { avisoDosTestes = ''; return null; }
1019
+ if (sinais) sinais.testes = !!r.ok;
975
1020
  registro.anotarTeste(livro, { comando: como.comando, ok: r.ok, code: r.codigo, saida: r.saida });
976
1021
  const ex = C.explicar(r, como);
977
1022
  if (r.ok) {
@@ -1284,6 +1329,7 @@ async function runAct(userPrompt, opts) {
1284
1329
  pedaços — a conversa dele inchou para 45 mil caracteres e uma resposta
1285
1330
  levou 22 minutos. Uma parte de um plano cabe em 20 voltas; na 15ª ele
1286
1331
  é avisado para fechar, e no teto volta com a entrega do que fez. */
1332
+ let ultimoModelo = null; let voltaDoResumo = -9; // lib/session/compactacao.js
1287
1333
  for (let iteration = 0; iteration < teto && !PARAR; iteration++) {
1288
1334
  voltas++;
1289
1335
  if (subagent && !avisouTeto && voltas >= teto - 5) {
@@ -1294,6 +1340,8 @@ async function runAct(userPrompt, opts) {
1294
1340
  // Recado digitado durante o passo anterior entra AGORA, antes da ida
1295
1341
  // ao modelo — o resultado das ferramentas já está na conversa.
1296
1342
  if (!subagent) entregarRecados(messages, profundidade);
1343
+ // Tarefa em segundo plano que acabou desde a última volta (lib/fundo.js): o modelo fica sabendo.
1344
+ if (!subagent) for (const av of require('./fundo').pegarAvisos()) messages.push({ role: 'user', content: av.texto });
1297
1345
  const sp = new Spinner(subagent ? 'subagente trabalhando' : PENSANDO).start();
1298
1346
 
1299
1347
  const toolCalls = [];
@@ -1315,11 +1363,27 @@ async function runAct(userPrompt, opts) {
1315
1363
  try {
1316
1364
  let folgado = false;
1317
1365
  try { folgado = !!require('./local/provedor.js').atual(); } catch { /* sem provedor próprio */ }
1366
+ /* RESUMO AUTOMÁTICO (lib/session/compactacao.js): passou de ~80% da janela do modelo → as voltas antigas viram
1367
+ um resumo feito pelo modelo (objetivo, decisões, arquivos, to-dos). Só depois o compactar() encolhe o resto. */
1368
+ if (voltas - voltaDoResumo >= 3) {
1369
+ try {
1370
+ const comp = require('./session/compactacao');
1371
+ if (comp.precisaCompactar(messages, { janela: comp.janelaDe(ultimoModelo) })) {
1372
+ voltaDoResumo = voltas;
1373
+ const r = await comp.compactarMensagens(messages, {
1374
+ todos: todosDaTarefa,
1375
+ resumir: ({ resumo, mensagens }, todos) => comp.resumirComFatos({ resumo, mensagens },
1376
+ (p) => motor.requestResumo(server, { ...p, token }).then((x) => x && x.resumo), todos),
1377
+ });
1378
+ if (r.ok) console.log(ident(profundidade) + c.dim(` ⎿ conversa passou de 80% da janela: ${r.resumidas} mensagens viraram resumo (${r.antes} → ${r.depois} tokens)`));
1379
+ }
1380
+ } catch { /* resumo é economia, não requisito */ }
1381
+ }
1318
1382
  const enviadas = compactar(messages, { folgado });
1319
1383
  traco(`volta ${voltas}${subagent ? ' (agente)' : ''} · ${enviadas.length} mensagens · ${JSON.stringify(enviadas).length} caracteres`);
1320
- await motor.requestAct(server, { messages: enviadas, model, effort, token, projectDir: pastaAtual, projectName, subagent, sinal: emVoo.signal }, (evt) => {
1384
+ await motor.requestAct(server, { messages: enviadas, model, preferido: preferido || undefined, effort, token, projectDir: pastaAtual, projectName, subagent, sinal: emVoo.signal }, (evt) => {
1321
1385
  if (evt.erro) { streamError = folgado ? evt.erro : erroDaCorrente(evt.erro); return; }
1322
- if (evt.modelo) { modeloDaVolta = evt; return; }
1386
+ if (evt.modelo) { modeloDaVolta = evt; ultimoModelo = evt.modelo; if (sinais) sinais.modelo = evt.modelo; require('./statusline').registrarModelo(evt.modelo); return; }
1323
1387
  if (evt.tool_call) {
1324
1388
  toolCalls.push(evt.tool_call);
1325
1389
  const a = evt.tool_call.arguments || {};
@@ -1997,8 +2061,9 @@ async function runAct(userPrompt, opts) {
1997
2061
  traco(`executando ${tc.name}`);
1998
2062
  const idAcao = idsAdiantados.get(tc.id) || anotar(tc, 'permitida');
1999
2063
  const result = adiantadas.has(tc.id) ? adiantadas.get(tc.id)
2000
- : await tools.execute(tc.name, tc.arguments, { projectDir: pastaAtual, server, token, api });
2064
+ : await tools.execute(tc.name, tc.arguments, { projectDir: pastaAtual, server, token, api, escolher: subagent ? undefined : escolher });
2001
2065
  auditoria.depois(idAcao, result);
2066
+ if (sinais && result && result.ok && /^(write_file|edit_file|multi_edit)$/.test(tc.name)) sinais.diagSujo = !!result.diagnosticos; // diagnóstico depois da edição
2002
2067
  if (antesDaEdicao != null && result && result.ok !== false) {
2003
2068
  let depois = null;
2004
2069
  try { depois = require('fs').readFileSync(arquivoDaEdicao, 'utf8'); } catch { depois = null; }
@@ -2233,10 +2298,14 @@ async function runSubagent(args, ctx) {
2233
2298
 
2234
2299
  // O ofício: o modelo diz quem faz (`especialista`), ou a tarefa sugere. O
2235
2300
  // briefing vai na frente da tarefa, na mesma mensagem — ver especialistas.js.
2236
- const esp = especialistas.escolher(args.especialista, args.prompt);
2237
- fala.agente(especialistas.rotulo(esp), descricao, profundidade);
2238
-
2239
- const r = await runAct(especialistas.montarTarefa(esp, args.prompt), {
2301
+ // Novo catálogo (agentes/): "id" ou "id1,id2" (até 3 olhares na mesma tarefa); o que não existe cai no legado.
2302
+ const novos = require('./agentes').resolver(args.especialista);
2303
+ const esp = novos.length ? null : especialistas.escolher(args.especialista, args.prompt);
2304
+ const agentes = require('./agentes');
2305
+ const rot = novos.length ? agentes.rotulo(novos) : especialistas.rotulo(esp);
2306
+ fala.agente(rot, descricao, profundidade);
2307
+
2308
+ const r = await runAct(novos.length ? agentes.montarTarefa(novos, args.prompt) : especialistas.montarTarefa(esp, args.prompt), {
2240
2309
  server, model, token, projectDir, projectName,
2241
2310
  history: [],
2242
2311
  profundidade: profundidade + 1,
@@ -2256,7 +2325,7 @@ async function runSubagent(args, ctx) {
2256
2325
 
2257
2326
  const resumo = r.assistantMessage.content || 'sem resultado';
2258
2327
  fala.agenteVoltou(resumo, profundidade);
2259
- return entregaDoAgente(especialistas.rotulo(esp), resumo, r.registro, { pasta: projectDir });
2328
+ return entregaDoAgente(rot, resumo, r.registro, { pasta: projectDir });
2260
2329
  }
2261
2330
 
2262
2331
  /* UM AGENTE DE CADA VEZ é o padrão do modelo grátis: no teste da loja
@@ -0,0 +1,146 @@
1
+ 'use strict';
2
+ /**
3
+ * lib/agentes — Mastermind + Geral (sempre ligados) e 90+ especialistas (sob demanda).
4
+ * Divulgação progressiva: o núcleo é curto; o índice e as receitas só entram quando o modelo busca.
5
+ * Formato: docs/rebuild/agentes-arquitetura.md
6
+ */
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+
10
+ const RAIZ = path.join(__dirname, '..', '..', 'agentes');
11
+
12
+ const semAcento = (s) => String(s || '').toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, '');
13
+ const palavras = (s) => semAcento(s).split(/[^a-z0-9+#.]+/).filter((w) => w.length > 1);
14
+
15
+ function frontmatter(texto) {
16
+ const m = String(texto).match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
17
+ if (!m) return null;
18
+ const meta = {};
19
+ for (const l of m[1].split('\n')) {
20
+ const i = l.indexOf(':');
21
+ if (i > 0) meta[l.slice(0, i).trim()] = l.slice(i + 1).trim();
22
+ }
23
+ return { meta, corpo: m[2].trim() };
24
+ }
25
+
26
+ let CACHE = null;
27
+ function carregar(raiz = RAIZ) {
28
+ if (CACHE && raiz === RAIZ) return CACHE;
29
+ const mapa = new Map();
30
+ // 1) índice: uma linha por agente. id|Nome|area|quando_usar|palavras
31
+ const idx = path.join(raiz, 'indice.md');
32
+ if (fs.existsSync(idx)) {
33
+ for (const l of fs.readFileSync(idx, 'utf8').split('\n')) {
34
+ if (!l || l.startsWith('#') || !l.includes('|')) continue;
35
+ const [id, nome, area, quando, chaves = ''] = l.split('|').map((s) => s.trim());
36
+ if (id) mapa.set(id, { id, nome, area, quando_usar: quando, palavras: chaves, briefing: null });
37
+ }
38
+ }
39
+ // 2) arquivos completos sobrescrevem a linha do índice
40
+ for (const f of fs.existsSync(raiz) ? fs.readdirSync(raiz) : []) {
41
+ if (!f.endsWith('.md') || f === 'indice.md') continue;
42
+ const p = frontmatter(fs.readFileSync(path.join(raiz, f), 'utf8'));
43
+ if (!p || !p.meta.id) throw new Error(`agente inválido: ${f}`);
44
+ mapa.set(p.meta.id, { ...p.meta, briefing: p.corpo });
45
+ }
46
+ if (raiz === RAIZ) CACHE = mapa;
47
+ return mapa;
48
+ }
49
+
50
+ /** Briefing de um agente; os só-índice ganham um briefing genérico honesto, ancorado na área. */
51
+ function briefing(a) {
52
+ if (a.briefing) return a.briefing;
53
+ return `Você é o especialista em ${a.nome} (${a.area}): ${a.quando_usar}. Leia o que já existe e siga o padrão dele; faça a menor mudança que resolve; `
54
+ + 'não invente API, flag ou fato que você não conferiu. Pronto só com prova: rode o comando/teste ou abra o resultado e cite a saída.';
55
+ }
56
+
57
+ function porId(id) {
58
+ const n = semAcento(id).trim();
59
+ const m = carregar();
60
+ if (m.has(n)) return m.get(n);
61
+ return null;
62
+ }
63
+
64
+ /** Os N agentes que melhor casam com a consulta (pontos por palavra em palavras > nome/id > quando_usar > area). */
65
+ function buscar(consulta, n = 5) {
66
+ const q = [...new Set(palavras(consulta))];
67
+ const lista = [];
68
+ for (const a of carregar().values()) {
69
+ const chaves = new Set(palavras(a.palavras));
70
+ const nome = new Set(palavras(`${a.id} ${a.nome}`));
71
+ const quando = new Set(palavras(a.quando_usar));
72
+ const area = new Set(palavras(a.area));
73
+ let pts = 0;
74
+ for (const w of q) {
75
+ if (chaves.has(w)) pts += 3;
76
+ if (nome.has(w)) pts += 3;
77
+ if (quando.has(w)) pts += 1;
78
+ if (area.has(w)) pts += 1;
79
+ }
80
+ if (pts > 0) lista.push({ a, pts });
81
+ }
82
+ lista.sort((x, y) => y.pts - x.pts || x.a.id.localeCompare(y.a.id));
83
+ return lista.slice(0, n).map((x) => x.a);
84
+ }
85
+
86
+ function linha(a) { return `${a.id} — ${a.nome} (${a.area}): ${a.quando_usar}`; }
87
+
88
+ /** A ferramenta agentes_buscar: lista curta; `ver` traz o briefing completo de um. */
89
+ function ferramentaBuscar({ consulta, ver }) {
90
+ if (ver) {
91
+ const a = porId(ver);
92
+ return a ? { ok: true, id: a.id, briefing: briefing(a) } : { ok: false, error: `sem agente "${ver}"; use agentes_buscar com uma consulta` };
93
+ }
94
+ if (!consulta) return { ok: true, total: carregar().size, areas: [...new Set([...carregar().values()].map((a) => a.area))] };
95
+ const achados = buscar(consulta, 6);
96
+ return { ok: true, total: carregar().size, resultados: achados.map(linha), dica: 'Chame spawn_agent com especialista="id" (ou "id1,id2" para dois olhares na mesma tarefa).' };
97
+ }
98
+
99
+ // ── capacidades do Geral ────────────────────────────────────────────────
100
+ let CAPS = null;
101
+ function capacidades() {
102
+ if (CAPS) return CAPS;
103
+ const dir = path.join(RAIZ, 'geral', 'capacidades');
104
+ CAPS = [];
105
+ for (const f of fs.existsSync(dir) ? fs.readdirSync(dir).sort() : []) {
106
+ if (!f.endsWith('.json')) continue;
107
+ const g = JSON.parse(fs.readFileSync(path.join(dir, f), 'utf8'));
108
+ for (const it of g.itens) CAPS.push({ ...it, grupo: g.grupo });
109
+ }
110
+ return CAPS;
111
+ }
112
+ function gruposDeCapacidades() {
113
+ const o = {};
114
+ for (const c of capacidades()) o[c.grupo] = (o[c.grupo] || 0) + 1;
115
+ return o;
116
+ }
117
+ function ferramentaCapacidade({ consulta, grupo }) {
118
+ if (!consulta && !grupo) return { ok: true, total: capacidades().length, grupos: gruposDeCapacidades() };
119
+ const q = [...new Set(palavras(consulta))];
120
+ const pts = capacidades().filter((c) => !grupo || c.grupo === grupo).map((c) => {
121
+ const alvo = new Set(palavras(`${c.nome} ${c.quando || ''} ${c.id}`));
122
+ return { c, p: q.reduce((s, w) => s + (alvo.has(w) ? 2 : 0) + (semAcento(c.receita).includes(w) ? 1 : 0), q.length ? 0 : 1) };
123
+ }).filter((x) => x.p > 0).sort((a, b) => b.p - a.p).slice(0, 5);
124
+ return { ok: true, resultados: pts.map(({ c }) => ({ id: c.id, grupo: c.grupo, nome: c.nome, receita: c.receita, prova: c.prova })) };
125
+ }
126
+
127
+ // ── delegação ───────────────────────────────────────────────────────────
128
+ /** "react" ou "react,seguranca": até 3 olhares na mesma tarefa. Devolve [] se nenhum id existe. */
129
+ function resolver(nomes) {
130
+ const ids = String(nomes || '').split(/[,+;]/).map((s) => s.trim()).filter(Boolean).slice(0, 3);
131
+ return ids.map(porId).filter(Boolean);
132
+ }
133
+ function montarTarefa(agentes, tarefa) {
134
+ if (!agentes.length) return String(tarefa || '');
135
+ const cab = agentes.map((a) => briefing(a)).join('\n\n');
136
+ const nota = agentes.length > 1 ? `\n\n(Você acumula ${agentes.length} olhares: ${agentes.map((a) => a.nome).join(' + ')}. Faça a tarefa uma vez, passando por cada checagem.)` : '';
137
+ return `${cab}${nota}\n\nSUA TAREFA:\n${tarefa}`;
138
+ }
139
+ function rotulo(agentes) { return agentes.length ? `Agente·${agentes.map((a) => a.id).join('+')}` : 'Agente'; }
140
+
141
+ /** O que o Mastermind faz com o que voltou de N especialistas (vai no resultado do lote). */
142
+ const REVISAO_DO_LOTE = '[mastermind] Entregas acima: confira cada "como provar", cruze os contratos entre partes (nomes, rotas, formatos) e mande de volta só o que falhou.';
143
+
144
+ function nucleo() { return require('./nucleo.js').NUCLEO; }
145
+
146
+ module.exports = { carregar, buscar, porId, briefing, ferramentaBuscar, ferramentaCapacidade, capacidades, gruposDeCapacidades, resolver, montarTarefa, rotulo, nucleo, REVISAO_DO_LOTE, frontmatter, RAIZ };
@@ -0,0 +1,18 @@
1
+ 'use strict';
2
+ /* O núcleo sempre ligado: Mastermind + Geral. Teto de ~1.5k tokens (test/agentes.test.js mede). */
3
+ const NUCLEO = `[mastermind]
4
+ Pense como um engenheiro sênior, mesmo sendo um modelo pequeno:
5
+ 1. ENTENDA: diga em uma linha o que será entregue e como saberá que ficou pronto. Pedido ambíguo e barato de adivinhar: assuma, escreva a suposição, siga. Só pergunte se errar custa caro.
6
+ 2. PLANEJE o menor caminho que prova o resultado. Pedido pequeno: faça direto. Grande: write_todos com 3 a 7 passos.
7
+ 3. OLHE antes de mexer: leia o que existe e siga o padrão da casa. Não invente API, arquivo, flag ou fato: confira (grep, ler, rodar) ou diga que não conferiu.
8
+ 4. FAÇA uma mudança por vez, a menor que resolve. Ações independentes vão juntas na mesma resposta.
9
+ 5. PROVE: rode o teste/comando ou abra o resultado e cite a saída. "Deve funcionar" não é prova. Falhou: leia o erro inteiro, mude a hipótese (nunca repita o mesmo comando), no máximo 3 tentativas e depois diga o bloqueio.
10
+ 6. PARE quando a prova passou. Sem extras, sem refatorar o que não foi pedido, sem reler o que acabou de escrever.
11
+ 7. FINALIZE com finish: o que mudou, a prova, o que ficou de fora.
12
+
13
+ [delegar]
14
+ Parte que pede ofício (linguagem, framework, segurança, design, banco...) ou 2+ partes independentes: agentes_buscar("o que preciso") acha o especialista; spawn_agent especialista="id" (ou "id1,id2" para dois olhares). Vários spawn_agent na mesma resposta rodam em paralelo. O prompt é completo: o subagente não vê esta conversa. Quando voltarem, VOCÊ revisa: confira o "como provar" de cada um, cruze contratos entre as partes, devolva só o que falhou. Edição pequena de um arquivo: faça você.
15
+
16
+ [geral]
17
+ Você é o agente que faz de tudo: código, pesquisa, texto, shell, git, depuração, dados, arquivos. Há um catálogo de receitas (capacidade_buscar("tarefa"), sem argumento lista os grupos): git, shell, depuração, código, pesquisa, escrita, dados, arquivos, web/API, segurança. Antes de improvisar tarefa conhecida (rebase, bisect, regex, curl com auth, migração), busque a receita: ela traz o passo e a prova. Segredo nunca vai no chat nem no commit. Ação destrutiva ou irreversível: diga o que vai acontecer e peça o sim.`;
18
+ module.exports = { NUCLEO };
package/lib/catalogo.js CHANGED
@@ -41,7 +41,7 @@ const GRUPOS = [
41
41
  resumo: 'rodar comando na sua máquina',
42
42
  itens: [
43
43
  { tool: 'run_shell', escreve: false, faz: 'roda comando e devolve a saída',
44
- args: 'command. timeout_ms (opcional, número, padrão 60000).' },
44
+ args: 'command. timeout_ms (opcional, número, padrão 60000). em_segundo_plano (opcional, true): roda solto, devolve um id e avisa quando terminar.' },
45
45
  { tool: 'run_in_new_terminal', escreve: false, faz: 'abre outro terminal quando a PESSOA quer ver o servidor ou o watch rodando',
46
46
  args: 'command. cwd (opcional): pasta onde rodar.' },
47
47
  { tool: 'run_background', escreve: false, faz: 'se o comando fica de pé (servidor, watch), roda em segundo plano e deixa você ler a saída depois',
@@ -254,6 +254,16 @@ const GRUPOS = [
254
254
  itens: [
255
255
  { tool: 'write_todos', escreve: false, faz: 'monta o plano e vai marcando o que terminou',
256
256
  args: 'todos: lista de {content: a tarefa curta no imperativo, status: pending|in_progress|completed}. Reescreve a lista inteira a cada chamada; só uma "in_progress" por vez.' },
257
+ { tool: 'perguntar', escreve: false, faz: 'pergunta ao usuário com 2 a 4 opções — só quando está bloqueado',
258
+ args: 'pergunta. opcoes: lista de 2 a 4 textos curtos (a recomendada primeiro). texto_livre (opcional, padrão true).' },
259
+ { tool: 'buscar_web', escreve: false, faz: 'busca na internet e devolve links com trecho',
260
+ args: 'consulta: poucas palavras. limite (opcional, número, padrão 6).' },
261
+ { tool: 'ler_url', escreve: false, faz: 'abre um link e devolve o texto da página em markdown',
262
+ args: 'url: link completo. max_chars (opcional), desde (opcional, número: continua uma leitura cortada).' },
263
+ { tool: 'agentes_buscar', escreve: false, faz: 'acha o especialista certo no catálogo de 100+ agentes (ou traz o briefing de um)',
264
+ args: 'consulta: o que você precisa, em poucas palavras. ver (opcional): id de um agente para ler o briefing completo. Sem argumentos: lista as áreas.' },
265
+ { tool: 'capacidade_buscar', escreve: false, faz: 'acha uma receita pronta (git, shell, depuração, código, pesquisa, escrita, dados, arquivos, web, segurança)',
266
+ args: 'consulta: a tarefa. grupo (opcional): limita a um grupo. Sem argumentos: lista os grupos.' },
257
267
  { tool: 'pesquisar_referencia', escreve: false, faz: 'lê docs e exemplos antes de escrever — sobe a qualidade',
258
268
  args: 'consulta: o que pesquisar, de preferência em inglês. paginas (opcional, número, padrão 3, máximo 5).' },
259
269
  // Vai para memory-geral.md, FORA da pasta do projeto — por isso não
@@ -272,7 +282,7 @@ const GRUPOS = [
272
282
  + 'Para só LER lá não precisa trocar: read_file/list_dir com o caminho absoluto, glob/grep com padrão absoluto.' },
273
283
  // Estas duas o loop do agente atende (lib/act.js), não o tools.execute.
274
284
  { tool: 'spawn_agent', escreve: false, faz: 'chama um subagente especialista para uma parte da tarefa', noLoop: true,
275
- args: 'description: 3 a 5 palavras, aparece na tela do usuário. prompt: instrução completa e autossuficiente — o subagente não vê esta conversa. especialista (opcional): frontend, backend, testes, design, dados, revisor ou ambiente — cada um chega com o briefing da profissão (lib/especialistas.js); sem ele, a tarefa sugere.' },
285
+ args: 'description: 3 a 5 palavras, aparece na tela do usuário. prompt: instrução completa e autossuficiente — o subagente não vê esta conversa. especialista (opcional): id de agentes_buscar (frontend, backend, testes, react, sql, seguranca…) ou dois separados por vírgula — cada um chega com o briefing da profissão (lib/especialistas.js); sem ele, a tarefa sugere.' },
276
286
  { tool: 'construir_app', escreve: true, noLoop: true,
277
287
  faz: 'constrói um app ou site inteiro com quatro papéis: engenheiro de prompt → protótipo → MVP → revisor que testa escondido, em laço até passar',
278
288
  args: 'pedido: o que o usuário quer, com as palavras dele. pasta (opcional): onde construir — sem ela, uma pasta nova com o nome do pedido. rodadas (opcional, 1 a 4, padrão 2): quantas voltas de correção. '