vigthoria-cli 1.6.23 → 1.6.25

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.
@@ -199,11 +199,20 @@ class AuthCommand {
199
199
  console.log(chalk_1.default.gray(' Self-hosted Models: ') + chalk_1.default.gray('disabled'));
200
200
  }
201
201
  const capabilitySpinner = (0, logger_js_1.createSpinner)('Checking live capability truth...').start();
202
- const capabilityStatus = await this.api.getCapabilityTruthStatus({
203
- workspacePath: process.cwd(),
204
- projectPath: process.cwd(),
205
- targetPath: process.cwd(),
206
- });
202
+ const capabilityStatus = await Promise.race([
203
+ this.api.getCapabilityTruthStatus({
204
+ workspacePath: process.cwd(),
205
+ projectPath: process.cwd(),
206
+ targetPath: process.cwd(),
207
+ }),
208
+ new Promise(resolve => setTimeout(() => resolve({
209
+ overallOk: false,
210
+ v3Agent: { name: 'V3 Agent', endpoint: '', ok: false, error: 'Timed out (8s)' },
211
+ hyperLoop: { name: 'Hyper Loop', endpoint: '', ok: false, error: 'Timed out (8s)' },
212
+ repoMemory: { name: 'Repo Memory', endpoint: '', ok: false, error: 'Timed out (8s)' },
213
+ devtoolsBridge: { name: 'DevTools Bridge', endpoint: '', ok: false, error: 'Timed out (8s)' },
214
+ }), 8000)),
215
+ ]);
207
216
  capabilitySpinner.stop();
208
217
  console.log();
209
218
  console.log(chalk_1.default.white('Capability Truth:'));
