terminal-smart-cli 0.74.0 → 0.75.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 (3) hide show
  1. package/lib/core.js +14 -1
  2. package/lib/meta.js +20 -12
  3. package/package.json +1 -1
package/lib/core.js CHANGED
@@ -109,7 +109,20 @@ function classifyError(msg) {
109
109
  // NÃO muta o raw. Convenções do TS: {erro} = falha; {codigo|exitCode}≠0 = falha de shell;
110
110
  // {written|created|resumo|stdout|...} = evidência de sucesso.
111
111
  function classifyToolResult(raw) {
112
- if (raw == null || typeof raw !== 'object') return { ok: true, status: 'ok', errorClass: null, retryable: false, evidence: raw != null ? String(raw).slice(0, 200) : '' };
112
+ if (raw == null) return { ok: true, status: 'ok', errorClass: null, retryable: false, evidence: '' };
113
+ // Alguns adaptadores legados retornam falhas como texto. Tratar todo texto como sucesso
114
+ // permitia que "ERRO ao executar..." virasse etapa concluída. Mantém strings normais como
115
+ // sucesso, mas reconhece prefixos inequívocos de falha/bloqueio.
116
+ if (typeof raw !== 'object') {
117
+ const text = String(raw);
118
+ const failedText = /^\s*(?:(?:erro|error|falha|failed|bloquead[oa])(?:\s*[:\-]|\s+(?:ao|a|de|do|da|em|no|na|to|while|executing|calling|opening|reading|writing|request|operation)\b|\s*$)|operação cancelada\b|o usuário recusou\b)/i.test(text);
119
+ if (failedText) {
120
+ const errorClass = classifyError(text);
121
+ return { ok: false, status: errorClass === 'blocked' ? 'blocked' : 'failed', errorClass,
122
+ retryable: RETRYABLE_CLASSES.has(errorClass), evidence: text.replace(/\s+/g, ' ').slice(0, 300) };
123
+ }
124
+ return { ok: true, status: 'ok', errorClass: null, retryable: false, evidence: text.slice(0, 200) };
125
+ }
113
126
  const exit = raw.codigo != null ? raw.codigo : (raw.exitCode != null ? raw.exitCode : null);
114
127
  const errMsg = raw.erro || raw.error || (exit != null && exit !== 0 ? (raw.stderr || raw.stdout || ('exit ' + exit)) : '');
115
128
  const failed = !!raw.erro || !!raw.error || (exit != null && exit !== 0);
package/lib/meta.js CHANGED
@@ -1011,7 +1011,8 @@ async function makeChecklist(goal, token) {
1011
1011
  // CRITÉRIOS EXECUTÁVEIS (verify): a prova FINAL da missão. Rodados quando os gates de
1012
1012
  // build/run passam, ANTES de declarar 'verified'. Se algum critério do usuário falha, a
1013
1013
  // missão NÃO é verificada (honestidade — não basta compilar/abrir; tem que CUMPRIR o pedido).
1014
- // Sem critérios nada a provar (retorna true). runAll nunca lança.
1014
+ // Sem critérios explícitos, os gates de build/run continuam sendo a prova. Erro interno do
1015
+ // verificador é fail-closed: nunca transforma ausência de prova em `verified`.
1015
1016
  async function _checkCriteria(st, dir) {
1016
1017
  if (!st.criteria || !st.criteria.length) return true;
1017
1018
  try {
@@ -1019,7 +1020,11 @@ async function _checkCriteria(st, dir) {
1019
1020
  st.verifyReport = { passed: rep.passed, total: rep.total, allOk: rep.allOk, results: rep.results.map(r => ({ ok: r.ok, label: r.label, detail: r.detail })) };
1020
1021
  if (!rep.allOk) st.runNote = (st.runNote || '') + ` critérios: ${rep.passed}/${rep.total} passaram · falharam: ${rep.results.filter(r => !r.ok).map(r => r.label).slice(0, 3).join(' | ')}`;
1021
1022
  return rep.allOk;
1022
- } catch (_) { return true; }
1023
+ } catch (e) {
1024
+ st.verifyReport = { passed: 0, total: st.criteria.length, allOk: false, results: [], error: String((e && e.message) || e).slice(0, 300) };
1025
+ st.runNote = (st.runNote || '') + ' verificador falhou internamente; missão não marcada como verified';
1026
+ return false;
1027
+ }
1023
1028
  }
1024
1029
 
1025
1030
  async function run(goal, opts = {}) {
@@ -1064,12 +1069,14 @@ async function run(goal, opts = {}) {
1064
1069
  }
1065
1070
  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
1071
  // 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, guarda o que o planner propôs (plannedCriteria) como referência, sem bloquear.
1072
+ // PLANNER propôs (mk.criteria, já validado) + os TESTES da stack — tudo recompilado.
1073
+ // Mesmo sem --provar, critérios determinísticos derivados da stack rodam por padrão;
1074
+ // critérios inventados pelo planner ficam apenas como referência até o usuário pedir prova ampla.
1069
1075
  const _V = require('./verify');
1070
1076
  let _criteria = [], _planned = mk.criteria || [];
1071
1077
  if (Array.isArray(opts.criteria) && opts.criteria.length) _criteria = opts.criteria;
1072
1078
  else if (prove) _criteria = _V.compileCriteria([..._V.deriveFromStack(dir), ..._planned]).criteria.slice(0, 8);
1079
+ else _criteria = _V.compileCriteria(_V.deriveFromStack(dir)).criteria.slice(0, 8);
1073
1080
  st = { goal: String(goal).slice(0, 4000), mockup: opts.mockup || null, archBrief, feasBrief, checklist: mk.itens, rounds: [], creditsSpent: (mk.credits || 0) + designCred + archCred + feasCred,
1074
1081
  budget, maxRounds, status: 'running', model: model || null, designBrief: designBrief || null, criteria: _criteria, plannedCriteria: _planned, created_at: new Date().toISOString() };
1075
1082
  save(st, dir);
@@ -1346,14 +1353,16 @@ async function run(goal, opts = {}) {
1346
1353
  // (Bug real do teste MultiApps: flash-lite bloqueou itens cujos arquivos existiam no disco.)
1347
1354
  let marks = [];
1348
1355
  const written = (out.actions || []).filter(a => a.name === 'escrever_arquivo' || a.name === 'editar_arquivo').map(a => String(a.target || ''));
1349
- const ranOk = (out.actions || []).some(a => a.name === 'executar_comando');
1350
1356
  const _basename = (p) => String(p).replace(/\\/g, '/').split('/').pop().toLowerCase();
1351
1357
  const _fileHints = (txt) => (String(txt).match(/[\w.\-]+\.(kt|java|xml|gradle|json|md|txt|kts|properties|pro|png|webp|py|js|ts|html|css|sh)\b/gi) || []).map(s => s.toLowerCase());
1352
1358
  const itemFiles = _fileHints(item.desc);
1353
- const wroteForItem = itemFiles.length
1354
- ? itemFiles.some(f => written.some(w => _basename(w) === f))
1355
- : written.length > 0; // item sem nome de arquivo explícito mas a rodada produziu algo
1356
- if (wroteForItem || (ranOk && !itemFiles.length)) marks.push(item.id);
1359
+ // Uma escrita/comando genérico não prova um item sem vínculo. Só há marcação automática
1360
+ // quando o próprio texto do item identifica o artefato e a rodada gravou esse artefato.
1361
+ // Itens abstratos continuam podendo ser avaliados pelo marcador, mas não "vazam" sucesso
1362
+ // para outros itens porque algum arquivo/comando apareceu na mesma rodada.
1363
+ const wroteForItem = itemFiles.length > 0
1364
+ && itemFiles.some(f => written.some(w => _basename(w) === f));
1365
+ if (wroteForItem) marks.push(item.id);
1357
1366
 
1358
1367
  // Item de correção de build: NÃO usa marcador — o PORTÃO DE BUILD é a única
1359
1368
  // autoridade (recompila de verdade). Marca provisório pra reabrir o gate; se o
@@ -1368,15 +1377,14 @@ async function run(goal, opts = {}) {
1368
1377
  continue;
1369
1378
  }
1370
1379
 
1371
- // Marcador IA como REFORÇO: confirma o alvo se a evidência objetiva não pegou, e
1372
- // credita OUTROS itens do checklist que a rodada também concluiu.
1380
+ // Marcador IA como REFORÇO do ITEM ALVO. Não aceita mais `tambem_concluidos`: uma
1381
+ // narrativa de uma rodada não é evidência suficiente para concluir itens não executados.
1373
1382
  try {
1374
1383
  const evid = written.length ? '\n\nARQUIVOS REALMENTE GRAVADOS nesta rodada (evidência objetiva):\n' + written.map(_basename).join(', ') : '';
1375
1384
  const mk = await _llmJson({ token, system: MARK_SYS,
1376
1385
  user: `OBJETIVO:\n${st.goal.slice(0, 600)}\n\nCHECKLIST (contexto):\n${fmtChecklist(st.checklist)}\n\nITEM ALVO: (${item.id}) ${item.desc}\n\nRESULTADO DA RODADA:\n${String(out.text || '').slice(0, 2000)}${evid}` });
1377
1386
  st.creditsSpent += mk.credits || 0;
1378
1387
  if (mk.json && mk.json.passou === true) marks.push(item.id);
1379
- if (Array.isArray(mk.json?.tambem_concluidos)) marks.push(...mk.json.tambem_concluidos.map(String));
1380
1388
  } catch (_) {}
1381
1389
  marks = [...new Set(marks)];
1382
1390
  for (const it of st.checklist) if (marks.includes(it.id)) it.passes = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "terminal-smart-cli",
3
- "version": "0.74.0",
3
+ "version": "0.75.0",
4
4
  "description": "Terminal Smart no seu terminal — pergunte, analise logs por pipe e orquestre agentes de IA. Comando: ts",
5
5
  "bin": {
6
6
  "ts": "bin/ts.js"