@mocoto/mahoraga 0.14.2 → 0.14.4
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/README.md +2 -32
- package/dist/analysts/architects/analyst-structure.js +19 -19
- package/dist/analysts/architects/archetypes-custom.js +8 -6
- package/dist/analysts/architects/detector-archetypes.js +30 -27
- package/dist/analysts/architects/signals-project-advanced.js +24 -26
- package/dist/analysts/architects/signals-project.js +1 -1
- package/dist/analysts/architects/strategist-structure.js +47 -66
- package/dist/analysts/azure-pipelines/analysts/analyst-azure.js +21 -14
- package/dist/analysts/azure-pipelines/corrections/correction-azure.js +22 -19
- package/dist/analysts/azure-pipelines/detectors/detector-azure-performance.js +21 -18
- package/dist/analysts/azure-pipelines/detectors/detector-azure-security.js +25 -20
- package/dist/analysts/azure-pipelines/detectors/detector-azure-structure.js +39 -37
- package/dist/analysts/azure-pipelines/scorers/scorer-azure.js +10 -7
- package/dist/analysts/circleci/analysts/analyst-circleci.js +23 -15
- package/dist/analysts/circleci/corrections/correction-circleci.js +22 -20
- package/dist/analysts/circleci/detectors/detector-circleci-performance.js +23 -19
- package/dist/analysts/circleci/detectors/detector-circleci-security.js +78 -82
- package/dist/analysts/circleci/detectors/detector-circleci-structure.js +20 -17
- package/dist/analysts/converters/engines/converter-gha-to-gitlab.js +17 -15
- package/dist/analysts/corrections/analyst-quick-fixes.js +25 -28
- package/dist/analysts/corrections/analyst-scoring.js +24 -21
- package/dist/analysts/corrections/corrector-structure.js +35 -35
- package/dist/analysts/corrections/fix-alias-imports.js +10 -6
- package/dist/analysts/corrections/fix-md-fences.js +23 -23
- package/dist/analysts/corrections/map-reversion.js +54 -51
- package/dist/analysts/corrections/pruning.js +19 -16
- package/dist/analysts/corrections/quick-fixes/fix-any-to-proper-type.js +57 -61
- package/dist/analysts/corrections/quick-fixes/fix-unknown-to-specific-type.js +30 -30
- package/dist/analysts/corrections/reorganize-barrels.js +10 -10
- package/dist/analysts/corrections/scoring-shared.js +19 -18
- package/dist/analysts/corrections/type-safety/context-analyzer.js +15 -10
- package/dist/analysts/corrections/type-safety/type-validator.js +2 -1
- package/dist/analysts/corrections/type-safety/usage-analyzer.js +36 -39
- package/dist/analysts/css/analysts/analyst-css.js +16 -15
- package/dist/analysts/css/corrections/correction-css.js +19 -11
- package/dist/analysts/css/detectors/detector-css-best-practices.js +129 -118
- package/dist/analysts/css/scorers/scorer-css.js +23 -18
- package/dist/analysts/css-in-js/analysts/analyst-css-in-js.js +24 -17
- package/dist/analysts/css-in-js/corrections/correction-css-in-js.js +31 -16
- package/dist/analysts/css-in-js/detectors/detector-css-in-js-best-practices.js +20 -15
- package/dist/analysts/css-in-js/scorers/scorer-css-in-js.js +79 -54
- package/dist/analysts/css-in-js/shared.js +7 -0
- package/dist/analysts/detectors/detector-architecture.js +89 -83
- package/dist/analysts/detectors/detector-bugs-ml.js +21 -19
- package/dist/analysts/detectors/detector-code-fragile.js +30 -26
- package/dist/analysts/detectors/detector-duplications.js +21 -18
- package/dist/analysts/detectors/detector-monorepo.js +41 -36
- package/dist/analysts/detectors/detector-performance.js +22 -16
- package/dist/analysts/detectors/detector-recommendation-architecture.js +39 -35
- package/dist/analysts/detectors/detector-security.js +43 -42
- package/dist/analysts/detectors/detector-structure.js +29 -26
- package/dist/analysts/js-ts/detectors/detector-anti-patterns-async.js +4 -1
- package/dist/analysts/js-ts/detectors/detector-constructions-syntactic.js +10 -16
- package/dist/analysts/plugins/detector-documentation.js +1 -0
- package/dist/analysts/react/analysts/analyst-react-hooks.js +4 -2
- package/dist/analysts/react/analysts/analyst-react.js +1 -1
- package/dist/analysts/react/corrections/correction-react.js +2 -2
- package/dist/analysts/react/detectors/detector-react-best-practices.js +1 -1
- package/dist/analysts/registry/autodiscovery.js +2 -2
- package/dist/analysts/tailwind/corrections/correction-tailwind.js +0 -8
- package/dist/analysts/tailwind/scorers/scorer-tailwind.js +1 -2
- package/dist/cli/commands/command-analysts.js +3 -2
- package/dist/cli/commands/command-formatters.js +6 -5
- package/dist/cli/commands/command-github-actions.js +1 -1
- package/dist/cli/commands/command-guardian.js +1 -1
- package/dist/cli/commands/command-marketplace.js +4 -4
- package/dist/cli/diagnostic/filters.js +9 -9
- package/dist/core/config/chalk-safe.js +4 -4
- package/dist/core/config/config.js +5 -5
- package/dist/core/execution/analysis-cache.js +1 -1
- package/dist/core/execution/ast-cache.js +1 -3
- package/dist/core/execution/executor.js +2 -4
- package/dist/core/execution/scanner.js +1 -1
- package/dist/core/messages/en/analysts/report-circleci-messages.js +7 -7
- package/dist/core/messages/en/caretakers/caretaker-messages.js +1 -7
- package/dist/core/messages/ja/analysts/report-circleci-messages.js +7 -7
- package/dist/core/messages/ja/caretakers/caretaker-messages.js +1 -7
- package/dist/core/messages/pt/analysts/report-circleci-messages.js +7 -7
- package/dist/core/messages/pt/caretakers/caretaker-messages.js +1 -7
- package/dist/core/messages/zh/analysts/report-circleci-messages.js +7 -7
- package/dist/core/messages/zh/caretakers/caretaker-messages.js +1 -7
- package/dist/core/parsing/parser.js +2 -3
- package/dist/core/parsing/utils.js +1 -0
- package/dist/core/workers/worker-pool.js +0 -0
- package/dist/guardian/records.js +1 -0
- package/dist/guardian/sentinel.js +1 -0
- package/dist/guardian/watcher-hidden.js +1 -0
- package/dist/licenses/fs-utils.js +2 -1
- package/dist/licenses/header-options.js +1 -0
- package/dist/licenses/index.js +1 -1
- package/dist/reports/generator-report.js +3 -3
- package/dist/reports/report-patterns-usage.js +1 -1
- package/dist/shared/data-processing/fragment-report.js +1 -1
- package/dist/shared/formatters/engines/stylelint.js +40 -34
- package/dist/shared/formatters/formatters/markdown.js +1 -1
- package/dist/shared/helpers/framework-detector.js +5 -1
- package/dist/shared/persistence/persistence.js +13 -5
- package/dist/shared/plugins/core-plugin.js +3 -1
- package/dist/types/analysts/tailwind-correction.js +2 -0
- package/dist/types/index.js +3 -3
- package/dist/types/processing/filters.js +5 -4
- package/dist/vulnerabilities/npm-audit.js +1 -0
- package/dist/vulnerabilities/scanner.js +1 -1
- package/dist/vulnerabilities/vulnerabilities.js +1 -0
- package/package.json +5 -10
package/README.md
CHANGED
|
@@ -2,45 +2,15 @@
|
|
|
2
2
|
Proveniência e Autoria: Este documento integra o projeto Mahoraga (licença MIT-0).
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
-
<img src="./svg/banner-mahoraga.svg" width="100%">
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
5
|
<div align="center">
|
|
10
6
|
|
|
11
|
-

