terminal-smart-cli 0.67.0 → 0.69.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/bin/ts.js +6 -6
- package/lib/agent.js +3 -0
- package/lib/meta.js +19 -5
- package/lib/recovery.js +72 -0
- package/package.json +1 -1
package/bin/ts.js
CHANGED
|
@@ -1185,18 +1185,18 @@ async function metaCmd() {
|
|
|
1185
1185
|
const maxJanelasEff = maxJanelas || ((budgetTotal || maxHoras) ? 0 : 20);
|
|
1186
1186
|
// CRITÉRIOS EXECUTÁVEIS (verify): --criterios <arquivo.json> = a PROVA de que a missão
|
|
1187
1187
|
// cumpriu o pedido (não basta compilar/abrir). Falhou → missão sai "NÃO verificada".
|
|
1188
|
+
// CRITÉRIOS (TaskSpec): --criterios <arquivo> (usuário manda) OU --provar (o meta auto-monta:
|
|
1189
|
+
// planner propõe + stack dá os testes, tudo dentro do run). Sem nada = comportamento atual.
|
|
1188
1190
|
let criteria = [];
|
|
1191
|
+
const prove = FLAGS.has('--provar') || FLAGS.has('--prove');
|
|
1189
1192
|
const _verify = require('../lib/verify');
|
|
1190
1193
|
const _critFile = (() => { const i = rawArgs.findIndex(a => a === '--criterios' || a === '--criteria'); return i >= 0 ? rawArgs[i + 1] : null; })();
|
|
1191
1194
|
if (_critFile) {
|
|
1192
1195
|
try { const j = JSON.parse(require('fs').readFileSync(_critFile, 'utf8')); const comp = _verify.compileCriteria(j); criteria = comp.criteria;
|
|
1193
1196
|
if (comp.dropped.length) console.log(' ' + C.dim((cfg.lang === 'en' ? 'ignored ' : 'ignorei ') + comp.dropped.length + (cfg.lang === 'en' ? ' invalid criterion(s)' : ' critério(s) inválido(s)'))); }
|
|
1194
1197
|
catch (e) { console.error(ui.errLine((cfg.lang === 'en' ? 'criteria file: ' : 'arquivo de critérios: ') + (e.message || e))); process.exit(2); }
|
|
1195
|
-
} else if (
|
|
1196
|
-
|
|
1197
|
-
criteria = _verify.deriveFromStack(dir);
|
|
1198
|
-
if (criteria.length) console.log(' ' + C.dim((cfg.lang === 'en' ? 'auto-derived proof: ' : 'prova auto-derivada: ') + criteria.map(c => c.label || c.type).join(' · ')) + '\n');
|
|
1199
|
-
else console.log(' ' + C.dim(cfg.lang === 'en' ? '--provar: no test command detected in this project (nothing to auto-prove)' : '--provar: nenhum comando de teste detectado neste projeto (nada a auto-provar)') + '\n');
|
|
1198
|
+
} else if (prove) {
|
|
1199
|
+
console.log(' ' + C.dim(cfg.lang === 'en' ? 'proof mode: the planner proposes criteria + the stack tests will be verified at the end' : 'modo prova: o planner propõe critérios + os testes da stack serão verificados no fim') + '\n');
|
|
1200
1200
|
}
|
|
1201
1201
|
|
|
1202
1202
|
const existing = metaMod.load(dir);
|
|
@@ -1244,7 +1244,7 @@ async function metaCmd() {
|
|
|
1244
1244
|
const goalArg = janela === 1 ? (goal || null) : null; // janelas seguintes só retomam
|
|
1245
1245
|
try {
|
|
1246
1246
|
st = await metaMod.run(goalArg, {
|
|
1247
|
-
token, lang: cfg.lang || 'pt', yes, budget, maxRounds, dir, model: forcedModel, thinker, maxMinutes, designer, design, eye, visualLadder, mockup, arch, criteria,
|
|
1247
|
+
token, lang: cfg.lang || 'pt', yes, budget, maxRounds, dir, model: forcedModel, thinker, maxMinutes, designer, design, eye, visualLadder, mockup, arch, criteria, prove,
|
|
1248
1248
|
onAlert: ({ type, text }) => {
|
|
1249
1249
|
sp.stop();
|
|
1250
1250
|
const ic = type === 'human' ? C.warn('🙋') : type === 'escalate' ? C.indigo('🧠') : type === 'stagnated' ? C.err('🛑') : type === 'design' ? C.cyan('🎨') : type === 'retry' || type === 'conn' ? C.warn('📡') : C.warn('⏱');
|
package/lib/agent.js
CHANGED
|
@@ -658,6 +658,9 @@ async function run(task, opts = {}) {
|
|
|
658
658
|
_toolErrs.push({ tool: name, class: _tr.errorClass, retryable: _tr.retryable, evidence: _tr.evidence });
|
|
659
659
|
// LEDGER DE ERROS (Evolve 3): registro determinístico, zero IA.
|
|
660
660
|
try { require('./memoria').logErro(confineDir || cwd, name, String(result.erro || result.stderr || ('exit ' + result.codigo))); } catch (_) {}
|
|
661
|
+
// RECOVERY ENGINE: erro de classe CONHECIDA → injeta a estratégia de conserto no
|
|
662
|
+
// resultado (o modelo aplica o padrão DevOps em vez de chutar/entrar em loop).
|
|
663
|
+
try { const _rh = require('./recovery').recoveryHint(_tr.errorClass); if (_rh && result && typeof result === 'object') result = Object.assign({}, result, { _recovery: _rh }); } catch (_) {}
|
|
661
664
|
}
|
|
662
665
|
// HOOK PostToolUse (determinístico): feedback (ex: lint/format) vai pro modelo ver.
|
|
663
666
|
if (_hooks._any) {
|
package/lib/meta.js
CHANGED
|
@@ -977,7 +977,11 @@ function _tolerantJson(text) {
|
|
|
977
977
|
|
|
978
978
|
const CHECKLIST_SYS = 'Quebre o OBJETIVO em itens VERIFICÁVEIS de entrega (3 a 15). Cada item deve ser '
|
|
979
979
|
+ 'concreto e conferível (arquivo criado, comando executado com sucesso, seção escrita) — nada vago tipo "melhorar". '
|
|
980
|
-
+ 'Responda APENAS JSON: {"itens":[{"id":"i1","desc":"..."},...]}. Ordene na sequência lógica de execução
|
|
980
|
+
+ 'Responda APENAS JSON: {"itens":[{"id":"i1","desc":"..."},...],"criterios":[...]}. Ordene os itens na sequência lógica de execução.\n'
|
|
981
|
+
+ 'Em "criterios" (OPCIONAL, 0 a 6), proponha PROVAS EXECUTÁVEIS de que o objetivo foi cumprido — que uma MÁQUINA cheque sem julgamento. '
|
|
982
|
+
+ 'Tipos válidos: {"type":"command","cmd":"npm test","exit":0} · {"type":"file_exists","path":"src/x.js"} · {"type":"file_contains","path":"server.js","text":"/api/login"} · {"type":"http","url":"http://localhost:3000/health","status":200} · {"type":"port","port":3000}. '
|
|
983
|
+
+ 'PREFIRA file_exists/file_contains/command (seguros e baratos); só use http/port se o objetivo é claramente subir um servidor. '
|
|
984
|
+
+ 'NÃO invente critério que possa FALHAR mesmo com a missão pronta. Na dúvida, "criterios":[].';
|
|
981
985
|
|
|
982
986
|
// Pergunta BINÁRIA sobre o item alvo (modelo pequeno erra listas, mas acerta sim/não).
|
|
983
987
|
const MARK_SYS = 'Você é o VERIFICADOR de uma missão. Recebe UM item do checklist e o RESULTADO da rodada que tentou executá-lo. '
|
|
@@ -987,11 +991,14 @@ const MARK_SYS = 'Você é o VERIFICADOR de uma missão. Recebe UM item do check
|
|
|
987
991
|
const fmtChecklist = (itens) => itens.map(it => `[${it.passes ? 'x' : it.blocked ? '!' : ' '}] (${it.id}) ${it.desc}`).join('\n');
|
|
988
992
|
|
|
989
993
|
async function makeChecklist(goal, token) {
|
|
990
|
-
const r = await _llmJson({ token, system: CHECKLIST_SYS, user: 'OBJETIVO:\n' + goal, maxTokens:
|
|
994
|
+
const r = await _llmJson({ token, system: CHECKLIST_SYS, user: 'OBJETIVO:\n' + goal, maxTokens: 1800 });
|
|
991
995
|
const itens = (Array.isArray(r.json?.itens) ? r.json.itens : []).slice(0, 15)
|
|
992
996
|
.map((it, i) => ({ id: String(it.id || 'i' + (i + 1)), desc: String(it.desc || '').slice(0, 300), passes: false, attempts: 0 }))
|
|
993
997
|
.filter(it => it.desc);
|
|
994
|
-
|
|
998
|
+
// Critérios propostos pelo planner → VALIDADOS pelo compilador (formato conhecido; malformados caem fora).
|
|
999
|
+
let criteria = [];
|
|
1000
|
+
try { criteria = require('./verify').compileCriteria(r.json?.criterios || []).criteria.slice(0, 6); } catch (_) {}
|
|
1001
|
+
return { itens: itens.length ? itens : [{ id: 'i1', desc: String(goal).slice(0, 300), passes: false, attempts: 0 }], criteria, credits: r.credits };
|
|
995
1002
|
}
|
|
996
1003
|
|
|
997
1004
|
/**
|
|
@@ -1016,7 +1023,7 @@ async function _checkCriteria(st, dir) {
|
|
|
1016
1023
|
}
|
|
1017
1024
|
|
|
1018
1025
|
async function run(goal, opts = {}) {
|
|
1019
|
-
const { token, lang = 'pt', yes = false, budget = 400, maxRounds = 20, dir = process.cwd(), model = null, thinker = null, maxMinutes = 0, designer = null, design = 'auto', runGate = true, eye = null, visualLadder = true } = opts;
|
|
1026
|
+
const { token, lang = 'pt', yes = false, budget = 400, maxRounds = 20, dir = process.cwd(), model = null, thinker = null, maxMinutes = 0, designer = null, design = 'auto', runGate = true, eye = null, visualLadder = true, prove = false } = opts;
|
|
1020
1027
|
const onChecklist = opts.onChecklist || (() => {});
|
|
1021
1028
|
const onRound = opts.onRound || (() => {});
|
|
1022
1029
|
const onRoundDone = opts.onRoundDone || (() => {});
|
|
@@ -1056,8 +1063,15 @@ async function run(goal, opts = {}) {
|
|
|
1056
1063
|
try { const a = await archPhase(goal, archModel, token, { dir }); archBrief = a.brief; archCred = a.credits || 0; try { fs.writeFileSync(path.join(dir, 'ARQUITETURA.md'), archBrief); } catch (_) {} onAlert({ type: 'design', text: `📐 ts: contrato de arquitetura criado pelo ${archModel} (${archCred} créditos) — componentes, arquivos e assinaturas fixados. Salvo em ARQUITETURA.md.` }); } catch (_e) { if (_e && _e.code === 'no_credits') throw _e; }
|
|
1057
1064
|
}
|
|
1058
1065
|
const mk = await makeChecklist(goal + (designBrief ? '\n\n[Há um DESIGN SYSTEM definido — o checklist deve refletir a aplicação desse visual]' : '') + (archBrief ? '\n\n[Há um CONTRATO DE ARQUITETURA definido — os itens devem seguir os componentes/arquivos dele]' : ''), token);
|
|
1066
|
+
// CRITÉRIOS (TaskSpec): usuário (--criterios) manda; senão, com --provar, combina o que o
|
|
1067
|
+
// PLANNER propôs (mk.criteria, já validado) + os TESTES da stack — tudo recompilado. Sem
|
|
1068
|
+
// --provar, só guarda o que o planner propôs (plannedCriteria) como referência, sem bloquear.
|
|
1069
|
+
const _V = require('./verify');
|
|
1070
|
+
let _criteria = [], _planned = mk.criteria || [];
|
|
1071
|
+
if (Array.isArray(opts.criteria) && opts.criteria.length) _criteria = opts.criteria;
|
|
1072
|
+
else if (prove) _criteria = _V.compileCriteria([..._V.deriveFromStack(dir), ..._planned]).criteria.slice(0, 8);
|
|
1059
1073
|
st = { goal: String(goal).slice(0, 4000), mockup: opts.mockup || null, archBrief, feasBrief, checklist: mk.itens, rounds: [], creditsSpent: (mk.credits || 0) + designCred + archCred + feasCred,
|
|
1060
|
-
budget, maxRounds, status: 'running', model: model || null, designBrief: designBrief || null, criteria:
|
|
1074
|
+
budget, maxRounds, status: 'running', model: model || null, designBrief: designBrief || null, criteria: _criteria, plannedCriteria: _planned, created_at: new Date().toISOString() };
|
|
1061
1075
|
save(st, dir);
|
|
1062
1076
|
} else {
|
|
1063
1077
|
st.status = 'running';
|
package/lib/recovery.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// RECOVERY ENGINE — conhecimento de conserto codificado POR CLASSE de erro.
|
|
2
|
+
// Insight das análises externas (GPT): "cada classe de erro deve ter sondas e estratégias
|
|
3
|
+
// conhecidas; só escalar pro modelo forte após 2 estratégias falharem". Aqui, quando uma
|
|
4
|
+
// ferramenta falha com classe CONHECIDA (core.classifyToolResult), injetamos a estratégia
|
|
5
|
+
// certa no resultado que o modelo lê — ele aplica o conserto DevOps padrão em vez de chutar
|
|
6
|
+
// (ex.: porta ocupada → matar SÓ o PID, nunca por nome; que era justo o loop do incidente).
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
// Cada classe: como DIAGNOSTICAR (sonda só-leitura) e como CONSERTAR (ação segura e cirúrgica).
|
|
10
|
+
const STRATEGIES = {
|
|
11
|
+
port_in_use: {
|
|
12
|
+
diag: 'ache o PID que segura a porta — Linux: `ss -ltnp | grep :<porta>` ou `lsof -i:<porta>`; Windows: `netstat -ano | findstr :<porta>`',
|
|
13
|
+
fix: 'mate SÓ aquele PID (`kill <pid>` / `taskkill /F /PID <pid>`) — NUNCA por nome (mata processos demais, inclusive o seu). OU suba o serviço em outra porta.',
|
|
14
|
+
},
|
|
15
|
+
missing_dep: {
|
|
16
|
+
diag: 'confirme o nome EXATO do módulo/binário que faltou na mensagem de erro',
|
|
17
|
+
fix: 'instale-o: Node `npm i <pkg>` · Python `pip install <pkg>` · sistema `sudo DEBIAN_FRONTEND=noninteractive apt-get install -y <pkg>` (timeout_s alto). Depois re-tente o comando original.',
|
|
18
|
+
},
|
|
19
|
+
package_lock: {
|
|
20
|
+
diag: 'veja qual processo segura o lock (apt/dpkg/npm) — NÃO o mate no meio',
|
|
21
|
+
fix: 'espere o processo dono do lock terminar e re-tente. Se o dpkg foi interrompido antes: `sudo dpkg --configure -a`. Nunca apague o lockfile na força.',
|
|
22
|
+
},
|
|
23
|
+
permission_denied: {
|
|
24
|
+
diag: 'cheque dono e modo do alvo: `ls -l <caminho>` (Linux) / atributos do arquivo',
|
|
25
|
+
fix: 'ajuste o dono/modo do arquivo ESPECÍFICO (`chown user:grp <arquivo>` / `chmod u+w <arquivo>`) ou use sudo só onde apropriado. NUNCA `chmod 777` nem recursivo na raiz.',
|
|
26
|
+
},
|
|
27
|
+
not_found: {
|
|
28
|
+
diag: 'confirme o caminho/rota EXATO com `listar_diretorio`/`buscar_arquivos` (maiúsculas e plural contam)',
|
|
29
|
+
fix: 'crie o arquivo/rota que falta OU corrija a referência pro caminho certo. Não invente o caminho — verifique antes.',
|
|
30
|
+
},
|
|
31
|
+
network: {
|
|
32
|
+
diag: 'teste a conectividade: `curl -I <url>` / `ping <host>` / cheque DNS e se o serviço-alvo está no ar',
|
|
33
|
+
fix: 're-tente com backoff; confirme URL/porta/firewall. Se o alvo estiver fora, reporte isso ao usuário em vez de insistir.',
|
|
34
|
+
},
|
|
35
|
+
timeout: {
|
|
36
|
+
diag: 'o comando demorou mais que o limite — veja se é build/install pesado ou algo travado',
|
|
37
|
+
fix: 'aumente o timeout (`timeout_s` maior) OU quebre em passos menores. Para build/install use timeout longo (300s+).',
|
|
38
|
+
},
|
|
39
|
+
compile_error: {
|
|
40
|
+
diag: 'leia a mensagem: ela dá ARQUIVO e LINHA do erro',
|
|
41
|
+
fix: 'vá DIRETO à linha (`buscar_codigo`/`ler_arquivo`) e conserte só o trecho (`editar_arquivo` por âncora) — não reescreva o arquivo inteiro. Confira sintaxe/tipos ao redor.',
|
|
42
|
+
},
|
|
43
|
+
test_failed: {
|
|
44
|
+
diag: 'rode o teste específico que falhou e leia o assert (esperado × obtido)',
|
|
45
|
+
fix: 'corrija a CAUSA no código (não o teste, a menos que o teste esteja claramente errado). Re-rode só aquele teste até passar.',
|
|
46
|
+
},
|
|
47
|
+
auth: {
|
|
48
|
+
diag: 'cheque token/credencial/variável de ambiente exigida (sem logar o segredo)',
|
|
49
|
+
fix: 'corrija a credencial/escopo. Se depende de um segredo do usuário, PARE e peça a ele — não chute nem exponha valores.',
|
|
50
|
+
},
|
|
51
|
+
git_conflict: {
|
|
52
|
+
diag: 'veja os arquivos em conflito: `git status`',
|
|
53
|
+
fix: 'abra cada arquivo, resolva os marcadores `<<<<<<<`/`=======`/`>>>>>>>`, `git add` e conclua o merge/rebase. Não faça `push --force`.',
|
|
54
|
+
},
|
|
55
|
+
invalid_schema: {
|
|
56
|
+
diag: 'valide a estrutura (JSON/YAML): vírgulas, aspas, chaves balanceadas',
|
|
57
|
+
fix: 'corrija o campo apontado; valide de novo antes de reusar.',
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const RECOVERABLE = Object.keys(STRATEGIES); // classes com estratégia conhecida (unknown/blocked não têm)
|
|
62
|
+
|
|
63
|
+
function strategiesFor(errorClass) { return STRATEGIES[errorClass] || null; }
|
|
64
|
+
|
|
65
|
+
// Texto acionável pra INJETAR no resultado da ferramenta que o modelo lê. '' se classe sem estratégia.
|
|
66
|
+
function recoveryHint(errorClass) {
|
|
67
|
+
const s = STRATEGIES[errorClass];
|
|
68
|
+
if (!s) return '';
|
|
69
|
+
return `ESTRATÉGIA CONHECIDA (${errorClass}) — não chute:\n1) DIAGNOSTIQUE: ${s.diag}\n2) CONSERTO: ${s.fix}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = { STRATEGIES, RECOVERABLE, strategiesFor, recoveryHint };
|