terminal-smart-cli 0.64.0 → 0.65.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 +44 -0
- package/lib/i18n.js +1 -0
- package/lib/verify.js +104 -0
- package/package.json +1 -1
package/bin/ts.js
CHANGED
|
@@ -1671,6 +1671,49 @@ function buscarCmd(args) {
|
|
|
1671
1671
|
], { title: 'ts buscar' }));
|
|
1672
1672
|
}
|
|
1673
1673
|
|
|
1674
|
+
// ── ts verificar — PROVA determinística de que a tarefa terminou (verificadores) ──
|
|
1675
|
+
async function verificarCmd(args) {
|
|
1676
|
+
const en = (cfg.lang === 'en');
|
|
1677
|
+
const _fs = require('fs');
|
|
1678
|
+
const verify = require('../lib/verify');
|
|
1679
|
+
const _val = (names) => { const i = rawArgs.findIndex(a => names.includes(a)); return i >= 0 ? rawArgs[i + 1] : null; };
|
|
1680
|
+
const cwd = process.cwd();
|
|
1681
|
+
|
|
1682
|
+
// 1) arquivo de critérios (JSON: array ou {criteria:[...]}) OU 2) critérios inline via flags
|
|
1683
|
+
let criteria = [];
|
|
1684
|
+
const file = (args || []).find(a => !a.startsWith('-') && /\.json$/i.test(a));
|
|
1685
|
+
if (file) {
|
|
1686
|
+
try { const j = JSON.parse(_fs.readFileSync(file, 'utf8')); criteria = Array.isArray(j) ? j : (j.criteria || []); }
|
|
1687
|
+
catch (e) { console.log(ui.errLine((en ? 'could not read criteria file: ' : 'não deu pra ler o arquivo de critérios: ') + (e.message || e))); process.exit(2); }
|
|
1688
|
+
} else {
|
|
1689
|
+
const cmd = _val(['--cmd', '--comando']); if (cmd) criteria.push({ type: 'command', cmd, exit: Number(_val(['--exit', '--codigo'])) || 0 });
|
|
1690
|
+
const ex = _val(['--existe', '--exists']); if (ex) criteria.push({ type: 'file_exists', path: ex });
|
|
1691
|
+
const arq = _val(['--arquivo', '--file']); const cont = _val(['--contem', '--contains']);
|
|
1692
|
+
if (arq && cont != null) criteria.push({ type: 'file_contains', path: arq, text: cont });
|
|
1693
|
+
const url = _val(['--url']); if (url) criteria.push({ type: 'http', url, status: Number(_val(['--status'])) || 200, ...(cont != null && !arq ? { contains: cont } : {}) });
|
|
1694
|
+
const porta = _val(['--porta', '--port']); if (porta) criteria.push({ type: 'port', port: Number(porta) });
|
|
1695
|
+
}
|
|
1696
|
+
if (!criteria.length) {
|
|
1697
|
+
console.log(ui.infoLine(en
|
|
1698
|
+
? 'usage: ts verificar criterios.json OR ts verificar --cmd "npm test" --url http://localhost:3000/health --status 200 --porta 3000 --existe dist/app.js'
|
|
1699
|
+
: 'uso: ts verificar criterios.json OU ts verificar --cmd "npm test" --url http://localhost:3000/health --status 200 --porta 3000 --existe dist/app.js'));
|
|
1700
|
+
return;
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
if (!JSON_OUT) console.log('\n' + C.dim(en ? 'proving completion — ' : 'provando conclusão — ') + criteria.length + (en ? ' criterion(s)…' : ' critério(s)…'));
|
|
1704
|
+
const rep = await verify.runAll(criteria, { cwd });
|
|
1705
|
+
if (JSON_OUT) { console.log(JSON.stringify(rep)); process.exit(rep.allOk ? 0 : 1); }
|
|
1706
|
+
|
|
1707
|
+
for (const r of rep.results) {
|
|
1708
|
+
const mark = r.ok ? C.ok('✔') : C.err('✗');
|
|
1709
|
+
console.log(' ' + mark + ' ' + C.bold(r.label || r.type) + C.dim(' ' + (r.detail || '')) + (r.errorClass ? C.dim(' [' + r.errorClass + ']') : ''));
|
|
1710
|
+
if (!r.ok && r.evidence) console.log(' ' + C.dim('┄ ' + String(r.evidence).slice(0, 120)));
|
|
1711
|
+
}
|
|
1712
|
+
console.log('\n' + (rep.allOk ? ui.okLine : ui.errLine)(
|
|
1713
|
+
(rep.allOk ? (en ? 'VERIFIED' : 'VERIFICADO') : (en ? 'NOT verified' : 'NÃO verificado')) + C.dim(` ${rep.passed}/${rep.total} ` + (en ? 'passed' : 'ok'))));
|
|
1714
|
+
process.exit(rep.allOk ? 0 : 1);
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1674
1717
|
// ── ts perfil — personas com COFRE DE MEMÓRIA isolado (Mempalace/Hermes) ──────
|
|
1675
1718
|
function perfilCmd(args) {
|
|
1676
1719
|
const en = (cfg.lang === 'en');
|
|
@@ -1933,6 +1976,7 @@ function recallCmd(args) {
|
|
|
1933
1976
|
case 'indexar': case 'index': return indexarCmd();
|
|
1934
1977
|
case 'buscar': case 'search': case 'procurar': return buscarCmd(POS.slice(1));
|
|
1935
1978
|
case 'perfil': case 'persona': case 'profile': return perfilCmd(POS.slice(1));
|
|
1979
|
+
case 'verificar': case 'verify': case 'provar': return verificarCmd(POS.slice(1));
|
|
1936
1980
|
case 'meta': case 'missao': case 'mission': return metaCmd();
|
|
1937
1981
|
case 'runs': return runsCmd();
|
|
1938
1982
|
case 'status': return statusCmd(POS[1]);
|
package/lib/i18n.js
CHANGED
|
@@ -45,6 +45,7 @@ const STR = {
|
|
|
45
45
|
['ts meta "objetivo grande"', 'MISSÃO: checklist + rodadas até terminar (noturno)'],
|
|
46
46
|
['ts meta --status', 'estado da missão deste diretório'],
|
|
47
47
|
['ts eval suite.json', 'avalia o agente numa suíte de casos (juiz de IA + nota)'],
|
|
48
|
+
['ts verificar --cmd "npm test" --url ... --porta N', 'PROVA determinística de conclusão (build/http/porta/arquivo); exit 1 pra CI'],
|
|
48
49
|
['ts acp', 'servidor Agent Client Protocol (conecta o ts a editores tipo Zed)'],
|
|
49
50
|
['ts mcp', 'servidores MCP: as ferramentas deles entram no agente'],
|
|
50
51
|
] },
|
package/lib/verify.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// VERIFICADORES POR DOMÍNIO — a PROVA determinística de que uma tarefa terminou.
|
|
2
|
+
// Insight das análises externas (GPT): "o modelo propõe; o harness observa e COMPROVA".
|
|
3
|
+
// Cada critério é EXECUTÁVEL e checado por código (não pela narrativa do modelo nem por um
|
|
4
|
+
// LLM-judge). É a base do TaskSpec (critérios executáveis) e do Evidence Ledger, e o motor
|
|
5
|
+
// do comando `ts verificar` (o "Prove que terminou"). Usa o classificador de erro do core.
|
|
6
|
+
'use strict';
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const net = require('net');
|
|
10
|
+
const { execSync } = require('child_process');
|
|
11
|
+
const core = require('./core');
|
|
12
|
+
|
|
13
|
+
// ── verificadores individuais (cada um devolve { ok, detail }) ───────────────
|
|
14
|
+
function _abs(cwd, p) { return path.isAbsolute(p) ? p : path.join(cwd || process.cwd(), p); }
|
|
15
|
+
|
|
16
|
+
function vCommand(c, cwd) {
|
|
17
|
+
const want = c.exit != null ? Number(c.exit) : 0;
|
|
18
|
+
try {
|
|
19
|
+
const out = execSync(c.cmd, { cwd, encoding: 'utf8', timeout: (c.timeout_s || 120) * 1000, stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });
|
|
20
|
+
const ok = want === 0;
|
|
21
|
+
return { ok, detail: ok ? 'exit 0' : `exit 0 mas esperava ${want}`, evidence: String(out).slice(-200).replace(/\s+/g, ' ') };
|
|
22
|
+
} catch (e) {
|
|
23
|
+
const code = typeof e.status === 'number' ? e.status : 1;
|
|
24
|
+
const ok = code === want;
|
|
25
|
+
const cls = core.classifyError(e.stderr || e.stdout || e.message);
|
|
26
|
+
return { ok, detail: ok ? `exit ${code} (esperado)` : `exit ${code} (esperava ${want})`, errorClass: ok ? null : cls, evidence: String(e.stderr || e.stdout || e.message || '').slice(-200).replace(/\s+/g, ' ') };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function vFileExists(c, cwd) { const p = _abs(cwd, c.path); const ok = fs.existsSync(p); return { ok, detail: ok ? 'existe' : 'não existe: ' + c.path }; }
|
|
30
|
+
function vFileAbsent(c, cwd) { const p = _abs(cwd, c.path); const ok = !fs.existsSync(p); return { ok, detail: ok ? 'ausente (ok)' : 'existe (não deveria): ' + c.path }; }
|
|
31
|
+
function vFileContains(c, cwd) {
|
|
32
|
+
const p = _abs(cwd, c.path);
|
|
33
|
+
let src = null; try { src = fs.readFileSync(p, 'utf8'); } catch (_) { return { ok: false, detail: 'não deu pra ler: ' + c.path }; }
|
|
34
|
+
const hit = c.regex ? new RegExp(c.text, c.flags || '').test(src) : src.includes(c.text);
|
|
35
|
+
const want = c.absent ? !hit : hit;
|
|
36
|
+
return { ok: want, detail: want ? 'ok' : (c.absent ? `contém "${c.text}" (não deveria)` : `NÃO contém "${c.text}"`) };
|
|
37
|
+
}
|
|
38
|
+
async function vHttp(c) {
|
|
39
|
+
const want = c.status != null ? Number(c.status) : 200;
|
|
40
|
+
const ctrl = new AbortController(); const t = setTimeout(() => ctrl.abort(), (c.timeout_s || 15) * 1000);
|
|
41
|
+
try {
|
|
42
|
+
const res = await fetch(c.url, { method: c.method || 'GET', signal: ctrl.signal, headers: c.headers || {}, ...(c.body ? { body: typeof c.body === 'string' ? c.body : JSON.stringify(c.body) } : {}) });
|
|
43
|
+
let body = ''; try { body = await res.text(); } catch (_) {}
|
|
44
|
+
const statusOk = res.status === want;
|
|
45
|
+
const containsOk = c.contains ? body.includes(c.contains) : true;
|
|
46
|
+
const ok = statusOk && containsOk;
|
|
47
|
+
return { ok, detail: ok ? `HTTP ${res.status}` : (!statusOk ? `HTTP ${res.status} (esperava ${want})` : `HTTP ${res.status} mas sem "${c.contains}"`), evidence: body.slice(0, 160).replace(/\s+/g, ' ') };
|
|
48
|
+
} catch (e) { return { ok: false, detail: 'sem resposta: ' + (e.message || e), errorClass: 'network' }; }
|
|
49
|
+
finally { clearTimeout(t); }
|
|
50
|
+
}
|
|
51
|
+
function vPort(c) {
|
|
52
|
+
return new Promise((resolve) => {
|
|
53
|
+
const host = c.host || '127.0.0.1'; const port = Number(c.port);
|
|
54
|
+
const sock = new net.Socket(); let done = false;
|
|
55
|
+
const finish = (ok, detail) => { if (done) return; done = true; try { sock.destroy(); } catch (_) {} resolve({ ok, detail }); };
|
|
56
|
+
sock.setTimeout((c.timeout_s || 5) * 1000);
|
|
57
|
+
sock.once('connect', () => finish(true, `porta ${port} escutando`));
|
|
58
|
+
sock.once('timeout', () => finish(false, `porta ${port} sem resposta (timeout)`));
|
|
59
|
+
sock.once('error', () => finish(false, `porta ${port} fechada/recusada`));
|
|
60
|
+
sock.connect(port, host);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const VERIFIERS = {
|
|
65
|
+
command: (c, cwd) => vCommand(c, cwd),
|
|
66
|
+
file_exists: (c, cwd) => vFileExists(c, cwd),
|
|
67
|
+
file_absent: (c, cwd) => vFileAbsent(c, cwd),
|
|
68
|
+
file_contains: (c, cwd) => vFileContains(c, cwd),
|
|
69
|
+
http: (c) => vHttp(c),
|
|
70
|
+
port: (c) => vPort(c),
|
|
71
|
+
};
|
|
72
|
+
const VERIFIER_TYPES = Object.keys(VERIFIERS);
|
|
73
|
+
|
|
74
|
+
// Roda UM critério. Sempre resolve { ok, type, detail, ... } — nunca lança.
|
|
75
|
+
async function runCriterion(c, opts = {}) {
|
|
76
|
+
const cwd = opts.cwd || process.cwd();
|
|
77
|
+
const type = c && c.type;
|
|
78
|
+
const fn = VERIFIERS[type];
|
|
79
|
+
if (!fn) return { ok: false, type: type || '?', detail: 'tipo de critério desconhecido: ' + type, unknown: true };
|
|
80
|
+
try { const r = await fn(c, cwd); return Object.assign({ type, label: c.label || _labelFor(c) }, r); }
|
|
81
|
+
catch (e) { return { ok: false, type, label: c.label || _labelFor(c), detail: 'erro no verificador: ' + (e.message || e) }; }
|
|
82
|
+
}
|
|
83
|
+
function _labelFor(c) {
|
|
84
|
+
switch (c.type) {
|
|
85
|
+
case 'command': return '`' + String(c.cmd).slice(0, 50) + '` → exit ' + (c.exit != null ? c.exit : 0);
|
|
86
|
+
case 'file_exists': return 'existe ' + c.path;
|
|
87
|
+
case 'file_absent': return 'ausente ' + c.path;
|
|
88
|
+
case 'file_contains': return c.path + (c.absent ? ' SEM ' : ' contém ') + '"' + String(c.text).slice(0, 30) + '"';
|
|
89
|
+
case 'http': return (c.method || 'GET') + ' ' + c.url + ' → ' + (c.status || 200);
|
|
90
|
+
case 'port': return 'porta ' + c.port + ' escutando';
|
|
91
|
+
default: return c.type || '?';
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Roda uma LISTA de critérios. Retorna resumo + resultados (o Evidence Ledger mínimo).
|
|
96
|
+
async function runAll(criteria, opts = {}) {
|
|
97
|
+
const list = Array.isArray(criteria) ? criteria : (criteria && Array.isArray(criteria.criteria) ? criteria.criteria : []);
|
|
98
|
+
const results = [];
|
|
99
|
+
for (const c of list) results.push(await runCriterion(c, opts));
|
|
100
|
+
const passed = results.filter(r => r.ok).length;
|
|
101
|
+
return { allOk: results.length > 0 && passed === results.length, passed, total: results.length, results };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = { runCriterion, runAll, VERIFIERS, VERIFIER_TYPES, _labelFor };
|