vigthoria-cli 1.6.20 → 1.6.21
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.d.ts +5 -0
- package/dist/commands/chat.js +51 -11
- package/dist/commands/deploy.js +16 -8
- package/dist/commands/edit.js +11 -1
- package/dist/commands/repo.js +14 -7
- package/dist/utils/api.d.ts +12 -0
- package/dist/utils/api.js +102 -28
- package/dist/utils/logger.d.ts +9 -1
- package/dist/utils/logger.js +13 -2
- package/package.json +1 -1
package/dist/commands/chat.d.ts
CHANGED
|
@@ -46,6 +46,11 @@ export declare class ChatCommand {
|
|
|
46
46
|
private resolveAgentExecutionPolicy;
|
|
47
47
|
private getMessagesForModel;
|
|
48
48
|
private isDiagnosticPrompt;
|
|
49
|
+
/**
|
|
50
|
+
* Returns true when the prompt is a simple lookup / analysis / read-only
|
|
51
|
+
* question — these should use analysis_only workflow, not full_autonomy.
|
|
52
|
+
*/
|
|
53
|
+
private isAnalysisLookupPrompt;
|
|
49
54
|
private isBrowserTaskPrompt;
|
|
50
55
|
private inferAgentTaskType;
|
|
51
56
|
private buildTaskShapingInstructions;
|
package/dist/commands/chat.js
CHANGED
|
@@ -195,6 +195,13 @@ class ChatCommand {
|
|
|
195
195
|
isDiagnosticPrompt(prompt) {
|
|
196
196
|
return /(startup|start up|won'?t start|doesn'?t start|crash|crashes|error|errors|failing|fails|issue|issues|bug|bugs|diagnos|debug|runtime|log|logs|exception|traceback|stack trace|yaml|blocking|blocker)/i.test(prompt);
|
|
197
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* Returns true when the prompt is a simple lookup / analysis / read-only
|
|
200
|
+
* question — these should use analysis_only workflow, not full_autonomy.
|
|
201
|
+
*/
|
|
202
|
+
isAnalysisLookupPrompt(prompt) {
|
|
203
|
+
return /^(what|which|where|how many|who|find|list|show|check|inspect|analyze|analyse|audit|explain|describe|summarize|summarise|review|overview|count|read|look at|tell me|locate|search for|does .* exist)/i.test(prompt.trim());
|
|
204
|
+
}
|
|
198
205
|
isBrowserTaskPrompt(prompt) {
|
|
199
206
|
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
207
|
}
|
|
@@ -360,7 +367,13 @@ class ChatCommand {
|
|
|
360
367
|
}
|
|
361
368
|
async run(options) {
|
|
362
369
|
if (!this.config.isAuthenticated()) {
|
|
363
|
-
|
|
370
|
+
if (options.json) {
|
|
371
|
+
process.exitCode = 1;
|
|
372
|
+
console.log(JSON.stringify({ success: false, error: 'Not authenticated. Run: vigthoria login' }, null, 2));
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
this.logger.error('Not authenticated. Run: vigthoria login');
|
|
376
|
+
}
|
|
364
377
|
return;
|
|
365
378
|
}
|
|
366
379
|
this.agentMode = options.agent === true;
|
|
@@ -611,7 +624,8 @@ class ChatCommand {
|
|
|
611
624
|
}
|
|
612
625
|
catch (error) {
|
|
613
626
|
if (spinner) {
|
|
614
|
-
spinner.
|
|
627
|
+
spinner.stop();
|
|
628
|
+
this.logger.error('Workflow target execution failed');
|
|
615
629
|
}
|
|
616
630
|
throw error;
|
|
617
631
|
}
|
|
@@ -624,7 +638,9 @@ class ChatCommand {
|
|
|
624
638
|
const runtimeContext = await this.getPromptRuntimeContext(prompt);
|
|
625
639
|
const spinner = this.jsonOutput ? null : (0, logger_js_1.createSpinner)({ text: 'Thinking like an operator...', spinner: 'clock' }).start();
|
|
626
640
|
const executionPrompt = this.buildExecutionPrompt(prompt);
|
|
627
|
-
const workflowType = this.isDiagnosticPrompt(prompt)
|
|
641
|
+
const workflowType = this.isDiagnosticPrompt(prompt) || this.isAnalysisLookupPrompt(prompt)
|
|
642
|
+
? 'analysis_only'
|
|
643
|
+
: 'full_autonomy';
|
|
628
644
|
try {
|
|
629
645
|
const response = await this.api.runOperatorWorkflow(executionPrompt, {
|
|
630
646
|
workspacePath: this.currentProjectPath,
|
|
@@ -665,9 +681,12 @@ class ChatCommand {
|
|
|
665
681
|
}
|
|
666
682
|
catch (error) {
|
|
667
683
|
if (spinner) {
|
|
668
|
-
spinner.
|
|
684
|
+
spinner.stop();
|
|
669
685
|
}
|
|
670
686
|
const errorMsg = error.message || 'Operator workflow failed with an unknown error.';
|
|
687
|
+
if (!this.jsonOutput) {
|
|
688
|
+
this.logger.error('Operator workflow failed');
|
|
689
|
+
}
|
|
671
690
|
if (this.jsonOutput) {
|
|
672
691
|
process.exitCode = 1;
|
|
673
692
|
console.log(JSON.stringify({
|
|
@@ -686,11 +705,26 @@ class ChatCommand {
|
|
|
686
705
|
this.lastActionableUserInput = prompt;
|
|
687
706
|
// In non-agent chat mode the model has no tool access. Inject a
|
|
688
707
|
// grounding constraint so it doesn't fabricate file contents.
|
|
708
|
+
// Also inject a real file listing so the model can reference actual
|
|
709
|
+
// paths instead of guessing.
|
|
689
710
|
const needsGrounding = /(repo|file|code|project|workspace|source|inspect|analyze|audit|review)/i.test(prompt);
|
|
690
711
|
if (needsGrounding && !this.messages.some(m => m.role === 'system' && m.content.includes('no direct file access'))) {
|
|
712
|
+
let groundingContent = 'You are in simple chat mode with no direct file access or tools. Do not fabricate file contents, search results, or analysis steps. If the user asks you to modify, edit, or deeply inspect files, advise them to use agent mode (vig chat --agent) for grounded repo analysis.';
|
|
713
|
+
// Inject a workspace file listing so the model has real paths
|
|
714
|
+
try {
|
|
715
|
+
const snapshot = this.api.getAgentWorkspaceSnapshot(this.currentProjectPath);
|
|
716
|
+
if (snapshot && snapshot.paths.length > 0) {
|
|
717
|
+
const listing = snapshot.paths.slice(0, 60).join('\n');
|
|
718
|
+
groundingContent += `\n\nWorkspace: ${this.currentProjectPath}\nFile listing (${snapshot.fileCount} files total):\n${listing}`;
|
|
719
|
+
if (snapshot.fileCount > 60) {
|
|
720
|
+
groundingContent += `\n... and ${snapshot.fileCount - 60} more files.`;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
catch { /* ignore snapshot errors */ }
|
|
691
725
|
this.messages.push({
|
|
692
726
|
role: 'system',
|
|
693
|
-
content:
|
|
727
|
+
content: groundingContent,
|
|
694
728
|
});
|
|
695
729
|
}
|
|
696
730
|
this.messages.push({ role: 'user', content: this.buildExecutionPrompt(prompt) });
|
|
@@ -719,7 +753,9 @@ class ChatCommand {
|
|
|
719
753
|
}
|
|
720
754
|
catch (error) {
|
|
721
755
|
if (spinner)
|
|
722
|
-
spinner.
|
|
756
|
+
spinner.stop();
|
|
757
|
+
if (!this.jsonOutput)
|
|
758
|
+
this.logger.error('Failed to get response');
|
|
723
759
|
const errorMsg = error.message;
|
|
724
760
|
if (this.jsonOutput) {
|
|
725
761
|
process.exitCode = 1;
|
|
@@ -830,7 +866,9 @@ class ChatCommand {
|
|
|
830
866
|
}
|
|
831
867
|
catch (error) {
|
|
832
868
|
if (spinner)
|
|
833
|
-
spinner.
|
|
869
|
+
spinner.stop();
|
|
870
|
+
if (!this.jsonOutput)
|
|
871
|
+
this.logger.error('Agent request failed');
|
|
834
872
|
if (this.jsonOutput) {
|
|
835
873
|
process.exitCode = 1;
|
|
836
874
|
console.log(JSON.stringify({
|
|
@@ -1040,14 +1078,15 @@ class ChatCommand {
|
|
|
1040
1078
|
if (!success) {
|
|
1041
1079
|
if (this.isLegacyAgentFallbackAllowed()) {
|
|
1042
1080
|
if (spinner) {
|
|
1043
|
-
spinner.
|
|
1081
|
+
spinner.stop();
|
|
1044
1082
|
}
|
|
1083
|
+
this.logger.warn('Falling back to legacy CLI agent loop');
|
|
1045
1084
|
this.logger.debug(`V3 agent workflow returned an incomplete result: ${previewGate?.error || 'workspace changes were not fully validated'}`);
|
|
1046
1085
|
return false;
|
|
1047
1086
|
}
|
|
1048
1087
|
const errorMessage = `V3 agent workflow returned an incomplete result and legacy fallback is disabled. ${previewGate?.error || 'Workspace changes were not fully validated.'}`;
|
|
1049
1088
|
if (spinner) {
|
|
1050
|
-
spinner.
|
|
1089
|
+
spinner.stop();
|
|
1051
1090
|
}
|
|
1052
1091
|
this.logger.error(errorMessage);
|
|
1053
1092
|
this.messages.push({ role: 'assistant', content: errorMessage });
|
|
@@ -1109,13 +1148,14 @@ class ChatCommand {
|
|
|
1109
1148
|
}
|
|
1110
1149
|
if (this.isLegacyAgentFallbackAllowed()) {
|
|
1111
1150
|
if (spinner) {
|
|
1112
|
-
spinner.
|
|
1151
|
+
spinner.stop();
|
|
1113
1152
|
}
|
|
1153
|
+
this.logger.warn('Falling back to legacy CLI agent loop');
|
|
1114
1154
|
this.logger.debug(`V3 agent workflow unavailable: ${error.message}`);
|
|
1115
1155
|
return false;
|
|
1116
1156
|
}
|
|
1117
1157
|
if (spinner) {
|
|
1118
|
-
spinner.
|
|
1158
|
+
spinner.stop();
|
|
1119
1159
|
}
|
|
1120
1160
|
const errorMessage = `Agent mode requires the V3 workflow and will not fall back to the legacy CLI loop. ${error.message}`;
|
|
1121
1161
|
this.logger.error(errorMessage);
|
package/dist/commands/deploy.js
CHANGED
|
@@ -151,7 +151,8 @@ class DeployCommand {
|
|
|
151
151
|
console.log(chalk_1.default.gray(' Upgrade to a subdomain for permanent hosting.\n'));
|
|
152
152
|
}
|
|
153
153
|
catch (error) {
|
|
154
|
-
spinner.
|
|
154
|
+
spinner.stop();
|
|
155
|
+
this.logger.error('Deploy failed');
|
|
155
156
|
const errMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
156
157
|
console.log(chalk_1.default.red(`\n❌ Error: ${errMsg}\n`));
|
|
157
158
|
}
|
|
@@ -204,7 +205,8 @@ class DeployCommand {
|
|
|
204
205
|
console.log(chalk_1.default.gray(' ✓ Unlimited traffic included\n'));
|
|
205
206
|
}
|
|
206
207
|
catch (error) {
|
|
207
|
-
spinner.
|
|
208
|
+
spinner.stop();
|
|
209
|
+
this.logger.error('Deploy failed');
|
|
208
210
|
const errMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
209
211
|
console.log(chalk_1.default.red(`\n❌ Error: ${errMsg}\n`));
|
|
210
212
|
}
|
|
@@ -258,7 +260,8 @@ class DeployCommand {
|
|
|
258
260
|
}
|
|
259
261
|
}
|
|
260
262
|
catch (error) {
|
|
261
|
-
spinner.
|
|
263
|
+
spinner.stop();
|
|
264
|
+
this.logger.error('Deploy failed');
|
|
262
265
|
const errMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
263
266
|
console.log(chalk_1.default.red(`\n❌ Error: ${errMsg}\n`));
|
|
264
267
|
}
|
|
@@ -334,7 +337,8 @@ class DeployCommand {
|
|
|
334
337
|
console.log(chalk_1.default.cyan('\n Subscribe: vig deploy --subdomain myapp\n'));
|
|
335
338
|
}
|
|
336
339
|
catch (error) {
|
|
337
|
-
spinner.
|
|
340
|
+
spinner.stop();
|
|
341
|
+
this.logger.error('Failed to fetch plans');
|
|
338
342
|
const errMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
339
343
|
console.log(chalk_1.default.red(`\n❌ Error: ${errMsg}\n`));
|
|
340
344
|
}
|
|
@@ -374,7 +378,8 @@ class DeployCommand {
|
|
|
374
378
|
}
|
|
375
379
|
}
|
|
376
380
|
catch (error) {
|
|
377
|
-
spinner.
|
|
381
|
+
spinner.stop();
|
|
382
|
+
this.logger.error('List failed');
|
|
378
383
|
const errMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
379
384
|
console.log(chalk_1.default.red(`\n❌ Error: ${errMsg}\n`));
|
|
380
385
|
}
|
|
@@ -401,7 +406,8 @@ class DeployCommand {
|
|
|
401
406
|
console.log(JSON.stringify(data, null, 2));
|
|
402
407
|
}
|
|
403
408
|
catch (error) {
|
|
404
|
-
spinner.
|
|
409
|
+
spinner.stop();
|
|
410
|
+
this.logger.error('Status check failed');
|
|
405
411
|
const errMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
406
412
|
console.log(chalk_1.default.red(`\n❌ Error: ${errMsg}\n`));
|
|
407
413
|
}
|
|
@@ -434,7 +440,8 @@ class DeployCommand {
|
|
|
434
440
|
}
|
|
435
441
|
}
|
|
436
442
|
catch (error) {
|
|
437
|
-
spinner.
|
|
443
|
+
spinner.stop();
|
|
444
|
+
this.logger.error('Verification failed');
|
|
438
445
|
const errMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
439
446
|
console.log(chalk_1.default.red(`\n❌ Error: ${errMsg}\n`));
|
|
440
447
|
}
|
|
@@ -468,7 +475,8 @@ class DeployCommand {
|
|
|
468
475
|
console.log(chalk_1.default.gray('\n Your project files are still in your repository.\n'));
|
|
469
476
|
}
|
|
470
477
|
catch (error) {
|
|
471
|
-
spinner.
|
|
478
|
+
spinner.stop();
|
|
479
|
+
this.logger.error('Remove failed');
|
|
472
480
|
const errMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
473
481
|
console.log(chalk_1.default.red(`\n❌ Error: ${errMsg}\n`));
|
|
474
482
|
}
|
package/dist/commands/edit.js
CHANGED
|
@@ -41,6 +41,11 @@ class EditCommand {
|
|
|
41
41
|
// Get instruction
|
|
42
42
|
let instruction = options.instruction;
|
|
43
43
|
if (!instruction) {
|
|
44
|
+
// --apply requires --instruction to avoid hanging on non-interactive terminals
|
|
45
|
+
if (options.apply) {
|
|
46
|
+
this.logger.error('The --apply flag requires --instruction. Example: vigthoria edit file.ts --apply --instruction "fix the bug"');
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
44
49
|
const answer = await inquirer_1.default.prompt([
|
|
45
50
|
{
|
|
46
51
|
type: 'input',
|
|
@@ -148,12 +153,17 @@ Return the complete modified code:`,
|
|
|
148
153
|
}
|
|
149
154
|
}
|
|
150
155
|
extractCode(response, language) {
|
|
151
|
-
// Try to extract code block
|
|
156
|
+
// Try to extract code block (language-specific first, then any)
|
|
152
157
|
const codeBlockRegex = new RegExp(`\`\`\`(?:${language})?\\n([\\s\\S]*?)\`\`\``, 'i');
|
|
153
158
|
const match = response.match(codeBlockRegex);
|
|
154
159
|
if (match) {
|
|
155
160
|
return match[1].trim();
|
|
156
161
|
}
|
|
162
|
+
// Try generic code block
|
|
163
|
+
const genericMatch = response.match(/```\w*\n([\s\S]*?)```/);
|
|
164
|
+
if (genericMatch) {
|
|
165
|
+
return genericMatch[1].trim();
|
|
166
|
+
}
|
|
157
167
|
// If no code block, check if response looks like code
|
|
158
168
|
const trimmed = response.trim();
|
|
159
169
|
if (!trimmed.startsWith('```') && !trimmed.includes('Here') && !trimmed.includes('I ')) {
|
package/dist/commands/repo.js
CHANGED
|
@@ -399,7 +399,8 @@ class RepoCommand {
|
|
|
399
399
|
console.log(chalk_1.default.gray('\nTip: Use `vigthoria repo pull <name>` to restore this project anywhere.\n'));
|
|
400
400
|
}
|
|
401
401
|
catch (error) {
|
|
402
|
-
spinner.
|
|
402
|
+
spinner.stop();
|
|
403
|
+
this.logger.error('Push failed');
|
|
403
404
|
const errMsg = this.formatRepoError(error);
|
|
404
405
|
console.log(chalk_1.default.red(`\n❌ Error: ${errMsg}\n`));
|
|
405
406
|
}
|
|
@@ -486,7 +487,8 @@ class RepoCommand {
|
|
|
486
487
|
console.log();
|
|
487
488
|
}
|
|
488
489
|
catch (error) {
|
|
489
|
-
spinner.
|
|
490
|
+
spinner.stop();
|
|
491
|
+
this.logger.error('Pull failed');
|
|
490
492
|
const errMsg = this.formatRepoError(error);
|
|
491
493
|
console.log(chalk_1.default.red(`\n❌ Error: ${errMsg}\n`));
|
|
492
494
|
}
|
|
@@ -584,7 +586,8 @@ class RepoCommand {
|
|
|
584
586
|
console.log(chalk_1.default.gray(' vigthoria repo share <name> - Share a project\n'));
|
|
585
587
|
}
|
|
586
588
|
catch (error) {
|
|
587
|
-
spinner.
|
|
589
|
+
spinner.stop();
|
|
590
|
+
this.logger.error('List failed');
|
|
588
591
|
const errMsg = this.formatRepoError(error);
|
|
589
592
|
console.log(chalk_1.default.red(`\n❌ Error: ${errMsg}\n`));
|
|
590
593
|
}
|
|
@@ -634,7 +637,8 @@ class RepoCommand {
|
|
|
634
637
|
}
|
|
635
638
|
}
|
|
636
639
|
catch (error) {
|
|
637
|
-
spinner.
|
|
640
|
+
spinner.stop();
|
|
641
|
+
this.logger.error('Status check failed');
|
|
638
642
|
const errMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
639
643
|
console.log(chalk_1.default.red(`\n❌ Error: ${errMsg}\n`));
|
|
640
644
|
}
|
|
@@ -666,7 +670,8 @@ class RepoCommand {
|
|
|
666
670
|
console.log(chalk_1.default.gray('\n Anyone with this link can view/download the project.\n'));
|
|
667
671
|
}
|
|
668
672
|
catch (error) {
|
|
669
|
-
spinner.
|
|
673
|
+
spinner.stop();
|
|
674
|
+
this.logger.error('Share failed');
|
|
670
675
|
const errMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
671
676
|
console.log(chalk_1.default.red(`\n❌ Error: ${errMsg}\n`));
|
|
672
677
|
}
|
|
@@ -700,7 +705,8 @@ class RepoCommand {
|
|
|
700
705
|
console.log(chalk_1.default.gray('\nNote: Your local files are not affected.\n'));
|
|
701
706
|
}
|
|
702
707
|
catch (error) {
|
|
703
|
-
spinner.
|
|
708
|
+
spinner.stop();
|
|
709
|
+
this.logger.error('Delete failed');
|
|
704
710
|
const errMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
705
711
|
console.log(chalk_1.default.red(`\n❌ Error: ${errMsg}\n`));
|
|
706
712
|
}
|
|
@@ -728,7 +734,8 @@ class RepoCommand {
|
|
|
728
734
|
await this.pull(data.project.project_name, options);
|
|
729
735
|
}
|
|
730
736
|
catch (error) {
|
|
731
|
-
spinner.
|
|
737
|
+
spinner.stop();
|
|
738
|
+
this.logger.error('Clone failed');
|
|
732
739
|
const errMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
733
740
|
console.log(chalk_1.default.red(`\n❌ Error: ${errMsg}\n`));
|
|
734
741
|
}
|
package/dist/utils/api.d.ts
CHANGED
|
@@ -217,6 +217,13 @@ export declare class APIClient {
|
|
|
217
217
|
private getVigFlowAccessToken;
|
|
218
218
|
private getVigFlowHeaders;
|
|
219
219
|
private withVigFlow;
|
|
220
|
+
/**
|
|
221
|
+
* Build the correct sub-path for VigFlow endpoints.
|
|
222
|
+
* Local servers (e.g. localhost:5060) need `/api/…` prefix.
|
|
223
|
+
* The remote gateway URL already ends with `/api/vigflow`, so appending
|
|
224
|
+
* another `/api/…` would double the prefix and cause 404s.
|
|
225
|
+
*/
|
|
226
|
+
private vigFlowEndpoint;
|
|
220
227
|
listVigFlowTemplates(options?: {
|
|
221
228
|
category?: string;
|
|
222
229
|
search?: string;
|
|
@@ -339,6 +346,11 @@ export declare class APIClient {
|
|
|
339
346
|
}[];
|
|
340
347
|
suggestions: string[];
|
|
341
348
|
}>;
|
|
349
|
+
/**
|
|
350
|
+
* Lightweight client-side heuristic scan: catches common code smells
|
|
351
|
+
* so review never returns "score 30, no issues".
|
|
352
|
+
*/
|
|
353
|
+
private heuristicCodeIssues;
|
|
342
354
|
fixCode(code: string, language: string, fixType: string): Promise<{
|
|
343
355
|
fixed: string;
|
|
344
356
|
changes: {
|
package/dist/utils/api.js
CHANGED
|
@@ -793,6 +793,19 @@ class APIClient {
|
|
|
793
793
|
}
|
|
794
794
|
throw lastError || new Error(`No VigFlow backend available for ${operation}.`);
|
|
795
795
|
}
|
|
796
|
+
/**
|
|
797
|
+
* Build the correct sub-path for VigFlow endpoints.
|
|
798
|
+
* Local servers (e.g. localhost:5060) need `/api/…` prefix.
|
|
799
|
+
* The remote gateway URL already ends with `/api/vigflow`, so appending
|
|
800
|
+
* another `/api/…` would double the prefix and cause 404s.
|
|
801
|
+
*/
|
|
802
|
+
vigFlowEndpoint(baseUrl, subPath) {
|
|
803
|
+
if (/\/api\/vigflow\/?$/i.test(baseUrl)) {
|
|
804
|
+
// Remote gateway – subPath like '/templates' is enough
|
|
805
|
+
return `${baseUrl.replace(/\/$/, '')}${subPath}`;
|
|
806
|
+
}
|
|
807
|
+
return `${baseUrl}/api${subPath}`;
|
|
808
|
+
}
|
|
796
809
|
async listVigFlowTemplates(options = {}) {
|
|
797
810
|
return this.withVigFlow('list templates', async (baseUrl, headers) => {
|
|
798
811
|
const query = new URLSearchParams();
|
|
@@ -802,7 +815,7 @@ class APIClient {
|
|
|
802
815
|
if (options.search) {
|
|
803
816
|
query.set('search', options.search);
|
|
804
817
|
}
|
|
805
|
-
const url = `${baseUrl
|
|
818
|
+
const url = `${this.vigFlowEndpoint(baseUrl, '/templates')}${query.size > 0 ? `?${query.toString()}` : ''}`;
|
|
806
819
|
const response = await axios_1.default.get(url, {
|
|
807
820
|
headers,
|
|
808
821
|
timeout: 30000,
|
|
@@ -813,7 +826,7 @@ class APIClient {
|
|
|
813
826
|
}
|
|
814
827
|
async listVigFlowWorkflows() {
|
|
815
828
|
return this.withVigFlow('list workflows', async (baseUrl, headers) => {
|
|
816
|
-
const response = await axios_1.default.get(
|
|
829
|
+
const response = await axios_1.default.get(this.vigFlowEndpoint(baseUrl, '/workflows'), {
|
|
817
830
|
headers,
|
|
818
831
|
timeout: 30000,
|
|
819
832
|
});
|
|
@@ -865,7 +878,7 @@ class APIClient {
|
|
|
865
878
|
}
|
|
866
879
|
async useVigFlowTemplate(templateId, options = {}) {
|
|
867
880
|
return this.withVigFlow('use template', async (baseUrl, headers) => {
|
|
868
|
-
const response = await axios_1.default.post(`${baseUrl
|
|
881
|
+
const response = await axios_1.default.post(`${this.vigFlowEndpoint(baseUrl, `/templates/${encodeURIComponent(templateId)}/use`)}`, {
|
|
869
882
|
name: options.name,
|
|
870
883
|
variables: options.variables || {},
|
|
871
884
|
}, {
|
|
@@ -881,7 +894,7 @@ class APIClient {
|
|
|
881
894
|
}
|
|
882
895
|
async runVigFlowWorkflow(workflowId, options = {}) {
|
|
883
896
|
return this.withVigFlow('run workflow', async (baseUrl, headers) => {
|
|
884
|
-
const response = await axios_1.default.post(`${baseUrl
|
|
897
|
+
const response = await axios_1.default.post(`${this.vigFlowEndpoint(baseUrl, `/executions/run/${encodeURIComponent(workflowId)}`)}`, {
|
|
885
898
|
data: options.data || {},
|
|
886
899
|
options: options.executionOptions || {},
|
|
887
900
|
}, {
|
|
@@ -897,7 +910,7 @@ class APIClient {
|
|
|
897
910
|
}
|
|
898
911
|
async getVigFlowExecutionStatus(executionId) {
|
|
899
912
|
return this.withVigFlow('execution status', async (baseUrl, headers) => {
|
|
900
|
-
const response = await axios_1.default.get(`${baseUrl
|
|
913
|
+
const response = await axios_1.default.get(`${this.vigFlowEndpoint(baseUrl, `/executions/${encodeURIComponent(executionId)}`)}`, {
|
|
901
914
|
headers,
|
|
902
915
|
timeout: 30000,
|
|
903
916
|
});
|
|
@@ -918,6 +931,11 @@ class APIClient {
|
|
|
918
931
|
const localWorkspaceSummary = this.buildLocalWorkspaceSummary(localWorkspacePath);
|
|
919
932
|
const requestedModel = String(resolvedContext.model || resolvedContext.requestedModel || 'agent');
|
|
920
933
|
const resolvedModel = this.resolvePermittedModelId(requestedModel);
|
|
934
|
+
// When the server cannot directly access the workspace (e.g. Windows
|
|
935
|
+
// client), use the local path as a hint and flag that the workspace
|
|
936
|
+
// files are provided inline in localWorkspaceSummary.workspaceFiles.
|
|
937
|
+
const effectiveWorkspacePath = serverWorkspacePath || localWorkspacePath || null;
|
|
938
|
+
const needsHydration = !serverWorkspacePath && !!localWorkspacePath;
|
|
921
939
|
const payload = {
|
|
922
940
|
workspace: resolvedContext.workspace || null,
|
|
923
941
|
activeFile: resolvedContext.activeFile || null,
|
|
@@ -931,12 +949,16 @@ class APIClient {
|
|
|
931
949
|
executionSurface: resolvedContext.executionSurface || 'cli',
|
|
932
950
|
clientSurface: resolvedContext.clientSurface || 'cli',
|
|
933
951
|
localMachineCapable: resolvedContext.localMachineCapable !== false,
|
|
934
|
-
workspacePath:
|
|
935
|
-
projectPath:
|
|
936
|
-
targetPath:
|
|
952
|
+
workspacePath: effectiveWorkspacePath,
|
|
953
|
+
projectPath: effectiveWorkspacePath,
|
|
954
|
+
targetPath: effectiveWorkspacePath,
|
|
937
955
|
localWorkspacePath: localWorkspacePath || null,
|
|
938
956
|
localWorkspaceName: localWorkspacePath ? path_1.default.basename(localWorkspacePath) : null,
|
|
939
957
|
localWorkspaceSummary,
|
|
958
|
+
// Signal to the server that the workspace filesystem is not locally
|
|
959
|
+
// accessible — it must hydrate a temp directory from the provided
|
|
960
|
+
// workspaceFiles before the agent starts using tools.
|
|
961
|
+
workspaceHydrationRequired: needsHydration,
|
|
940
962
|
contextId: resolvedContext.contextId,
|
|
941
963
|
traceId: resolvedContext.traceId,
|
|
942
964
|
mcpContextId: resolvedContext.mcpContextId || null,
|
|
@@ -2333,7 +2355,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
2333
2355
|
const toolResults = [];
|
|
2334
2356
|
const filesRead = [];
|
|
2335
2357
|
const filesWritten = [];
|
|
2336
|
-
|
|
2358
|
+
const assistantFragments = [];
|
|
2337
2359
|
for (const event of events) {
|
|
2338
2360
|
if (!event)
|
|
2339
2361
|
continue;
|
|
@@ -2352,16 +2374,22 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
2352
2374
|
}
|
|
2353
2375
|
}
|
|
2354
2376
|
if (event.type === 'assistant' && typeof event.content === 'string' && event.content.trim()) {
|
|
2355
|
-
|
|
2377
|
+
assistantFragments.push(event.content.trim());
|
|
2356
2378
|
}
|
|
2357
2379
|
// Some servers emit 'text' events for incremental assistant text
|
|
2358
2380
|
if (event.type === 'text' && typeof event.content === 'string' && event.content.trim()) {
|
|
2359
|
-
|
|
2381
|
+
assistantFragments.push(event.content.trim());
|
|
2382
|
+
}
|
|
2383
|
+
// Some servers emit content_block_delta for streamed text
|
|
2384
|
+
if (event.type === 'content_block_delta' && typeof event.delta?.text === 'string' && event.delta.text.trim()) {
|
|
2385
|
+
assistantFragments.push(event.delta.text.trim());
|
|
2360
2386
|
}
|
|
2361
2387
|
}
|
|
2362
|
-
//
|
|
2363
|
-
|
|
2364
|
-
|
|
2388
|
+
// Concatenate ALL assistant text fragments in order — keeps full
|
|
2389
|
+
// multi-turn reasoning instead of only the last fragment.
|
|
2390
|
+
const fullAssistantText = assistantFragments.join('\n\n').trim();
|
|
2391
|
+
if (fullAssistantText.length > 20) {
|
|
2392
|
+
return fullAssistantText;
|
|
2365
2393
|
}
|
|
2366
2394
|
// Otherwise build a summary from tool evidence
|
|
2367
2395
|
const sections = [];
|
|
@@ -3200,13 +3228,19 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3200
3228
|
// Prepend a forceful scope-enforcement instruction so the model
|
|
3201
3229
|
// doesn't expand a small task into an oversized glossy page.
|
|
3202
3230
|
const scopedPrompt = [
|
|
3203
|
-
'IMPORTANT — SCOPE CONSTRAINTS:',
|
|
3204
|
-
'1.
|
|
3205
|
-
'2. If the
|
|
3206
|
-
'3.
|
|
3207
|
-
'4.
|
|
3208
|
-
'
|
|
3209
|
-
'
|
|
3231
|
+
'IMPORTANT — MANDATORY SCOPE CONSTRAINTS (violation = failure):',
|
|
3232
|
+
'1. Output ONLY what the user explicitly asked for. Nothing more.',
|
|
3233
|
+
'2. If the prompt is ≤ 15 words, produce ≤ 80 lines of code maximum.',
|
|
3234
|
+
'3. If the user says "tiny", "small", "simple", "minimal", or "basic", produce ≤ 50 lines.',
|
|
3235
|
+
'4. NEVER add any of these unless the user EXPLICITLY requests them:',
|
|
3236
|
+
' - Hero sections, CTAs, testimonials, pricing tables, footers, navbars',
|
|
3237
|
+
' - CSS animations, gradients, neon effects, glass-morphism, particles',
|
|
3238
|
+
' - Google Fonts, Font Awesome, external CDN links, icon libraries',
|
|
3239
|
+
' - Responsive breakpoints, media queries (unless asked)',
|
|
3240
|
+
' - Multiple pages or components when one was requested',
|
|
3241
|
+
'5. Prefer inline styles or a small <style> block. No CSS frameworks.',
|
|
3242
|
+
'6. Return raw code only — no markdown fences, no explanations, no comments about what could be added.',
|
|
3243
|
+
'7. Match the complexity of the request: a "hello world" is 1-5 lines, a "button" is 5-15 lines.',
|
|
3210
3244
|
'',
|
|
3211
3245
|
prompt,
|
|
3212
3246
|
].join('\n');
|
|
@@ -3243,22 +3277,62 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3243
3277
|
const response = await this.client.post('/api/ai/review', {
|
|
3244
3278
|
code,
|
|
3245
3279
|
language,
|
|
3280
|
+
instructions: 'Return concrete, line-specific issues with severity. Every issue MUST reference a line number. If the score is below 50, you MUST list at least 2 specific issues.',
|
|
3246
3281
|
});
|
|
3247
3282
|
const raw = response.data ?? {};
|
|
3248
3283
|
const score = typeof raw.score === 'number' ? raw.score : 0;
|
|
3249
3284
|
const issues = Array.isArray(raw.issues) ? raw.issues : [];
|
|
3250
3285
|
const suggestions = Array.isArray(raw.suggestions) ? raw.suggestions : [];
|
|
3251
|
-
// Prevent contradictory output: low score but zero issues
|
|
3286
|
+
// Prevent contradictory output: low score but zero issues.
|
|
3287
|
+
// Run lightweight client-side heuristics to produce concrete findings.
|
|
3252
3288
|
if (score < 50 && issues.length === 0) {
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3289
|
+
const heuristic = this.heuristicCodeIssues(code, language);
|
|
3290
|
+
if (heuristic.length > 0) {
|
|
3291
|
+
issues.push(...heuristic);
|
|
3292
|
+
}
|
|
3293
|
+
else {
|
|
3294
|
+
issues.push({
|
|
3295
|
+
type: 'quality',
|
|
3296
|
+
line: 1,
|
|
3297
|
+
message: `The analysis returned a low quality score (${score}/100) but did not enumerate specific issues. Re-run the review or inspect the file manually.`,
|
|
3298
|
+
severity: 'warning',
|
|
3299
|
+
});
|
|
3300
|
+
}
|
|
3259
3301
|
}
|
|
3260
3302
|
return { score, issues, suggestions };
|
|
3261
3303
|
}
|
|
3304
|
+
/**
|
|
3305
|
+
* Lightweight client-side heuristic scan: catches common code smells
|
|
3306
|
+
* so review never returns "score 30, no issues".
|
|
3307
|
+
*/
|
|
3308
|
+
heuristicCodeIssues(code, language) {
|
|
3309
|
+
const issues = [];
|
|
3310
|
+
const lines = code.split('\n');
|
|
3311
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3312
|
+
const line = lines[i];
|
|
3313
|
+
const lineNum = i + 1;
|
|
3314
|
+
// console.log left in production code
|
|
3315
|
+
if (/\bconsole\.(log|debug|info)\b/.test(line) && !/\/\//.test(line.slice(0, line.indexOf('console')))) {
|
|
3316
|
+
issues.push({ type: 'quality', line: lineNum, message: 'console.log/debug statement — remove or replace with proper logging.', severity: 'warning' });
|
|
3317
|
+
}
|
|
3318
|
+
// TODO/FIXME/HACK comments
|
|
3319
|
+
if (/\b(TODO|FIXME|HACK|XXX)\b/.test(line)) {
|
|
3320
|
+
issues.push({ type: 'maintainability', line: lineNum, message: `Unresolved ${line.match(/\b(TODO|FIXME|HACK|XXX)\b/)?.[0]} comment.`, severity: 'info' });
|
|
3321
|
+
}
|
|
3322
|
+
// Empty catch blocks
|
|
3323
|
+
if (/catch\s*\([^)]*\)\s*\{\s*\}/.test(line)) {
|
|
3324
|
+
issues.push({ type: 'error-handling', line: lineNum, message: 'Empty catch block — errors are silently swallowed.', severity: 'warning' });
|
|
3325
|
+
}
|
|
3326
|
+
// Very long lines (> 200 chars)
|
|
3327
|
+
if (line.length > 200) {
|
|
3328
|
+
issues.push({ type: 'style', line: lineNum, message: `Line exceeds 200 characters (${line.length}).`, severity: 'info' });
|
|
3329
|
+
}
|
|
3330
|
+
// Limit to 10 heuristic issues
|
|
3331
|
+
if (issues.length >= 10)
|
|
3332
|
+
break;
|
|
3333
|
+
}
|
|
3334
|
+
return issues;
|
|
3335
|
+
}
|
|
3262
3336
|
async fixCode(code, language, fixType) {
|
|
3263
3337
|
// Client-side syntax pre-check: detect obvious errors and include
|
|
3264
3338
|
// them in the request so the model has concrete signals.
|
package/dist/utils/logger.d.ts
CHANGED
|
@@ -5,7 +5,15 @@ import { type Options as OraOptions, type Ora } from 'ora';
|
|
|
5
5
|
export type { Ora };
|
|
6
6
|
/**
|
|
7
7
|
* Create an ora spinner that writes to stderr so it never
|
|
8
|
-
* pollutes stdout JSON output
|
|
8
|
+
* pollutes stdout JSON output.
|
|
9
|
+
*
|
|
10
|
+
* IMPORTANT: On Windows PowerShell, any stderr output triggers
|
|
11
|
+
* NativeCommandError styling. The spinner animation itself is
|
|
12
|
+
* tolerable, but `spinner.fail(msg)` writes the message to stderr
|
|
13
|
+
* which produces ugly red PowerShell errors.
|
|
14
|
+
*
|
|
15
|
+
* Prefer: spinner.stop() then Logger.error(msg) — which writes to
|
|
16
|
+
* stdout — instead of spinner.fail(msg).
|
|
9
17
|
*/
|
|
10
18
|
export declare function createSpinner(textOrOpts: string | OraOptions): Ora;
|
|
11
19
|
export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'success';
|
package/dist/utils/logger.js
CHANGED
|
@@ -12,7 +12,15 @@ const chalk_1 = __importDefault(require("chalk"));
|
|
|
12
12
|
const ora_1 = __importDefault(require("ora"));
|
|
13
13
|
/**
|
|
14
14
|
* Create an ora spinner that writes to stderr so it never
|
|
15
|
-
* pollutes stdout JSON output
|
|
15
|
+
* pollutes stdout JSON output.
|
|
16
|
+
*
|
|
17
|
+
* IMPORTANT: On Windows PowerShell, any stderr output triggers
|
|
18
|
+
* NativeCommandError styling. The spinner animation itself is
|
|
19
|
+
* tolerable, but `spinner.fail(msg)` writes the message to stderr
|
|
20
|
+
* which produces ugly red PowerShell errors.
|
|
21
|
+
*
|
|
22
|
+
* Prefer: spinner.stop() then Logger.error(msg) — which writes to
|
|
23
|
+
* stdout — instead of spinner.fail(msg).
|
|
16
24
|
*/
|
|
17
25
|
function createSpinner(textOrOpts) {
|
|
18
26
|
const opts = typeof textOrOpts === 'string' ? { text: textOrOpts } : textOrOpts;
|
|
@@ -35,7 +43,10 @@ class Logger {
|
|
|
35
43
|
console.log(chalk_1.default.yellow('⚠'), ...args);
|
|
36
44
|
}
|
|
37
45
|
error(...args) {
|
|
38
|
-
|
|
46
|
+
// Write error messages to stdout (not stderr) to avoid triggering
|
|
47
|
+
// PowerShell NativeCommandError styling. The red ✗ prefix already
|
|
48
|
+
// signals an error visually; stderr redirection is unnecessary.
|
|
49
|
+
console.log(chalk_1.default.red('✗'), ...args);
|
|
39
50
|
}
|
|
40
51
|
success(...args) {
|
|
41
52
|
console.log(chalk_1.default.green('✓'), ...args);
|