@gilbert_oliveira/commit-wizard 1.2.2 → 2.0.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/.commit-wizardrc +17 -0
- package/LICENSE +3 -3
- package/README.md +374 -211
- package/bin/commit-wizard.ts +51 -0
- package/dist/commit-wizard.js +195 -0
- package/package.json +71 -64
- package/src/config/index.ts +237 -0
- package/src/core/cache.ts +210 -0
- package/src/core/index.ts +381 -0
- package/src/core/openai.ts +336 -0
- package/src/core/smart-split.ts +698 -0
- package/src/git/index.ts +177 -0
- package/src/ui/index.ts +204 -0
- package/src/ui/smart-split.ts +141 -0
- package/src/utils/args.ts +56 -0
- package/src/utils/polyfill.ts +82 -0
- package/dist/ai-service.d.ts +0 -44
- package/dist/ai-service.js +0 -287
- package/dist/ai-service.js.map +0 -1
- package/dist/commit-splitter.d.ts +0 -48
- package/dist/commit-splitter.js +0 -227
- package/dist/commit-splitter.js.map +0 -1
- package/dist/config.d.ts +0 -22
- package/dist/config.js +0 -84
- package/dist/config.js.map +0 -1
- package/dist/diff-processor.d.ts +0 -39
- package/dist/diff-processor.js +0 -156
- package/dist/diff-processor.js.map +0 -1
- package/dist/git-utils.d.ts +0 -72
- package/dist/git-utils.js +0 -373
- package/dist/git-utils.js.map +0 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -486
- package/dist/index.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,86 +1,93 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gilbert_oliveira/commit-wizard",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"displayName": "Commit Wizard",
|
|
4
|
+
"publisher": "gilbert-oliveira",
|
|
5
|
+
"description": "CLI inteligente para gerar mensagens de commit usando OpenAI",
|
|
6
|
+
"version": "2.0.0",
|
|
7
|
+
"categories": [
|
|
8
|
+
"Other",
|
|
9
|
+
"SCM Providers"
|
|
10
|
+
],
|
|
11
|
+
"main": "dist/commit-wizard.js",
|
|
12
|
+
"module": "index.ts",
|
|
6
13
|
"type": "module",
|
|
7
14
|
"bin": {
|
|
8
|
-
"commit-wizard": "dist/
|
|
9
|
-
},
|
|
10
|
-
"publishConfig": {
|
|
11
|
-
"access": "public"
|
|
15
|
+
"commit-wizard": "./dist/commit-wizard.js"
|
|
12
16
|
},
|
|
13
17
|
"scripts": {
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"test": "
|
|
18
|
-
"test:
|
|
19
|
-
"test:coverage": "
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"
|
|
30
|
-
"
|
|
18
|
+
"dev": "bun run bin/commit-wizard.ts",
|
|
19
|
+
"build": "bun build bin/commit-wizard.ts --outdir=dist --target=bun --minify",
|
|
20
|
+
"test": "bun test",
|
|
21
|
+
"test:watch": "bun test --watch",
|
|
22
|
+
"test:coverage": "bun test && node scripts/generate-lcov.js",
|
|
23
|
+
"test:coverage:report": "c8 --reporter=html --reporter=text --include='src/**/*.ts' --exclude='**/*.test.ts' bun test && open coverage/lcov-report/index.html",
|
|
24
|
+
"test:coverage:upload": "bun test && node scripts/generate-lcov.js && node scripts/upload-codecov.js",
|
|
25
|
+
"prepublishOnly": "bun run build && bun test",
|
|
26
|
+
"start": "bun run bin/commit-wizard.ts",
|
|
27
|
+
"ci:test": "bun test --reporter=verbose",
|
|
28
|
+
"ci:build": "bun run build",
|
|
29
|
+
"ci:lint": "bun run tsc --noEmit",
|
|
30
|
+
"ci:security": "bun audit",
|
|
31
|
+
"ci:integration": "bun test tests/integration.test.ts tests/smart-split.test.ts",
|
|
32
|
+
"release:patch": "./scripts/release.sh patch",
|
|
33
|
+
"release:minor": "./scripts/release.sh minor",
|
|
34
|
+
"release:major": "./scripts/release.sh major",
|
|
35
|
+
"type-check": "bun run tsc --noEmit",
|
|
36
|
+
"lint": "bun run eslint --fix",
|
|
37
|
+
"format": "bun run prettier --write ."
|
|
31
38
|
},
|
|
32
39
|
"keywords": [
|
|
33
|
-
"commit",
|
|
34
|
-
"wizard",
|
|
35
|
-
"conventional",
|
|
36
|
-
"commits",
|
|
37
|
-
"conventional-commits",
|
|
38
|
-
"commitizen",
|
|
39
|
-
"commitlint",
|
|
40
|
-
"husky",
|
|
41
|
-
"lint-staged",
|
|
42
40
|
"git",
|
|
41
|
+
"commit",
|
|
43
42
|
"ai",
|
|
44
43
|
"openai",
|
|
45
|
-
"
|
|
44
|
+
"cli",
|
|
45
|
+
"automation",
|
|
46
|
+
"conventional-commits"
|
|
46
47
|
],
|
|
47
|
-
"author": "Gilbert
|
|
48
|
-
"engines": {
|
|
49
|
-
"node": ">=18.0.0"
|
|
50
|
-
},
|
|
48
|
+
"author": "Gilbert <contato@gilbert.dev.br>",
|
|
51
49
|
"license": "MIT",
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "git+https://github.com/gilbert-oliveira/commit-wizard.git"
|
|
53
|
+
},
|
|
52
54
|
"bugs": {
|
|
53
55
|
"url": "https://github.com/gilbert-oliveira/commit-wizard/issues"
|
|
54
56
|
},
|
|
55
57
|
"homepage": "https://github.com/gilbert-oliveira/commit-wizard#readme",
|
|
58
|
+
"engines": {
|
|
59
|
+
"node": ">=18.0.0",
|
|
60
|
+
"bun": ">=1.0.0"
|
|
61
|
+
},
|
|
62
|
+
"files": [
|
|
63
|
+
"bin/",
|
|
64
|
+
"src/",
|
|
65
|
+
"dist/",
|
|
66
|
+
"README.md",
|
|
67
|
+
"LICENSE",
|
|
68
|
+
".commit-wizardrc"
|
|
69
|
+
],
|
|
56
70
|
"dependencies": {
|
|
57
|
-
"@
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"inquirer": "^12.5.2",
|
|
62
|
-
"ora": "^8.2.0",
|
|
63
|
-
"ts-node": "^10.9.2",
|
|
64
|
-
"typescript": "^5.8.3"
|
|
71
|
+
"@clack/prompts": "^0.11.0",
|
|
72
|
+
"clipboardy": "^4.0.0",
|
|
73
|
+
"simple-git": "^3.25.0",
|
|
74
|
+
"dotenv": "^17.2.0"
|
|
65
75
|
},
|
|
66
76
|
"devDependencies": {
|
|
67
|
-
"@
|
|
68
|
-
"@types/
|
|
69
|
-
"@
|
|
70
|
-
"@
|
|
71
|
-
"
|
|
72
|
-
"
|
|
73
|
-
"eslint": "^9.
|
|
74
|
-
"
|
|
75
|
-
"eslint-plugin-prettier": "^5.1.3",
|
|
76
|
-
"jest": "^29.7.0",
|
|
77
|
-
"prettier": "^3.2.5",
|
|
78
|
-
"rimraf": "^5.0.5",
|
|
79
|
-
"ts-jest": "^29.1.2"
|
|
77
|
+
"@types/bun": "latest",
|
|
78
|
+
"@types/node": "^24.0.13",
|
|
79
|
+
"@typescript-eslint/eslint-plugin": "^8.36.0",
|
|
80
|
+
"@typescript-eslint/parser": "^8.36.0",
|
|
81
|
+
"c8": "^10.1.3",
|
|
82
|
+
"chalk": "^5.3.0",
|
|
83
|
+
"eslint": "^9.30.1",
|
|
84
|
+
"prettier": "^3.6.2"
|
|
80
85
|
},
|
|
81
|
-
"
|
|
82
|
-
"
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
+
"peerDependencies": {
|
|
87
|
+
"typescript": "^5"
|
|
88
|
+
},
|
|
89
|
+
"preferGlobal": true,
|
|
90
|
+
"publishConfig": {
|
|
91
|
+
"access": "public"
|
|
92
|
+
}
|
|
86
93
|
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'fs';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import dotenv from 'dotenv';
|
|
5
|
+
|
|
6
|
+
// Carregar variáveis de ambiente
|
|
7
|
+
dotenv.config();
|
|
8
|
+
|
|
9
|
+
export interface Config {
|
|
10
|
+
openai: {
|
|
11
|
+
model: string;
|
|
12
|
+
maxTokens: number;
|
|
13
|
+
temperature: number;
|
|
14
|
+
apiKey?: string;
|
|
15
|
+
timeout: number;
|
|
16
|
+
retries: number;
|
|
17
|
+
};
|
|
18
|
+
language: string;
|
|
19
|
+
commitStyle: 'conventional' | 'simple' | 'detailed';
|
|
20
|
+
autoCommit: boolean;
|
|
21
|
+
splitCommits: boolean;
|
|
22
|
+
dryRun: boolean;
|
|
23
|
+
smartSplit: {
|
|
24
|
+
enabled: boolean;
|
|
25
|
+
minGroupSize: number;
|
|
26
|
+
maxGroups: number;
|
|
27
|
+
confidenceThreshold: number;
|
|
28
|
+
};
|
|
29
|
+
cache: {
|
|
30
|
+
enabled: boolean;
|
|
31
|
+
ttl: number; // Time to live em minutos
|
|
32
|
+
maxSize: number; // Número máximo de entradas
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const DEFAULT_CONFIG: Config = {
|
|
37
|
+
openai: {
|
|
38
|
+
model: 'gpt-4o',
|
|
39
|
+
maxTokens: 150,
|
|
40
|
+
temperature: 0.7,
|
|
41
|
+
timeout: 30000, // 30 segundos
|
|
42
|
+
retries: 2,
|
|
43
|
+
},
|
|
44
|
+
language: 'pt',
|
|
45
|
+
commitStyle: 'conventional',
|
|
46
|
+
autoCommit: false,
|
|
47
|
+
splitCommits: false,
|
|
48
|
+
dryRun: false,
|
|
49
|
+
smartSplit: {
|
|
50
|
+
enabled: true,
|
|
51
|
+
minGroupSize: 1,
|
|
52
|
+
maxGroups: 5,
|
|
53
|
+
confidenceThreshold: 0.7,
|
|
54
|
+
},
|
|
55
|
+
cache: {
|
|
56
|
+
enabled: true,
|
|
57
|
+
ttl: 60, // 1 hora
|
|
58
|
+
maxSize: 100,
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Adicionar interface para userConfig
|
|
63
|
+
interface UserConfig {
|
|
64
|
+
openai?: Partial<Config['openai']>;
|
|
65
|
+
language?: string;
|
|
66
|
+
commitStyle?: Config['commitStyle'];
|
|
67
|
+
autoCommit?: boolean;
|
|
68
|
+
splitCommits?: boolean;
|
|
69
|
+
dryRun?: boolean;
|
|
70
|
+
smartSplit?: Partial<Config['smartSplit']>;
|
|
71
|
+
cache?: Partial<Config['cache']>;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function loadConfig(configPath?: string): Config {
|
|
75
|
+
// Usar um caminho mais seguro para ambientes CI
|
|
76
|
+
let defaultConfigPath: string;
|
|
77
|
+
try {
|
|
78
|
+
defaultConfigPath = join(process.cwd(), '.commit-wizardrc');
|
|
79
|
+
} catch {
|
|
80
|
+
// Fallback para ambientes onde process.cwd() falha
|
|
81
|
+
defaultConfigPath = '/tmp/.commit-wizardrc';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const globalConfigPath = join(
|
|
85
|
+
process.env.HOME || process.env.USERPROFILE || '/tmp',
|
|
86
|
+
'.commit-wizardrc'
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
let config = { ...DEFAULT_CONFIG };
|
|
90
|
+
|
|
91
|
+
// Carregar configuração global primeiro
|
|
92
|
+
try {
|
|
93
|
+
if (existsSync(globalConfigPath)) {
|
|
94
|
+
const fileContent = readFileSync(globalConfigPath, 'utf-8');
|
|
95
|
+
const globalConfig = JSON.parse(fileContent);
|
|
96
|
+
config = mergeConfig(config, globalConfig);
|
|
97
|
+
}
|
|
98
|
+
} catch {
|
|
99
|
+
console.warn(`⚠️ Erro ao ler configuração global: Erro desconhecido`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Carregar configuração local (sobrescreve a global)
|
|
103
|
+
const actualConfigPath = configPath || defaultConfigPath;
|
|
104
|
+
try {
|
|
105
|
+
if (existsSync(actualConfigPath)) {
|
|
106
|
+
const fileContent = readFileSync(actualConfigPath, 'utf-8');
|
|
107
|
+
const userConfig = JSON.parse(fileContent);
|
|
108
|
+
config = mergeConfig(config, userConfig);
|
|
109
|
+
}
|
|
110
|
+
} catch {
|
|
111
|
+
console.warn(`⚠️ Erro ao ler .commit-wizardrc: Erro desconhecido`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Adicionar chave da OpenAI das variáveis de ambiente
|
|
115
|
+
config.openai.apiKey = process.env.OPENAI_API_KEY;
|
|
116
|
+
|
|
117
|
+
// Aplicar configurações de ambiente
|
|
118
|
+
if (process.env.COMMIT_WIZARD_DEBUG === 'true') {
|
|
119
|
+
// config.advanced.enableDebug = true; // Removed advanced options
|
|
120
|
+
// config.advanced.logLevel = 'debug'; // Removed advanced options
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (process.env.COMMIT_WIZARD_DRY_RUN === 'true') {
|
|
124
|
+
config.dryRun = true;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return config;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function mergeConfig(defaultConfig: Config, userConfig: UserConfig): Config {
|
|
131
|
+
return {
|
|
132
|
+
...defaultConfig,
|
|
133
|
+
...userConfig,
|
|
134
|
+
openai: {
|
|
135
|
+
...defaultConfig.openai,
|
|
136
|
+
...userConfig.openai,
|
|
137
|
+
},
|
|
138
|
+
smartSplit: {
|
|
139
|
+
...defaultConfig.smartSplit,
|
|
140
|
+
...userConfig.smartSplit,
|
|
141
|
+
},
|
|
142
|
+
cache: {
|
|
143
|
+
...defaultConfig.cache,
|
|
144
|
+
...userConfig.cache,
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function validateConfig(config: Config): string[] {
|
|
150
|
+
const errors: string[] = [];
|
|
151
|
+
|
|
152
|
+
// Validação básica
|
|
153
|
+
if (!config.openai.apiKey) {
|
|
154
|
+
errors.push('OPENAI_API_KEY não encontrada nas variáveis de ambiente');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (config.openai.maxTokens < 10 || config.openai.maxTokens > 4000) {
|
|
158
|
+
errors.push('maxTokens deve estar entre 10 e 4000');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (config.openai.temperature < 0 || config.openai.temperature > 2) {
|
|
162
|
+
errors.push('temperature deve estar entre 0 e 2');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (
|
|
166
|
+
!['pt', 'en', 'es', 'fr', 'de', 'it', 'ja', 'ko', 'zh'].includes(
|
|
167
|
+
config.language
|
|
168
|
+
)
|
|
169
|
+
) {
|
|
170
|
+
errors.push(
|
|
171
|
+
'language deve ser um idioma suportado (pt, en, es, fr, de, it, ja, ko, zh)'
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (!['conventional', 'simple', 'detailed'].includes(config.commitStyle)) {
|
|
176
|
+
errors.push('commitStyle deve ser conventional, simple ou detailed');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Validação Smart Split
|
|
180
|
+
if (config.smartSplit.minGroupSize < 1) {
|
|
181
|
+
errors.push('smartSplit.minGroupSize deve ser pelo menos 1');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (config.smartSplit.maxGroups < 1 || config.smartSplit.maxGroups > 10) {
|
|
185
|
+
errors.push('smartSplit.maxGroups deve estar entre 1 e 10');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (
|
|
189
|
+
config.smartSplit.confidenceThreshold < 0 ||
|
|
190
|
+
config.smartSplit.confidenceThreshold > 1
|
|
191
|
+
) {
|
|
192
|
+
errors.push('smartSplit.confidenceThreshold deve estar entre 0 e 1');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Validação Cache
|
|
196
|
+
if (config.cache.ttl < 1) {
|
|
197
|
+
errors.push('cache.ttl deve ser pelo menos 1 minuto');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (config.cache.maxSize < 1) {
|
|
201
|
+
errors.push('cache.maxSize deve ser pelo menos 1');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return errors;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Cria um arquivo de configuração exemplo
|
|
209
|
+
*/
|
|
210
|
+
export function createExampleConfig(path: string = '.commit-wizardrc'): void {
|
|
211
|
+
const exampleConfig = {
|
|
212
|
+
language: 'pt',
|
|
213
|
+
commitStyle: 'conventional',
|
|
214
|
+
autoCommit: false,
|
|
215
|
+
splitCommits: true,
|
|
216
|
+
openai: {
|
|
217
|
+
model: 'gpt-4o',
|
|
218
|
+
maxTokens: 200,
|
|
219
|
+
temperature: 0.7,
|
|
220
|
+
timeout: 30000,
|
|
221
|
+
retries: 2,
|
|
222
|
+
},
|
|
223
|
+
smartSplit: {
|
|
224
|
+
enabled: true,
|
|
225
|
+
minGroupSize: 1,
|
|
226
|
+
maxGroups: 5,
|
|
227
|
+
confidenceThreshold: 0.7,
|
|
228
|
+
},
|
|
229
|
+
cache: {
|
|
230
|
+
enabled: true,
|
|
231
|
+
ttl: 60,
|
|
232
|
+
maxSize: 100,
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
fs.writeFileSync(path, JSON.stringify(exampleConfig, null, 2));
|
|
237
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import type { Config } from '../config/index.ts';
|
|
2
|
+
import type { FileGroup } from './smart-split.ts';
|
|
3
|
+
import crypto from 'crypto';
|
|
4
|
+
|
|
5
|
+
export interface CacheEntry {
|
|
6
|
+
groups: FileGroup[];
|
|
7
|
+
timestamp: number;
|
|
8
|
+
hash: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface CacheResult {
|
|
12
|
+
hit: boolean;
|
|
13
|
+
groups?: FileGroup[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class AnalysisCache {
|
|
17
|
+
private cache: Map<string, CacheEntry> = new Map();
|
|
18
|
+
private config: Config;
|
|
19
|
+
|
|
20
|
+
constructor(config: Config) {
|
|
21
|
+
this.config = config;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Gera hash do contexto para identificar análises similares
|
|
26
|
+
*/
|
|
27
|
+
private generateHash(files: string[], overallDiff: string): string {
|
|
28
|
+
const context = {
|
|
29
|
+
files: files.sort(), // Ordenar para consistência
|
|
30
|
+
diff: overallDiff.substring(0, 1000), // Limitar tamanho do diff
|
|
31
|
+
model: this.config.openai.model,
|
|
32
|
+
temperature: this.config.openai.temperature,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return crypto
|
|
36
|
+
.createHash('md5')
|
|
37
|
+
.update(JSON.stringify(context))
|
|
38
|
+
.digest('hex');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Verifica se há cache válido para o contexto
|
|
43
|
+
*/
|
|
44
|
+
get(files: string[], overallDiff: string): CacheResult {
|
|
45
|
+
if (!this.config.cache.enabled) {
|
|
46
|
+
return { hit: false };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const hash = this.generateHash(files, overallDiff);
|
|
50
|
+
const entry = this.cache.get(hash);
|
|
51
|
+
|
|
52
|
+
if (!entry) {
|
|
53
|
+
return { hit: false };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Verificar se o cache expirou
|
|
57
|
+
const now = Date.now();
|
|
58
|
+
const ttlMs = this.config.cache.ttl * 60 * 1000; // Converter minutos para ms
|
|
59
|
+
|
|
60
|
+
if (now - entry.timestamp > ttlMs) {
|
|
61
|
+
this.cache.delete(hash);
|
|
62
|
+
return { hit: false };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return { hit: true, groups: entry.groups };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Armazena resultado no cache
|
|
70
|
+
*/
|
|
71
|
+
set(files: string[], overallDiff: string, groups: FileGroup[]): void {
|
|
72
|
+
if (!this.config.cache.enabled) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Limpar cache se exceder tamanho máximo
|
|
77
|
+
if (this.cache.size >= this.config.cache.maxSize) {
|
|
78
|
+
this.cleanup();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Se ainda exceder após cleanup, não adicionar
|
|
82
|
+
if (this.cache.size >= this.config.cache.maxSize) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const hash = this.generateHash(files, overallDiff);
|
|
87
|
+
const entry: CacheEntry = {
|
|
88
|
+
groups,
|
|
89
|
+
timestamp: Date.now(),
|
|
90
|
+
hash,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
this.cache.set(hash, entry);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Limpa cache expirado e reduz tamanho se necessário
|
|
98
|
+
*/
|
|
99
|
+
private cleanup(): void {
|
|
100
|
+
const now = Date.now();
|
|
101
|
+
const ttlMs = this.config.cache.ttl * 60 * 1000;
|
|
102
|
+
|
|
103
|
+
// Remover entradas expiradas
|
|
104
|
+
for (const [hash, entry] of this.cache.entries()) {
|
|
105
|
+
if (now - entry.timestamp > ttlMs) {
|
|
106
|
+
this.cache.delete(hash);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Se ainda exceder tamanho máximo, remover entradas mais antigas
|
|
111
|
+
if (this.cache.size >= this.config.cache.maxSize) {
|
|
112
|
+
const entries = Array.from(this.cache.entries()).sort(
|
|
113
|
+
(a, b) => a[1].timestamp - b[1].timestamp
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
// Remover 50% das entradas mais antigas para garantir espaço
|
|
117
|
+
const toRemove = entries.slice(
|
|
118
|
+
0,
|
|
119
|
+
Math.ceil(this.config.cache.maxSize * 0.5)
|
|
120
|
+
);
|
|
121
|
+
for (const [hash] of toRemove) {
|
|
122
|
+
this.cache.delete(hash);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Limpa todo o cache
|
|
129
|
+
*/
|
|
130
|
+
clear(): void {
|
|
131
|
+
this.cache.clear();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Retorna estatísticas do cache
|
|
136
|
+
*/
|
|
137
|
+
getStats(): { size: number; maxSize: number; enabled: boolean } {
|
|
138
|
+
return {
|
|
139
|
+
size: this.cache.size,
|
|
140
|
+
maxSize: this.config.cache.maxSize,
|
|
141
|
+
enabled: this.config.cache.enabled,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Instância global do cache
|
|
147
|
+
let globalCache: AnalysisCache | null = null;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Inicializa o cache global
|
|
151
|
+
*/
|
|
152
|
+
export function initializeCache(config: Config): void {
|
|
153
|
+
globalCache = new AnalysisCache(config);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Obtém o cache global
|
|
158
|
+
*/
|
|
159
|
+
export function getCache(): AnalysisCache | null {
|
|
160
|
+
return globalCache;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Verifica se há cache válido para o contexto
|
|
165
|
+
*/
|
|
166
|
+
export function getCachedAnalysis(
|
|
167
|
+
files: string[],
|
|
168
|
+
overallDiff: string
|
|
169
|
+
): CacheResult {
|
|
170
|
+
const cache = getCache();
|
|
171
|
+
return cache ? cache.get(files, overallDiff) : { hit: false };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Armazena resultado no cache
|
|
176
|
+
*/
|
|
177
|
+
export function setCachedAnalysis(
|
|
178
|
+
files: string[],
|
|
179
|
+
overallDiff: string,
|
|
180
|
+
groups: FileGroup[]
|
|
181
|
+
): void {
|
|
182
|
+
const cache = getCache();
|
|
183
|
+
if (cache) {
|
|
184
|
+
cache.set(files, overallDiff, groups);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Retorna estatísticas do cache
|
|
190
|
+
*/
|
|
191
|
+
export function getCacheStats(): {
|
|
192
|
+
size: number;
|
|
193
|
+
maxSize: number;
|
|
194
|
+
enabled: boolean;
|
|
195
|
+
} | null {
|
|
196
|
+
const cache = getCache();
|
|
197
|
+
return cache ? cache.getStats() : null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Limpa o cache global
|
|
202
|
+
*/
|
|
203
|
+
export function clearCache(): void {
|
|
204
|
+
const cache = getCache();
|
|
205
|
+
if (cache) {
|
|
206
|
+
cache.clear();
|
|
207
|
+
}
|
|
208
|
+
// Resetar a instância global
|
|
209
|
+
globalCache = null;
|
|
210
|
+
}
|