vigthoria-cli 1.6.32 → 1.6.34

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.
@@ -53,6 +53,12 @@ export declare class ChatCommand {
53
53
  */
54
54
  private isAnalysisLookupPrompt;
55
55
  private isBrowserTaskPrompt;
56
+ /**
57
+ * Returns true when a prompt can be answered directly without the full
58
+ * BMAD operator workflow. Matches analysis/lookup questions.
59
+ * Infrastructure action verbs are already filtered at the routing level.
60
+ */
61
+ private isOperatorDirectAnswerable;
56
62
  private inferAgentTaskType;
57
63
  private buildTaskShapingInstructions;
58
64
  private buildExecutionPrompt;
@@ -206,6 +206,14 @@ class ChatCommand {
206
206
  isBrowserTaskPrompt(prompt) {
207
207
  return /(browser|chrome|devtools|console|dom|network tab|network request|frontend runtime|client-side|client side|rendering|page load|websocket|ui bug|inspect element)/i.test(prompt);
208
208
  }
209
+ /**
210
+ * Returns true when a prompt can be answered directly without the full
211
+ * BMAD operator workflow. Matches analysis/lookup questions.
212
+ * Infrastructure action verbs are already filtered at the routing level.
213
+ */
214
+ isOperatorDirectAnswerable(prompt) {
215
+ return this.isAnalysisLookupPrompt(prompt.trim());
216
+ }
209
217
  inferAgentTaskType(prompt) {
210
218
  if (this.isDiagnosticPrompt(prompt))
211
219
  return 'debugging';
@@ -665,6 +673,13 @@ class ChatCommand {
665
673
  this.logger.error(this.operatorAccessMessage());
666
674
  return;
667
675
  }
676
+ // Smart routing: infrastructure action verbs always need the BMAD
677
+ // workflow. Everything else can be answered with a direct chat.
678
+ const isInfraAction = /(deploy|provision|scale|replicas|rollback|roll back|migrate|tear.?down|restart|stop\s+\w+|start\s+\w+|configure|set.?up|upgrade|pipeline)/i.test(prompt.trim());
679
+ if (!isInfraAction && (this.isSimpleDirectPrompt(prompt) || this.isOperatorDirectAnswerable(prompt))) {
680
+ await this.runSimplePrompt(prompt);
681
+ return;
682
+ }
668
683
  await this.runOperatorTurn(prompt);
669
684
  return;
670
685
  }
@@ -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;
@@ -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,12 +313,12 @@ 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 at the very end — trailing stutter
271
- deduped.push(lines[i]);
319
+ else if (runLen === 2 && isAtEffectiveEnd && lines[i].trimEnd() === lines[i + 1].trimEnd()) {
320
+ // Exactly 2 identical lines (same leading indent) at end
321
+ deduped.push(lines[i].trimEnd());
272
322
  }
273
323
  else {
274
324
  // 1 line, or a pair in the middle — keep all
@@ -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
- let depth = 0;
3458
- for (const ch of code) {
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
- depth++;
3493
+ braces++;
3461
3494
  else if (ch === '}')
3462
- depth--;
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
- if (depth > 0) {
3465
- let result = code.trimEnd();
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.
@@ -3589,9 +3644,10 @@ document.addEventListener('DOMContentLoaded', () => {
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.',
3592
- '- For trivial/short code (< 10 lines), report at most 2 issues unless there are genuinely more distinct bugs.',
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.',
3593
3649
  '- Prioritize REAL BUGS: wrong operators, logic errors, off-by-one, type mismatches.',
3594
- '- Only report style/robustness issues if there are no real bugs to report.',
3650
+ '- Do NOT suggest adding error handling, input validation, or documentation as issues unless the user explicitly asked for a style review.',
3595
3651
  '- Return ONLY the JSON object, no markdown fences or extra text.',
3596
3652
  ].join('\n');
3597
3653
  let raw = {};
@@ -3608,8 +3664,13 @@ document.addEventListener('DOMContentLoaded', () => {
3608
3664
  const suggestions = Array.isArray(raw.suggestions) ? raw.suggestions : [];
3609
3665
  // Merge client-side heuristics, but with tight dedup to avoid
3610
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) {
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')
3673
+ continue;
3613
3674
  // Semantic duplicate check: same line + (similar type OR overlapping
3614
3675
  // keywords in the message). This catches cases where the model
3615
3676
  // and heuristic describe the same bug with different wording.
@@ -3783,9 +3844,11 @@ document.addEventListener('DOMContentLoaded', () => {
3783
3844
  const sysPrompt = [
3784
3845
  `You are a ${language} code fixer. Fix the code for: ${fixType}.`,
3785
3846
  'Return a JSON object with:',
3786
- ' "fixed": the corrected code as a string,',
3847
+ ' "fixed": the COMPLETE corrected source code as a string (not a snippet — the full file),',
3787
3848
  ' "changes": [{ "line": number, "before": string, "after": string, "reason": string }]',
3788
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.',
3789
3852
  '- Fix ONLY the issues related to the fix type.',
3790
3853
  '- Do not add comments, do not restructure beyond the minimal fix.',
3791
3854
  '- Return ONLY the JSON object, no markdown fences.',
@@ -3825,6 +3888,26 @@ document.addEventListener('DOMContentLoaded', () => {
3825
3888
  if (fixType === 'syntax' && fixed !== code) {
3826
3889
  fixed = this.repairBracketBalance(code, fixed);
3827
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
+ }
3828
3911
  // If there are still no changes but the fixed code differs, compute
3829
3912
  // a semantic diff using LCS so inserted/removed lines don't cause
3830
3913
  // every subsequent line to appear as changed.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vigthoria-cli",
3
- "version": "1.6.32",
3
+ "version": "1.6.34",
4
4
  "description": "Vigthoria Coder CLI - AI-powered terminal coding assistant",
5
5
  "main": "dist/index.js",
6
6
  "files": [