terminal-smart-cli 0.97.47 → 0.97.48

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.
@@ -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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "terminal-smart-cli",
3
- "version": "0.97.47",
3
+ "version": "0.97.48",
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"