vigthoria-cli 1.6.19 → 1.6.20
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/chat.js +33 -5
- package/dist/utils/api.d.ts +15 -0
- package/dist/utils/api.js +232 -16
- package/package.json +1 -1
package/dist/commands/chat.js
CHANGED
|
@@ -199,7 +199,11 @@ class ChatCommand {
|
|
|
199
199
|
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);
|
|
200
200
|
}
|
|
201
201
|
inferAgentTaskType(prompt) {
|
|
202
|
-
|
|
202
|
+
if (this.isDiagnosticPrompt(prompt))
|
|
203
|
+
return 'debugging';
|
|
204
|
+
if (/^(what|which|how many|list|show|check|inspect|analyze|analyse|audit|explain|describe|summarize|summarise|review|overview|find|count|read|look at|tell me)/i.test(prompt.trim()))
|
|
205
|
+
return 'analysis';
|
|
206
|
+
return 'implementation';
|
|
203
207
|
}
|
|
204
208
|
buildTaskShapingInstructions(prompt) {
|
|
205
209
|
const instructions = [];
|
|
@@ -695,17 +699,41 @@ class ChatCommand {
|
|
|
695
699
|
const response = await this.api.chat(this.getMessagesForModel(), this.currentModel);
|
|
696
700
|
if (spinner)
|
|
697
701
|
spinner.stop();
|
|
698
|
-
const finalText = response.message.trim();
|
|
699
|
-
if (
|
|
702
|
+
const finalText = (response.message || '').trim();
|
|
703
|
+
if (this.jsonOutput) {
|
|
704
|
+
console.log(JSON.stringify({
|
|
705
|
+
success: true,
|
|
706
|
+
mode: 'chat',
|
|
707
|
+
model: this.currentModel,
|
|
708
|
+
content: finalText || 'The model returned an empty response. Try rephrasing or use --agent for grounded file analysis.',
|
|
709
|
+
}, null, 2));
|
|
710
|
+
}
|
|
711
|
+
else if (finalText) {
|
|
700
712
|
console.log(finalText);
|
|
701
713
|
}
|
|
702
|
-
|
|
714
|
+
else {
|
|
715
|
+
console.log(chalk_1.default.yellow('The model returned an empty response. Try rephrasing your question, or use --agent mode for grounded repo analysis.'));
|
|
716
|
+
}
|
|
717
|
+
this.messages.push({ role: 'assistant', content: response.message || '' });
|
|
703
718
|
this.saveSession();
|
|
704
719
|
}
|
|
705
720
|
catch (error) {
|
|
706
721
|
if (spinner)
|
|
707
722
|
spinner.fail('Failed to get response');
|
|
708
|
-
|
|
723
|
+
const errorMsg = error.message;
|
|
724
|
+
if (this.jsonOutput) {
|
|
725
|
+
process.exitCode = 1;
|
|
726
|
+
console.log(JSON.stringify({
|
|
727
|
+
success: false,
|
|
728
|
+
mode: 'chat',
|
|
729
|
+
model: this.currentModel,
|
|
730
|
+
content: '',
|
|
731
|
+
error: errorMsg,
|
|
732
|
+
}, null, 2));
|
|
733
|
+
}
|
|
734
|
+
else {
|
|
735
|
+
this.logger.error(errorMsg);
|
|
736
|
+
}
|
|
709
737
|
}
|
|
710
738
|
}
|
|
711
739
|
async runAgentTurn(prompt) {
|
package/dist/utils/api.d.ts
CHANGED
|
@@ -195,6 +195,11 @@ export declare class APIClient {
|
|
|
195
195
|
getVigFlowBaseUrls(): string[];
|
|
196
196
|
getTemplateServiceBaseUrls(): string[];
|
|
197
197
|
private isFrontendTask;
|
|
198
|
+
/**
|
|
199
|
+
* Returns true when the prompt describes a read-only / analysis task.
|
|
200
|
+
* Used to suppress preview gate and other write-oriented side effects.
|
|
201
|
+
*/
|
|
202
|
+
isAnalysisOnlyTask(message?: string, context?: Record<string, any>): boolean;
|
|
198
203
|
private normalizeWorkspaceRelativePath;
|
|
199
204
|
private listFrontendWorkspaceFiles;
|
|
200
205
|
private chooseFrontendPreviewEntry;
|
|
@@ -273,6 +278,11 @@ export declare class APIClient {
|
|
|
273
278
|
private buildFallbackFrontendJs;
|
|
274
279
|
private replaceMissingLocalAssetReferences;
|
|
275
280
|
formatV3AgentResponse(data: any): string;
|
|
281
|
+
/**
|
|
282
|
+
* Build a human-readable answer from the tool results in a V3 event
|
|
283
|
+
* stream when the server didn't emit a proper complete/message event.
|
|
284
|
+
*/
|
|
285
|
+
private synthesizeAnswerFromV3Events;
|
|
276
286
|
collectV3AgentStream(response: Response, context?: Record<string, any>): Promise<any>;
|
|
277
287
|
runV3AgentWorkflow(message: string, context?: Record<string, any>): Promise<V3AgentWorkflowResponse>;
|
|
278
288
|
private formatOperatorResponse;
|
|
@@ -338,6 +348,11 @@ export declare class APIClient {
|
|
|
338
348
|
reason: string;
|
|
339
349
|
}[];
|
|
340
350
|
}>;
|
|
351
|
+
/**
|
|
352
|
+
* Lightweight client-side syntax error detection.
|
|
353
|
+
* Returns a human-readable description of obvious errors, or empty string.
|
|
354
|
+
*/
|
|
355
|
+
private detectSyntaxErrors;
|
|
341
356
|
private resolveModelId;
|
|
342
357
|
private getCoderHealth;
|
|
343
358
|
private getModelsHealth;
|
package/dist/utils/api.js
CHANGED
|
@@ -328,11 +328,29 @@ class APIClient {
|
|
|
328
328
|
return [...new Set(urls)];
|
|
329
329
|
}
|
|
330
330
|
isFrontendTask(message = '', context = {}) {
|
|
331
|
+
// Never treat analysis-only tasks as frontend tasks — preview gate
|
|
332
|
+
// should not fire for read-only inspection prompts.
|
|
333
|
+
if (this.isAnalysisOnlyTask(message, context)) {
|
|
334
|
+
return false;
|
|
335
|
+
}
|
|
331
336
|
const prompt = String(message || '');
|
|
332
337
|
const expectedFiles = this.extractExpectedWorkspaceFiles(message, context);
|
|
333
338
|
return /(premium|polished|landing|site|page|dashboard|saas|frontend|ui|pricing|showcase|hero|responsive)/i.test(prompt)
|
|
334
339
|
|| expectedFiles.some((filePath) => /\.(html|css|js)$/i.test(filePath));
|
|
335
340
|
}
|
|
341
|
+
/**
|
|
342
|
+
* Returns true when the prompt describes a read-only / analysis task.
|
|
343
|
+
* Used to suppress preview gate and other write-oriented side effects.
|
|
344
|
+
*/
|
|
345
|
+
isAnalysisOnlyTask(message = '', context = {}) {
|
|
346
|
+
const prompt = String(message || '').toLowerCase();
|
|
347
|
+
const taskType = String(context.agentTaskType || '').toLowerCase();
|
|
348
|
+
if (taskType === 'debugging' || taskType === 'analysis' || taskType === 'analysis_only')
|
|
349
|
+
return true;
|
|
350
|
+
if (context.workflowType === 'analysis_only')
|
|
351
|
+
return true;
|
|
352
|
+
return /^(what|which|how many|list|show|check|inspect|analyze|analyse|audit|explain|describe|summarize|summarise|review|overview|find|count|read|look at|tell me|diagnos)/i.test(prompt.trim());
|
|
353
|
+
}
|
|
336
354
|
normalizeWorkspaceRelativePath(filePath) {
|
|
337
355
|
return String(filePath || '').trim().replace(/\\/g, '/').replace(/^\.\//, '');
|
|
338
356
|
}
|
|
@@ -596,7 +614,7 @@ class APIClient {
|
|
|
596
614
|
};
|
|
597
615
|
}
|
|
598
616
|
// Cap artifact sizes to prevent 413 Payload Too Large
|
|
599
|
-
const PREVIEW_MAX_ARTIFACT_BYTES =
|
|
617
|
+
const PREVIEW_MAX_ARTIFACT_BYTES = 250 * 1024;
|
|
600
618
|
const html = artifacts.html.slice(0, PREVIEW_MAX_ARTIFACT_BYTES);
|
|
601
619
|
const css = (artifacts.css || '').slice(0, PREVIEW_MAX_ARTIFACT_BYTES);
|
|
602
620
|
const js = (artifacts.js || '').slice(0, PREVIEW_MAX_ARTIFACT_BYTES);
|
|
@@ -2292,10 +2310,74 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
2292
2310
|
if (messageEvent) {
|
|
2293
2311
|
return messageEvent.content;
|
|
2294
2312
|
}
|
|
2313
|
+
// Synthesize a grounded answer from the tool-call evidence the
|
|
2314
|
+
// agent produced, rather than dumping the raw event trace.
|
|
2315
|
+
const answer = this.synthesizeAnswerFromV3Events(data.events);
|
|
2316
|
+
if (answer) {
|
|
2317
|
+
return answer;
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
// Last resort: if data has files written, report them.
|
|
2321
|
+
if (data?.files && typeof data.files === 'object' && Object.keys(data.files).length > 0) {
|
|
2322
|
+
const fileList = Object.keys(data.files).join(', ');
|
|
2323
|
+
return `Agent wrote workspace files: ${fileList}`;
|
|
2295
2324
|
}
|
|
2296
2325
|
const text = JSON.stringify(data, null, 2);
|
|
2297
2326
|
return text.length > 12000 ? `${text.slice(0, 12000)}\n\n[V3 agent output truncated]` : text;
|
|
2298
2327
|
}
|
|
2328
|
+
/**
|
|
2329
|
+
* Build a human-readable answer from the tool results in a V3 event
|
|
2330
|
+
* stream when the server didn't emit a proper complete/message event.
|
|
2331
|
+
*/
|
|
2332
|
+
synthesizeAnswerFromV3Events(events) {
|
|
2333
|
+
const toolResults = [];
|
|
2334
|
+
const filesRead = [];
|
|
2335
|
+
const filesWritten = [];
|
|
2336
|
+
let lastAssistantText = '';
|
|
2337
|
+
for (const event of events) {
|
|
2338
|
+
if (!event)
|
|
2339
|
+
continue;
|
|
2340
|
+
if (event.type === 'tool_result' && event.success && typeof event.output === 'string') {
|
|
2341
|
+
const name = event.name || 'unknown_tool';
|
|
2342
|
+
if (name === 'read_file' && typeof event.target === 'string') {
|
|
2343
|
+
filesRead.push(event.target);
|
|
2344
|
+
}
|
|
2345
|
+
else if ((name === 'write_file' || name === 'create_file') && typeof event.target === 'string') {
|
|
2346
|
+
filesWritten.push(event.target);
|
|
2347
|
+
}
|
|
2348
|
+
else {
|
|
2349
|
+
// Keep last ~300 chars of output for context
|
|
2350
|
+
const excerpt = event.output.length > 300 ? event.output.slice(-300) : event.output;
|
|
2351
|
+
toolResults.push(`[${name}] ${excerpt}`);
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
if (event.type === 'assistant' && typeof event.content === 'string' && event.content.trim()) {
|
|
2355
|
+
lastAssistantText = event.content.trim();
|
|
2356
|
+
}
|
|
2357
|
+
// Some servers emit 'text' events for incremental assistant text
|
|
2358
|
+
if (event.type === 'text' && typeof event.content === 'string' && event.content.trim()) {
|
|
2359
|
+
lastAssistantText = event.content.trim();
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
// Prefer the last assistant text the model emitted
|
|
2363
|
+
if (lastAssistantText.length > 20) {
|
|
2364
|
+
return lastAssistantText;
|
|
2365
|
+
}
|
|
2366
|
+
// Otherwise build a summary from tool evidence
|
|
2367
|
+
const sections = [];
|
|
2368
|
+
if (filesRead.length > 0) {
|
|
2369
|
+
sections.push(`Files inspected: ${filesRead.join(', ')}`);
|
|
2370
|
+
}
|
|
2371
|
+
if (filesWritten.length > 0) {
|
|
2372
|
+
sections.push(`Files written: ${filesWritten.join(', ')}`);
|
|
2373
|
+
}
|
|
2374
|
+
if (toolResults.length > 0) {
|
|
2375
|
+
sections.push(toolResults.slice(-5).join('\n'));
|
|
2376
|
+
}
|
|
2377
|
+
return sections.length > 0
|
|
2378
|
+
? sections.join('\n\n')
|
|
2379
|
+
: '';
|
|
2380
|
+
}
|
|
2299
2381
|
async collectV3AgentStream(response, context = {}) {
|
|
2300
2382
|
if (!response.body || typeof response.body.getReader !== 'function') {
|
|
2301
2383
|
return response.json();
|
|
@@ -2660,6 +2742,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
2660
2742
|
const timeoutMs = context.operatorTimeoutMs || DEFAULT_OPERATOR_TIMEOUT_MS;
|
|
2661
2743
|
const errors = [];
|
|
2662
2744
|
const authToken = this.config.get('authToken');
|
|
2745
|
+
// Collect a lightweight workspace file listing so the operator can
|
|
2746
|
+
// ground its analysis in real files rather than returning files_analyzed: 0.
|
|
2747
|
+
const workspacePath = executionContext.workspacePath || executionContext.projectPath || executionContext.targetPath || process.cwd();
|
|
2748
|
+
const workspaceSummary = this.buildLocalWorkspaceSummary(workspacePath);
|
|
2663
2749
|
for (const baseUrl of this.getOperatorBaseUrls()) {
|
|
2664
2750
|
const controller = new AbortController();
|
|
2665
2751
|
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
@@ -2683,8 +2769,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
2683
2769
|
trace_id: executionContext.traceId,
|
|
2684
2770
|
mcp_context_id: executionContext.mcpContextId || null,
|
|
2685
2771
|
context: {
|
|
2686
|
-
workspace: { path:
|
|
2687
|
-
workspace_path:
|
|
2772
|
+
workspace: { path: workspacePath },
|
|
2773
|
+
workspace_path: workspacePath,
|
|
2774
|
+
workspace_summary: workspaceSummary,
|
|
2688
2775
|
model: this.resolveModelId(executionContext.model || 'code-8b'),
|
|
2689
2776
|
history: executionContext.history || [],
|
|
2690
2777
|
executionSurface: executionContext.executionSurface || 'cli',
|
|
@@ -3110,9 +3197,19 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3110
3197
|
}
|
|
3111
3198
|
// Code operations - Using Vigthoria Centralized API
|
|
3112
3199
|
async generateCode(prompt, language, model) {
|
|
3113
|
-
// Prepend scope-enforcement instruction so the model
|
|
3114
|
-
// a small task into an oversized glossy
|
|
3115
|
-
const scopedPrompt =
|
|
3200
|
+
// Prepend a forceful scope-enforcement instruction so the model
|
|
3201
|
+
// doesn't expand a small task into an oversized glossy page.
|
|
3202
|
+
const scopedPrompt = [
|
|
3203
|
+
'IMPORTANT — SCOPE CONSTRAINTS:',
|
|
3204
|
+
'1. Follow the user\'s scope literally. Output ONLY what was requested.',
|
|
3205
|
+
'2. If the user says "tiny", "small", "simple", or "minimal", produce ≤ 50 lines of code.',
|
|
3206
|
+
'3. Do NOT add: hero sections, animations, gradients, Google Fonts, Font Awesome, responsive breakpoints, or landing-page patterns UNLESS the user explicitly requests them.',
|
|
3207
|
+
'4. Do NOT add external CDN links or dependencies unless the user asks for them.',
|
|
3208
|
+
'5. Prefer inline styles or a small <style> block. No large CSS frameworks.',
|
|
3209
|
+
'6. Return raw code only — no markdown fences, no explanations.',
|
|
3210
|
+
'',
|
|
3211
|
+
prompt,
|
|
3212
|
+
].join('\n');
|
|
3116
3213
|
const response = await this.client.post('/api/ai/generate', {
|
|
3117
3214
|
prompt: scopedPrompt,
|
|
3118
3215
|
language,
|
|
@@ -3148,23 +3245,142 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3148
3245
|
language,
|
|
3149
3246
|
});
|
|
3150
3247
|
const raw = response.data ?? {};
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3248
|
+
const score = typeof raw.score === 'number' ? raw.score : 0;
|
|
3249
|
+
const issues = Array.isArray(raw.issues) ? raw.issues : [];
|
|
3250
|
+
const suggestions = Array.isArray(raw.suggestions) ? raw.suggestions : [];
|
|
3251
|
+
// Prevent contradictory output: low score but zero issues
|
|
3252
|
+
if (score < 50 && issues.length === 0) {
|
|
3253
|
+
issues.push({
|
|
3254
|
+
type: 'quality',
|
|
3255
|
+
line: 0,
|
|
3256
|
+
message: `The analysis returned a low quality score (${score}/100) but did not enumerate specific issues. This typically means the review model returned incomplete data. Consider re-running the review or inspecting the file manually.`,
|
|
3257
|
+
severity: 'warning',
|
|
3258
|
+
});
|
|
3259
|
+
}
|
|
3260
|
+
return { score, issues, suggestions };
|
|
3156
3261
|
}
|
|
3157
3262
|
async fixCode(code, language, fixType) {
|
|
3263
|
+
// Client-side syntax pre-check: detect obvious errors and include
|
|
3264
|
+
// them in the request so the model has concrete signals.
|
|
3265
|
+
const syntaxHints = this.detectSyntaxErrors(code, language);
|
|
3266
|
+
const augmentedCode = syntaxHints
|
|
3267
|
+
? `// SYNTAX ERRORS DETECTED BY CLIENT:\n// ${syntaxHints}\n\n${code}`
|
|
3268
|
+
: code;
|
|
3158
3269
|
const response = await this.client.post('/api/ai/fix', {
|
|
3159
|
-
code,
|
|
3270
|
+
code: augmentedCode,
|
|
3160
3271
|
language,
|
|
3161
3272
|
fixType,
|
|
3162
3273
|
});
|
|
3163
3274
|
const raw = response.data ?? {};
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3275
|
+
let fixed = typeof raw.fixed === 'string' ? raw.fixed : (typeof raw.code === 'string' ? raw.code : code);
|
|
3276
|
+
let changes = Array.isArray(raw.changes) ? raw.changes : [];
|
|
3277
|
+
// If server returned no changes but we found syntax errors, strip
|
|
3278
|
+
// our injected comment prefix from the returned code and attempt
|
|
3279
|
+
// a basic client-side repair.
|
|
3280
|
+
if (changes.length === 0 && syntaxHints && fixed === augmentedCode) {
|
|
3281
|
+
fixed = code; // restore original
|
|
3282
|
+
}
|
|
3283
|
+
// Strip the injected comment block if it leaked into the output
|
|
3284
|
+
if (fixed.startsWith('// SYNTAX ERRORS DETECTED BY CLIENT:')) {
|
|
3285
|
+
const idx = fixed.indexOf('\n\n');
|
|
3286
|
+
if (idx !== -1) {
|
|
3287
|
+
fixed = fixed.slice(idx + 2);
|
|
3288
|
+
}
|
|
3289
|
+
}
|
|
3290
|
+
// If there are still no changes but the fixed code differs, build a
|
|
3291
|
+
// synthetic change entry so the user sees something actionable.
|
|
3292
|
+
if (changes.length === 0 && fixed !== code) {
|
|
3293
|
+
changes = [{ line: 1, before: '(original code)', after: '(fixed code)', reason: syntaxHints || 'AI-suggested fix' }];
|
|
3294
|
+
}
|
|
3295
|
+
return { fixed, changes };
|
|
3296
|
+
}
|
|
3297
|
+
/**
|
|
3298
|
+
* Lightweight client-side syntax error detection.
|
|
3299
|
+
* Returns a human-readable description of obvious errors, or empty string.
|
|
3300
|
+
*/
|
|
3301
|
+
detectSyntaxErrors(code, language) {
|
|
3302
|
+
const lang = language.toLowerCase();
|
|
3303
|
+
const errors = [];
|
|
3304
|
+
if (lang === 'javascript' || lang === 'typescript' || lang === 'js' || lang === 'ts') {
|
|
3305
|
+
// Bracket matching
|
|
3306
|
+
let braces = 0, brackets = 0, parens = 0;
|
|
3307
|
+
// Track string context (simple heuristic — not a full parser)
|
|
3308
|
+
let inString = null;
|
|
3309
|
+
let inLineComment = false;
|
|
3310
|
+
let inBlockComment = false;
|
|
3311
|
+
for (let i = 0; i < code.length; i++) {
|
|
3312
|
+
const ch = code[i];
|
|
3313
|
+
const next = code[i + 1] || '';
|
|
3314
|
+
if (inLineComment) {
|
|
3315
|
+
if (ch === '\n')
|
|
3316
|
+
inLineComment = false;
|
|
3317
|
+
continue;
|
|
3318
|
+
}
|
|
3319
|
+
if (inBlockComment) {
|
|
3320
|
+
if (ch === '*' && next === '/') {
|
|
3321
|
+
inBlockComment = false;
|
|
3322
|
+
i++;
|
|
3323
|
+
}
|
|
3324
|
+
continue;
|
|
3325
|
+
}
|
|
3326
|
+
if (inString) {
|
|
3327
|
+
if (ch === inString && code[i - 1] !== '\\')
|
|
3328
|
+
inString = null;
|
|
3329
|
+
continue;
|
|
3330
|
+
}
|
|
3331
|
+
if (ch === '/' && next === '/') {
|
|
3332
|
+
inLineComment = true;
|
|
3333
|
+
continue;
|
|
3334
|
+
}
|
|
3335
|
+
if (ch === '/' && next === '*') {
|
|
3336
|
+
inBlockComment = true;
|
|
3337
|
+
continue;
|
|
3338
|
+
}
|
|
3339
|
+
if (ch === '"' || ch === "'" || ch === '`') {
|
|
3340
|
+
inString = ch;
|
|
3341
|
+
continue;
|
|
3342
|
+
}
|
|
3343
|
+
if (ch === '{')
|
|
3344
|
+
braces++;
|
|
3345
|
+
else if (ch === '}')
|
|
3346
|
+
braces--;
|
|
3347
|
+
else if (ch === '[')
|
|
3348
|
+
brackets++;
|
|
3349
|
+
else if (ch === ']')
|
|
3350
|
+
brackets--;
|
|
3351
|
+
else if (ch === '(')
|
|
3352
|
+
parens++;
|
|
3353
|
+
else if (ch === ')')
|
|
3354
|
+
parens--;
|
|
3355
|
+
}
|
|
3356
|
+
if (braces !== 0)
|
|
3357
|
+
errors.push(`Mismatched braces (balance: ${braces > 0 ? '+' : ''}${braces})`);
|
|
3358
|
+
if (brackets !== 0)
|
|
3359
|
+
errors.push(`Mismatched brackets (balance: ${brackets > 0 ? '+' : ''}${brackets})`);
|
|
3360
|
+
if (parens !== 0)
|
|
3361
|
+
errors.push(`Mismatched parentheses (balance: ${parens > 0 ? '+' : ''}${parens})`);
|
|
3362
|
+
// Detect obvious keyword typos
|
|
3363
|
+
const lines = code.split('\n');
|
|
3364
|
+
for (let li = 0; li < lines.length; li++) {
|
|
3365
|
+
const line = lines[li];
|
|
3366
|
+
if (/\bfuncion\b|\bfuction\b|\bfuntion\b/i.test(line))
|
|
3367
|
+
errors.push(`Line ${li + 1}: Misspelled 'function'`);
|
|
3368
|
+
if (/\bretrun\b/i.test(line))
|
|
3369
|
+
errors.push(`Line ${li + 1}: Misspelled 'return'`);
|
|
3370
|
+
if (/\bconts\b|\bcosnt\b/i.test(line))
|
|
3371
|
+
errors.push(`Line ${li + 1}: Misspelled 'const'`);
|
|
3372
|
+
}
|
|
3373
|
+
}
|
|
3374
|
+
if (lang === 'python' || lang === 'py') {
|
|
3375
|
+
const lines = code.split('\n');
|
|
3376
|
+
for (let li = 0; li < lines.length; li++) {
|
|
3377
|
+
const line = lines[li];
|
|
3378
|
+
if (/^\s*(def|class|if|elif|else|for|while|try|except|with)\b/.test(line) && !line.trimEnd().endsWith(':') && !line.trimEnd().endsWith('\\')) {
|
|
3379
|
+
errors.push(`Line ${li + 1}: Missing colon after '${line.trim().split(/\s/)[0]}'`);
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
3382
|
+
}
|
|
3383
|
+
return errors.join('; ');
|
|
3168
3384
|
}
|
|
3169
3385
|
// Model resolution - maps Vigthoria model names to internal IDs
|
|
3170
3386
|
// INTERNAL USE ONLY - users see only Vigthoria branding
|