vigthoria-cli 1.6.29 → 1.6.31
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 +6 -2
- package/dist/commands/edit.js +31 -2
- package/dist/commands/explain.js +6 -0
- package/dist/commands/generate.js +6 -0
- package/dist/commands/review.js +6 -0
- package/dist/utils/api.d.ts +12 -0
- package/dist/utils/api.js +135 -54
- package/package.json +1 -1
package/dist/commands/auth.js
CHANGED
|
@@ -240,10 +240,14 @@ class AuthCommand {
|
|
|
240
240
|
if (capabilityStatus.devtoolsBridge.error) {
|
|
241
241
|
console.log(chalk_1.default.gray(' Error: ') + chalk_1.default.gray(capabilityStatus.devtoolsBridge.error));
|
|
242
242
|
}
|
|
243
|
-
// Auth scope summary
|
|
243
|
+
// Auth scope summary — use a real server-side probe for Model Auth
|
|
244
|
+
const tokenValidation = await this.api.validateToken();
|
|
244
245
|
console.log();
|
|
245
246
|
console.log(chalk_1.default.white('Auth Scopes:'));
|
|
246
|
-
console.log(chalk_1.default.gray(' Model Auth: ') + (
|
|
247
|
+
console.log(chalk_1.default.gray(' Model Auth: ') + (tokenValidation.valid ? chalk_1.default.green('Valid') : chalk_1.default.red('Invalid')) + chalk_1.default.gray(' (used by chat, agent, review, explain, generate, fix)'));
|
|
248
|
+
if (!tokenValidation.valid && tokenValidation.error) {
|
|
249
|
+
console.log(chalk_1.default.gray(' ') + chalk_1.default.red(tokenValidation.error));
|
|
250
|
+
}
|
|
247
251
|
console.log(chalk_1.default.gray(' Repo Auth: ') + (capabilityStatus.repoMemory.ok ? chalk_1.default.green('Active') : chalk_1.default.yellow('Inactive')) + chalk_1.default.gray(' (used by repo push/pull/list only)'));
|
|
248
252
|
console.log(chalk_1.default.gray(' Bridge Auth: ') + (capabilityStatus.devtoolsBridge.ok ? chalk_1.default.green('Connected') : chalk_1.default.gray('N/A')) + chalk_1.default.gray(' (used by --bridge flag only)'));
|
|
249
253
|
console.log();
|
package/dist/commands/edit.js
CHANGED
|
@@ -29,6 +29,12 @@ class EditCommand {
|
|
|
29
29
|
this.logger.error('Not authenticated. Run: vigthoria login');
|
|
30
30
|
return;
|
|
31
31
|
}
|
|
32
|
+
// Server-side token validation — fail fast instead of waiting for 401
|
|
33
|
+
const tokenCheck = await this.api.validateToken();
|
|
34
|
+
if (!tokenCheck.valid) {
|
|
35
|
+
this.logger.error(tokenCheck.error || 'Auth token is invalid. Run: vigthoria login');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
32
38
|
// Read file
|
|
33
39
|
const file = this.fileUtils.readFile(filePath);
|
|
34
40
|
if (!file) {
|
|
@@ -46,6 +52,11 @@ class EditCommand {
|
|
|
46
52
|
this.logger.error('The --apply flag requires --instruction. Example: vigthoria edit file.ts --apply --instruction "fix the bug"');
|
|
47
53
|
return;
|
|
48
54
|
}
|
|
55
|
+
// Non-TTY stdin cannot prompt interactively
|
|
56
|
+
if (!process.stdin.isTTY) {
|
|
57
|
+
this.logger.error('No --instruction provided and stdin is not interactive. Use: vigthoria edit file.ts --instruction "..."');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
49
60
|
const answer = await inquirer_1.default.prompt([
|
|
50
61
|
{
|
|
51
62
|
type: 'input',
|
|
@@ -93,11 +104,14 @@ Return the complete modified file content:`,
|
|
|
93
104
|
], options.model);
|
|
94
105
|
spinner.stop();
|
|
95
106
|
// Extract code from response
|
|
96
|
-
|
|
107
|
+
let modifiedCode = this.extractCode(response.message, file.language);
|
|
97
108
|
if (!modifiedCode) {
|
|
98
109
|
this.logger.error('Failed to generate valid code changes');
|
|
99
110
|
return;
|
|
100
111
|
}
|
|
112
|
+
// Always deduplicate — extractCode only dedupes on the no-fence
|
|
113
|
+
// fallback path, so fenced responses (the common case) need this.
|
|
114
|
+
modifiedCode = this.deduplicateCode(modifiedCode);
|
|
101
115
|
// Show diff and apply
|
|
102
116
|
if (options.apply) {
|
|
103
117
|
await this.applyFix(file.path, file.content, modifiedCode);
|
|
@@ -118,6 +132,12 @@ Return the complete modified file content:`,
|
|
|
118
132
|
this.logger.error('Not authenticated. Run: vigthoria login');
|
|
119
133
|
return;
|
|
120
134
|
}
|
|
135
|
+
// Server-side token validation — fail fast instead of waiting for 401
|
|
136
|
+
const tokenCheck = await this.api.validateToken();
|
|
137
|
+
if (!tokenCheck.valid) {
|
|
138
|
+
this.logger.error(tokenCheck.error || 'Auth token is invalid. Run: vigthoria login');
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
121
141
|
// Read file
|
|
122
142
|
const file = this.fileUtils.readFile(filePath);
|
|
123
143
|
if (!file) {
|
|
@@ -238,11 +258,15 @@ Return the complete modified file content:`,
|
|
|
238
258
|
runEnd++;
|
|
239
259
|
}
|
|
240
260
|
const runLen = runEnd - i;
|
|
261
|
+
// Check if this run is "effectively at end" — only trailing empty
|
|
262
|
+
// lines follow the duplicate pair.
|
|
263
|
+
const isAtEffectiveEnd = runEnd === lines.length
|
|
264
|
+
|| lines.slice(runEnd).every(l => l.trim() === '');
|
|
241
265
|
if (runLen >= 3) {
|
|
242
266
|
// 3+ identical lines is almost certainly stutter — keep one
|
|
243
267
|
deduped.push(lines[i]);
|
|
244
268
|
}
|
|
245
|
-
else if (runLen === 2 &&
|
|
269
|
+
else if (runLen === 2 && isAtEffectiveEnd) {
|
|
246
270
|
// Exactly 2 identical lines at the very end — trailing stutter
|
|
247
271
|
deduped.push(lines[i]);
|
|
248
272
|
}
|
|
@@ -297,6 +321,11 @@ Return the complete modified file content:`,
|
|
|
297
321
|
await this.applyFix(filePath, original, modified);
|
|
298
322
|
return;
|
|
299
323
|
}
|
|
324
|
+
// Non-TTY: show the diff but don't try to prompt — re-run with --apply
|
|
325
|
+
if (!process.stdin.isTTY) {
|
|
326
|
+
this.logger.info('Non-interactive mode. Re-run with --apply to apply changes.');
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
300
329
|
const { action } = await inquirer_1.default.prompt([
|
|
301
330
|
{
|
|
302
331
|
type: 'list',
|
package/dist/commands/explain.js
CHANGED
|
@@ -33,6 +33,12 @@ class ExplainCommand {
|
|
|
33
33
|
this.logger.error('Not authenticated. Run: vigthoria login');
|
|
34
34
|
return;
|
|
35
35
|
}
|
|
36
|
+
// Server-side token validation — fail fast instead of waiting for 401
|
|
37
|
+
const tokenCheck = await this.api.validateToken();
|
|
38
|
+
if (!tokenCheck.valid) {
|
|
39
|
+
this.logger.error(tokenCheck.error || 'Auth token is invalid. Run: vigthoria login');
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
36
42
|
// Read file
|
|
37
43
|
const file = this.fileUtils.readFile(filePath);
|
|
38
44
|
if (!file) {
|
|
@@ -31,6 +31,12 @@ class GenerateCommand {
|
|
|
31
31
|
this.logger.error('Not authenticated. Run: vigthoria login');
|
|
32
32
|
return;
|
|
33
33
|
}
|
|
34
|
+
// Server-side token validation — fail fast instead of waiting for 401
|
|
35
|
+
const tokenCheck = await this.api.validateToken();
|
|
36
|
+
if (!tokenCheck.valid) {
|
|
37
|
+
this.logger.error(tokenCheck.error || 'Auth token is invalid. Run: vigthoria login');
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
34
40
|
// Determine mode
|
|
35
41
|
const proMode = options.pro === true;
|
|
36
42
|
// Auto-detect language from description if not explicitly specified
|
package/dist/commands/review.js
CHANGED
|
@@ -33,6 +33,12 @@ class ReviewCommand {
|
|
|
33
33
|
this.logger.error('Not authenticated. Run: vigthoria login');
|
|
34
34
|
return;
|
|
35
35
|
}
|
|
36
|
+
// Server-side token validation — fail fast instead of waiting for 401
|
|
37
|
+
const tokenCheck = await this.api.validateToken();
|
|
38
|
+
if (!tokenCheck.valid) {
|
|
39
|
+
this.logger.error(tokenCheck.error || 'Auth token is invalid. Run: vigthoria login');
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
36
42
|
// Read file
|
|
37
43
|
const file = this.fileUtils.readFile(filePath);
|
|
38
44
|
if (!file) {
|
package/dist/utils/api.d.ts
CHANGED
|
@@ -201,6 +201,17 @@ export declare class APIClient {
|
|
|
201
201
|
private refreshToken;
|
|
202
202
|
getSubscriptionStatus(): Promise<void>;
|
|
203
203
|
private getAccessToken;
|
|
204
|
+
/**
|
|
205
|
+
* Validate the current auth token against the Coder API.
|
|
206
|
+
* Returns { valid: true } when the server accepts the token,
|
|
207
|
+
* { valid: false, error } when the token is rejected (401/403),
|
|
208
|
+
* and { valid: true } when the server is unreachable (network error)
|
|
209
|
+
* so that offline/degraded scenarios don't block the user.
|
|
210
|
+
*/
|
|
211
|
+
validateToken(): Promise<{
|
|
212
|
+
valid: boolean;
|
|
213
|
+
error?: string;
|
|
214
|
+
}>;
|
|
204
215
|
getV3AgentBaseUrls(preferLocal?: boolean): string[];
|
|
205
216
|
getV3AgentRunUrl(baseUrl: string): string;
|
|
206
217
|
getV3AgentContinueUrl(baseUrl: string): string;
|
|
@@ -334,6 +345,7 @@ export declare class APIClient {
|
|
|
334
345
|
private getSelfHostedFallbackModelId;
|
|
335
346
|
chatStream(messages: ChatMessage[], model: string): AsyncGenerator<StreamChunk>;
|
|
336
347
|
chatWithCallback(messages: ChatMessage[], model: string, onChunk: (chunk: string) => void, onDone: () => void, onError: (error: Error) => void): Promise<void>;
|
|
348
|
+
private chatComplete;
|
|
337
349
|
generateCode(prompt: string, language: string, model: string): Promise<string>;
|
|
338
350
|
/**
|
|
339
351
|
* Ensure code has balanced curly braces by appending missing closing braces.
|
package/dist/utils/api.js
CHANGED
|
@@ -340,6 +340,49 @@ class APIClient {
|
|
|
340
340
|
|| this.config.get('authToken')
|
|
341
341
|
|| null;
|
|
342
342
|
}
|
|
343
|
+
/**
|
|
344
|
+
* Validate the current auth token against the Coder API.
|
|
345
|
+
* Returns { valid: true } when the server accepts the token,
|
|
346
|
+
* { valid: false, error } when the token is rejected (401/403),
|
|
347
|
+
* and { valid: true } when the server is unreachable (network error)
|
|
348
|
+
* so that offline/degraded scenarios don't block the user.
|
|
349
|
+
*/
|
|
350
|
+
async validateToken() {
|
|
351
|
+
const token = this.getAccessToken();
|
|
352
|
+
if (!token) {
|
|
353
|
+
return { valid: false, error: 'No auth token configured. Run: vigthoria login' };
|
|
354
|
+
}
|
|
355
|
+
// Verify auth against the Model Router (api.vigthoria.io) which is
|
|
356
|
+
// the backend all AI commands actually use. Falls back to the Coder
|
|
357
|
+
// profile endpoint when the Model Router is unreachable so that
|
|
358
|
+
// offline/degraded scenarios don't block the user.
|
|
359
|
+
try {
|
|
360
|
+
await this.modelRouterClient.get('/v1/models', { timeout: 10000 });
|
|
361
|
+
return { valid: true };
|
|
362
|
+
}
|
|
363
|
+
catch (mrError) {
|
|
364
|
+
const mrAxErr = mrError;
|
|
365
|
+
if (mrAxErr.response?.status === 401 || mrAxErr.response?.status === 403) {
|
|
366
|
+
return { valid: false, error: 'Auth token expired or invalid. Run: vigthoria login' };
|
|
367
|
+
}
|
|
368
|
+
// Model Router unreachable — try Coder profile as fallback
|
|
369
|
+
try {
|
|
370
|
+
await this.client.get('/api/user/profile', { timeout: 10000 });
|
|
371
|
+
return { valid: true };
|
|
372
|
+
}
|
|
373
|
+
catch (error) {
|
|
374
|
+
if (error instanceof CLIError && error.category === 'auth') {
|
|
375
|
+
return { valid: false, error: 'Auth token expired or invalid. Run: vigthoria login' };
|
|
376
|
+
}
|
|
377
|
+
const axErr = error;
|
|
378
|
+
if (axErr.response?.status === 401 || axErr.response?.status === 403) {
|
|
379
|
+
return { valid: false, error: 'Auth token expired or invalid. Run: vigthoria login' };
|
|
380
|
+
}
|
|
381
|
+
// Both unreachable — don't assume token is bad
|
|
382
|
+
return { valid: true };
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
343
386
|
getV3AgentBaseUrls(preferLocal = false) {
|
|
344
387
|
const configuredApiUrl = String(this.config.get('apiUrl') || 'https://coder.vigthoria.io').replace(/\/$/, '');
|
|
345
388
|
const allowLocalV3Agent = process.env.VIGTHORIA_ALLOW_LOCAL_V3_AGENT === '1' || preferLocal;
|
|
@@ -3326,7 +3369,26 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3326
3369
|
});
|
|
3327
3370
|
});
|
|
3328
3371
|
}
|
|
3329
|
-
//
|
|
3372
|
+
// ─── Chat completion helper ────────────────────────────────────────
|
|
3373
|
+
// Routes all AI file commands through the Model Router
|
|
3374
|
+
// (/v1/chat/completions on api.vigthoria.io) which is the only
|
|
3375
|
+
// backend that reliably accepts our auth token.
|
|
3376
|
+
async chatComplete(systemPrompt, userPrompt, model, maxTokens) {
|
|
3377
|
+
const resolvedModel = model ? this.resolvePermittedModelId(model) : 'vigthoria-v3-code-30b';
|
|
3378
|
+
const response = await this.modelRouterClient.post('/v1/chat/completions', {
|
|
3379
|
+
model: resolvedModel,
|
|
3380
|
+
messages: [
|
|
3381
|
+
{ role: 'system', content: systemPrompt },
|
|
3382
|
+
{ role: 'user', content: userPrompt },
|
|
3383
|
+
],
|
|
3384
|
+
max_tokens: maxTokens || this.config.get('preferences').maxTokens || 4096,
|
|
3385
|
+
temperature: 0.3,
|
|
3386
|
+
stream: false,
|
|
3387
|
+
});
|
|
3388
|
+
const content = response.data.choices?.[0]?.message?.content || response.data.choices?.[0]?.text || '';
|
|
3389
|
+
return typeof content === 'string' ? content : '';
|
|
3390
|
+
}
|
|
3391
|
+
// Code operations - Using Vigthoria Centralized API (via Model Router)
|
|
3330
3392
|
async generateCode(prompt, language, model) {
|
|
3331
3393
|
const isNonHtmlLang = !['html', 'css'].includes(language.toLowerCase());
|
|
3332
3394
|
const wordCount = prompt.trim().split(/\s+/).length;
|
|
@@ -3362,24 +3424,18 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3362
3424
|
lines.push('', prompt);
|
|
3363
3425
|
return lines.join('\n');
|
|
3364
3426
|
};
|
|
3365
|
-
// First attempt
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
});
|
|
3371
|
-
let code = response.data.code || '';
|
|
3427
|
+
// First attempt — route through Model Router chat completions
|
|
3428
|
+
const systemPrompt = `You are a code generator. Output ONLY raw ${language} code. No markdown fences, no explanations, no commentary. Just the code.`;
|
|
3429
|
+
let code = await this.chatComplete(systemPrompt, buildScopedPrompt(false), model);
|
|
3430
|
+
// Strip markdown fences if model included them
|
|
3431
|
+
code = code.replace(/^```[\w]*\n?/gm, '').replace(/\n?```$/gm, '').trim();
|
|
3372
3432
|
// Client-side validation: reject DOM-polluted or over-engineered responses for non-HTML languages
|
|
3373
3433
|
const needsRetry = isNonHtmlLang && (this.codeContainsDomPollution(code) ||
|
|
3374
3434
|
this.codeIsOverEngineered(code, prompt));
|
|
3375
3435
|
if (needsRetry) {
|
|
3376
|
-
// Retry once with stronger constraint
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
language,
|
|
3380
|
-
model: this.resolvePermittedModelId(model),
|
|
3381
|
-
});
|
|
3382
|
-
code = response.data.code || '';
|
|
3436
|
+
// Retry once with stronger constraint — via Model Router
|
|
3437
|
+
code = await this.chatComplete(systemPrompt, buildScopedPrompt(true), model);
|
|
3438
|
+
code = code.replace(/^```[\w]*\n?/gm, '').replace(/\n?```$/gm, '').trim();
|
|
3383
3439
|
// If still polluted, strip DOM code client-side
|
|
3384
3440
|
if (this.codeContainsDomPollution(code)) {
|
|
3385
3441
|
code = this.stripDomPollution(code, language);
|
|
@@ -3497,43 +3553,55 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3497
3553
|
}
|
|
3498
3554
|
// Senior Developer Mode - Planning + Generation + Quality Check
|
|
3499
3555
|
async generateProject(prompt, projectType, model) {
|
|
3500
|
-
const
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3556
|
+
const sysPrompt = [
|
|
3557
|
+
`You are a senior developer. Generate a complete ${projectType} project.`,
|
|
3558
|
+
'Return a JSON object with these fields:',
|
|
3559
|
+
' "code": the full source code as a string,',
|
|
3560
|
+
' "plan": an object describing the architecture,',
|
|
3561
|
+
' "quality": { "lineCount": number, "score": number (0-100) }',
|
|
3562
|
+
'Return ONLY the JSON object, no markdown fences.',
|
|
3563
|
+
].join('\n');
|
|
3564
|
+
const raw = await this.chatComplete(sysPrompt, prompt, model, 8192);
|
|
3565
|
+
try {
|
|
3566
|
+
const cleaned = raw.replace(/^```[\w]*\n?/gm, '').replace(/\n?```$/gm, '').trim();
|
|
3567
|
+
const parsed = JSON.parse(cleaned);
|
|
3568
|
+
return {
|
|
3569
|
+
code: parsed.code || raw,
|
|
3570
|
+
plan: parsed.plan,
|
|
3571
|
+
quality: parsed.quality,
|
|
3572
|
+
};
|
|
3573
|
+
}
|
|
3574
|
+
catch {
|
|
3575
|
+
return { code: raw.replace(/^```[\w]*\n?/gm, '').replace(/\n?```$/gm, '').trim() };
|
|
3576
|
+
}
|
|
3512
3577
|
}
|
|
3513
3578
|
async explainCode(code, language) {
|
|
3514
|
-
const
|
|
3515
|
-
|
|
3516
|
-
language,
|
|
3517
|
-
});
|
|
3518
|
-
return response.data.explanation;
|
|
3579
|
+
const sysPrompt = `You are a code explainer. Explain the following ${language} code clearly and concisely. Focus on what it does, how it works, and any notable patterns or potential issues.`;
|
|
3580
|
+
return this.chatComplete(sysPrompt, code);
|
|
3519
3581
|
}
|
|
3520
3582
|
async reviewCode(code, language) {
|
|
3521
|
-
const
|
|
3522
|
-
code
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3583
|
+
const sysPrompt = [
|
|
3584
|
+
`You are a senior code reviewer for ${language}. Analyze the code and return a JSON object with:`,
|
|
3585
|
+
' "score": number 0-100,',
|
|
3586
|
+
' "issues": [{ "type": string, "line": number, "message": string, "severity": "error"|"warning"|"info" }],',
|
|
3587
|
+
' "suggestions": [string]',
|
|
3588
|
+
'Rules:',
|
|
3589
|
+
'- Return concrete, line-specific issues with severity.',
|
|
3590
|
+
'- Every issue MUST reference a line number.',
|
|
3591
|
+
'- If the score is below 50, list at least 2 specific issues.',
|
|
3592
|
+
'- Prioritize REAL BUGS: wrong operators, logic errors, off-by-one, type mismatches.',
|
|
3593
|
+
'- Only report style issues AFTER listing all real bugs.',
|
|
3594
|
+
'- Return ONLY the JSON object, no markdown fences or extra text.',
|
|
3595
|
+
].join('\n');
|
|
3596
|
+
let raw = {};
|
|
3597
|
+
try {
|
|
3598
|
+
const result = await this.chatComplete(sysPrompt, code);
|
|
3599
|
+
const cleaned = result.replace(/^```[\w]*\n?/gm, '').replace(/\n?```$/gm, '').trim();
|
|
3600
|
+
raw = JSON.parse(cleaned);
|
|
3601
|
+
}
|
|
3602
|
+
catch {
|
|
3603
|
+
// If parsing fails, return minimal review
|
|
3604
|
+
}
|
|
3537
3605
|
const score = typeof raw.score === 'number' ? raw.score : 0;
|
|
3538
3606
|
const issues = Array.isArray(raw.issues) ? raw.issues : [];
|
|
3539
3607
|
const suggestions = Array.isArray(raw.suggestions) ? raw.suggestions : [];
|
|
@@ -3701,12 +3769,25 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3701
3769
|
: '';
|
|
3702
3770
|
}
|
|
3703
3771
|
const augmentedCode = preamble ? `${preamble}${code}` : code;
|
|
3704
|
-
const
|
|
3705
|
-
code:
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3772
|
+
const sysPrompt = [
|
|
3773
|
+
`You are a ${language} code fixer. Fix the code for: ${fixType}.`,
|
|
3774
|
+
'Return a JSON object with:',
|
|
3775
|
+
' "fixed": the corrected code as a string,',
|
|
3776
|
+
' "changes": [{ "line": number, "before": string, "after": string, "reason": string }]',
|
|
3777
|
+
'Rules:',
|
|
3778
|
+
'- Fix ONLY the issues related to the fix type.',
|
|
3779
|
+
'- Do not add comments, do not restructure beyond the minimal fix.',
|
|
3780
|
+
'- Return ONLY the JSON object, no markdown fences.',
|
|
3781
|
+
].join('\n');
|
|
3782
|
+
let raw = {};
|
|
3783
|
+
try {
|
|
3784
|
+
const result = await this.chatComplete(sysPrompt, augmentedCode);
|
|
3785
|
+
const cleaned = result.replace(/^```[\w]*\n?/gm, '').replace(/\n?```$/gm, '').trim();
|
|
3786
|
+
raw = JSON.parse(cleaned);
|
|
3787
|
+
}
|
|
3788
|
+
catch {
|
|
3789
|
+
// If parsing fails, fall through to client-side handling
|
|
3790
|
+
}
|
|
3710
3791
|
let fixed = typeof raw.fixed === 'string' ? raw.fixed : (typeof raw.code === 'string' ? raw.code : code);
|
|
3711
3792
|
let changes = Array.isArray(raw.changes) ? raw.changes : [];
|
|
3712
3793
|
// If server returned no changes but we found issues, strip
|