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 +21 -1
- package/lib/i18n.js +2 -0
- package/lib/meta.js +20 -5
- package/lib/verify.js +28 -1
- package/package.json +1 -1
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;
|
|
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
|
-
|
|
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 };
|