vigthoria-cli 1.6.31 → 1.6.33
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/dist/commands/edit.d.ts +4 -0
- package/dist/commands/edit.js +54 -4
- package/dist/commands/generate.js +2 -1
- package/dist/utils/api.d.ts +5 -0
- package/dist/utils/api.js +124 -30
- package/package.json +1 -1
package/dist/commands/edit.d.ts
CHANGED
|
@@ -27,6 +27,10 @@ export declare class EditCommand {
|
|
|
27
27
|
* Applies iteratively until no further duplicates are found.
|
|
28
28
|
*/
|
|
29
29
|
private deduplicateCode;
|
|
30
|
+
/**
|
|
31
|
+
* Append missing closing delimiters if dedup left brackets unbalanced.
|
|
32
|
+
*/
|
|
33
|
+
private repairBrackets;
|
|
30
34
|
private deduplicateOnce;
|
|
31
35
|
private showDiffAndConfirm;
|
|
32
36
|
private showFullDiff;
|
package/dist/commands/edit.js
CHANGED
|
@@ -231,8 +231,55 @@ Return the complete modified file content:`,
|
|
|
231
231
|
break;
|
|
232
232
|
result = deduped;
|
|
233
233
|
}
|
|
234
|
+
// Safety net: if dedup accidentally removed structural delimiters,
|
|
235
|
+
// re-balance braces/parens/brackets by appending missing closers.
|
|
236
|
+
if (result !== code) {
|
|
237
|
+
result = this.repairBrackets(result);
|
|
238
|
+
}
|
|
234
239
|
return result;
|
|
235
240
|
}
|
|
241
|
+
/**
|
|
242
|
+
* Append missing closing delimiters if dedup left brackets unbalanced.
|
|
243
|
+
*/
|
|
244
|
+
repairBrackets(code) {
|
|
245
|
+
const stack = [];
|
|
246
|
+
const closer = { '{': '}', '(': ')', '[': ']' };
|
|
247
|
+
const openers = new Set(Object.keys(closer));
|
|
248
|
+
const closers = new Set(Object.values(closer));
|
|
249
|
+
let inString = null;
|
|
250
|
+
let escaped = false;
|
|
251
|
+
for (const ch of code) {
|
|
252
|
+
if (escaped) {
|
|
253
|
+
escaped = false;
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (ch === '\\') {
|
|
257
|
+
escaped = true;
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
if (inString) {
|
|
261
|
+
if (ch === inString)
|
|
262
|
+
inString = null;
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
if (ch === '"' || ch === "'" || ch === '`') {
|
|
266
|
+
inString = ch;
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
if (openers.has(ch))
|
|
270
|
+
stack.push(ch);
|
|
271
|
+
else if (closers.has(ch)) {
|
|
272
|
+
if (stack.length > 0 && closer[stack[stack.length - 1]] === ch) {
|
|
273
|
+
stack.pop();
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (stack.length === 0)
|
|
278
|
+
return code;
|
|
279
|
+
// Append missing closers in reverse order (innermost first)
|
|
280
|
+
const suffix = stack.reverse().map(o => closer[o]).join('\n');
|
|
281
|
+
return code + '\n' + suffix;
|
|
282
|
+
}
|
|
236
283
|
deduplicateOnce(code) {
|
|
237
284
|
const lines = code.split('\n');
|
|
238
285
|
const len = lines.length;
|
|
@@ -252,7 +299,10 @@ Return the complete modified file content:`,
|
|
|
252
299
|
i++;
|
|
253
300
|
continue;
|
|
254
301
|
}
|
|
255
|
-
// Count the run length of identical consecutive lines
|
|
302
|
+
// Count the run length of identical consecutive lines.
|
|
303
|
+
// For runs of 3+, trimmed comparison is fine (clear stutter).
|
|
304
|
+
// For trailing pairs, require EXACT match (including indentation)
|
|
305
|
+
// so that e.g. " }" and "}" (different blocks) are not collapsed.
|
|
256
306
|
let runEnd = i + 1;
|
|
257
307
|
while (runEnd < lines.length && lines[runEnd].trim() === trimmed) {
|
|
258
308
|
runEnd++;
|
|
@@ -263,11 +313,11 @@ Return the complete modified file content:`,
|
|
|
263
313
|
const isAtEffectiveEnd = runEnd === lines.length
|
|
264
314
|
|| lines.slice(runEnd).every(l => l.trim() === '');
|
|
265
315
|
if (runLen >= 3) {
|
|
266
|
-
// 3+ identical lines is almost certainly stutter — keep one
|
|
316
|
+
// 3+ identical (trimmed) lines is almost certainly stutter — keep one
|
|
267
317
|
deduped.push(lines[i]);
|
|
268
318
|
}
|
|
269
|
-
else if (runLen === 2 && isAtEffectiveEnd) {
|
|
270
|
-
// Exactly 2 identical lines
|
|
319
|
+
else if (runLen === 2 && isAtEffectiveEnd && lines[i] === lines[i + 1]) {
|
|
320
|
+
// Exactly 2 identical lines (EXACT, including indentation) at end
|
|
271
321
|
deduped.push(lines[i]);
|
|
272
322
|
}
|
|
273
323
|
else {
|
|
@@ -105,9 +105,10 @@ class GenerateCommand {
|
|
|
105
105
|
if (options.output) {
|
|
106
106
|
await this.saveToFile(options.output, code);
|
|
107
107
|
}
|
|
108
|
-
else {
|
|
108
|
+
else if (process.stdout.isTTY) {
|
|
109
109
|
await this.promptForAction(code, options.language);
|
|
110
110
|
}
|
|
111
|
+
// Non-TTY: code was already printed above — skip interactive menu
|
|
111
112
|
}
|
|
112
113
|
catch (error) {
|
|
113
114
|
spinner.stop();
|
package/dist/utils/api.d.ts
CHANGED
|
@@ -351,6 +351,11 @@ export declare class APIClient {
|
|
|
351
351
|
* Ensure code has balanced curly braces by appending missing closing braces.
|
|
352
352
|
*/
|
|
353
353
|
private ensureBalancedBraces;
|
|
354
|
+
/**
|
|
355
|
+
* Quick JS/TS syntax validation using Node's built-in parser.
|
|
356
|
+
* Returns true if the code parses without errors.
|
|
357
|
+
*/
|
|
358
|
+
private validateJsSyntax;
|
|
354
359
|
/**
|
|
355
360
|
* Extract the first complete function/class from code.
|
|
356
361
|
* Used as last-resort when the model keeps over-producing.
|
package/dist/utils/api.js
CHANGED
|
@@ -3454,21 +3454,76 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3454
3454
|
* Ensure code has balanced curly braces by appending missing closing braces.
|
|
3455
3455
|
*/
|
|
3456
3456
|
ensureBalancedBraces(code) {
|
|
3457
|
-
|
|
3458
|
-
|
|
3457
|
+
// Count braces/parens/brackets outside strings and comments
|
|
3458
|
+
let braces = 0, parens = 0, brackets = 0;
|
|
3459
|
+
let inStr = null;
|
|
3460
|
+
let inLine = false, inBlock = false;
|
|
3461
|
+
for (let i = 0; i < code.length; i++) {
|
|
3462
|
+
const ch = code[i], nx = code[i + 1] || '';
|
|
3463
|
+
if (inLine) {
|
|
3464
|
+
if (ch === '\n')
|
|
3465
|
+
inLine = false;
|
|
3466
|
+
continue;
|
|
3467
|
+
}
|
|
3468
|
+
if (inBlock) {
|
|
3469
|
+
if (ch === '*' && nx === '/') {
|
|
3470
|
+
inBlock = false;
|
|
3471
|
+
i++;
|
|
3472
|
+
}
|
|
3473
|
+
continue;
|
|
3474
|
+
}
|
|
3475
|
+
if (inStr) {
|
|
3476
|
+
if (ch === inStr && code[i - 1] !== '\\')
|
|
3477
|
+
inStr = null;
|
|
3478
|
+
continue;
|
|
3479
|
+
}
|
|
3480
|
+
if (ch === '/' && nx === '/') {
|
|
3481
|
+
inLine = true;
|
|
3482
|
+
continue;
|
|
3483
|
+
}
|
|
3484
|
+
if (ch === '/' && nx === '*') {
|
|
3485
|
+
inBlock = true;
|
|
3486
|
+
continue;
|
|
3487
|
+
}
|
|
3488
|
+
if (ch === '"' || ch === "'" || ch === '`') {
|
|
3489
|
+
inStr = ch;
|
|
3490
|
+
continue;
|
|
3491
|
+
}
|
|
3459
3492
|
if (ch === '{')
|
|
3460
|
-
|
|
3493
|
+
braces++;
|
|
3461
3494
|
else if (ch === '}')
|
|
3462
|
-
|
|
3495
|
+
braces--;
|
|
3496
|
+
else if (ch === '(')
|
|
3497
|
+
parens++;
|
|
3498
|
+
else if (ch === ')')
|
|
3499
|
+
parens--;
|
|
3500
|
+
else if (ch === '[')
|
|
3501
|
+
brackets++;
|
|
3502
|
+
else if (ch === ']')
|
|
3503
|
+
brackets--;
|
|
3504
|
+
}
|
|
3505
|
+
let result = code.trimEnd();
|
|
3506
|
+
for (let i = 0; i < braces; i++)
|
|
3507
|
+
result += '\n}';
|
|
3508
|
+
for (let i = 0; i < parens; i++)
|
|
3509
|
+
result += ')';
|
|
3510
|
+
for (let i = 0; i < brackets; i++)
|
|
3511
|
+
result += ']';
|
|
3512
|
+
return braces > 0 || parens > 0 || brackets > 0 ? result : code;
|
|
3513
|
+
}
|
|
3514
|
+
/**
|
|
3515
|
+
* Quick JS/TS syntax validation using Node's built-in parser.
|
|
3516
|
+
* Returns true if the code parses without errors.
|
|
3517
|
+
*/
|
|
3518
|
+
validateJsSyntax(code) {
|
|
3519
|
+
try {
|
|
3520
|
+
// Use Function constructor to check syntax without executing
|
|
3521
|
+
new Function(code);
|
|
3522
|
+
return true;
|
|
3463
3523
|
}
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
for (let i = 0; i < depth; i++) {
|
|
3467
|
-
result += '\n}';
|
|
3468
|
-
}
|
|
3469
|
-
code = result;
|
|
3524
|
+
catch {
|
|
3525
|
+
return false;
|
|
3470
3526
|
}
|
|
3471
|
-
return code;
|
|
3472
3527
|
}
|
|
3473
3528
|
/**
|
|
3474
3529
|
* Extract the first complete function/class from code.
|
|
@@ -3588,9 +3643,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3588
3643
|
'Rules:',
|
|
3589
3644
|
'- Return concrete, line-specific issues with severity.',
|
|
3590
3645
|
'- Every issue MUST reference a line number.',
|
|
3591
|
-
'-
|
|
3646
|
+
'- Report each distinct bug ONCE. Do NOT report the same bug multiple times with different wording.',
|
|
3647
|
+
'- For trivial/short code (< 10 lines), report ONLY actual bugs. Do NOT pad with style, robustness, or best-practice suggestions.',
|
|
3648
|
+
'- If you find a real bug (wrong operator, logic error, type mismatch), report ONLY that bug. Do NOT also suggest input validation, type checking, or error handling unless those are ACTUAL bugs.',
|
|
3592
3649
|
'- Prioritize REAL BUGS: wrong operators, logic errors, off-by-one, type mismatches.',
|
|
3593
|
-
'-
|
|
3650
|
+
'- Do NOT suggest adding error handling, input validation, or documentation as issues unless the user explicitly asked for a style review.',
|
|
3594
3651
|
'- Return ONLY the JSON object, no markdown fences or extra text.',
|
|
3595
3652
|
].join('\n');
|
|
3596
3653
|
let raw = {};
|
|
@@ -3605,25 +3662,40 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3605
3662
|
const score = typeof raw.score === 'number' ? raw.score : 0;
|
|
3606
3663
|
const issues = Array.isArray(raw.issues) ? raw.issues : [];
|
|
3607
3664
|
const suggestions = Array.isArray(raw.suggestions) ? raw.suggestions : [];
|
|
3608
|
-
//
|
|
3609
|
-
//
|
|
3610
|
-
|
|
3665
|
+
// Merge client-side heuristics, but with tight dedup to avoid
|
|
3666
|
+
// redundant over-reporting when the model already found the bug.
|
|
3667
|
+
const modelFoundError = issues.some(i => i.severity === 'error');
|
|
3611
3668
|
const heuristic = this.heuristicCodeIssues(code, language);
|
|
3612
3669
|
for (const h of heuristic) {
|
|
3613
|
-
//
|
|
3614
|
-
//
|
|
3615
|
-
|
|
3616
|
-
if (h.severity === 'error') {
|
|
3617
|
-
const exactDuplicate = issues.some((existing) => existing.line === h.line && existing.message === h.message);
|
|
3618
|
-
if (!exactDuplicate) {
|
|
3619
|
-
issues.push(h);
|
|
3620
|
-
}
|
|
3670
|
+
// If the model already found a real error, skip non-error heuristics
|
|
3671
|
+
// entirely — they're just padding (style, robustness, etc.)
|
|
3672
|
+
if (modelFoundError && h.severity !== 'error')
|
|
3621
3673
|
continue;
|
|
3622
|
-
|
|
3623
|
-
//
|
|
3624
|
-
//
|
|
3625
|
-
const
|
|
3626
|
-
|
|
3674
|
+
// Semantic duplicate check: same line + (similar type OR overlapping
|
|
3675
|
+
// keywords in the message). This catches cases where the model
|
|
3676
|
+
// and heuristic describe the same bug with different wording.
|
|
3677
|
+
const hWords = new Set(h.message.toLowerCase().split(/\W+/).filter(w => w.length > 3));
|
|
3678
|
+
const hTypeNorm = h.type.toLowerCase().replace(/[^a-z]/g, '');
|
|
3679
|
+
const isSemanticallyDuplicate = issues.some((existing) => {
|
|
3680
|
+
if (existing.line !== h.line)
|
|
3681
|
+
return false;
|
|
3682
|
+
// Normalize types: "logic-error", "logic_error", "logic" all match
|
|
3683
|
+
const eTypeNorm = existing.type.toLowerCase().replace(/[^a-z]/g, '');
|
|
3684
|
+
if (eTypeNorm === hTypeNorm || eTypeNorm.startsWith(hTypeNorm) || hTypeNorm.startsWith(eTypeNorm))
|
|
3685
|
+
return true;
|
|
3686
|
+
// Both errors on same line about the same category of problem
|
|
3687
|
+
if (existing.severity === 'error' && h.severity === 'error')
|
|
3688
|
+
return true;
|
|
3689
|
+
// Check keyword overlap — if ≥2 significant words match, it's the same finding
|
|
3690
|
+
const eWords = existing.message.toLowerCase().split(/\W+/).filter(w => w.length > 3);
|
|
3691
|
+
let overlap = 0;
|
|
3692
|
+
for (const w of eWords) {
|
|
3693
|
+
if (hWords.has(w))
|
|
3694
|
+
overlap++;
|
|
3695
|
+
}
|
|
3696
|
+
return overlap >= 2;
|
|
3697
|
+
});
|
|
3698
|
+
if (!isSemanticallyDuplicate) {
|
|
3627
3699
|
issues.push(h);
|
|
3628
3700
|
}
|
|
3629
3701
|
}
|
|
@@ -3772,9 +3844,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3772
3844
|
const sysPrompt = [
|
|
3773
3845
|
`You are a ${language} code fixer. Fix the code for: ${fixType}.`,
|
|
3774
3846
|
'Return a JSON object with:',
|
|
3775
|
-
' "fixed": the corrected code as a string,',
|
|
3847
|
+
' "fixed": the COMPLETE corrected source code as a string (not a snippet — the full file),',
|
|
3776
3848
|
' "changes": [{ "line": number, "before": string, "after": string, "reason": string }]',
|
|
3777
3849
|
'Rules:',
|
|
3850
|
+
'- The "fixed" field MUST contain the entire corrected source code with ALL lines, including unchanged lines.',
|
|
3851
|
+
'- The "fixed" code MUST have balanced braces, parentheses, and brackets.',
|
|
3778
3852
|
'- Fix ONLY the issues related to the fix type.',
|
|
3779
3853
|
'- Do not add comments, do not restructure beyond the minimal fix.',
|
|
3780
3854
|
'- Return ONLY the JSON object, no markdown fences.',
|
|
@@ -3814,6 +3888,26 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3814
3888
|
if (fixType === 'syntax' && fixed !== code) {
|
|
3815
3889
|
fixed = this.repairBracketBalance(code, fixed);
|
|
3816
3890
|
}
|
|
3891
|
+
// Final bracket-balance guarantee — ensure the emitted code has
|
|
3892
|
+
// balanced braces/parens/brackets regardless of what the model returned.
|
|
3893
|
+
fixed = this.ensureBalancedBraces(fixed);
|
|
3894
|
+
// For JS/TS syntax fixes, validate the output actually parses.
|
|
3895
|
+
// If it doesn't, attempt a more aggressive bracket repair.
|
|
3896
|
+
if ((fixType === 'syntax' || fixType === 'bugs') && fixed !== code) {
|
|
3897
|
+
const lang = language.toLowerCase();
|
|
3898
|
+
if (['javascript', 'js', 'typescript', 'ts'].includes(lang)) {
|
|
3899
|
+
if (!this.validateJsSyntax(fixed)) {
|
|
3900
|
+
// Try once more: strip any remaining injected comments and re-balance
|
|
3901
|
+
let repaired = this.stripInjectedComments(code, fixed, language);
|
|
3902
|
+
repaired = this.ensureBalancedBraces(repaired);
|
|
3903
|
+
if (this.validateJsSyntax(repaired)) {
|
|
3904
|
+
fixed = repaired;
|
|
3905
|
+
}
|
|
3906
|
+
// If still invalid, return the best-effort fix — better than
|
|
3907
|
+
// silently reverting to the original broken code.
|
|
3908
|
+
}
|
|
3909
|
+
}
|
|
3910
|
+
}
|
|
3817
3911
|
// If there are still no changes but the fixed code differs, compute
|
|
3818
3912
|
// a semantic diff using LCS so inserted/removed lines don't cause
|
|
3819
3913
|
// every subsequent line to appear as changed.
|