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.
- package/dist/commands/auth.js +14 -5
- package/dist/commands/chat.d.ts +7 -0
- package/dist/commands/chat.js +55 -3
- package/dist/commands/generate.d.ts +5 -0
- package/dist/commands/generate.js +39 -0
- package/dist/index.js +2 -2
- package/dist/utils/api.d.ts +24 -0
- package/dist/utils/api.js +230 -31
- package/dist/utils/bridge-client.js +4 -0
- package/package.json +1 -1
package/dist/commands/auth.js
CHANGED
|
@@ -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
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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:'));
|
package/dist/commands/chat.d.ts
CHANGED
|
@@ -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;
|
package/dist/commands/chat.js
CHANGED
|
@@ -486,7 +486,37 @@ class ChatCommand {
|
|
|
486
486
|
if (options.newProject) {
|
|
487
487
|
return true;
|
|
488
488
|
}
|
|
489
|
-
|
|
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
|
-
|
|
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.
|
|
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 (
|
|
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)
|
package/dist/utils/api.d.ts
CHANGED
|
@@ -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
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3402
|
-
|
|
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
|
|
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 &&
|
|
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,
|
|
3426
|
-
//
|
|
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
|
-
|
|
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
|
|
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
|
|
3745
|
-
const
|
|
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
|
}
|