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.
- package/LICENSES/opencode-MIT.txt +24 -0
- package/LICENSES/pptxgenjs-MIT.txt +31 -0
- package/README.md +16 -3
- package/agentes/acessibilidade.md +10 -0
- package/agentes/ambiente.md +10 -0
- package/agentes/api.md +10 -0
- package/agentes/backend.md +10 -0
- package/agentes/copy.md +10 -0
- package/agentes/css-layout.md +10 -0
- package/agentes/dados.md +10 -0
- package/agentes/depuracao.md +10 -0
- package/agentes/design.md +10 -0
- package/agentes/docs.md +10 -0
- package/agentes/frontend.md +10 -0
- package/agentes/geral/capacidades/arquivos.json +132 -0
- package/agentes/geral/capacidades/codigo.json +132 -0
- package/agentes/geral/capacidades/dados.json +126 -0
- package/agentes/geral/capacidades/depuracao.json +126 -0
- package/agentes/geral/capacidades/escrita.json +126 -0
- package/agentes/geral/capacidades/git.json +126 -0
- package/agentes/geral/capacidades/pesquisa.json +126 -0
- package/agentes/geral/capacidades/seguranca.json +132 -0
- package/agentes/geral/capacidades/shell.json +132 -0
- package/agentes/geral/capacidades/web.json +126 -0
- package/agentes/git-pr.md +10 -0
- package/agentes/indice.md +86 -0
- package/agentes/nextjs.md +10 -0
- package/agentes/node.md +10 -0
- package/agentes/performance.md +10 -0
- package/agentes/pesquisa.md +10 -0
- package/agentes/python.md +10 -0
- package/agentes/react.md +10 -0
- package/agentes/refatoracao.md +10 -0
- package/agentes/revisor.md +10 -0
- package/agentes/seguranca.md +10 -0
- package/agentes/sql.md +10 -0
- package/agentes/testes.md +10 -0
- package/agentes/typescript.md +10 -0
- package/agentes/ux.md +10 -0
- package/bin/primocode.js +65 -8
- package/editor/helpers/camera_viva.py +28 -2
- package/lib/act.js +79 -10
- package/lib/agentes/index.js +146 -0
- package/lib/agentes/nucleo.js +18 -0
- package/lib/catalogo.js +12 -2
- package/lib/cli/review.js +64 -0
- package/lib/cli/subcomandos.js +34 -0
- package/lib/cli/tentativas.js +107 -0
- package/lib/cli/util.js +51 -0
- package/lib/comandos-extra.js +4 -2
- package/lib/editor-pro-maquina.js +165 -0
- package/lib/editor-pro.js +10 -7
- package/lib/entregaveis/cli.js +26 -0
- package/lib/entregaveis/especialistas.js +50 -0
- package/lib/entregaveis/nucleo.js +111 -0
- package/lib/entregaveis/slides.js +221 -0
- package/lib/estudio/materiais.js +172 -0
- package/lib/fundo.js +34 -1
- package/lib/kits.js +41 -2
- package/lib/nomes.js +6 -0
- package/lib/placar.js +173 -0
- package/lib/plugin/autoteste.js +99 -0
- package/lib/plugin/edicao-visual.js +133 -0
- package/lib/plugin/pesquisar.js +92 -0
- package/lib/plugin/preview-cli.js +38 -0
- package/lib/plugin/preview.js +89 -0
- package/lib/plugin/tunel.js +27 -0
- package/lib/review.js +134 -0
- package/lib/session/compactacao.js +124 -0
- package/lib/statusline.js +52 -0
- package/lib/tool/diagnostico.js +100 -0
- package/lib/tool/perguntar.js +60 -0
- package/lib/tool/reparar-entrada.js +66 -0
- package/lib/tool/web.js +102 -0
- package/lib/tools.js +20 -1
- package/lib/ui.js +1 -1
- package/materiais/saas-linear/base.ts +84 -0
- package/materiais/saas-linear/camera/Camera.tsx +38 -0
- package/materiais/saas-linear/fontes/OFL-Inter.txt +93 -0
- package/materiais/saas-linear/fontes/OFL-Newsreader.txt +93 -0
- package/materiais/saas-linear/fontes/inter-400.woff2 +0 -0
- package/materiais/saas-linear/fontes/inter-600.woff2 +0 -0
- package/materiais/saas-linear/fontes/newsreader-400.woff2 +0 -0
- package/materiais/saas-linear/formas/Agenda.tsx +46 -0
- package/materiais/saas-linear/formas/CaixaPrompt.tsx +41 -0
- package/materiais/saas-linear/formas/CartoesIntegracao.tsx +57 -0
- package/materiais/saas-linear/formas/Cursor.tsx +40 -0
- package/materiais/saas-linear/formas/Menu.tsx +46 -0
- package/materiais/saas-linear/formas/Monitor.tsx +28 -0
- package/materiais/saas-linear/paleta.json +18 -0
- package/materiais/saas-linear/pecas.json +590 -0
- package/materiais/saas-linear/regras.md +40 -0
- package/materiais/saas-linear/sons/Som.tsx +39 -0
- package/materiais/saas-linear/sons/assinatura.mp3 +0 -0
- package/materiais/saas-linear/sons/clique.mp3 +0 -0
- package/materiais/saas-linear/sons/impacto.mp3 +0 -0
- package/materiais/saas-linear/sons/pop-cartao.mp3 +0 -0
- package/materiais/saas-linear/sons/pop-palavra.mp3 +0 -0
- package/materiais/saas-linear/sons/subida.mp3 +0 -0
- package/materiais/saas-linear/sons/swoosh.mp3 +0 -0
- package/materiais/saas-linear/sons/trilha.mp3 +0 -0
- package/materiais/saas-linear/sons/whoosh-entra.mp3 +0 -0
- package/materiais/saas-linear/sons/whoosh-sai.mp3 +0 -0
- package/materiais/saas-linear/texturas/Fundo.tsx +18 -0
- package/materiais/saas-linear/texturas/Particulas.tsx +22 -0
- package/materiais/saas-linear/tipografia/FraseSerifa.tsx +25 -0
- package/materiais/saas-linear/tipografia/Marca.tsx +20 -0
- package/materiais/saas-linear/tipografia/MarcaFinal.tsx +22 -0
- package/materiais/saas-linear/tipografia/Saudacao.tsx +27 -0
- package/materiais/saas-linear/transicoes/Cena.tsx +38 -0
- package/materiais/saas-linear/transicoes/CorteImpacto.tsx +16 -0
- package/package.json +3 -1
- package/skills/deck-conferido/SKILL.md +29 -0
- package/vendor/pptxgenjs/carregar.js +12 -0
- package/vendor/pptxgenjs/jszip.min.js +13 -0
- 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
|
-
|
|
505
|
-
|
|
506
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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=
|
|
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
|
-
|
|
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
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
const
|
|
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(
|
|
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,
|
|
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. '
|