korvet-intellijs 1.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/README.md +3 -0
- package/TODO.md +21 -0
- package/bin/index.js +379 -0
- package/bin/kijs.js +373 -0
- package/korvet.config.js +19 -0
- package/package.json +33 -0
- package/src/analyzer.js +8 -0
- package/src/complexity.js +61 -0
- package/src/config.js +89 -0
- package/src/debugger.js +218 -0
- package/src/engine.js +217 -0
- package/src/fixer.js +57 -0
- package/src/memory.js +210 -0
- package/src/optimizer.js +187 -0
- package/src/parser.js +44 -0
- package/src/reporter.js +145 -0
- package/src/rules/async.js +132 -0
- package/src/rules/errors.js +153 -0
- package/src/rules/memory.js +208 -0
- package/src/rules/perf.js +81 -0
- package/src/rules/unused.js +63 -0
- package/src/utils.js +83 -0
- package/test/sample.js +21 -0
- package/test-fixtures/async-bugs.js +43 -0
- package/test-fixtures/clean-code.js +35 -0
- package/test-fixtures/memory-leaks.js +33 -0
- package/test-fixtures/performance.js +60 -0
package/README.md
ADDED
package/TODO.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# TODO — Korvet IntelliJS
|
|
2
|
+
|
|
3
|
+
## Step 1 — Repo alignment (done)
|
|
4
|
+
- Confirm repo is ESM (`type: module`) and fixes are applied via `issue.fix` + `src/fixer.js` called from `bin/kijs.js`.
|
|
5
|
+
- Confirm `src/engine.js` currently runs offline AST rules only.
|
|
6
|
+
|
|
7
|
+
## Step 2 — Port Step 9 AST self-heal into ESM pipeline (in progress)
|
|
8
|
+
- Update `src/engine.js` to generate additional **fixable issues** from AST:
|
|
9
|
+
- `var` -> `const` on the declaration line
|
|
10
|
+
- `==` -> `===` and `!=` -> `!==` on the binary expression line
|
|
11
|
+
- Remove `console.log/warn/info/debug` lines via `fix: ''`
|
|
12
|
+
- (Optional) bare `.then()` / `.catch()` heuristic (skip if risky)
|
|
13
|
+
- Ensure `fix` strings are full-line replacements (or empty string for removal) compatible with existing `src/fixer.js`.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## Step 3 — Verify fixes
|
|
17
|
+
- Run:
|
|
18
|
+
- `npm test`
|
|
19
|
+
- `node bin/kijs.js debug --fix ./test-fixtures/buggy-file.js`
|
|
20
|
+
- `node bin/kijs.js debug ./test-fixtures/buggy-file.js`
|
|
21
|
+
|
package/bin/index.js
ADDED
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import fs from 'fs-extra';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { glob } from 'glob';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
|
|
9
|
+
import { loadConfig } from '../src/config.js';
|
|
10
|
+
import { analyzeFile } from '../src/analyzer.js';
|
|
11
|
+
import { debugCode } from '../src/debugger.js';
|
|
12
|
+
import { optimizeCode, applyAutoFixes } from '../src/optimizer.js';
|
|
13
|
+
import { analyzeMemory } from '../src/memory.js';
|
|
14
|
+
import * as reporter from '../src/reporter.js';
|
|
15
|
+
|
|
16
|
+
const program = new Command();
|
|
17
|
+
|
|
18
|
+
program
|
|
19
|
+
.name('kijs')
|
|
20
|
+
.description('Korvet IntelliJS: AI-powered and AST-driven code intelligence CLI')
|
|
21
|
+
.version('1.0.0');
|
|
22
|
+
|
|
23
|
+
// Shared config option
|
|
24
|
+
program.option('-c, --config <path>', 'custom path to korvet.config.js configuration file');
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Common helper to gather files to process based on configurations and pattern overrides.
|
|
28
|
+
*/
|
|
29
|
+
async function resolveFiles(config, overridePath = null) {
|
|
30
|
+
if (overridePath) {
|
|
31
|
+
const resolved = path.resolve(process.cwd(), overridePath);
|
|
32
|
+
if (await fs.pathExists(resolved)) {
|
|
33
|
+
return [path.normalize(resolved)];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// If it is a dynamic path pattern (glob) passed as an override
|
|
37
|
+
try {
|
|
38
|
+
const matched = await glob(overridePath, {
|
|
39
|
+
absolute: true,
|
|
40
|
+
nodir: true
|
|
41
|
+
});
|
|
42
|
+
return matched.map(f => path.normalize(f));
|
|
43
|
+
} catch (err) {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const include = config.include;
|
|
49
|
+
const exclude = config.exclude;
|
|
50
|
+
|
|
51
|
+
const filesSet = new Set();
|
|
52
|
+
for (const pattern of include) {
|
|
53
|
+
try {
|
|
54
|
+
const matched = await glob(pattern, {
|
|
55
|
+
ignore: exclude,
|
|
56
|
+
absolute: true,
|
|
57
|
+
nodir: true
|
|
58
|
+
});
|
|
59
|
+
matched.forEach(f => filesSet.add(path.normalize(f)));
|
|
60
|
+
} catch (err) {
|
|
61
|
+
console.error(chalk.red(`[Error] Failed resolving file pattern "${pattern}": ${err.message}`));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return Array.from(filesSet);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* CLI COMMAND: init
|
|
69
|
+
* Scaffolds a default configuration file if not already present.
|
|
70
|
+
*/
|
|
71
|
+
program
|
|
72
|
+
.command('init')
|
|
73
|
+
.description('Initialize default korvet.config.js template in the current directory')
|
|
74
|
+
.action(async () => {
|
|
75
|
+
reporter.printHeader();
|
|
76
|
+
const targetPath = path.resolve(process.cwd(), 'korvet.config.js');
|
|
77
|
+
|
|
78
|
+
if (await fs.pathExists(targetPath)) {
|
|
79
|
+
console.log(chalk.yellow(` ⚠ Configuration file already exists at ${targetPath}`));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const template = `export default {
|
|
84
|
+
// Gemini API key. If empty, falls back to GEMINI_API_KEY environment variable.
|
|
85
|
+
apiKey: "",
|
|
86
|
+
|
|
87
|
+
// File paths to include in analysis
|
|
88
|
+
include: ["src/**/*.js", "*.js"],
|
|
89
|
+
|
|
90
|
+
// File paths to exclude from analysis
|
|
91
|
+
exclude: [
|
|
92
|
+
"node_modules/**",
|
|
93
|
+
"dist/**",
|
|
94
|
+
"build/**",
|
|
95
|
+
"test-fixtures/**",
|
|
96
|
+
"coverage/**"
|
|
97
|
+
],
|
|
98
|
+
|
|
99
|
+
// Control module execution
|
|
100
|
+
modules: {
|
|
101
|
+
analyzer: true, // Gemini AI Engine
|
|
102
|
+
debugger: true, // Predictive Control-Flow Debugger
|
|
103
|
+
optimizer: true, // AST Performance Optimizer
|
|
104
|
+
memory: true // AST Memory Leak Check
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
// Minimum reporting severity: "info", "warning", "error"
|
|
108
|
+
severity: "info"
|
|
109
|
+
};
|
|
110
|
+
`;
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
await fs.writeFile(targetPath, template, 'utf-8');
|
|
114
|
+
console.log(chalk.green.bold(` ✔ Successfully created korvet.config.js default template!`));
|
|
115
|
+
console.log(chalk.gray(` Get started by setting your Gemini API key inside it or as an environment variable.`));
|
|
116
|
+
} catch (err) {
|
|
117
|
+
console.error(chalk.red(` ✘ Failed to create configuration template: ${err.message}`));
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* CLI COMMAND: analyze
|
|
123
|
+
* Runs AI-powered Gemini analysis.
|
|
124
|
+
*/
|
|
125
|
+
program
|
|
126
|
+
.command('analyze [path]')
|
|
127
|
+
.description('Execute AI-powered semantic analysis using the Gemini Engine')
|
|
128
|
+
.action(async (overridePath) => {
|
|
129
|
+
reporter.printHeader();
|
|
130
|
+
const options = program.opts();
|
|
131
|
+
const config = await loadConfig(options.config);
|
|
132
|
+
|
|
133
|
+
if (!config.apiKey) {
|
|
134
|
+
console.log(chalk.red.bold(` ✘ API Key Missing! Cannot run AI-powered analysis.`));
|
|
135
|
+
console.log(chalk.gray(` Define your "apiKey" in korvet.config.js or set a GEMINI_API_KEY env variable.`));
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const files = await resolveFiles(config, overridePath);
|
|
140
|
+
if (files.length === 0) {
|
|
141
|
+
console.log(chalk.yellow(` ⚠ No matching files found to analyze.`));
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
console.log(chalk.cyan(` Initiating Gemini Engine for ${files.length} file(s)...`));
|
|
146
|
+
|
|
147
|
+
let totalIssues = [];
|
|
148
|
+
for (const file of files) {
|
|
149
|
+
const relative = path.relative(process.cwd(), file);
|
|
150
|
+
console.log(chalk.gray(` Scanning ${relative}...`));
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
const content = await fs.readFile(file, 'utf-8');
|
|
154
|
+
const fileIssues = await analyzeFile(relative, content, config.apiKey);
|
|
155
|
+
|
|
156
|
+
// Filter by minimum configured severity
|
|
157
|
+
const filtered = filterIssues(fileIssues, config.severity);
|
|
158
|
+
totalIssues.push(...filtered);
|
|
159
|
+
|
|
160
|
+
reporter.reportFileIssues(relative, content, filtered);
|
|
161
|
+
} catch (err) {
|
|
162
|
+
console.error(chalk.red(` ✘ Failed to analyze file ${relative}: ${err.message}`));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
reporter.printSummaryDashboard(files.length, totalIssues);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* CLI COMMAND: debug
|
|
171
|
+
* Runs AST-based control flow predictive debugger.
|
|
172
|
+
*/
|
|
173
|
+
program
|
|
174
|
+
.command('debug [path]')
|
|
175
|
+
.description('Perform predictive control-flow and logical bug diagnostics via AST')
|
|
176
|
+
.action(async (overridePath) => {
|
|
177
|
+
reporter.printHeader();
|
|
178
|
+
const options = program.opts();
|
|
179
|
+
const config = await loadConfig(options.config);
|
|
180
|
+
|
|
181
|
+
const files = await resolveFiles(config, overridePath);
|
|
182
|
+
if (files.length === 0) {
|
|
183
|
+
console.log(chalk.yellow(` ⚠ No matching files found to check.`));
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
let totalIssues = [];
|
|
188
|
+
for (const file of files) {
|
|
189
|
+
const relative = path.relative(process.cwd(), file);
|
|
190
|
+
try {
|
|
191
|
+
const content = await fs.readFile(file, 'utf-8');
|
|
192
|
+
const fileIssues = debugCode(relative, content);
|
|
193
|
+
const filtered = filterIssues(fileIssues, config.severity);
|
|
194
|
+
totalIssues.push(...filtered);
|
|
195
|
+
reporter.reportFileIssues(relative, content, filtered);
|
|
196
|
+
} catch (err) {
|
|
197
|
+
console.error(chalk.red(` ✘ Failed to debug file ${relative}: ${err.message}`));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
reporter.printSummaryDashboard(files.length, totalIssues);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* CLI COMMAND: optimize
|
|
206
|
+
* Runs performance analyzer with optional --fix flag.
|
|
207
|
+
*/
|
|
208
|
+
program
|
|
209
|
+
.command('optimize [path]')
|
|
210
|
+
.description('Identify and fix performance bottlenecks, redundant declarations, and unused code')
|
|
211
|
+
.option('--fix', 'automatically apply clean optimization and variables removals')
|
|
212
|
+
.action(async (overridePath, cmdOpts) => {
|
|
213
|
+
reporter.printHeader();
|
|
214
|
+
const options = program.opts();
|
|
215
|
+
const config = await loadConfig(options.config);
|
|
216
|
+
|
|
217
|
+
const files = await resolveFiles(config, overridePath);
|
|
218
|
+
if (files.length === 0) {
|
|
219
|
+
console.log(chalk.yellow(` ⚠ No matching JS files matched optimization scopes.`));
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
let totalIssues = [];
|
|
224
|
+
for (const file of files) {
|
|
225
|
+
const relative = path.relative(process.cwd(), file);
|
|
226
|
+
try {
|
|
227
|
+
let content = await fs.readFile(file, 'utf-8');
|
|
228
|
+
const fileIssues = optimizeCode(relative, content);
|
|
229
|
+
const filtered = filterIssues(fileIssues, config.severity);
|
|
230
|
+
|
|
231
|
+
totalIssues.push(...filtered);
|
|
232
|
+
reporter.reportFileIssues(relative, content, filtered);
|
|
233
|
+
|
|
234
|
+
if (cmdOpts.fix && filtered.length > 0) {
|
|
235
|
+
console.log(chalk.green(` Applying auto-fixes for ${relative}...`));
|
|
236
|
+
await applyAutoFixes(file, content, filtered);
|
|
237
|
+
}
|
|
238
|
+
} catch (err) {
|
|
239
|
+
console.error(chalk.red(` ✘ Optimization run failed on ${relative}: ${err.message}`));
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
reporter.printSummaryDashboard(files.length, totalIssues);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* CLI COMMAND: leak-check
|
|
248
|
+
* Runs memory analyzer.
|
|
249
|
+
*/
|
|
250
|
+
program
|
|
251
|
+
.command('leak-check [path]')
|
|
252
|
+
.description('Scan source code to identify memory leaks, uncleaned intervals, and dynamic globals')
|
|
253
|
+
.action(async (overridePath) => {
|
|
254
|
+
reporter.printHeader();
|
|
255
|
+
const options = program.opts();
|
|
256
|
+
const config = await loadConfig(options.config);
|
|
257
|
+
|
|
258
|
+
const files = await resolveFiles(config, overridePath);
|
|
259
|
+
if (files.length === 0) {
|
|
260
|
+
console.log(chalk.yellow(` ⚠ No matching files found to evaluate leaks.`));
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
let totalIssues = [];
|
|
265
|
+
for (const file of files) {
|
|
266
|
+
const relative = path.relative(process.cwd(), file);
|
|
267
|
+
try {
|
|
268
|
+
const content = await fs.readFile(file, 'utf-8');
|
|
269
|
+
const fileIssues = analyzeMemory(relative, content);
|
|
270
|
+
const filtered = filterIssues(fileIssues, config.severity);
|
|
271
|
+
totalIssues.push(...filtered);
|
|
272
|
+
reporter.reportFileIssues(relative, content, filtered);
|
|
273
|
+
} catch (err) {
|
|
274
|
+
console.error(chalk.red(` ✘ Leak-check execution failed on ${relative}: ${err.message}`));
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
reporter.printSummaryDashboard(files.length, totalIssues);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* CLI COMMAND: scan
|
|
283
|
+
* Runs ALL modules sequentially.
|
|
284
|
+
*/
|
|
285
|
+
program
|
|
286
|
+
.command('scan [path]')
|
|
287
|
+
.description('Run full analysis suite (AI + AST debugging, optimizations, and leak audits)')
|
|
288
|
+
.option('--fix', 'auto-fix performance issues, unused codes, and clearable patterns')
|
|
289
|
+
.action(async (overridePath, cmdOpts) => {
|
|
290
|
+
reporter.printHeader();
|
|
291
|
+
const options = program.opts();
|
|
292
|
+
const config = await loadConfig(options.config);
|
|
293
|
+
|
|
294
|
+
const files = await resolveFiles(config, overridePath);
|
|
295
|
+
if (files.length === 0) {
|
|
296
|
+
console.log(chalk.yellow(` ⚠ No matching files found to scan.`));
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Friendly API Key check to prevent analyzer failures while letting other modules proceed
|
|
301
|
+
let runAnalyzer = config.modules.analyzer;
|
|
302
|
+
if (runAnalyzer && !config.apiKey) {
|
|
303
|
+
console.log(chalk.yellow(` ⚠ [Config Warning] Gemini API key not found. Skipping AI-powered semantic analysis.\n` +
|
|
304
|
+
` Set GEMINI_API_KEY inside korvet.config.js or environment to enable AI rules.`));
|
|
305
|
+
runAnalyzer = false;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
let totalIssues = [];
|
|
309
|
+
for (const file of files) {
|
|
310
|
+
const relative = path.relative(process.cwd(), file);
|
|
311
|
+
console.log(chalk.cyan(`\n Scanning ${relative}...`));
|
|
312
|
+
|
|
313
|
+
try {
|
|
314
|
+
let content = await fs.readFile(file, 'utf-8');
|
|
315
|
+
let fileIssues = [];
|
|
316
|
+
|
|
317
|
+
// 1. Gemini Engine
|
|
318
|
+
if (runAnalyzer) {
|
|
319
|
+
try {
|
|
320
|
+
console.log(chalk.gray(` - Querying Gemini AI Analyzer...`));
|
|
321
|
+
const aiIssues = await analyzeFile(relative, content, config.apiKey);
|
|
322
|
+
fileIssues.push(...aiIssues);
|
|
323
|
+
} catch (aiErr) {
|
|
324
|
+
console.warn(chalk.yellow(` ⚠ AI Analyzer failed on this file: ${aiErr.message}`));
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// 2. Control Flow Debugger
|
|
329
|
+
if (config.modules.debugger) {
|
|
330
|
+
const debugIssues = debugCode(relative, content);
|
|
331
|
+
fileIssues.push(...debugIssues);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// 3. Performance Optimizer
|
|
335
|
+
if (config.modules.optimizer) {
|
|
336
|
+
const optIssues = optimizeCode(relative, content);
|
|
337
|
+
fileIssues.push(...optIssues);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// 4. Memory Leak Auditor
|
|
341
|
+
if (config.modules.memory) {
|
|
342
|
+
const memIssues = analyzeMemory(relative, content);
|
|
343
|
+
fileIssues.push(...memIssues);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Filter and display
|
|
347
|
+
const filtered = filterIssues(fileIssues, config.severity);
|
|
348
|
+
totalIssues.push(...filtered);
|
|
349
|
+
reporter.reportFileIssues(relative, content, filtered);
|
|
350
|
+
|
|
351
|
+
// Apply auto-fixes if requested and applicable
|
|
352
|
+
if (cmdOpts.fix && filtered.length > 0) {
|
|
353
|
+
console.log(chalk.green(` Applying auto-fixes for ${relative}...`));
|
|
354
|
+
const fixedContent = await applyAutoFixes(file, content, filtered);
|
|
355
|
+
content = fixedContent; // Refresh contents
|
|
356
|
+
}
|
|
357
|
+
} catch (err) {
|
|
358
|
+
console.error(chalk.red(` ✘ Processing failed on ${relative}: ${err.message}`));
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
reporter.printSummaryDashboard(files.length, totalIssues);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Filters issues array based on the configured minimum severity.
|
|
367
|
+
*/
|
|
368
|
+
function filterIssues(issues, minSeverity = 'info') {
|
|
369
|
+
const levels = { info: 1, warning: 2, error: 3 };
|
|
370
|
+
const minVal = levels[minSeverity.toLowerCase()] || 1;
|
|
371
|
+
|
|
372
|
+
return issues.filter(issue => {
|
|
373
|
+
const issueVal = levels[(issue.severity || 'info').toLowerCase()] || 1;
|
|
374
|
+
return issueVal >= minVal;
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Start processing CLI instructions
|
|
379
|
+
program.parse(process.argv);
|