@@ -75,6 +75,13 @@ export declare class ChatCommand {
75
75
  private slugifyWorkspaceName;
76
76
  private allocateManagedWorkspacePath;
77
77
  private initializeSession;
78
+ /**
79
+ * Determine whether a direct --prompt call is simple enough to skip the
80
+ * full V3 Agent planner and answer directly via the simple chat pipeline.
81
+ * This prevents trivial prompts like "Reply with OK" from triggering a
82
+ * multi-step planning workflow with quality_profile: premium-visual-build.
83
+ */
84
+ private isSimpleDirectPrompt;
78
85
  private handleDirectPrompt;
79
86
  private formatWorkflowTargetResult;
80
87
  private runWorkflowTargetPrompt;
@@ -486,7 +486,37 @@ class ChatCommand {
486
486
  if (options.newProject) {
487
487
  return true;
488
488
  }
489
- return Boolean(options.prompt) && (options.agent === true || options.operator === true);
489
+ // Only use managed workspace when --prompt is given AND the CWD looks
490
+ // like a home/root directory (no project signals). When the user runs
491
+ // `vigthoria agent --prompt "..."` inside an actual project folder we
492
+ // should use that folder, not invent a new managed workspace.
493
+ if (Boolean(options.prompt) && (options.agent === true || options.operator === true)) {
494
+ const cwd = process.cwd();
495
+ const homeDir = os.homedir();
496
+ // If CWD is home dir, root, or a system temp dir → use managed workspace
497
+ if (cwd === homeDir || cwd === '/' || cwd === 'C:\\' || cwd.toLowerCase().includes('temp')) {
498
+ return true;
499
+ }
500
+ // If CWD has any project signals → use CWD
501
+ const projectSignals = ['.git', 'package.json', 'Cargo.toml', 'go.mod', 'pom.xml', 'requirements.txt', 'pyproject.toml', '.vigthoria', 'Makefile', 'CMakeLists.txt', 'tsconfig.json'];
502
+ for (const signal of projectSignals) {
503
+ if (fs.existsSync(path.join(cwd, signal))) {
504
+ return false;
505
+ }
506
+ }
507
+ // If CWD has files (not just an empty dir) → use CWD
508
+ try {
509
+ const entries = fs.readdirSync(cwd);
510
+ if (entries.length > 0) {
511
+ return false;
512
+ }
513
+ }
514
+ catch {
515
+ // Cannot read CWD — fall through to managed workspace
516
+ }
517
+ return true;
518
+ }
519
+ return false;
490
520
  }
491
521
  getManagedWorkspaceRoot() {
492
522
  const envRoot = String(process.env.VIGTHORIA_DEFAULT_WORKSPACE_ROOT || '').trim();
@@ -546,6 +576,27 @@ class ChatCommand {
546
576
  this.currentSession = this.sessionManager.create(this.currentProjectPath, this.currentModel, this.agentMode, this.operatorMode);
547
577
  this.messages = [...this.currentSession.messages];
548
578
  }
579
+ /**
580
+ * Determine whether a direct --prompt call is simple enough to skip the
581
+ * full V3 Agent planner and answer directly via the simple chat pipeline.
582
+ * This prevents trivial prompts like "Reply with OK" from triggering a
583
+ * multi-step planning workflow with quality_profile: premium-visual-build.
584
+ */
585
+ isSimpleDirectPrompt(prompt) {
586
+ const trimmed = prompt.trim();
587
+ // Short prompts (≤ 12 words) that don't reference files, builds, or code tasks
588
+ const wordCount = trimmed.split(/\s+/).length;
589
+ if (wordCount <= 12 && !/(create|build|generate|implement|write.*function|add.*feature|refactor|deploy|fix|edit|modify|update|delete|remove)\b/i.test(trimmed)) {
590
+ return true;
591
+ }
592
+ // Conversational / Q&A prompts
593
+ if (/^(what|who|when|where|why|how|is|are|do|does|can|could|would|should|tell|explain|describe|list|show|help|reply|say|respond|answer|translate|summarize|define)\b/i.test(trimmed)) {
594
+ if (!/(file|code|project|workspace|repo|module|component|function|class|api|endpoint|route|database|schema|migration|docker|deploy|build|test)\b/i.test(trimmed)) {
595
+ return true;
596
+ }
597
+ }
598
+ return false;
599
+ }
549
600
  async handleDirectPrompt(prompt) {
550
601
  if (!this.jsonOutput) {
551
602
  console.log(chalk_1.default.cyan('Running single prompt in direct mode.'));
@@ -560,7 +611,8 @@ class ChatCommand {
560
611
  await this.runWorkflowTargetPrompt(prompt);
561
612
  return;
562
613
  }
563
- if (this.agentMode) {
614
+ // Smart routing: skip agent planner for simple/conversational prompts
615
+ if (this.agentMode && !this.isSimpleDirectPrompt(prompt)) {
564
616
  await this.runAgentTurn(prompt);
565
617
  return;
566
618
  }
@@ -1581,7 +1633,7 @@ class ChatCommand {
1581
1633
  if (this.isServerBindableWorkspace(this.currentProjectPath)) {
1582
1634
  return false;
1583
1635
  }
1584
- return /(analyse|analyze|audit|overview|inspect|explain|review|debug|diagnos|read|summari[sz]e|investigate)/i.test(prompt);
1636
+ return /(analyse|analyze|audit|overview|inspect|explain|review|debug|diagnos|read|summari[sz]e|investigate|list|show|find|search|check|scan|count|what|describe)/i.test(prompt);
1585
1637
  }
1586
1638
  shouldRequireV3AgentWorkflow(prompt) {
1587
1639
  if (!this.directPromptMode) {
@@ -28,5 +28,10 @@ export declare class GenerateCommand {
28
28
  private saveToFile;
29
29
  private promptForAction;
30
30
  private suggestFilename;
31
+ /**
32
+ * Auto-detect the target language from the user's description.
33
+ * Falls back to 'javascript' if no language is recognized.
34
+ */
35
+ private detectLanguageFromDescription;
31
36
  }
32
37
  export {};
@@ -33,6 +33,10 @@ class GenerateCommand {
33
33
  }
34
34
  // Determine mode
35
35
  const proMode = options.pro === true;
36
+ // Auto-detect language from description if not explicitly specified
37
+ if (!options.language) {
38
+ options.language = this.detectLanguageFromDescription(description);
39
+ }
36
40
  this.logger.section(proMode ? '🚀 Senior Developer Mode' : 'Code Generation');
37
41
  console.log(chalk_1.default.gray(`Language: ${options.language}`));
38
42
  console.log(chalk_1.default.gray(`Description: ${description}`));
@@ -214,5 +218,40 @@ class GenerateCommand {
214
218
  };
215
219
  return extensions[language] || 'generated.txt';
216
220
  }
221
+ /**
222
+ * Auto-detect the target language from the user's description.
223
+ * Falls back to 'javascript' if no language is recognized.
224
+ */
225
+ detectLanguageFromDescription(description) {
226
+ const lower = description.toLowerCase();
227
+ const patterns = [
228
+ [/\b(typescript|\.ts\b)/i, 'typescript'],
229
+ [/\b(javascript|\.js\b|node\.?js|es6|ecmascript)\b/i, 'javascript'],
230
+ [/\b(python|\.py\b|django|flask|fastapi)\b/i, 'python'],
231
+ [/\b(rust|\.rs\b|cargo)\b/i, 'rust'],
232
+ [/\b(go|golang|\.go\b)\b/i, 'go'],
233
+ [/\b(java\b|\.java\b|spring\b)/i, 'java'],
234
+ [/\b(c#|csharp|\.cs\b|dotnet|\.net)\b/i, 'csharp'],
235
+ [/\b(c\+\+|cpp|\.cpp\b|\.hpp\b)\b/i, 'cpp'],
236
+ [/\b(ruby|\.rb\b|rails)\b/i, 'ruby'],
237
+ [/\b(php|\.php\b|laravel)\b/i, 'php'],
238
+ [/\b(swift|\.swift\b|swiftui)\b/i, 'swift'],
239
+ [/\b(kotlin|\.kt\b)\b/i, 'kotlin'],
240
+ [/\b(html|\.html\b|webpage|web page|website|landing page)\b/i, 'html'],
241
+ [/\b(css|\.css\b|stylesheet)\b/i, 'css'],
242
+ [/\b(sql|\.sql\b|database query|select\s+\*)\b/i, 'sql'],
243
+ [/\b(bash|shell|\.sh\b|script)\b/i, 'bash'],
244
+ ];
245
+ for (const [regex, lang] of patterns) {
246
+ if (regex.test(lower)) {
247
+ return lang;
248
+ }
249
+ }
250
+ // If the description mentions "function" without any language hint, default to javascript
251
+ if (/\bfunction\b/i.test(lower)) {
252
+ return 'javascript';
253
+ }
254
+ return 'javascript';
255
+ }
217
256
  }
218
257
  exports.GenerateCommand = GenerateCommand;
package/dist/index.js CHANGED
@@ -98,7 +98,7 @@ function getVersion() {
98
98
  catch (e) {
99
99
  // Fallback to hardcoded version
100
100
  }
101
- return '1.6.22';
101
+ return '1.6.25';
102
102
  }
103
103
  const VERSION = getVersion();
104
104
  /**
@@ -334,7 +334,7 @@ async function main() {
334
334
  .command('generate <description>')
335
335
  .alias('g')
336
336
  .description('Generate code from description')
337
- .option('-l, --language <lang>', 'Target language (html, typescript, python, etc.)', 'html')
337
+ .option('-l, --language <lang>', 'Target language (auto-detected from description, or specify: javascript, typescript, python, html, etc.)')
338
338
  .option('-o, --output <file>', 'Output file path')
339
339
  .option('-m, --model <model>', 'Select AI model', 'code')
340
340
  .option('-p, --pro', 'Senior Developer Mode: plan, generate, quality check (recommended)', false)
@@ -320,6 +320,30 @@ export declare class APIClient {
320
320
  chatStream(messages: ChatMessage[], model: string): AsyncGenerator<StreamChunk>;
321
321
  chatWithCallback(messages: ChatMessage[], model: string, onChunk: (chunk: string) => void, onDone: () => void, onError: (error: Error) => void): Promise<void>;
322
322
  generateCode(prompt: string, language: string, model: string): Promise<string>;
323
+ /**
324
+ * Ensure code has balanced curly braces by appending missing closing braces.
325
+ */
326
+ private ensureBalancedBraces;
327
+ /**
328
+ * Extract the first complete function/class from code.
329
+ * Used as last-resort when the model keeps over-producing.
330
+ */
331
+ private extractFirstFunction;
332
+ /**
333
+ * Detect if code is excessively over-engineered for a short prompt.
334
+ * E.g. a "multiply function" request producing 20+ lines.
335
+ */
336
+ private codeIsOverEngineered;
337
+ /**
338
+ * Detect if generated code contains DOM/HTML pollution inappropriate
339
+ * for a pure programming language like JavaScript, Python, etc.
340
+ */
341
+ private codeContainsDomPollution;
342
+ /**
343
+ * Strip DOM pollution from generated code, keeping only the pure logic.
344
+ * Used as last-resort fallback when the model repeatedly ignores constraints.
345
+ */
346
+ private stripDomPollution;
323
347
  generateProject(prompt: string, projectType: string, model: string): Promise<{
324
348
  code: string;
325
349
  plan?: any;
package/dist/utils/api.js CHANGED
@@ -990,6 +990,9 @@ class APIClient {
990
990
  let json = JSON.stringify(payload);
991
991
  if (json.length <= LIMIT)
992
992
  return json;
993
+ if (process.env.DEBUG || process.env.VIGTHORIA_DEBUG) {
994
+ console.log(`[context] Payload ${json.length} chars exceeds ${LIMIT} limit, compacting...`);
995
+ }
993
996
  // Phase 1 — shrink workspaceFiles to fit
994
997
  const summary = payload.localWorkspaceSummary;
995
998
  if (summary?.workspaceFiles && typeof summary.workspaceFiles === 'object') {
@@ -3239,31 +3242,171 @@ document.addEventListener('DOMContentLoaded', () => {
3239
3242
  }
3240
3243
  // Code operations - Using Vigthoria Centralized API
3241
3244
  async generateCode(prompt, language, model) {
3245
+ const isNonHtmlLang = !['html', 'css'].includes(language.toLowerCase());
3246
+ const wordCount = prompt.trim().split(/\s+/).length;
3242
3247
  // Prepend a forceful scope-enforcement instruction so the model
3243
3248
  // doesn't expand a small task into an oversized glossy page.
3244
- const scopedPrompt = [
3245
- 'IMPORTANT — MANDATORY SCOPE CONSTRAINTS (violation = failure):',
3246
- '1. Output ONLY what the user explicitly asked for. Nothing more.',
3247
- '2. If the prompt is 15 words, produce 80 lines of code maximum.',
3248
- '3. If the user says "tiny", "small", "simple", "minimal", or "basic", produce ≤ 50 lines.',
3249
- '4. NEVER add any of these unless the user EXPLICITLY requests them:',
3250
- ' - Hero sections, CTAs, testimonials, pricing tables, footers, navbars',
3251
- ' - CSS animations, gradients, neon effects, glass-morphism, particles',
3252
- ' - Google Fonts, Font Awesome, external CDN links, icon libraries',
3253
- ' - Responsive breakpoints, media queries (unless asked)',
3254
- ' - Multiple pages or components when one was requested',
3255
- '5. Prefer inline styles or a small <style> block. No CSS frameworks.',
3256
- '6. Return raw code only no markdown fences, no explanations, no comments about what could be added.',
3257
- '7. Match the complexity of the request: a "hello world" is 1-5 lines, a "button" is 5-15 lines.',
3258
- '',
3259
- prompt,
3260
- ].join('\n');
3261
- const response = await this.client.post('/api/ai/generate', {
3262
- prompt: scopedPrompt,
3249
+ const buildScopedPrompt = (retry) => {
3250
+ if (retry && isNonHtmlLang) {
3251
+ // Ultra-minimal retry prompt no long instruction block
3252
+ return `OUTPUT ONLY RAW ${language.toUpperCase()} CODE. MAXIMUM 10 LINES. NO DOM. NO HTML. NO CSS. NO ANIMATION. NO VALIDATION. NO COMMENTS.\n\n${prompt}`;
3253
+ }
3254
+ const lines = [
3255
+ 'IMPORTANT MANDATORY SCOPE CONSTRAINTS (violation = failure):',
3256
+ `1. You MUST output ONLY ${language.toUpperCase()} code. Do NOT output HTML, CSS, or DOM manipulation unless the language is html/css.`,
3257
+ `2. The target language is: ${language}. Do not switch to a different language.`,
3258
+ '3. Output ONLY what the user explicitly asked for. Nothing more.',
3259
+ '4. If the prompt is 15 words, produce ≤ 80 lines of code maximum.',
3260
+ '5. If the user says "tiny", "small", "simple", "minimal", or "basic", produce ≤ 50 lines.',
3261
+ '6. NEVER add any of these unless the user EXPLICITLY requests them:',
3262
+ ' - Hero sections, CTAs, testimonials, pricing tables, footers, navbars',
3263
+ ' - CSS animations, gradients, neon effects, glass-morphism, particles',
3264
+ ' - Google Fonts, Font Awesome, external CDN links, icon libraries',
3265
+ ' - DOM manipulation, document.createElement, innerHTML',
3266
+ ' - Responsive breakpoints, media queries (unless asked)',
3267
+ ' - Multiple pages or components when one was requested',
3268
+ ' - Input validation, error handling beyond what was requested',
3269
+ '7. Prefer inline styles or a small <style> block ONLY if the language is html. No CSS frameworks.',
3270
+ '8. Return raw code only — no markdown fences, no explanations, no comments about what could be added.',
3271
+ '9. Match the complexity of the request: a "hello world" is 1-5 lines, a "multiply function" is 3-5 lines, a "button" is 5-15 lines.',
3272
+ ];
3273
+ if (isNonHtmlLang) {
3274
+ lines.push(`10. This is a PURE ${language.toUpperCase()} task. ABSOLUTELY NO document.*, window.*, DOM, createElement, innerHTML, querySelector, addEventListener. Output a plain ${language} function/module ONLY.`);
3275
+ }
3276
+ lines.push('', prompt);
3277
+ return lines.join('\n');
3278
+ };
3279
+ // First attempt
3280
+ let response = await this.client.post('/api/ai/generate', {
3281
+ prompt: buildScopedPrompt(false),
3263
3282
  language,
3264
3283
  model: this.resolvePermittedModelId(model),
3265
3284
  });
3266
- return response.data.code;
3285
+ let code = response.data.code || '';
3286
+ // Client-side validation: reject DOM-polluted or over-engineered responses for non-HTML languages
3287
+ const needsRetry = isNonHtmlLang && (this.codeContainsDomPollution(code) ||
3288
+ this.codeIsOverEngineered(code, prompt));
3289
+ if (needsRetry) {
3290
+ // Retry once with stronger constraint
3291
+ response = await this.client.post('/api/ai/generate', {
3292
+ prompt: buildScopedPrompt(true),
3293
+ language,
3294
+ model: this.resolvePermittedModelId(model),
3295
+ });
3296
+ code = response.data.code || '';
3297
+ // If still polluted, strip DOM code client-side
3298
+ if (this.codeContainsDomPollution(code)) {
3299
+ code = this.stripDomPollution(code, language);
3300
+ }
3301
+ }
3302
+ // Final cleanup: for non-HTML, if still over-engineered after retry,
3303
+ // extract only the first complete function/class definition.
3304
+ if (isNonHtmlLang && this.codeIsOverEngineered(code, prompt)) {
3305
+ code = this.extractFirstFunction(code);
3306
+ }
3307
+ // Ensure balanced braces — model sometimes truncates closing braces
3308
+ code = this.ensureBalancedBraces(code);
3309
+ return code;
3310
+ }
3311
+ /**
3312
+ * Ensure code has balanced curly braces by appending missing closing braces.
3313
+ */
3314
+ ensureBalancedBraces(code) {
3315
+ let depth = 0;
3316
+ for (const ch of code) {
3317
+ if (ch === '{')
3318
+ depth++;
3319
+ else if (ch === '}')
3320
+ depth--;
3321
+ }
3322
+ if (depth > 0) {
3323
+ const trimmed = code.trimEnd();
3324
+ for (let i = 0; i < depth; i++) {
3325
+ code = trimmed + '\n};';
3326
+ }
3327
+ }
3328
+ return code;
3329
+ }
3330
+ /**
3331
+ * Extract the first complete function/class from code.
3332
+ * Used as last-resort when the model keeps over-producing.
3333
+ */
3334
+ extractFirstFunction(code) {
3335
+ const lines = code.split('\n');
3336
+ const funcStart = lines.findIndex(l => /^(export\s+)?(const\s+\w+\s*=\s*(\(|function)|function\s+\w+|class\s+\w+|def\s+\w+|fn\s+\w+|func\s+\w+)/.test(l.trim()));
3337
+ if (funcStart === -1)
3338
+ return code;
3339
+ let braceDepth = 0;
3340
+ let foundOpen = false;
3341
+ const result = [];
3342
+ for (let i = funcStart; i < lines.length; i++) {
3343
+ const line = lines[i];
3344
+ result.push(line);
3345
+ braceDepth += (line.match(/{/g) || []).length;
3346
+ braceDepth -= (line.match(/}/g) || []).length;
3347
+ if (braceDepth > 0)
3348
+ foundOpen = true;
3349
+ if (foundOpen && braceDepth <= 0) {
3350
+ break;
3351
+ }
3352
+ }
3353
+ // If we reached the end without balancing braces, add closing
3354
+ if (braceDepth > 0) {
3355
+ result.push('};');
3356
+ }
3357
+ return result.join('\n');
3358
+ }
3359
+ /**
3360
+ * Detect if code is excessively over-engineered for a short prompt.
3361
+ * E.g. a "multiply function" request producing 20+ lines.
3362
+ */
3363
+ codeIsOverEngineered(code, prompt) {
3364
+ const wordCount = prompt.trim().split(/\s+/).length;
3365
+ const lineCount = code.trim().split('\n').length;
3366
+ // Short prompt (≤15 words) should not produce more than 15 lines
3367
+ if (wordCount <= 15 && lineCount > 15)
3368
+ return true;
3369
+ return false;
3370
+ }
3371
+ /**
3372
+ * Detect if generated code contains DOM/HTML pollution inappropriate
3373
+ * for a pure programming language like JavaScript, Python, etc.
3374
+ */
3375
+ codeContainsDomPollution(code) {
3376
+ const domPatterns = /document\.(createElement|querySelector|getElementById|getElementsBy|body|head|addEventListener)|innerHTML|\.style\.(cssText|position|transform|animation)|@keyframes|\.appendChild|\.removeChild|window\.(onload|addEventListener)/;
3377
+ return domPatterns.test(code);
3378
+ }
3379
+ /**
3380
+ * Strip DOM pollution from generated code, keeping only the pure logic.
3381
+ * Used as last-resort fallback when the model repeatedly ignores constraints.
3382
+ */
3383
+ stripDomPollution(code, language) {
3384
+ const lines = code.split('\n');
3385
+ const cleanLines = [];
3386
+ let insideDomBlock = false;
3387
+ let braceDepth = 0;
3388
+ for (const line of lines) {
3389
+ // Detect start of DOM blocks
3390
+ if (/document\.|\.style\.|\.appendChild|\.removeChild|\.textContent\s*=|@keyframes|addEventListener/.test(line) && !insideDomBlock) {
3391
+ insideDomBlock = true;
3392
+ braceDepth = 0;
3393
+ }
3394
+ if (insideDomBlock) {
3395
+ braceDepth += (line.match(/{/g) || []).length;
3396
+ braceDepth -= (line.match(/}/g) || []).length;
3397
+ if (braceDepth <= 0) {
3398
+ insideDomBlock = false;
3399
+ }
3400
+ continue; // Skip DOM lines
3401
+ }
3402
+ cleanLines.push(line);
3403
+ }
3404
+ const cleaned = cleanLines.join('\n').trim();
3405
+ // If we stripped too aggressively (less than 2 lines left), return a minimal stub
3406
+ if (cleaned.split('\n').length < 2) {
3407
+ return `// Auto-generated: model returned DOM code for a ${language} task.\n// Please regenerate or provide more specific instructions.`;
3408
+ }
3409
+ return cleaned;
3267
3410
  }
3268
3411
  // Senior Developer Mode - Planning + Generation + Quality Check
3269
3412
  async generateProject(prompt, projectType, model) {
@@ -3386,6 +3529,36 @@ document.addEventListener('DOMContentLoaded', () => {
3386
3529
  }
3387
3530
  }
3388
3531
  }
3532
+ // Accumulator/reducer bug: subtraction used in a reduce/total/sum context
3533
+ // e.g. "sum - item.price" in a function named calculateTotal, getTotal, etc.
3534
+ if (/\w+\s*-\s*\w+\.\s*(price|cost|amount|value|total|quantity|count)\b/.test(line) || /\w+\.\s*(price|cost|amount|value|total)\s*-\s*/.test(line)) {
3535
+ // Check enclosing function/method name for total/sum/aggregate semantics
3536
+ for (let j = i; j >= Math.max(0, i - 10); j--) {
3537
+ if (/(function\s+)?(calculate|calc|get|compute|sum|total|aggregate|accumulate)\s*(Total|Sum|Cost|Price|Amount|All)?\s*[=(]/i.test(lines[j]) || /\.(reduce|forEach|map)\s*\(/.test(lines[j])) {
3538
+ // Also check if this looks like an accumulator pattern (sum - something instead of sum + something)
3539
+ if (/\w+\s*-\s*\w+\.\s*(price|cost|amount|value|total|quantity)\b/.test(line)) {
3540
+ issues.push({ type: 'logic', line: lineNum, message: 'Subtraction used in an accumulation context (total/sum/reduce) — likely should be addition (+).', severity: 'error' });
3541
+ }
3542
+ break;
3543
+ }
3544
+ }
3545
+ }
3546
+ // Mismatched function name vs operator: sum/total function using wrong operator
3547
+ if (/\breturn\b/.test(line)) {
3548
+ for (let j = i; j >= Math.max(0, i - 8); j--) {
3549
+ const funcMatch = lines[j].match(/function\s+(\w+)/i) || lines[j].match(/(?:const|let|var)\s+(\w+)\s*=/i);
3550
+ if (funcMatch) {
3551
+ const funcName = funcMatch[1].toLowerCase();
3552
+ if ((funcName.includes('total') || funcName.includes('sum') || funcName.includes('add')) && /\s-\s/.test(line) && !/\s\+\s/.test(line)) {
3553
+ const alreadyReported = issues.some(iss => iss.line === lineNum && iss.type === 'logic');
3554
+ if (!alreadyReported) {
3555
+ issues.push({ type: 'logic', line: lineNum, message: `Subtraction operator in "${funcMatch[1]}" — function name implies addition. Likely should use + instead of -.`, severity: 'error' });
3556
+ }
3557
+ }
3558
+ break;
3559
+ }
3560
+ }
3561
+ }
3389
3562
  // Off-by-one: comparing with === where <= or >= may be needed
3390
3563
  // (not implemented yet — would require more complex AST analysis)
3391
3564
  // Limit to 15 heuristic issues
@@ -3398,8 +3571,14 @@ document.addEventListener('DOMContentLoaded', () => {
3398
3571
  // Client-side syntax pre-check: detect obvious errors and include
3399
3572
  // them in the request so the model has concrete signals.
3400
3573
  const syntaxHints = this.detectSyntaxErrors(code, language);
3401
- const augmentedCode = syntaxHints
3402
- ? `// SYNTAX ERRORS DETECTED BY CLIENT:\n// ${syntaxHints}\n\n${code}`
3574
+ // Also run heuristic logic analysis to find real bugs
3575
+ const heuristicIssues = this.heuristicCodeIssues(code, language);
3576
+ const logicHints = heuristicIssues
3577
+ .map(i => `Line ${i.line}: [${i.type}] ${i.message}`)
3578
+ .join('\n// ');
3579
+ const allHints = [syntaxHints, logicHints].filter(Boolean).join('\n// ');
3580
+ const augmentedCode = allHints
3581
+ ? `// BUGS DETECTED BY STATIC ANALYSIS — YOU MUST FIX THESE:\n// ${allHints}\n\n${code}`
3403
3582
  : code;
3404
3583
  const response = await this.client.post('/api/ai/fix', {
3405
3584
  code: augmentedCode,
@@ -3409,23 +3588,41 @@ document.addEventListener('DOMContentLoaded', () => {
3409
3588
  const raw = response.data ?? {};
3410
3589
  let fixed = typeof raw.fixed === 'string' ? raw.fixed : (typeof raw.code === 'string' ? raw.code : code);
3411
3590
  let changes = Array.isArray(raw.changes) ? raw.changes : [];
3412
- // If server returned no changes but we found syntax errors, strip
3591
+ // If server returned no changes but we found issues, strip
3413
3592
  // our injected comment prefix from the returned code and attempt
3414
3593
  // a basic client-side repair.
3415
- if (changes.length === 0 && syntaxHints && fixed === augmentedCode) {
3594
+ if (changes.length === 0 && allHints && fixed === augmentedCode) {
3416
3595
  fixed = code; // restore original
3417
3596
  }
3418
3597
  // Strip the injected comment block if it leaked into the output
3419
- if (fixed.startsWith('// SYNTAX ERRORS DETECTED BY CLIENT:')) {
3598
+ if (fixed.startsWith('// BUGS DETECTED BY STATIC ANALYSIS') || fixed.startsWith('// SYNTAX ERRORS DETECTED BY CLIENT:')) {
3420
3599
  const idx = fixed.indexOf('\n\n');
3421
3600
  if (idx !== -1) {
3422
3601
  fixed = fixed.slice(idx + 2);
3423
3602
  }
3424
3603
  }
3425
- // If there are still no changes but the fixed code differs, build a
3426
- // synthetic change entry so the user sees something actionable.
3604
+ // If there are still no changes but the fixed code differs, compute
3605
+ // a line-level diff so the user sees exactly what changed.
3427
3606
  if (changes.length === 0 && fixed !== code) {
3428
- changes = [{ line: 1, before: '(original code)', after: '(fixed code)', reason: syntaxHints || 'AI-suggested fix' }];
3607
+ const origLines = code.split('\n');
3608
+ const fixedLines = fixed.split('\n');
3609
+ const maxLen = Math.max(origLines.length, fixedLines.length);
3610
+ for (let i = 0; i < maxLen; i++) {
3611
+ const orig = origLines[i] ?? '';
3612
+ const fixd = fixedLines[i] ?? '';
3613
+ if (orig !== fixd) {
3614
+ changes.push({
3615
+ line: i + 1,
3616
+ before: orig || '(empty line)',
3617
+ after: fixd || '(line removed)',
3618
+ reason: allHints || 'AI-suggested fix',
3619
+ });
3620
+ }
3621
+ }
3622
+ // If diff produced nothing (whitespace-only?), at least show a summary
3623
+ if (changes.length === 0) {
3624
+ changes = [{ line: 1, before: '(whitespace changes)', after: '(see fixed file)', reason: allHints || 'AI-suggested fix' }];
3625
+ }
3429
3626
  }
3430
3627
  return { fixed, changes };
3431
3628
  }
@@ -3712,7 +3909,8 @@ document.addEventListener('DOMContentLoaded', () => {
3712
3909
  };
3713
3910
  }
3714
3911
  async getHyperLoopHealth() {
3715
- const endpoint = process.env.VIGTHORIA_HYPERLOOP_URL || 'http://127.0.0.1:8020/api/hyperloop/health';
3912
+ const configuredApiUrl = String(this.config.get('apiUrl') || 'https://coder.vigthoria.io').replace(/\/$/, '');
3913
+ const endpoint = process.env.VIGTHORIA_HYPERLOOP_URL || `${configuredApiUrl}/api/hyperloop/health`;
3716
3914
  try {
3717
3915
  const token = this.getAccessToken();
3718
3916
  const response = await fetch(endpoint, {
@@ -3741,8 +3939,9 @@ document.addEventListener('DOMContentLoaded', () => {
3741
3939
  }
3742
3940
  }
3743
3941
  async getRepoMemoryHealth(context = {}) {
3744
- const endpoint = process.env.VIGTHORIA_HYPERLOOP_EXECUTE_URL || 'http://127.0.0.1:8020/api/hyperloop/execute';
3745
- const modulesEndpoint = process.env.VIGTHORIA_HYPERLOOP_MODULES_URL || 'http://127.0.0.1:8020/api/hyperloop/modules';
3942
+ const configuredApiUrl = String(this.config.get('apiUrl') || 'https://coder.vigthoria.io').replace(/\/$/, '');
3943
+ const endpoint = process.env.VIGTHORIA_HYPERLOOP_EXECUTE_URL || `${configuredApiUrl}/api/hyperloop/execute`;
3944
+ const modulesEndpoint = process.env.VIGTHORIA_HYPERLOOP_MODULES_URL || `${configuredApiUrl}/api/hyperloop/modules`;
3746
3945
  const token = this.getAccessToken();
3747
3946
  const projectPath = this.resolveAgentTargetPath(context);
3748
3947
  try {
@@ -53,6 +53,7 @@ exports.BridgeClient = void 0;
53
53
  exports.getBridgeClient = getBridgeClient;
54
54
  const ws_1 = __importDefault(require("ws"));
55
55
  const os = __importStar(require("os"));
56
+ const chalk_1 = __importDefault(require("chalk"));
56
57
  // ── Singleton accessor ───────────────────────────────────────────────
57
58
  let _instance = null;
58
59
  /** Get the active bridge client (may be null if --bridge was not used). */
@@ -134,6 +135,9 @@ class BridgeClient {
134
135
  this.connected = false;
135
136
  this.stopHeartbeat();
136
137
  this.scheduleReconnect();
138
+ if (process.env.DEBUG || process.env.VIGTHORIA_DEBUG) {
139
+ console.log(chalk_1.default.yellow('⚠ Bridge: connection failed, will retry in background.'));
140
+ }
137
141
  resolve(); // resolve even on failure – must never block CLI
138
142
  });
139
143
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vigthoria-cli",
3
- "version": "1.6.23",
3
+ "version": "1.6.25",
4
4
  "description": "Vigthoria Coder CLI - AI-powered terminal coding assistant",
5
5
  "main": "dist/index.js",
6
6
  "files": [