|
|
12
|
-

|
|
13
|
-

|
|
14
|
-

|
|
15
|
-

|
|
16
|
-
---
|
|
17
|
-
|
|
18
7
|
<p><b>CLI modular para análise, diagnóstico e manutenção de projetos JavaScript/TypeScript com suporte multi-linguagem</b><p>
|
|
19
8
|
|
|
20
9
|
</div>
|
|
21
10
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
## Índice
|
|
25
|
-
|
|
26
|
-
- [Começar Rápido (30 segundos)](#começar-rápido-30-segundos)
|
|
27
|
-
- [Fluxo Passo a Passo](#fluxo-passo-a-passo)
|
|
28
|
-
- [Funcionalidades](#funcionalidades)
|
|
29
|
-
- [Instalação](#instalação)
|
|
30
|
-
- [Configuração](#configuração)
|
|
31
|
-
- [Comandos da CLI](#comandos-da-cli)
|
|
32
|
-
- [REST API](#rest-api)
|
|
33
|
-
- [SDK Programático](#sdk-programático)
|
|
34
|
-
- [GitHub App](#github-app)
|
|
35
|
-
- [Editor (LSP / VSCode)](#editor-lsp--vscode)
|
|
36
|
-
- [CI/CD](#cicd)
|
|
37
|
-
- [Linguagens Suportadas](#linguagens-suportadas)
|
|
38
|
-
- [Documentação Completa](#documentação-completa)
|
|
39
|
-
- [Desenvolvimento](#desenvolvimento)
|
|
40
|
-
- [Contribuir](#contribuir)
|
|
41
|
-
- [Licença](#licença)
|
|
11
|
+
<p> Estou ficando desanimado de continuar com esse projeto, caso goste do projeto e queira ajudar pra eu não desanimar entre em contato por esse E-mail:</p>
|
|
42
12
|
|
|
43
|
-
|
|
13
|
+
[Gmail](mailto:mocoto.persona@gmail.com)
|
|
44
14
|
|
|
45
15
|
## Começar Rápido (30 segundos)
|
|
46
16
|
|
|
@@ -17,27 +17,27 @@ export const CAMADAS = {
|
|
|
17
17
|
yaml: 'config',
|
|
18
18
|
// Adicione mais mapeamentos conforme necessário
|
|
19
19
|
};
|
|
20
|
+
function determinarCamadaIdeal(entry) {
|
|
21
|
+
const rel = entry.relPath;
|
|
22
|
+
const normalizado = rel.replace(/\\/g, '/');
|
|
23
|
+
const atual = normalizado.split('/')[0] || '';
|
|
24
|
+
let ideal = null;
|
|
25
|
+
const matchDireta = Object.entries(CAMADAS).find(([, dir]) => normalizado.startsWith(`${dir.replace(/\\/g, '/')}/`));
|
|
26
|
+
if (matchDireta) {
|
|
27
|
+
ideal = matchDireta[1];
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
const nome = path.basename(rel);
|
|
31
|
+
const [, tipo] = /\.([^.]+)\.[^.]+$/.exec(nome) ?? [];
|
|
32
|
+
if (tipo && CAMADAS[tipo]) {
|
|
33
|
+
ideal = CAMADAS[tipo];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return { arquivo: rel, atual, ideal };
|
|
37
|
+
}
|
|
20
38
|
export async function analisarEstrutura(fileEntries, _baseDir = process.cwd()) {
|
|
21
39
|
const limit = pLimit(CONCORRENCIA);
|
|
22
|
-
const resultados = await Promise.all(fileEntries.map((entry) => limit(() =>
|
|
23
|
-
const rel = entry.relPath;
|
|
24
|
-
// Normaliza para separador POSIX para evitar dependência de platform e necessidade de mock em testes
|
|
25
|
-
const normalizado = rel.replace(/\\/g, '/');
|
|
26
|
-
const atual = normalizado.split('/')[0] || '';
|
|
27
|
-
let ideal = null;
|
|
28
|
-
const matchDireta = Object.entries(CAMADAS).find(([, dir]) => normalizado.startsWith(`${dir.replace(/\\/g, '/')}/`));
|
|
29
|
-
if (matchDireta) {
|
|
30
|
-
ideal = matchDireta[1];
|
|
31
|
-
}
|
|
32
|
-
else {
|
|
33
|
-
const nome = path.basename(rel);
|
|
34
|
-
const [, tipo] = /\.([^.]+)\.[^.]+$/.exec(nome) ?? [];
|
|
35
|
-
if (tipo && CAMADAS[tipo]) {
|
|
36
|
-
ideal = CAMADAS[tipo];
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
return { arquivo: rel, atual, ideal };
|
|
40
|
-
})));
|
|
40
|
+
const resultados = await Promise.all(fileEntries.map((entry) => limit(() => determinarCamadaIdeal(entry))));
|
|
41
41
|
return resultados;
|
|
42
42
|
}
|
|
43
43
|
export { analisarEstrutura as alinhamentoEstrutural };
|
|
@@ -19,6 +19,13 @@ import { lerEstado, salvarEstado } from '../../shared/persistence/index.js';
|
|
|
19
19
|
const { log, ArquetiposExtraMensagens } = getMessages();
|
|
20
20
|
// Nome do arquivo legado (para compatibilidade)
|
|
21
21
|
const ARQUETIPO_PERSONALIZADO_FILENAME = 'mahoraga.repo.arquetipo.json';
|
|
22
|
+
function logVerbose(fn) {
|
|
23
|
+
const isTest = (process.env.VITEST ?? '') !== '';
|
|
24
|
+
const isVerbose = log.verbose ?? false;
|
|
25
|
+
if (!isTest && isVerbose) {
|
|
26
|
+
fn();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
22
29
|
/**
|
|
23
30
|
* Carrega o arquétipo personalizado do projeto atual
|
|
24
31
|
* Tenta primeiro o novo caminho (.mahoraga/estrutura.arquetipo.json),
|
|
@@ -38,12 +45,7 @@ export async function carregarArquetipoPersonalizado(baseDir = process.cwd()) {
|
|
|
38
45
|
const arquetipoLegado = await lerEstado(caminhoLegado, null);
|
|
39
46
|
// Validação básica
|
|
40
47
|
if (!arquetipoLegado?.nome || !arquetipoLegado.arquetipoOficial) {
|
|
41
|
-
|
|
42
|
-
const isTest = (process.env.VITEST ?? '') !== '';
|
|
43
|
-
const isVerbose = log.verbose ?? false;
|
|
44
|
-
if (!isTest && isVerbose) {
|
|
45
|
-
log.aviso(ArquetiposExtraMensagens.naoEncontrado.replace('{novo}', novoCaminho).replace('{legado}', caminhoLegado));
|
|
46
|
-
}
|
|
48
|
+
logVerbose(() => { log.aviso(ArquetiposExtraMensagens.naoEncontrado.replace('{novo}', novoCaminho).replace('{legado}', caminhoLegado)); });
|
|
47
49
|
return null;
|
|
48
50
|
}
|
|
49
51
|
return arquetipoLegado;
|
|
@@ -41,6 +41,34 @@ if (process.env.VITEST) {
|
|
|
41
41
|
scoreArquetipo
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
|
+
function aplicarBoostConfianca(candidato, arquetiposCandidatos, melhorDeteccao) {
|
|
45
|
+
if (arquetiposCandidatos.some((arq) => candidato.nome.includes(arq) || arq.includes(candidato.nome))) {
|
|
46
|
+
const boostConfianca = Math.min(melhorDeteccao.confiancaTotal * 100 * 0.8, 60);
|
|
47
|
+
return {
|
|
48
|
+
...candidato,
|
|
49
|
+
confidence: Math.max(candidato.confidence + boostConfianca, 70),
|
|
50
|
+
explicacaoSimilaridade: `[Boost contextual: ${melhorDeteccao.tecnologia} detectado com ${(melhorDeteccao.confiancaTotal * 100).toFixed(0)}% confiança] ${candidato.explicacaoSimilaridade ?? ''}`
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return candidato;
|
|
54
|
+
}
|
|
55
|
+
function criarArquetipoVirtual(melhorDeteccao, tecnologiaDetectada) {
|
|
56
|
+
return {
|
|
57
|
+
nome: tecnologiaDetectada,
|
|
58
|
+
score: Math.round(melhorDeteccao.confiancaTotal * 100),
|
|
59
|
+
confidence: Math.round(melhorDeteccao.confiancaTotal * 100),
|
|
60
|
+
matchedRequired: [],
|
|
61
|
+
missingRequired: [],
|
|
62
|
+
matchedOptional: [],
|
|
63
|
+
dependencyMatches: (melhorDeteccao.evidencias ?? []).filter(erro => erro.tipo === 'dependencia').map(erro => erro.valor),
|
|
64
|
+
filePadraoMatches: (melhorDeteccao.evidencias ?? []).filter(erro => erro.tipo === 'estrutura').map(erro => erro.valor),
|
|
65
|
+
forbiddenPresent: [],
|
|
66
|
+
anomalias: [],
|
|
67
|
+
sugestaoPadronizacao: (melhorDeteccao.sugestoesMelhoria ?? []).join('; '),
|
|
68
|
+
explicacaoSimilaridade: `Detectado via sistema inteligente contextual com ${(melhorDeteccao.confiancaTotal * 100).toFixed(0)}% confiança`,
|
|
69
|
+
descricao: `Projeto ${melhorDeteccao.tecnologia} identificado por análise contextual`
|
|
70
|
+
};
|
|
71
|
+
}
|
|
44
72
|
export async function detectarArquetipos(contexto, baseDir, options) {
|
|
45
73
|
if (!options?.quiet && config.VERBOSE) {
|
|
46
74
|
console.log(DetectorArquetiposExtraMensagens.debugChamado, contexto.arquivos.length, 'arquivos');
|
|
@@ -115,34 +143,9 @@ export async function detectarArquetipos(contexto, baseDir, options) {
|
|
|
115
143
|
else {
|
|
116
144
|
// Aplicar boost de confiança para arquétipos relacionados
|
|
117
145
|
const boostAplicado = candidatos.some((candidato) => arquetiposCandidatos.some((arq) => candidato.nome.includes(arq) || arq.includes(candidato.nome)));
|
|
118
|
-
candidatos = candidatos.map((candidato) =>
|
|
119
|
-
if (arquetiposCandidatos.some((arq) => candidato.nome.includes(arq) || arq.includes(candidato.nome))) {
|
|
120
|
-
const boostConfianca = Math.min(melhorDeteccao.confiancaTotal * 100 * 0.8, 60);
|
|
121
|
-
return {
|
|
122
|
-
...candidato,
|
|
123
|
-
confidence: Math.max(candidato.confidence + boostConfianca, 70),
|
|
124
|
-
explicacaoSimilaridade: `[Boost contextual: ${melhorDeteccao.tecnologia} detectado com ${(melhorDeteccao.confiancaTotal * 100).toFixed(0)}% confiança] ${candidato.explicacaoSimilaridade ?? ''}`
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
return candidato;
|
|
128
|
-
});
|
|
146
|
+
candidatos = candidatos.map((candidato) => aplicarBoostConfianca(candidato, arquetiposCandidatos, melhorDeteccao));
|
|
129
147
|
if (!boostAplicado) {
|
|
130
|
-
|
|
131
|
-
nome: tecnologiaDetectada,
|
|
132
|
-
score: Math.round(melhorDeteccao.confiancaTotal * 100),
|
|
133
|
-
confidence: Math.round(melhorDeteccao.confiancaTotal * 100),
|
|
134
|
-
matchedRequired: [],
|
|
135
|
-
missingRequired: [],
|
|
136
|
-
matchedOptional: [],
|
|
137
|
-
dependencyMatches: (melhorDeteccao.evidencias ?? []).filter(erro => erro.tipo === 'dependencia').map(erro => erro.valor),
|
|
138
|
-
filePadraoMatches: (melhorDeteccao.evidencias ?? []).filter(erro => erro.tipo === 'estrutura').map(erro => erro.valor),
|
|
139
|
-
forbiddenPresent: [],
|
|
140
|
-
anomalias: [],
|
|
141
|
-
sugestaoPadronizacao: (melhorDeteccao.sugestoesMelhoria ?? []).join('; '),
|
|
142
|
-
explicacaoSimilaridade: `Detectado via sistema inteligente contextual com ${(melhorDeteccao.confiancaTotal * 100).toFixed(0)}% confiança`,
|
|
143
|
-
descricao: `Projeto ${melhorDeteccao.tecnologia} identificado por análise contextual`
|
|
144
|
-
};
|
|
145
|
-
candidatos.unshift(arquetipoVirtual);
|
|
148
|
+
candidatos.unshift(criarArquetipoVirtual(melhorDeteccao, tecnologiaDetectada));
|
|
146
149
|
}
|
|
147
150
|
}
|
|
148
151
|
}
|
|
@@ -17,6 +17,28 @@ _sinaisAvancados // prefixo _ para ignorar warning de unused
|
|
|
17
17
|
anomalias: []
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
|
+
function processarImportsFramework(imports, tecnologias, sinais) {
|
|
21
|
+
for (const indice of imports) {
|
|
22
|
+
if (typeof indice.source.value === 'string') {
|
|
23
|
+
const importSource = indice.source.value.toLowerCase();
|
|
24
|
+
if (importSource.includes('react') || importSource.includes('vue') || importSource.includes('angular')) {
|
|
25
|
+
tecnologias.set('frontend-framework', (tecnologias.get('frontend-framework') ?? 0) + 1);
|
|
26
|
+
}
|
|
27
|
+
if (importSource.includes('express') || importSource.includes('fastify') || importSource.includes('nestjs')) {
|
|
28
|
+
tecnologias.set('backend-framework', (tecnologias.get('backend-framework') ?? 0) + 1);
|
|
29
|
+
}
|
|
30
|
+
if (importSource.includes('prisma') || importSource.includes('mongoose') || importSource.includes('typeorm')) {
|
|
31
|
+
tecnologias.set('orm', (tecnologias.get('orm') ?? 0) + 1);
|
|
32
|
+
}
|
|
33
|
+
if (importSource.includes('jest') || importSource.includes('vitest') || importSource.includes('mocha')) {
|
|
34
|
+
tecnologias.set('testing-framework', (tecnologias.get('testing-framework') ?? 0) + 1);
|
|
35
|
+
}
|
|
36
|
+
if (/express|react|next|electron|discord\.js|telegraf/.test(importSource)) {
|
|
37
|
+
sinais.frameworksDetectados.push(indice.source.value);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
20
42
|
export function extrairSinaisAvancados(fileEntries, packageJson, _p0, _baseDir, _arquivos) {
|
|
21
43
|
// Auxiliar para checar se o nó possui id.name string
|
|
22
44
|
const hasIdNome = (node) => {
|
|
@@ -46,7 +68,7 @@ export function extrairSinaisAvancados(fileEntries, packageJson, _p0, _baseDir,
|
|
|
46
68
|
for (const fe of fileEntries) {
|
|
47
69
|
let body = [];
|
|
48
70
|
if (fe.ast && 'node' in fe.ast && Array.isArray(fe.ast.node.body)) {
|
|
49
|
-
body = fe.ast.node.body;
|
|
71
|
+
body = (fe.ast.node).body;
|
|
50
72
|
}
|
|
51
73
|
// Análise inteligente de padrões
|
|
52
74
|
const conteudo = fe.content ?? '';
|
|
@@ -134,31 +156,7 @@ export function extrairSinaisAvancados(fileEntries, packageJson, _p0, _baseDir,
|
|
|
134
156
|
// Classes
|
|
135
157
|
sinais.classes += body.filter((node) => node.type === 'ClassDeclaration').length;
|
|
136
158
|
// Frameworks por import (expandido)
|
|
137
|
-
|
|
138
|
-
if (typeof indice.source.value === 'string') {
|
|
139
|
-
const importSource = indice.source.value.toLowerCase();
|
|
140
|
-
// Frameworks web
|
|
141
|
-
if (importSource.includes('react') || importSource.includes('vue') || importSource.includes('angular')) {
|
|
142
|
-
tecnologias.set('frontend-framework', (tecnologias.get('frontend-framework') ?? 0) + 1);
|
|
143
|
-
}
|
|
144
|
-
// Frameworks backend
|
|
145
|
-
if (importSource.includes('express') || importSource.includes('fastify') || importSource.includes('nestjs')) {
|
|
146
|
-
tecnologias.set('backend-framework', (tecnologias.get('backend-framework') ?? 0) + 1);
|
|
147
|
-
}
|
|
148
|
-
// ORMs
|
|
149
|
-
if (importSource.includes('prisma') || importSource.includes('mongoose') || importSource.includes('typeorm')) {
|
|
150
|
-
tecnologias.set('orm', (tecnologias.get('orm') ?? 0) + 1);
|
|
151
|
-
}
|
|
152
|
-
// Testing
|
|
153
|
-
if (importSource.includes('jest') || importSource.includes('vitest') || importSource.includes('mocha')) {
|
|
154
|
-
tecnologias.set('testing-framework', (tecnologias.get('testing-framework') ?? 0) + 1);
|
|
155
|
-
}
|
|
156
|
-
// Lista original mantida
|
|
157
|
-
if (/express|react|next|electron|discord\.js|telegraf/.test(importSource)) {
|
|
158
|
-
sinais.frameworksDetectados.push(indice.source.value);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
159
|
+
processarImportsFramework(imports, tecnologias, sinais);
|
|
162
160
|
// Padrões de pastas/arquivos (expandido)
|
|
163
161
|
const rel = fe.relPath.replace(/\\/g, '/');
|
|
164
162
|
if (/src\/controllers|pages|api|prisma|packages|apps|src\/routes|src\/services|src\/repositories/.test(rel)) {
|
|
@@ -23,7 +23,7 @@ export function extrairSinaisProjeto(fileEntries, packageJson) {
|
|
|
23
23
|
for (const fe of fileEntries) {
|
|
24
24
|
let body = [];
|
|
25
25
|
if (fe.ast && 'node' in fe.ast && Array.isArray(fe.ast.node.body)) {
|
|
26
|
-
body = fe.ast.node.body;
|
|
26
|
+
body = (fe.ast.node).body;
|
|
27
27
|
}
|
|
28
28
|
// Funções
|
|
29
29
|
sinais.funcoes += body.filter((node) => node.type === 'FunctionDeclaration').length;
|
|
@@ -3,15 +3,52 @@ import { statSync } from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { getTypesDirectoryRelPosix } from '../../core/config/index.js';
|
|
5
5
|
import { carregarConfigEstrategia, destinoPara, deveIgnorar, normalizarRel } from '../../shared/helpers/index.js';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
6
|
+
function processarArquivoParaMovimento(rel, cfg, baseDir, sinaisAvancados, rels, mover, conflitos) {
|
|
7
|
+
if (deveIgnorar(rel, cfg.ignorarPastas)) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
if (/__(tests|mocks)__/.test(rel) || /\.(test|spec)\.[jt]sx?$/.test(rel) || /fixtures\//.test(rel)) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
if (!rel.endsWith('.ts') && !rel.endsWith('.js')) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const base = path.posix.basename(rel);
|
|
17
|
+
if (/^(eslint|vitest|next|tailwind|postcss|prettier|jest)\.config\.[jt]s$/i.test(base)) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const res = destinoPara(rel, cfg.raizCodigo, cfg.criarSubpastasPorEntidade, cfg.apenasCategoriasConfiguradas, cfg.categoriasMapa);
|
|
21
|
+
if (!res.destinoDir) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const currentDir = path.posix.dirname(rel);
|
|
25
|
+
const alreadyInTarget = currentDir === res.destinoDir || currentDir.startsWith(`${res.destinoDir}/`);
|
|
26
|
+
if (alreadyInTarget) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
let destinoDirAjustado = res.destinoDir;
|
|
30
|
+
const motivoAjustado = res.motivo ?? 'Reorganização padrão';
|
|
31
|
+
if (sinaisAvancados) {
|
|
32
|
+
destinoDirAjustado = ajustarDestinoPorSinais(rel, res.destinoDir, sinaisAvancados, motivoAjustado);
|
|
33
|
+
}
|
|
34
|
+
const destino = path.posix.join(destinoDirAjustado, path.posix.basename(rel));
|
|
35
|
+
let destinoExiste = rels.includes(destino);
|
|
36
|
+
if (!destinoExiste) {
|
|
37
|
+
try {
|
|
38
|
+
const abs = path.join(baseDir, destino.replace(/\\/g, '/'));
|
|
39
|
+
statSync(abs);
|
|
40
|
+
destinoExiste = true;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
destinoExiste = false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (destinoExiste) {
|
|
47
|
+
conflitos.push({ alvo: destino, motivo: 'destino já existe' });
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
mover.push({ de: rel, para: destino, motivo: motivoAjustado });
|
|
51
|
+
}
|
|
15
52
|
export async function gerarPlanoEstrategico(contexto, opcoes = {}, sinaisAvancados) {
|
|
16
53
|
const typesDir = getTypesDirectoryRelPosix();
|
|
17
54
|
const cfg = await carregarConfigEstrategia(contexto.baseDir, {
|
|
@@ -24,63 +61,7 @@ export async function gerarPlanoEstrategico(contexto, opcoes = {}, sinaisAvancad
|
|
|
24
61
|
const rels = contexto.arquivos.map(arquivo => normalizarRel(arquivo.relPath));
|
|
25
62
|
const isTestLike = (caminho) => /__(tests|mocks)__/.test(caminho) || /\.(test|spec)\.[jt]sx?$/.test(caminho) || /fixtures\//.test(caminho);
|
|
26
63
|
for (const rel of rels) {
|
|
27
|
-
|
|
28
|
-
continue;
|
|
29
|
-
}
|
|
30
|
-
if (isTestLike(rel)) {
|
|
31
|
-
continue;
|
|
32
|
-
} // não mover testes/fixtures
|
|
33
|
-
// Evitar mexer em arquivos fora do escopo de código (por agora)
|
|
34
|
-
if (!rel.endsWith('.ts') && !rel.endsWith('.js')) {
|
|
35
|
-
continue;
|
|
36
|
-
}
|
|
37
|
-
// Respeita convenções de ferramentas no root: não mover configs globais
|
|
38
|
-
const base = path.posix.basename(rel);
|
|
39
|
-
if (/^(eslint|vitest|next|tailwind|postcss|prettier|jest)\.config\.[jt]s$/i.test(base)) {
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
const res = destinoPara(rel, cfg.raizCodigo, cfg.criarSubpastasPorEntidade, cfg.apenasCategoriasConfiguradas, cfg.categoriasMapa);
|
|
43
|
-
if (!res.destinoDir) {
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
const currentDir = path.posix.dirname(rel);
|
|
47
|
-
const alreadyInTarget = currentDir === res.destinoDir || currentDir.startsWith(`${res.destinoDir}/`);
|
|
48
|
-
if (alreadyInTarget) {
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
// [LANCAMENTO] INTELIGÊNCIA CONTEXTUAL: Ajustar destino baseado em sinais avançados
|
|
52
|
-
let destinoDirAjustado = res.destinoDir;
|
|
53
|
-
const motivoAjustado = res.motivo ?? 'Reorganização padrão';
|
|
54
|
-
if (sinaisAvancados) {
|
|
55
|
-
destinoDirAjustado = ajustarDestinoPorSinais(rel, res.destinoDir, sinaisAvancados, motivoAjustado);
|
|
56
|
-
}
|
|
57
|
-
// Mantém o mesmo nome do arquivo; apenas move para pasta de destino
|
|
58
|
-
const destino = path.posix.join(destinoDirAjustado, path.posix.basename(rel));
|
|
59
|
-
// Conflito se já existe arquivo listado ou presente no filesystem
|
|
60
|
-
let destinoExiste = rels.includes(destino);
|
|
61
|
-
if (!destinoExiste) {
|
|
62
|
-
try {
|
|
63
|
-
const abs = path.join(contexto.baseDir, destino.replace(/\\/g, '/'));
|
|
64
|
-
// fs.statSync usado de forma segura; se falhar, considera inexistente
|
|
65
|
-
statSync(abs);
|
|
66
|
-
destinoExiste = true;
|
|
67
|
-
}
|
|
68
|
-
catch {
|
|
69
|
-
destinoExiste = false;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
if (destinoExiste) {
|
|
73
|
-
conflitos.push({
|
|
74
|
-
alvo: destino,
|
|
75
|
-
motivo: 'destino já existe'
|
|
76
|
-
});
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
mover.push({
|
|
80
|
-
de: rel,
|
|
81
|
-
para: destino,
|
|
82
|
-
motivo: motivoAjustado
|
|
83
|
-
});
|
|
64
|
+
processarArquivoParaMovimento(rel, cfg, contexto.baseDir, sinaisAvancados, rels, mover, conflitos);
|
|
84
65
|
}
|
|
85
66
|
// Deduplicação simples
|
|
86
67
|
const seen = new Set();
|
|
@@ -11,6 +11,21 @@ export function registrarDetectorAzure(detector) {
|
|
|
11
11
|
export function obterDetectoresAzure() {
|
|
12
12
|
return [...detectoresRegistrados];
|
|
13
13
|
}
|
|
14
|
+
function processarResultadosPlugins(plugResults, ocorrencias, relPath) {
|
|
15
|
+
for (const results of plugResults) {
|
|
16
|
+
for (const item of results) {
|
|
17
|
+
ocorrencias.push({
|
|
18
|
+
tipo: `AZURE_${item.tipo.toUpperCase().replace(/-/g, '_')}`,
|
|
19
|
+
mensagem: item.descricao,
|
|
20
|
+
relPath,
|
|
21
|
+
linha: item.linha ?? 1,
|
|
22
|
+
coluna: 1,
|
|
23
|
+
sugestao: item.sugestao ?? '',
|
|
24
|
+
nivel: item.severidade === 'critica' || item.severidade === 'alta' ? 'erro' : item.severidade === 'media' ? 'aviso' : 'info',
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
14
29
|
export const analistaAzurePipelines = {
|
|
15
30
|
nome: 'azure-pipelines',
|
|
16
31
|
categoria: 'workflows',
|
|
@@ -23,7 +38,11 @@ export const analistaAzurePipelines = {
|
|
|
23
38
|
const securityResults = await Promise.resolve(detectorAzureSecurity.aplicar(conteudo, relPath, null, _fc, contexto));
|
|
24
39
|
const performanceResults = await Promise.resolve(detectorAzurePerformance.aplicar(conteudo, relPath, null, _fc, contexto));
|
|
25
40
|
const estruturaResults = await Promise.resolve(detectorAzureEstrutura.aplicar(conteudo, relPath, null, _fc, contexto));
|
|
26
|
-
|
|
41
|
+
for (const r of [securityResults, performanceResults, estruturaResults]) {
|
|
42
|
+
if (r) {
|
|
43
|
+
ocorrencias.push(...(Array.isArray(r) ? r : [r]));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
27
46
|
// 2. Análise Estrutural básica
|
|
28
47
|
const doc = parseDocument(conteudo);
|
|
29
48
|
const configData = doc.toJS();
|
|
@@ -56,19 +75,7 @@ export const analistaAzurePipelines = {
|
|
|
56
75
|
// 3. Plugins registrados
|
|
57
76
|
const caminhos = contexto?.arquivos.map(arquivo => arquivo.relPath) ?? [];
|
|
58
77
|
const plugResults = await Promise.all(detectoresRegistrados.map(detector => Promise.resolve(detector.testar(configData, { conteudo, caminhos })).catch(() => [])));
|
|
59
|
-
|
|
60
|
-
for (const item of results) {
|
|
61
|
-
ocorrencias.push({
|
|
62
|
-
tipo: `AZURE_${item.tipo.toUpperCase().replace(/-/g, '_')}`,
|
|
63
|
-
mensagem: item.descricao,
|
|
64
|
-
relPath,
|
|
65
|
-
linha: item.linha ?? 1,
|
|
66
|
-
coluna: 1,
|
|
67
|
-
sugestao: item.sugestao ?? '',
|
|
68
|
-
nivel: item.severidade === 'critica' || item.severidade === 'alta' ? 'erro' : item.severidade === 'media' ? 'aviso' : 'info',
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
}
|
|
78
|
+
processarResultadosPlugins(plugResults, ocorrencias, relPath);
|
|
72
79
|
}
|
|
73
80
|
catch {
|
|
74
81
|
// Falha silenciosa no parse
|
|
@@ -2,30 +2,33 @@
|
|
|
2
2
|
import { messages } from '../../../core/messages/index.js';
|
|
3
3
|
import { splitLines } from '../../../shared/helpers/index.js';
|
|
4
4
|
import { criarOcorrencia } from '../../../types/index.js';
|
|
5
|
+
function processarLinhaAzure(linha, numeroLinha, correcoes) {
|
|
6
|
+
if (/vmImage:\s*['"]?.*latest['"]?/i.test(linha)) {
|
|
7
|
+
correcoes.push({
|
|
8
|
+
tipo: 'azure-fix-latest-image',
|
|
9
|
+
original: linha,
|
|
10
|
+
sugerido: linha.replace(/latest/i, 'ubuntu-22.04'),
|
|
11
|
+
linha: numeroLinha,
|
|
12
|
+
confianca: 90
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
if (/(password|token|secret|key):\s*['"]?([^$({][a-zA-Z0-9_\-]{8,})['"]?/gi.test(linha)) {
|
|
16
|
+
const replaced = linha.replace(/['"][^'"]+['"]/, '$(SECRET_NAME)');
|
|
17
|
+
correcoes.push({
|
|
18
|
+
tipo: 'azure-use-variable',
|
|
19
|
+
original: linha,
|
|
20
|
+
sugerido: replaced,
|
|
21
|
+
linha: numeroLinha,
|
|
22
|
+
confianca: 90
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
5
26
|
export function detectarCorrecoesAzure(src, _relPath) {
|
|
6
27
|
const correcoes = [];
|
|
7
28
|
const linhas = splitLines(src);
|
|
8
29
|
linhas.forEach((linha, index) => {
|
|
9
30
|
const numeroLinha = index + 1;
|
|
10
|
-
|
|
11
|
-
correcoes.push({
|
|
12
|
-
tipo: 'azure-fix-latest-image',
|
|
13
|
-
original: linha,
|
|
14
|
-
sugerido: linha.replace(/latest/i, 'ubuntu-22.04'),
|
|
15
|
-
linha: numeroLinha,
|
|
16
|
-
confianca: 90
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
if (/(password|token|secret|key):\s*['"]?([^$({][a-zA-Z0-9_\-]{8,})['"]?/gi.test(linha)) {
|
|
20
|
-
const replaced = linha.replace(/['"][^'"]+['"]/, '$(SECRET_NAME)');
|
|
21
|
-
correcoes.push({
|
|
22
|
-
tipo: 'azure-use-variable',
|
|
23
|
-
original: linha,
|
|
24
|
-
sugerido: replaced,
|
|
25
|
-
linha: numeroLinha,
|
|
26
|
-
confianca: 90
|
|
27
|
-
});
|
|
28
|
-
}
|
|
31
|
+
processarLinhaAzure(linha, numeroLinha, correcoes);
|
|
29
32
|
});
|
|
30
33
|
if (!/task:\s*Cache@/i.test(src) && !/CacheBeta@/i.test(src)) {
|
|
31
34
|
correcoes.push({
|
|
@@ -1,6 +1,26 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
import { messages } from '../../../core/messages/index.js';
|
|
3
3
|
import { criarOcorrencia } from '../../../types/index.js';
|
|
4
|
+
function detectarParallelDeployment(src, relPath, ocorrencias) {
|
|
5
|
+
if (/environment:/i.test(src) && /deploy/i.test(src)) {
|
|
6
|
+
const deployJobs = src.match(/- job:\s*\w+/g);
|
|
7
|
+
const deployStages = src.match(/- stage:\s*\w+/g);
|
|
8
|
+
const totalDeployUnits = (deployJobs ? deployJobs.length : 0) + (deployStages ? deployStages.length : 0);
|
|
9
|
+
if (totalDeployUnits > 1) {
|
|
10
|
+
const hasDependsOn = /dependsOn:/i.test(src);
|
|
11
|
+
if (!hasDependsOn) {
|
|
12
|
+
ocorrencias.push(criarOcorrencia({
|
|
13
|
+
tipo: 'azure-pipelines-missing-parallel-deployment',
|
|
14
|
+
nivel: 'info',
|
|
15
|
+
mensagem: messages.RelatorioAzureMensagens.missingParallelDeployment,
|
|
16
|
+
relPath,
|
|
17
|
+
linha: 1,
|
|
18
|
+
sugestao: messages.RelatorioAzureMensagens.missingParallelDeploymentSugestao,
|
|
19
|
+
}));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
4
24
|
export const detectorAzurePerformance = {
|
|
5
25
|
nome: 'detector-azure-performance',
|
|
6
26
|
categoria: 'performance',
|
|
@@ -47,24 +67,7 @@ export const detectorAzurePerformance = {
|
|
|
47
67
|
}));
|
|
48
68
|
}
|
|
49
69
|
// 4. Parallel deployment detection
|
|
50
|
-
|
|
51
|
-
const deployJobs = src.match(/- job:\s*\w+/g);
|
|
52
|
-
const deployStages = src.match(/- stage:\s*\w+/g);
|
|
53
|
-
const totalDeployUnits = (deployJobs ? deployJobs.length : 0) + (deployStages ? deployStages.length : 0);
|
|
54
|
-
if (totalDeployUnits > 1) {
|
|
55
|
-
const hasDependsOn = /dependsOn:/i.test(src);
|
|
56
|
-
if (!hasDependsOn) {
|
|
57
|
-
ocorrencias.push(criarOcorrencia({
|
|
58
|
-
tipo: 'azure-pipelines-missing-parallel-deployment',
|
|
59
|
-
nivel: 'info',
|
|
60
|
-
mensagem: messages.RelatorioAzureMensagens.missingParallelDeployment,
|
|
61
|
-
relPath,
|
|
62
|
-
linha: 1,
|
|
63
|
-
sugestao: messages.RelatorioAzureMensagens.missingParallelDeploymentSugestao,
|
|
64
|
-
}));
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
70
|
+
detectarParallelDeployment(src, relPath, ocorrencias);
|
|
68
71
|
// 5. Large artifact staging
|
|
69
72
|
const publishArtifacts = src.match(/task:\s*PublishBuildArtifacts@/gi);
|
|
70
73
|
if (publishArtifacts && publishArtifacts.length > 3) {
|
|
@@ -16,6 +16,30 @@ const SECRET_PATTERNS = [
|
|
|
16
16
|
/(connection[_-]?string)\s*[:=]\s*['"][^'"]+['"]/gi,
|
|
17
17
|
/(azure[_-]?devops[_-]?pat)\s*[:=]\s*['"][^'"]+['"]/gi,
|
|
18
18
|
];
|
|
19
|
+
function detectarSecretVarsPlainText(src, relPath, ocorrencias) {
|
|
20
|
+
const variableLines = src.match(/^\s*variables\s*:\s*$/im);
|
|
21
|
+
if (!variableLines) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const varSection = src.slice(src.indexOf('variables:'));
|
|
25
|
+
const secretVars = varSection.match(/([a-zA-Z_]\w*)\s*:\s*['"][^'"]+['"]/g);
|
|
26
|
+
if (!secretVars) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
for (const sv of secretVars) {
|
|
30
|
+
const varName = sv.split(':')[0].trim();
|
|
31
|
+
if (/(password|token|secret|key|connection)/i.test(varName)) {
|
|
32
|
+
ocorrencias.push(criarOcorrencia({
|
|
33
|
+
tipo: 'azure-pipelines-secret-variable-plaintext',
|
|
34
|
+
nivel: 'erro',
|
|
35
|
+
mensagem: `Variável '${varName}' definida em texto plano na seção variables`,
|
|
36
|
+
relPath,
|
|
37
|
+
linha: 1,
|
|
38
|
+
sugestao: 'Use um grupo de variáveis com proteção de segredo ou referencie como $(varName)',
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
19
43
|
export const detectorAzureSecurity = {
|
|
20
44
|
nome: 'detector-azure-security',
|
|
21
45
|
categoria: 'seguranca',
|
|
@@ -148,26 +172,7 @@ export const detectorAzureSecurity = {
|
|
|
148
172
|
}
|
|
149
173
|
}
|
|
150
174
|
// 10. Secret variable detection in plain text
|
|
151
|
-
|
|
152
|
-
if (variableLines) {
|
|
153
|
-
const varSection = src.slice(src.indexOf('variables:'));
|
|
154
|
-
const secretVars = varSection.match(/([a-zA-Z_]\w*)\s*:\s*['"][^'"]+['"]/g);
|
|
155
|
-
if (secretVars) {
|
|
156
|
-
for (const sv of secretVars) {
|
|
157
|
-
const varName = sv.split(':')[0].trim();
|
|
158
|
-
if (/(password|token|secret|key|connection)/i.test(varName)) {
|
|
159
|
-
ocorrencias.push(criarOcorrencia({
|
|
160
|
-
tipo: 'azure-pipelines-secret-variable-plaintext',
|
|
161
|
-
nivel: 'erro',
|
|
162
|
-
mensagem: `Variável '${varName}' definida em texto plano na seção variables`,
|
|
163
|
-
relPath,
|
|
164
|
-
linha: 1,
|
|
165
|
-
sugestao: 'Use um grupo de variáveis com proteção de segredo ou referencie como $(varName)',
|
|
166
|
-
}));
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
175
|
+
detectarSecretVarsPlainText(src, relPath, ocorrencias);
|
|
171
176
|
return ocorrencias;
|
|
172
177
|
}
|
|
173
178
|
};
|