@fontdata/ai-harness 2.3.0 → 2.3.1
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fontdata/ai-harness",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.1",
|
|
4
4
|
"description": "Harness de engenharia com IA da Fontdata para Claude Code: roda os slash commands direto na pasta do projeto via npx, sem instalar nada — ou instala-os em .claude/commands.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,7 +9,8 @@ const DENSIDADE_ALVO = 0.1;
|
|
|
9
9
|
const EXTENSOES = new Set([
|
|
10
10
|
'.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs',
|
|
11
11
|
'.py', '.go', '.rs', '.java', '.cs', '.php',
|
|
12
|
-
'.pas', '.dpr', '.sql', '.sh',
|
|
12
|
+
'.pas', '.dpr', '.dpk', '.lpr', '.sql', '.sh', '.bash', '.zsh',
|
|
13
|
+
'.ps1', '.psm1', '.bat', '.cmd',
|
|
13
14
|
]);
|
|
14
15
|
|
|
15
16
|
const IGNORAR = [
|
|
@@ -21,7 +22,7 @@ const IGNORAR = [
|
|
|
21
22
|
];
|
|
22
23
|
|
|
23
24
|
const CABECALHO_PERMITIDO =
|
|
24
|
-
/eslint|ts-nocheck|ts-check|ts-ignore|prettier|@license|copyright|spdx|jshint|globals
|
|
25
|
+
/eslint|ts-nocheck|ts-check|ts-ignore|prettier|@license|copyright|spdx|jshint|globals|use (strict|client|server)|code generated|do not edit|auto-generated/i;
|
|
25
26
|
|
|
26
27
|
const BANNER = /^[-=─-╿_*#~+]{3,}|[-=─-╿_*#~+]{3,}$/;
|
|
27
28
|
|
|
@@ -42,33 +43,136 @@ const DATA = /(^|[^"'\w])20\d{2}-\d{2}-\d{2}([^"'\w]|$)/;
|
|
|
42
43
|
const CODIGO_TOKEN = /(^|\s)(const|let|var|function|return|if|for|while|await|import|export|def|class)\b/;
|
|
43
44
|
const CODIGO_FIM = /[;{}]\s*$/;
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
46
|
+
const so = (...exts) => new Set(exts);
|
|
47
|
+
const JS = so('.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs');
|
|
48
|
+
const C_LIKE = so(...JS, '.go', '.rs', '.java', '.cs', '.php');
|
|
49
|
+
const PASCAL = so('.pas', '.dpr', '.dpk', '.lpr');
|
|
50
|
+
const SHELL = so('.sh', '.bash', '.zsh');
|
|
51
|
+
const POWERSHELL = so('.ps1', '.psm1');
|
|
52
|
+
const BATCH = so('.bat', '.cmd');
|
|
53
|
+
|
|
54
|
+
const SINTAXE = [
|
|
55
|
+
[so('.php'), { linha: ['//', '#'], blocos: [['/*', '*/']], naoComentario: /^#\[/ }],
|
|
56
|
+
[C_LIKE, { linha: ['//'], blocos: [['/*', '*/']] }],
|
|
57
|
+
[PASCAL, { linha: ['//'], blocos: [['{', '}'], ['(*', '*)']] }],
|
|
58
|
+
[so('.sql'), { linha: ['--'], blocos: [['/*', '*/']] }],
|
|
59
|
+
[POWERSHELL, { linha: ['#'], blocos: [['<#', '#>']] }],
|
|
60
|
+
[so('.py', ...SHELL), { linha: ['#'], blocos: [] }],
|
|
61
|
+
[BATCH, { linha: ['::'], blocos: [] }],
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
// Têm forma de comentário, mas são lidas pelo compilador, pelo interpretador ou por uma ferramenta.
|
|
65
|
+
const DIRETIVA = Symbol('diretiva');
|
|
66
|
+
const DIRETIVAS = [
|
|
67
|
+
[PASCAL, /^(\{|\(\*)[$%]/],
|
|
68
|
+
[C_LIKE, /^\/[/*]\s*(#?region\b|#?endregion\b|<\/?editor-fold|@formatter:)/i],
|
|
69
|
+
[JS, /^\/\/\/\s*<(reference|amd-module|amd-dependency)\b/],
|
|
70
|
+
[JS, /^\/[/*]\*?\s*(@ts-(check|nocheck|ignore|expect-error)\b|eslint|jshint|jscs:|tslint:|prettier-ignore|(istanbul|c8|v8) ignore|biome-ignore|deno-|oxlint-|@flow\b|@jsx|@vite-ignore|@vitest-environment|@jest-environment|webpack[A-Z]\w*:|@refresh\b)/i],
|
|
71
|
+
[JS, /^\/\*+\s*globals?\s/],
|
|
72
|
+
[JS, /^\/\/[#@]\s*source(Mapping)?URL=/],
|
|
73
|
+
[so('.go'), /^\/\/(go:|line |export |extern )|^\/\/\s*(\+build\b|nolint\b|lint:ignore\b)/],
|
|
74
|
+
[so('.rs'), /^#!\[/],
|
|
75
|
+
[so('.java'), /^\/[/*]\s*(noinspection\b|NOSONAR\b|CHECKSTYLE[:.]|\$NON-NLS)/],
|
|
76
|
+
[so('.cs'), /^#\s*(region|endregion|if|elif|else|endif|define|undef|pragma|nullable|warning|error|line)\b/],
|
|
77
|
+
[so('.cs'), /^\/\/\s*(ReSharper\s|<\/?auto-generated)/i],
|
|
78
|
+
[so('.php'), /^\/[/*]\*?\s*(phpcs:|@phpstan-|@psalm-|@codeCoverageIgnore|@noinspection\b|@var\s)/],
|
|
79
|
+
[so('.sql'), /^\/\*[!+]/],
|
|
80
|
+
[so('.sql'), /^--\s*(liquibase\s|changeset\s|rollback\b|precondition|comment:|validCheckSum\b|ignoreLines:|\+(goose|migrate)\b|name:\s*\S+\s*:|noqa\b|sqlfluff:|#?(region|endregion)\b)/i],
|
|
81
|
+
[so('.py'), /^#\s*(-\*-.*-\*-|(en)?coding[:=]|vim?:|noqa\b|nosec\b|type:|pylint:|mypy:|pyright:|pyre-|pytype:|ruff:|isort:|fmt:|pragma\b)/i],
|
|
82
|
+
[SHELL, /^#\s*(shellcheck\s|-\*-.*-\*-|vim?:|ex:)|^#(compdef|autoload)\b/i],
|
|
83
|
+
[POWERSHELL, /^#(requires|region|endregion)\b/i],
|
|
84
|
+
];
|
|
85
|
+
const BLOCOS_DIRETIVA = [
|
|
86
|
+
[SHELL, /^###\s*BEGIN INIT INFO/, /^###\s*END INIT INFO/],
|
|
87
|
+
[POWERSHELL, /^#\s*SIG\s*#\s*Begin signature block/i, /^#\s*SIG\s*#\s*End signature block/i],
|
|
88
|
+
];
|
|
89
|
+
const HEREDOC = /(?<!<)<<(?!<)-?\s*(['"]?)([A-Za-z_]\w*)\1/g;
|
|
90
|
+
|
|
91
|
+
const limpar = (texto) => texto.replace(/^\*+/, '').trim();
|
|
92
|
+
|
|
93
|
+
function comentarioDeLinha(t, sintaxe, ext) {
|
|
94
|
+
if (BATCH.has(ext)) {
|
|
95
|
+
if (t.startsWith('::')) return t.replace(/^:+/, '').trim();
|
|
96
|
+
const rem = t.match(/^@?rem(?:[\s.:/\\](.*))?$/i);
|
|
97
|
+
return rem ? (rem[1] ?? '').trim() : null;
|
|
53
98
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
99
|
+
const marcador = sintaxe.linha.find((m) => t.startsWith(m));
|
|
100
|
+
if (!marcador || sintaxe.naoComentario?.test(t)) return null;
|
|
101
|
+
let k = 0;
|
|
102
|
+
while (t[k] === marcador[0]) k++;
|
|
103
|
+
return t.slice(k).trim();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Devolve, por linha: o texto do comentário, DIRETIVA, ou null para código e linha em branco.
|
|
107
|
+
function classificar(linhas, ext) {
|
|
108
|
+
const sintaxe = SINTAXE.find(([exts]) => exts.has(ext))?.[1] ?? { linha: ['//'], blocos: [['/*', '*/']] };
|
|
109
|
+
const saida = [];
|
|
110
|
+
const heredocs = [];
|
|
111
|
+
let fimTexto = null;
|
|
112
|
+
let fimDiretiva = null;
|
|
113
|
+
let bloco = null;
|
|
114
|
+
for (let i = 0; i < linhas.length; i++) {
|
|
115
|
+
const t = linhas[i].trim();
|
|
116
|
+
if (heredocs.length) {
|
|
117
|
+
if (t === heredocs[0]) heredocs.shift();
|
|
118
|
+
saida.push(null);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (fimTexto) {
|
|
122
|
+
if (fimTexto.test(t)) fimTexto = null;
|
|
123
|
+
saida.push(null);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (bloco) {
|
|
127
|
+
const pos = t.indexOf(bloco.fim);
|
|
128
|
+
saida.push(bloco.diretiva ? DIRETIVA : limpar(pos === -1 ? t : t.slice(0, pos)));
|
|
129
|
+
if (pos !== -1) bloco = null;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (!t) { saida.push(null); continue; }
|
|
133
|
+
if (fimDiretiva) {
|
|
134
|
+
if (fimDiretiva.test(t)) fimDiretiva = null;
|
|
135
|
+
saida.push(DIRETIVA);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
const linhaDiretiva = BLOCOS_DIRETIVA.find(([exts, abre]) => exts.has(ext) && abre.test(t));
|
|
139
|
+
if (linhaDiretiva) { fimDiretiva = linhaDiretiva[2]; saida.push(DIRETIVA); continue; }
|
|
140
|
+
|
|
141
|
+
const diretiva = (i === 0 && t.startsWith('#!')) || DIRETIVAS.some(([exts, re]) => exts.has(ext) && re.test(t));
|
|
142
|
+
const abre = sintaxe.blocos.find(([inicio]) => t.startsWith(inicio));
|
|
143
|
+
if (abre) {
|
|
144
|
+
const resto = t.slice(abre[0].length);
|
|
145
|
+
const pos = resto.indexOf(abre[1]);
|
|
146
|
+
if (pos === -1) bloco = { fim: abre[1], diretiva };
|
|
147
|
+
saida.push(diretiva ? DIRETIVA : limpar(pos === -1 ? resto : resto.slice(0, pos)));
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (diretiva) { saida.push(DIRETIVA); continue; }
|
|
151
|
+
|
|
152
|
+
const c = comentarioDeLinha(t, sintaxe, ext);
|
|
153
|
+
saida.push(c);
|
|
154
|
+
if (c !== null) continue;
|
|
155
|
+
if (SHELL.has(ext)) for (const m of t.matchAll(HEREDOC)) heredocs.push(m[2]);
|
|
156
|
+
if (POWERSHELL.has(ext) && /@["']$/.test(t)) fimTexto = t.endsWith('"') ? /^"@/ : /^'@/;
|
|
59
157
|
}
|
|
60
|
-
|
|
61
|
-
if (
|
|
62
|
-
|
|
63
|
-
|
|
158
|
+
// O comentário colado em `import "C"` é o preâmbulo do cgo: código C, não comentário.
|
|
159
|
+
if (ext === '.go') {
|
|
160
|
+
for (let i = 0; i < linhas.length; i++) {
|
|
161
|
+
if (linhas[i].trim() !== 'import "C"') continue;
|
|
162
|
+
for (let j = i - 1; j >= 0 && typeof saida[j] === 'string'; j--) saida[j] = DIRETIVA;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return saida;
|
|
64
166
|
}
|
|
65
167
|
|
|
168
|
+
const ehTexto = (c) => typeof c === 'string';
|
|
169
|
+
|
|
66
170
|
function analisar(caminho, fonte) {
|
|
67
171
|
const ext = extname(caminho);
|
|
68
172
|
const linhas = fonte.split('\n');
|
|
69
173
|
const achados = [];
|
|
70
174
|
const marca = (linha, msg) => achados.push({ linha, msg });
|
|
71
|
-
const ehComentario = linhas
|
|
175
|
+
const ehComentario = classificar(linhas, ext);
|
|
72
176
|
|
|
73
177
|
let primeiroCodigo = -1;
|
|
74
178
|
for (let i = 0; i < linhas.length; i++) {
|
|
@@ -77,10 +181,10 @@ function analisar(caminho, fonte) {
|
|
|
77
181
|
|
|
78
182
|
let i = 0;
|
|
79
183
|
while (i < linhas.length) {
|
|
80
|
-
if (ehComentario[i]
|
|
184
|
+
if (!ehTexto(ehComentario[i])) { i++; continue; }
|
|
81
185
|
const inicio = i;
|
|
82
186
|
const conteudo = [];
|
|
83
|
-
while (i < linhas.length && ehComentario[i]
|
|
187
|
+
while (i < linhas.length && ehTexto(ehComentario[i])) { conteudo.push(ehComentario[i]); i++; }
|
|
84
188
|
const fim = i - 1;
|
|
85
189
|
const corpo = conteudo.filter(Boolean);
|
|
86
190
|
const texto = corpo.join(' ');
|
|
@@ -103,9 +207,9 @@ function analisar(caminho, fonte) {
|
|
|
103
207
|
}
|
|
104
208
|
|
|
105
209
|
let proxima = fim + 1;
|
|
106
|
-
while (proxima < linhas.length && !linhas[proxima].trim()) proxima++;
|
|
210
|
+
while (proxima < linhas.length && (!linhas[proxima].trim() || ehComentario[proxima] === DIRETIVA)) proxima++;
|
|
107
211
|
const ehDocstring =
|
|
108
|
-
/^(\/\*\*|"""|'''
|
|
212
|
+
/^(\/\*\*|"""|'''|\/\/\/|<#)/.test(linhas[inicio].trim()) &&
|
|
109
213
|
proxima < linhas.length &&
|
|
110
214
|
ehComentario[proxima] === null;
|
|
111
215
|
const teto = ehDocstring ? MAX_DOCSTRING : MAX_BLOCO;
|
|
@@ -120,7 +224,7 @@ function analisar(caminho, fonte) {
|
|
|
120
224
|
}
|
|
121
225
|
}
|
|
122
226
|
|
|
123
|
-
const comentadas = ehComentario.filter(
|
|
227
|
+
const comentadas = ehComentario.filter(ehTexto).length;
|
|
124
228
|
const uteis = linhas.filter((l) => l.trim()).length;
|
|
125
229
|
return { achados, comentadas, uteis, densidade: uteis ? comentadas / uteis : 0 };
|
|
126
230
|
}
|