terminal-smart-cli 0.65.0 → 0.67.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 CHANGED
@@ -1183,6 +1183,21 @@ async function metaCmd() {
1183
1183
  const intervaloMin = flagNum('--intervalo', 5); // min de espera entre janelas ao aguardar crédito
1184
1184
  // Anti-loop-infinito: se ninguém definiu teto, cai num limite de janelas seguro.
1185
1185
  const maxJanelasEff = maxJanelas || ((budgetTotal || maxHoras) ? 0 : 20);
1186
+ // CRITÉRIOS EXECUTÁVEIS (verify): --criterios <arquivo.json> = a PROVA de que a missão
1187
+ // cumpriu o pedido (não basta compilar/abrir). Falhou → missão sai "NÃO verificada".
1188
+ let criteria = [];
1189
+ const _verify = require('../lib/verify');
1190
+ const _critFile = (() => { const i = rawArgs.findIndex(a => a === '--criterios' || a === '--criteria'); return i >= 0 ? rawArgs[i + 1] : null; })();
1191
+ if (_critFile) {
1192
+ try { const j = JSON.parse(require('fs').readFileSync(_critFile, 'utf8')); const comp = _verify.compileCriteria(j); criteria = comp.criteria;
1193
+ 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
+ catch (e) { console.error(ui.errLine((cfg.lang === 'en' ? 'criteria file: ' : 'arquivo de critérios: ') + (e.message || e))); process.exit(2); }
1195
+ } else if (FLAGS.has('--provar') || FLAGS.has('--prove')) {
1196
+ // TaskSpec automático: deriva os critérios da stack (testes) — sem json na mão.
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');
1200
+ }
1186
1201
 
1187
1202
  const existing = metaMod.load(dir);
1188
1203
  if (FLAGS.has('--status')) {
@@ -1229,7 +1244,7 @@ async function metaCmd() {
1229
1244
  const goalArg = janela === 1 ? (goal || null) : null; // janelas seguintes só retomam
1230
1245
  try {
1231
1246
  st = await metaMod.run(goalArg, {
1232
- token, lang: cfg.lang || 'pt', yes, budget, maxRounds, dir, model: forcedModel, thinker, maxMinutes, designer, design, eye, visualLadder, mockup, arch,
1247
+ token, lang: cfg.lang || 'pt', yes, budget, maxRounds, dir, model: forcedModel, thinker, maxMinutes, designer, design, eye, visualLadder, mockup, arch, criteria,
1233
1248
  onAlert: ({ type, text }) => {
1234
1249
  sp.stop();
1235
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('⏱');
@@ -1304,6 +1319,11 @@ async function metaCmd() {
1304
1319
  const _vtag = (_vl.ok ? C.ok : C.warn)(` · ${_vl.icon} ${_vl.txt}`);
1305
1320
  console.log((_vl.ok ? ui.okLine : ui.infoLine)(C.bold(T.meta_done) + C.dim(` · ${st.rounds.length} rodada(s) · ${st.creditsSpent} créditos · ${secs} min` + (st.escalations ? ` · ${st.escalations} escalonamento(s)` : '')) + _vtag));
1306
1321
  if (!_vl.ok) console.log(' ' + C.dim((cfg.lang === 'en' ? 'Heads up: the deliverable was produced but NOT fully proven — check it before relying on it.' : 'Atenção: o entregável foi produzido mas NÃO foi totalmente provado — confira antes de confiar.') + (st.runNote ? ' (' + String(st.runNote).trim() + ')' : '')));
1322
+ // Evidence Ledger da missão: mostra cada critério executável provado/falho.
1323
+ if (st.verifyReport && st.verifyReport.total) {
1324
+ console.log(' ' + C.dim((cfg.lang === 'en' ? 'Criteria: ' : 'Critérios: ') + st.verifyReport.passed + '/' + st.verifyReport.total));
1325
+ for (const r of st.verifyReport.results) console.log(' ' + (r.ok ? C.ok('✔') : C.err('✗')) + ' ' + C.dim((r.label || '') + (r.ok ? '' : ' — ' + (r.detail || ''))));
1326
+ }
1307
1327
  metaMod.notify(token, T.meta_notify_done(st.goal, done, st.creditsSpent) + (_vl.ok ? '' : `\n⚠️ ${_vl.txt}`));
1308
1328
  } else if (st.pause_reason === 'awaiting_human' && st.humanRequest) {
1309
1329
  console.log(ui.infoLine(C.bold('🙋 Preciso de você: ') + st.humanRequest.motivo));
package/lib/i18n.js CHANGED
@@ -43,6 +43,8 @@ const STR = {
43
43
  ['ts agente --continuar "..."', 'retoma o trabalho anterior desta pasta'],
44
44
  ['ts agente "..." --navegador', 'EXPERIMENTAL: dá um Chrome real ao agente (abrir/ler/clicar/print+visão)'],
45
45
  ['ts meta "objetivo grande"', 'MISSÃO: checklist + rodadas até terminar (noturno)'],
46
+ ['ts meta "..." --criterios prova.json', 'só declara "verificada" se os critérios executáveis (verify) passarem de verdade'],
47
+ ['ts meta "..." --provar', 'auto-deriva a prova da stack (roda os testes do projeto) — sem escrever json'],
46
48
  ['ts meta --status', 'estado da missão deste diretório'],
47
49
  ['ts eval suite.json', 'avalia o agente numa suíte de casos (juiz de IA + nota)'],
48
50
  ['ts verificar --cmd "npm test" --url ... --porta N', 'PROVA determinística de conclusão (build/http/porta/arquivo); exit 1 pra CI'],
package/lib/meta.js CHANGED
@@ -1001,6 +1001,20 @@ async function makeChecklist(goal, token) {
1001
1001
  * onStep/askApprove/onRemote (repassados pro agente)
1002
1002
  * Retorna o estado final {status: done|paused, ...}.
1003
1003
  */
1004
+ // CRITÉRIOS EXECUTÁVEIS (verify): a prova FINAL da missão. Rodados quando os gates de
1005
+ // build/run passam, ANTES de declarar 'verified'. Se algum critério do usuário falha, a
1006
+ // missão NÃO é verificada (honestidade — não basta compilar/abrir; tem que CUMPRIR o pedido).
1007
+ // Sem critérios → nada a provar (retorna true). runAll nunca lança.
1008
+ async function _checkCriteria(st, dir) {
1009
+ if (!st.criteria || !st.criteria.length) return true;
1010
+ try {
1011
+ const rep = await require('./verify').runAll(st.criteria, { cwd: dir });
1012
+ 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 })) };
1013
+ 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(' | ')}`;
1014
+ return rep.allOk;
1015
+ } catch (_) { return true; }
1016
+ }
1017
+
1004
1018
  async function run(goal, opts = {}) {
1005
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;
1006
1020
  const onChecklist = opts.onChecklist || (() => {});
@@ -1043,7 +1057,7 @@ async function run(goal, opts = {}) {
1043
1057
  }
1044
1058
  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);
1045
1059
  st = { goal: String(goal).slice(0, 4000), mockup: opts.mockup || null, archBrief, feasBrief, checklist: mk.itens, rounds: [], creditsSpent: (mk.credits || 0) + designCred + archCred + feasCred,
1046
- budget, maxRounds, status: 'running', model: model || null, designBrief: designBrief || null, created_at: new Date().toISOString() };
1060
+ budget, maxRounds, status: 'running', model: model || null, designBrief: designBrief || null, criteria: Array.isArray(opts.criteria) ? opts.criteria : [], created_at: new Date().toISOString() };
1047
1061
  save(st, dir);
1048
1062
  } else {
1049
1063
  st.status = 'running';
@@ -1126,9 +1140,9 @@ async function run(goal, opts = {}) {
1126
1140
  // Aprovado: impecável OU só com nitpicks cosméticos (não vale queimar rodada cara).
1127
1141
  st.visualOk = true;
1128
1142
  if (vg.severity === 'ressalvas') { st.runNote = (st.runNote || '') + ' visual aprovado com ressalvas cosméticas (sem defeito estrutural)'; onAlert({ type: 'design', text: `👁 ts: visual APROVADO — sobrou só nitpick cosmético (sem defeito estrutural). Não vou gastar polimento caro à toa.` }); }
1129
- st.verification = 'verified'; st.status = 'done'; st.finished_at = new Date().toISOString(); save(st, dir); onRoundDone({ checklist: st.checklist, spent: st.creditsSpent }); return st;
1143
+ st.verification = (await _checkCriteria(st, dir)) ? 'verified' : 'unverified'; st.status = 'done'; st.finished_at = new Date().toISOString(); save(st, dir); onRoundDone({ checklist: st.checklist, spent: st.creditsSpent }); return st;
1130
1144
  }
1131
- } else { st.visualOk = true; st.verification = 'verified'; st.status = 'done'; st.finished_at = new Date().toISOString(); save(st, dir); onRoundDone({ checklist: st.checklist, spent: st.creditsSpent }); return st; }
1145
+ } else { st.visualOk = true; st.verification = (await _checkCriteria(st, dir)) ? 'verified' : 'unverified'; st.status = 'done'; st.finished_at = new Date().toISOString(); save(st, dir); onRoundDone({ checklist: st.checklist, spent: st.creditsSpent }); return st; }
1132
1146
  } else {
1133
1147
  // CRASHOU ao abrir → trata como erro pro executor corrigir (loop igual ao build)
1134
1148
  st.buildFixes = (st.buildFixes || 0) + 1;
@@ -1143,7 +1157,8 @@ async function run(goal, opts = {}) {
1143
1157
  let wg = { ok: true }; try { wg = await webRunGate(b, { token, eye, eyeGate: !!eye && (st.visualFixes || 0) < MAX_VISUAL_FIXES }); } catch (_) {}
1144
1158
  st.creditsSpent += wg.credits || 0;
1145
1159
  if (wg.ok) {
1146
- st.buildVerified = true; st.runVerified = true; st.visualOk = true; st.verification = 'verified';
1160
+ st.buildVerified = true; st.runVerified = true; st.visualOk = true;
1161
+ st.verification = (await _checkCriteria(st, dir)) ? 'verified' : 'unverified';
1147
1162
  if (wg.shot) st.webShot = wg.shot;
1148
1163
  st.status = 'done'; st.finished_at = new Date().toISOString(); save(st, dir); onRoundDone({ checklist: st.checklist, spent: st.creditsSpent }); return st;
1149
1164
  } else {
@@ -1381,4 +1396,4 @@ function verificationLabel(ver, lang) {
1381
1396
  }
1382
1397
  }
1383
1398
 
1384
- module.exports = { webRunGate, run, load, notify, stateFile, detectBuild, ensureToolchain, runBuild, escalate, designPhase, archPhase, looksVisual, looksComplex, runApp, visualGate, ensureEmulator, _projKind, _llmVision, verificationLabel };
1399
+ module.exports = { webRunGate, run, load, notify, stateFile, detectBuild, ensureToolchain, runBuild, escalate, designPhase, archPhase, looksVisual, looksComplex, runApp, visualGate, ensureEmulator, _projKind, _llmVision, verificationLabel, _checkCriteria };
package/lib/verify.js CHANGED
@@ -101,4 +101,31 @@ async function runAll(criteria, opts = {}) {
101
101
  return { allOk: results.length > 0 && passed === results.length, passed, total: results.length, results };
102
102
  }
103
103
 
104
- module.exports = { runCriterion, runAll, VERIFIERS, VERIFIER_TYPES, _labelFor };
104
+ // ── COMPILADOR de critérios (TaskSpec): o LLM/usuário PROPÕE, o harness VALIDA ────
105
+ // Aceita só tipos conhecidos com os campos obrigatórios preenchidos; descarta o resto
106
+ // (o modelo não impõe critério malformado). Determinístico. Retorna { criteria, dropped }.
107
+ const _REQUIRED = { command: ['cmd'], file_exists: ['path'], file_absent: ['path'], file_contains: ['path', 'text'], http: ['url'], port: ['port'] };
108
+ function compileCriteria(raw) {
109
+ const list = Array.isArray(raw) ? raw : (raw && Array.isArray(raw.criteria) ? raw.criteria : []);
110
+ const criteria = [], dropped = [];
111
+ for (const c of list) {
112
+ if (!c || typeof c !== 'object' || !_REQUIRED[c.type]) { dropped.push(c); continue; }
113
+ const missing = _REQUIRED[c.type].some(k => c[k] === undefined || c[k] === null || c[k] === '');
114
+ if (missing) { dropped.push(c); continue; }
115
+ const clean = { type: c.type };
116
+ for (const k of ['cmd', 'exit', 'path', 'text', 'regex', 'flags', 'absent', 'url', 'method', 'status', 'contains', 'port', 'host', 'timeout_s', 'label']) if (c[k] !== undefined) clean[k] = c[k];
117
+ criteria.push(clean);
118
+ }
119
+ return { criteria, dropped };
120
+ }
121
+
122
+ // Deriva critérios DETERMINÍSTICOS da stack (os TESTES, que os gates de build/run NÃO cobrem).
123
+ // "A missão só é verificada se os testes passarem" — TaskSpec automático, zero json na mão.
124
+ function deriveFromStack(cwd) {
125
+ let st; try { st = require('./stack').detectStack(cwd); } catch (_) { return []; }
126
+ const out = [];
127
+ for (const t of (st.test || [])) { if (/test/i.test(t)) out.push({ type: 'command', cmd: t, exit: 0, label: 'testes passam (' + t + ')' }); }
128
+ return out;
129
+ }
130
+
131
+ module.exports = { runCriterion, runAll, compileCriteria, deriveFromStack, VERIFIERS, VERIFIER_TYPES, _labelFor };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "terminal-smart-cli",
3
- "version": "0.65.0",
3
+ "version": "0.67.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"