terminal-smart-cli 0.97.47 → 0.97.49
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 +15 -6
- package/lib/content-quality.js +45 -0
- package/lib/verify.js +8 -1
- package/package.json +1 -1
package/bin/ts.js
CHANGED
|
@@ -2535,6 +2535,7 @@ async function verificarCmd(args) {
|
|
|
2535
2535
|
|
|
2536
2536
|
// 1) arquivo de critérios (JSON: array ou {criteria:[...]}) OU 2) critérios inline via flags
|
|
2537
2537
|
let criteria = [];
|
|
2538
|
+
if (rawArgs.includes('--qualidade') || rawArgs.includes('--quality')) criteria.push({ type:'project_text_quality', label:en ? 'text files preserve UTF-8' : 'textos e HTML preservam UTF-8' });
|
|
2538
2539
|
const file = (args || []).find(a => !a.startsWith('-') && /\.json$/i.test(a));
|
|
2539
2540
|
if (file) {
|
|
2540
2541
|
try { const j = JSON.parse(_fs.readFileSync(file, 'utf8')); criteria = Array.isArray(j) ? j : (j.criteria || []); }
|
|
@@ -2547,12 +2548,7 @@ async function verificarCmd(args) {
|
|
|
2547
2548
|
const url = _val(['--url']); if (url) criteria.push({ type: 'http', url, status: Number(_val(['--status'])) || 200, ...(cont != null && !arq ? { contains: cont } : {}) });
|
|
2548
2549
|
const porta = _val(['--porta', '--port']); if (porta) criteria.push({ type: 'port', port: Number(porta) });
|
|
2549
2550
|
}
|
|
2550
|
-
if (!criteria.length)
|
|
2551
|
-
console.log(ui.infoLine(en
|
|
2552
|
-
? 'usage: ts verificar criterios.json OR ts verificar --cmd "npm test" --url http://localhost:3000/health --status 200 --porta 3000 --existe dist/app.js'
|
|
2553
|
-
: 'uso: ts verificar criterios.json OU ts verificar --cmd "npm test" --url http://localhost:3000/health --status 200 --porta 3000 --existe dist/app.js'));
|
|
2554
|
-
return;
|
|
2555
|
-
}
|
|
2551
|
+
if (!criteria.length) criteria=verify.deriveFromStack(cwd);
|
|
2556
2552
|
|
|
2557
2553
|
if (!JSON_OUT) console.log('\n' + C.dim(en ? 'proving completion — ' : 'provando conclusão — ') + criteria.length + (en ? ' criterion(s)…' : ' critério(s)…'));
|
|
2558
2554
|
// remoteExec: liga o verificador ldd remoto à conexão SSH ativa (binário PW mora na VPS).
|
|
@@ -3069,6 +3065,17 @@ async function cloudCmd(args) {
|
|
|
3069
3065
|
const sub = String(args[0] || 'status').toLowerCase();
|
|
3070
3066
|
const rest = args.slice(1).filter(a => !a.startsWith('-'));
|
|
3071
3067
|
const call = (path, opts = {}) => api('/api/cloud/' + path, { token, ...opts });
|
|
3068
|
+
const mutatesWebText = cmd => /(?:>|>>|\btee\b|\bsed\b|\bperl\b|\bcp\b|\bmv\b|\bbase64\b)[\s\S]{0,500}(?:\.html?|\.css|\.jsx?|\.tsx?|\.json|\.md|\/workspace)/i.test(String(cmd || ''))
|
|
3069
|
+
|| /(?:\.html?|\.css|\.jsx?|\.tsx?|\.json|\.md|\/workspace)[\s\S]{0,500}(?:>|>>|\btee\b|\bsed\b|\bperl\b|\bcp\b|\bmv\b|\bbase64\b)/i.test(String(cmd || ''));
|
|
3070
|
+
const verifyCloudText = async cmd => {
|
|
3071
|
+
if (!mutatesWebText(cmd)) return true;
|
|
3072
|
+
const check=await call('exec',{method:'POST',body:{confirmed:true,cmd:'cd /workspace && ts verificar --qualidade --json'},timeoutMs:120000});
|
|
3073
|
+
if (check.ok) return true;
|
|
3074
|
+
const evidence=String(check.out || check.error || '').replace(/\s+/g,' ').slice(0,500);
|
|
3075
|
+
console.log(ui.errLine((en ? 'Cloud quality gate failed: ' : 'Gate de qualidade da Cloud Box falhou: ') + evidence));
|
|
3076
|
+
process.exitCode=1;
|
|
3077
|
+
return false;
|
|
3078
|
+
};
|
|
3072
3079
|
const cloudSlug = require('../lib/cloud-slug');
|
|
3073
3080
|
const chooseSlug = async (provided) => {
|
|
3074
3081
|
let value = String(provided || '').trim();
|
|
@@ -3154,12 +3161,14 @@ async function cloudCmd(args) {
|
|
|
3154
3161
|
if (process.stdout.isTTY) {
|
|
3155
3162
|
try {
|
|
3156
3163
|
await sse('/api/cloud/exec-stream', { token, body: { confirmed: true, cmd, timeoutMs: 300000 }, timeoutMs: 310000, onEvent: (e) => { if (e.chunk) process.stdout.write(e.chunk); } });
|
|
3164
|
+
await verifyCloudText(cmd);
|
|
3157
3165
|
return;
|
|
3158
3166
|
} catch (e) { if (e.code === 'auth') { console.log(ui.errLine(T.need_login)); return; } }
|
|
3159
3167
|
}
|
|
3160
3168
|
const r = await call('exec', { method: 'POST', body: { confirmed: true, cmd }, timeoutMs: 300000 });
|
|
3161
3169
|
if (r.out) process.stdout.write(r.out);
|
|
3162
3170
|
if (!r.ok && r.error) console.log(ui.errLine(r.error));
|
|
3171
|
+
if (r.ok) await verifyCloudText(cmd);
|
|
3163
3172
|
return;
|
|
3164
3173
|
}
|
|
3165
3174
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs=require('fs');
|
|
3
|
+
const path=require('path');
|
|
4
|
+
|
|
5
|
+
const TEXT_EXT=new Set(['.html','.htm','.css','.js','.mjs','.cjs','.ts','.tsx','.jsx','.json','.md','.txt','.svg','.xml','.yml','.yaml']);
|
|
6
|
+
const SKIP=new Set(['node_modules','.git','.cache','.idea','.vscode','coverage']);
|
|
7
|
+
const MOJIBAKE=[
|
|
8
|
+
{ regex:/\uFFFD/g, label:'caractere de substituição Unicode (�)' },
|
|
9
|
+
{ regex:/Ã[\u0080-\u00BF]/g, label:'UTF-8 interpretado como Latin-1 (Ã…)' },
|
|
10
|
+
{ regex:/Â[\u0080-\u00BF\u00A0]/g, label:'UTF-8 interpretado como Latin-1 (Â…)' },
|
|
11
|
+
{ regex:/â(?:€|€™|€œ|€|€“|€”|€¦)/g, label:'pontuação UTF-8 corrompida (â…)' },
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
function inspectText(value,{html=false}={}) {
|
|
15
|
+
const source=String(value || ''); const defects=[];
|
|
16
|
+
for(const item of MOJIBAKE){ const hits=source.match(item.regex); if(hits?.length) defects.push(`${item.label}: ${hits.length} ocorrência(s)`); }
|
|
17
|
+
if(html && /<!doctype html|<html[\s>]/i.test(source)
|
|
18
|
+
&& !/<meta\s+[^>]*charset\s*=\s*["']?utf-8\b/i.test(source)
|
|
19
|
+
&& !/<meta\s+[^>]*http-equiv\s*=\s*["']content-type["'][^>]*charset\s*=\s*["']?utf-8\b/i.test(source)) defects.push('HTML completo sem <meta charset="utf-8">');
|
|
20
|
+
return {ok:defects.length===0,defects};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function scanProject(root,{maxFiles=1500,maxBytes=2*1024*1024}={}) {
|
|
24
|
+
const defects=[]; let scanned=0;
|
|
25
|
+
const walk=dir=>{
|
|
26
|
+
if(scanned>=maxFiles) return;
|
|
27
|
+
let entries=[]; try{entries=fs.readdirSync(dir,{withFileTypes:true});}catch(_){return;}
|
|
28
|
+
for(const entry of entries){
|
|
29
|
+
if(scanned>=maxFiles) break;
|
|
30
|
+
if(entry.isDirectory()){ if(!SKIP.has(entry.name)) walk(path.join(dir,entry.name)); continue; }
|
|
31
|
+
const file=path.join(dir,entry.name); const ext=path.extname(entry.name).toLowerCase();
|
|
32
|
+
if(!TEXT_EXT.has(ext)) continue;
|
|
33
|
+
let stat; try{stat=fs.statSync(file);}catch(_){continue;}
|
|
34
|
+
if(stat.size>maxBytes) continue;
|
|
35
|
+
let source; try{source=fs.readFileSync(file,'utf8');}catch(_){continue;}
|
|
36
|
+
scanned++;
|
|
37
|
+
const result=inspectText(source,{html:ext==='.html'||ext==='.htm'});
|
|
38
|
+
for(const defect of result.defects) defects.push(`${path.relative(root,file)}: ${defect}`);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
walk(path.resolve(root));
|
|
42
|
+
return {ok:defects.length===0,defects,scanned,truncated:scanned>=maxFiles};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports={inspectText,scanProject};
|
package/lib/verify.js
CHANGED
|
@@ -73,6 +73,10 @@ function vPort(c) {
|
|
|
73
73
|
sock.connect(port, host);
|
|
74
74
|
});
|
|
75
75
|
}
|
|
76
|
+
function vProjectTextQuality(c,cwd) {
|
|
77
|
+
const result=require('./content-quality').scanProject(cwd,{maxFiles:c.max_files||1500});
|
|
78
|
+
return {ok:result.ok,detail:result.ok ? `${result.scanned} arquivo(s) de texto com UTF-8 íntegro` : `${result.defects.length} problema(s) de codificação`,evidence:result.defects.slice(0,8).join(' | ')};
|
|
79
|
+
}
|
|
76
80
|
|
|
77
81
|
// Analisa a saída de `ldd`: quais libs NÃO resolvem. Separado pra ser testável sem ldd real.
|
|
78
82
|
// "arquivo está no lugar" ≠ "roda": foi o buraco do deploy que só checou `file` (arquitetura).
|
|
@@ -111,6 +115,7 @@ const VERIFIERS = {
|
|
|
111
115
|
http: (c) => vHttp(c),
|
|
112
116
|
port: (c) => vPort(c),
|
|
113
117
|
ldd: (c, cwd, opts) => vLdd(c, cwd, opts),
|
|
118
|
+
project_text_quality: (c,cwd) => vProjectTextQuality(c,cwd),
|
|
114
119
|
};
|
|
115
120
|
const VERIFIER_TYPES = Object.keys(VERIFIERS);
|
|
116
121
|
|
|
@@ -132,6 +137,7 @@ function _labelFor(c) {
|
|
|
132
137
|
case 'http': return (c.method || 'GET') + ' ' + c.url + ' → ' + (c.status || 200);
|
|
133
138
|
case 'port': return 'porta ' + c.port + ' escutando';
|
|
134
139
|
case 'ldd': return 'libs de ' + c.path + ' resolvem' + (c.remote ? ' (VPS)' : '');
|
|
140
|
+
case 'project_text_quality': return 'textos e HTML sem caracteres corrompidos';
|
|
135
141
|
default: return c.type || '?';
|
|
136
142
|
}
|
|
137
143
|
}
|
|
@@ -148,7 +154,7 @@ async function runAll(criteria, opts = {}) {
|
|
|
148
154
|
// ── COMPILADOR de critérios (TaskSpec): o LLM/usuário PROPÕE, o harness VALIDA ────
|
|
149
155
|
// Aceita só tipos conhecidos com os campos obrigatórios preenchidos; descarta o resto
|
|
150
156
|
// (o modelo não impõe critério malformado). Determinístico. Retorna { criteria, dropped }.
|
|
151
|
-
const _REQUIRED = { command: ['cmd'], file_exists: ['path'], file_absent: ['path'], file_contains: ['path', 'text'], http: ['url'], port: ['port'], ldd: ['path'] };
|
|
157
|
+
const _REQUIRED = { command: ['cmd'], file_exists: ['path'], file_absent: ['path'], file_contains: ['path', 'text'], http: ['url'], port: ['port'], ldd: ['path'], project_text_quality:[] };
|
|
152
158
|
function compileCriteria(raw) {
|
|
153
159
|
const list = Array.isArray(raw) ? raw : (raw && Array.isArray(raw.criteria) ? raw.criteria : []);
|
|
154
160
|
const criteria = [], dropped = [];
|
|
@@ -168,6 +174,7 @@ function compileCriteria(raw) {
|
|
|
168
174
|
function deriveFromStack(cwd) {
|
|
169
175
|
let st; try { st = require('./stack').detectStack(cwd); } catch (_) { return []; }
|
|
170
176
|
const out = [];
|
|
177
|
+
out.push({type:'project_text_quality',label:'textos e HTML preservam UTF-8'});
|
|
171
178
|
for (const t of (st.test || [])) { if (/test/i.test(t)) out.push({ type: 'command', cmd: t, exit: 0, label: 'testes passam (' + t + ')' }); }
|
|
172
179
|
return out;
|
|
173
180
|
}
|