@probelabs/visor 0.1.20 ā 0.1.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/action-cli-bridge.d.ts +0 -1
- package/dist/action-cli-bridge.d.ts.map +1 -1
- package/dist/ai-review-service.d.ts +0 -1
- package/dist/ai-review-service.d.ts.map +1 -1
- package/dist/check-execution-engine.d.ts +0 -1
- package/dist/check-execution-engine.d.ts.map +1 -1
- package/dist/cli-main.d.ts +0 -2
- package/dist/cli-main.d.ts.map +1 -1
- package/dist/cli.d.ts +0 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/commands.d.ts +0 -1
- package/dist/commands.d.ts.map +1 -1
- package/dist/config.d.ts +0 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/dependency-resolver.d.ts +0 -1
- package/dist/dependency-resolver.d.ts.map +1 -1
- package/dist/event-mapper.d.ts +0 -1
- package/dist/event-mapper.d.ts.map +1 -1
- package/dist/failure-condition-evaluator.d.ts +0 -1
- package/dist/failure-condition-evaluator.d.ts.map +1 -1
- package/dist/git-repository-analyzer.d.ts +0 -1
- package/dist/git-repository-analyzer.d.ts.map +1 -1
- package/dist/github-check-service.d.ts +0 -1
- package/dist/github-check-service.d.ts.map +1 -1
- package/dist/github-comments.d.ts +0 -1
- package/dist/github-comments.d.ts.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +127704 -805
- package/dist/output-formatters.d.ts +0 -1
- package/dist/output-formatters.d.ts.map +1 -1
- package/dist/pr-analyzer.d.ts +0 -1
- package/dist/pr-analyzer.d.ts.map +1 -1
- package/dist/pr-detector.d.ts +0 -1
- package/dist/pr-detector.d.ts.map +1 -1
- package/dist/providers/ai-check-provider.d.ts.map +1 -1
- package/dist/providers/check-provider-registry.d.ts.map +1 -1
- package/dist/providers/check-provider.interface.d.ts.map +1 -1
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/noop-check-provider.d.ts.map +1 -1
- package/dist/providers/tool-check-provider.d.ts.map +1 -1
- package/dist/providers/webhook-check-provider.d.ts.map +1 -1
- package/dist/reviewer.d.ts +0 -1
- package/dist/reviewer.d.ts.map +1 -1
- package/dist/session-registry.d.ts +0 -1
- package/dist/session-registry.d.ts.map +1 -1
- package/dist/types/cli.d.ts.map +1 -1
- package/dist/types/config.d.ts.map +1 -1
- package/dist/utils/env-resolver.d.ts.map +1 -1
- package/package.json +3 -3
- package/dist/action-cli-bridge.js +0 -387
- package/dist/action-cli-bridge.js.map +0 -1
- package/dist/ai-review-service.js +0 -854
- package/dist/ai-review-service.js.map +0 -1
- package/dist/check-execution-engine.js +0 -1720
- package/dist/check-execution-engine.js.map +0 -1
- package/dist/cli-main.js +0 -249
- package/dist/cli-main.js.map +0 -1
- package/dist/cli.js +0 -241
- package/dist/cli.js.map +0 -1
- package/dist/commands.js +0 -53
- package/dist/commands.js.map +0 -1
- package/dist/config.js +0 -437
- package/dist/config.js.map +0 -1
- package/dist/dependency-resolver.js +0 -163
- package/dist/dependency-resolver.js.map +0 -1
- package/dist/event-mapper.js +0 -316
- package/dist/event-mapper.js.map +0 -1
- package/dist/failure-condition-evaluator.js +0 -481
- package/dist/failure-condition-evaluator.js.map +0 -1
- package/dist/git-repository-analyzer.js +0 -285
- package/dist/git-repository-analyzer.js.map +0 -1
- package/dist/github-check-service.js +0 -369
- package/dist/github-check-service.js.map +0 -1
- package/dist/github-comments.js +0 -289
- package/dist/github-comments.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/output-formatters.js +0 -624
- package/dist/output-formatters.js.map +0 -1
- package/dist/pr-analyzer.js +0 -195
- package/dist/pr-analyzer.js.map +0 -1
- package/dist/pr-detector.js +0 -357
- package/dist/pr-detector.js.map +0 -1
- package/dist/providers/ai-check-provider.js +0 -437
- package/dist/providers/ai-check-provider.js.map +0 -1
- package/dist/providers/check-provider-registry.js +0 -138
- package/dist/providers/check-provider-registry.js.map +0 -1
- package/dist/providers/check-provider.interface.js +0 -11
- package/dist/providers/check-provider.interface.js.map +0 -1
- package/dist/providers/index.js +0 -19
- package/dist/providers/index.js.map +0 -1
- package/dist/providers/noop-check-provider.js +0 -55
- package/dist/providers/noop-check-provider.js.map +0 -1
- package/dist/providers/tool-check-provider.js +0 -174
- package/dist/providers/tool-check-provider.js.map +0 -1
- package/dist/providers/webhook-check-provider.js +0 -173
- package/dist/providers/webhook-check-provider.js.map +0 -1
- package/dist/reviewer.js +0 -260
- package/dist/reviewer.js.map +0 -1
- package/dist/session-registry.js +0 -67
- package/dist/session-registry.js.map +0 -1
- package/dist/types/cli.js +0 -3
- package/dist/types/cli.js.map +0 -1
- package/dist/types/config.js +0 -6
- package/dist/types/config.js.map +0 -1
- package/dist/utils/env-resolver.js +0 -130
- package/dist/utils/env-resolver.js.map +0 -1
|
@@ -1,854 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.AIReviewService = void 0;
|
|
4
|
-
const probe_1 = require("@probelabs/probe");
|
|
5
|
-
const session_registry_1 = require("./session-registry");
|
|
6
|
-
/**
|
|
7
|
-
* Helper function to log messages respecting JSON/SARIF output format
|
|
8
|
-
* Routes to stderr for JSON/SARIF to avoid contaminating structured output
|
|
9
|
-
*/
|
|
10
|
-
function log(...args) {
|
|
11
|
-
const isStructuredOutput = process.env.VISOR_OUTPUT_FORMAT === 'json' || process.env.VISOR_OUTPUT_FORMAT === 'sarif';
|
|
12
|
-
const logFn = isStructuredOutput ? console.error : console.log;
|
|
13
|
-
logFn(...args);
|
|
14
|
-
}
|
|
15
|
-
class AIReviewService {
|
|
16
|
-
config;
|
|
17
|
-
sessionRegistry;
|
|
18
|
-
constructor(config = {}) {
|
|
19
|
-
this.config = {
|
|
20
|
-
timeout: 600000, // Increased timeout to 10 minutes for AI responses
|
|
21
|
-
...config,
|
|
22
|
-
};
|
|
23
|
-
this.sessionRegistry = session_registry_1.SessionRegistry.getInstance();
|
|
24
|
-
// Auto-detect provider and API key from environment
|
|
25
|
-
if (!this.config.apiKey) {
|
|
26
|
-
if (process.env.GOOGLE_API_KEY) {
|
|
27
|
-
this.config.apiKey = process.env.GOOGLE_API_KEY;
|
|
28
|
-
this.config.provider = 'google';
|
|
29
|
-
}
|
|
30
|
-
else if (process.env.ANTHROPIC_API_KEY) {
|
|
31
|
-
this.config.apiKey = process.env.ANTHROPIC_API_KEY;
|
|
32
|
-
this.config.provider = 'anthropic';
|
|
33
|
-
}
|
|
34
|
-
else if (process.env.OPENAI_API_KEY) {
|
|
35
|
-
this.config.apiKey = process.env.OPENAI_API_KEY;
|
|
36
|
-
this.config.provider = 'openai';
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
// Auto-detect model from environment
|
|
40
|
-
if (!this.config.model && process.env.MODEL_NAME) {
|
|
41
|
-
this.config.model = process.env.MODEL_NAME;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Execute AI review using probe agent
|
|
46
|
-
*/
|
|
47
|
-
async executeReview(prInfo, customPrompt, schema, _checkName, sessionId) {
|
|
48
|
-
const startTime = Date.now();
|
|
49
|
-
const timestamp = new Date().toISOString();
|
|
50
|
-
// Build prompt from custom instructions
|
|
51
|
-
const prompt = await this.buildCustomPrompt(prInfo, customPrompt, schema);
|
|
52
|
-
log(`Executing AI review with ${this.config.provider} provider...`);
|
|
53
|
-
log(`š§ Debug: Raw schema parameter: ${JSON.stringify(schema)} (type: ${typeof schema})`);
|
|
54
|
-
log(`Schema type: ${schema || 'none (no schema)'}`);
|
|
55
|
-
let debugInfo;
|
|
56
|
-
if (this.config.debug) {
|
|
57
|
-
debugInfo = {
|
|
58
|
-
prompt,
|
|
59
|
-
rawResponse: '',
|
|
60
|
-
provider: this.config.provider || 'unknown',
|
|
61
|
-
model: this.config.model || 'default',
|
|
62
|
-
apiKeySource: this.getApiKeySource(),
|
|
63
|
-
processingTime: 0,
|
|
64
|
-
promptLength: prompt.length,
|
|
65
|
-
responseLength: 0,
|
|
66
|
-
errors: [],
|
|
67
|
-
jsonParseSuccess: false,
|
|
68
|
-
timestamp,
|
|
69
|
-
schemaName: schema,
|
|
70
|
-
schema: undefined, // Will be populated when schema is loaded
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
// Handle mock model/provider first (no API key needed)
|
|
74
|
-
if (this.config.model === 'mock' || this.config.provider === 'mock') {
|
|
75
|
-
log('š Using mock AI model/provider for testing - skipping API key validation');
|
|
76
|
-
}
|
|
77
|
-
else {
|
|
78
|
-
// Check if API key is available for real AI models
|
|
79
|
-
if (!this.config.apiKey) {
|
|
80
|
-
const errorMessage = 'No API key configured. Please set GOOGLE_API_KEY, ANTHROPIC_API_KEY, or OPENAI_API_KEY environment variable.';
|
|
81
|
-
// In debug mode, return a review with the error captured
|
|
82
|
-
if (debugInfo) {
|
|
83
|
-
debugInfo.errors = [errorMessage];
|
|
84
|
-
debugInfo.processingTime = Date.now() - startTime;
|
|
85
|
-
debugInfo.rawResponse = 'API call not attempted - no API key configured';
|
|
86
|
-
return {
|
|
87
|
-
issues: [
|
|
88
|
-
{
|
|
89
|
-
file: 'system',
|
|
90
|
-
line: 0,
|
|
91
|
-
ruleId: 'system/api-key-missing',
|
|
92
|
-
message: errorMessage,
|
|
93
|
-
severity: 'error',
|
|
94
|
-
category: 'logic',
|
|
95
|
-
},
|
|
96
|
-
],
|
|
97
|
-
suggestions: [
|
|
98
|
-
'Configure API keys in your GitHub repository secrets or environment variables',
|
|
99
|
-
],
|
|
100
|
-
debug: debugInfo,
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
throw new Error(errorMessage);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
try {
|
|
107
|
-
const { response, effectiveSchema } = await this.callProbeAgent(prompt, schema, debugInfo, _checkName, sessionId);
|
|
108
|
-
const processingTime = Date.now() - startTime;
|
|
109
|
-
if (debugInfo) {
|
|
110
|
-
debugInfo.rawResponse = response;
|
|
111
|
-
debugInfo.responseLength = response.length;
|
|
112
|
-
debugInfo.processingTime = processingTime;
|
|
113
|
-
}
|
|
114
|
-
const result = this.parseAIResponse(response, debugInfo, effectiveSchema);
|
|
115
|
-
if (debugInfo) {
|
|
116
|
-
result.debug = debugInfo;
|
|
117
|
-
}
|
|
118
|
-
return result;
|
|
119
|
-
}
|
|
120
|
-
catch (error) {
|
|
121
|
-
if (debugInfo) {
|
|
122
|
-
debugInfo.errors = [error instanceof Error ? error.message : String(error)];
|
|
123
|
-
debugInfo.processingTime = Date.now() - startTime;
|
|
124
|
-
// In debug mode, return a review with the error captured
|
|
125
|
-
return {
|
|
126
|
-
issues: [
|
|
127
|
-
{
|
|
128
|
-
file: 'system',
|
|
129
|
-
line: 0,
|
|
130
|
-
ruleId: 'system/ai-execution-error',
|
|
131
|
-
message: error instanceof Error ? error.message : String(error),
|
|
132
|
-
severity: 'error',
|
|
133
|
-
category: 'logic',
|
|
134
|
-
},
|
|
135
|
-
],
|
|
136
|
-
suggestions: ['Check AI service configuration and API key validity'],
|
|
137
|
-
debug: debugInfo,
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
throw error;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
/**
|
|
144
|
-
* Execute AI review using session reuse - reuses an existing ProbeAgent session
|
|
145
|
-
*/
|
|
146
|
-
async executeReviewWithSessionReuse(prInfo, customPrompt, parentSessionId, schema, checkName) {
|
|
147
|
-
const startTime = Date.now();
|
|
148
|
-
const timestamp = new Date().toISOString();
|
|
149
|
-
// Get the existing session
|
|
150
|
-
const existingAgent = this.sessionRegistry.getSession(parentSessionId);
|
|
151
|
-
if (!existingAgent) {
|
|
152
|
-
throw new Error(`Session not found for reuse: ${parentSessionId}. Ensure the parent check completed successfully.`);
|
|
153
|
-
}
|
|
154
|
-
// Build prompt from custom instructions
|
|
155
|
-
const prompt = await this.buildCustomPrompt(prInfo, customPrompt, schema);
|
|
156
|
-
log(`š Reusing AI session ${parentSessionId} for review...`);
|
|
157
|
-
log(`š§ Debug: Raw schema parameter: ${JSON.stringify(schema)} (type: ${typeof schema})`);
|
|
158
|
-
log(`Schema type: ${schema || 'none (no schema)'}`);
|
|
159
|
-
let debugInfo;
|
|
160
|
-
if (this.config.debug) {
|
|
161
|
-
debugInfo = {
|
|
162
|
-
prompt,
|
|
163
|
-
rawResponse: '',
|
|
164
|
-
provider: this.config.provider || 'unknown',
|
|
165
|
-
model: this.config.model || 'default',
|
|
166
|
-
apiKeySource: this.getApiKeySource(),
|
|
167
|
-
processingTime: 0,
|
|
168
|
-
promptLength: prompt.length,
|
|
169
|
-
responseLength: 0,
|
|
170
|
-
errors: [],
|
|
171
|
-
jsonParseSuccess: false,
|
|
172
|
-
timestamp,
|
|
173
|
-
schemaName: schema,
|
|
174
|
-
schema: undefined, // Will be populated when schema is loaded
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
try {
|
|
178
|
-
// Use existing agent's answer method instead of creating new agent
|
|
179
|
-
const { response, effectiveSchema } = await this.callProbeAgentWithExistingSession(existingAgent, prompt, schema, debugInfo, checkName);
|
|
180
|
-
const processingTime = Date.now() - startTime;
|
|
181
|
-
if (debugInfo) {
|
|
182
|
-
debugInfo.rawResponse = response;
|
|
183
|
-
debugInfo.responseLength = response.length;
|
|
184
|
-
debugInfo.processingTime = processingTime;
|
|
185
|
-
}
|
|
186
|
-
const result = this.parseAIResponse(response, debugInfo, effectiveSchema);
|
|
187
|
-
if (debugInfo) {
|
|
188
|
-
result.debug = debugInfo;
|
|
189
|
-
}
|
|
190
|
-
return result;
|
|
191
|
-
}
|
|
192
|
-
catch (error) {
|
|
193
|
-
if (debugInfo) {
|
|
194
|
-
debugInfo.errors = [error instanceof Error ? error.message : String(error)];
|
|
195
|
-
debugInfo.processingTime = Date.now() - startTime;
|
|
196
|
-
// In debug mode, return a review with the error captured
|
|
197
|
-
return {
|
|
198
|
-
issues: [
|
|
199
|
-
{
|
|
200
|
-
file: 'system',
|
|
201
|
-
line: 0,
|
|
202
|
-
ruleId: 'system/ai-session-reuse-error',
|
|
203
|
-
message: error instanceof Error ? error.message : String(error),
|
|
204
|
-
severity: 'error',
|
|
205
|
-
category: 'logic',
|
|
206
|
-
},
|
|
207
|
-
],
|
|
208
|
-
suggestions: [
|
|
209
|
-
'Check session reuse configuration and ensure parent check completed successfully',
|
|
210
|
-
],
|
|
211
|
-
debug: debugInfo,
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
throw error;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
/**
|
|
218
|
-
* Register a new AI session in the session registry
|
|
219
|
-
*/
|
|
220
|
-
registerSession(sessionId, agent) {
|
|
221
|
-
this.sessionRegistry.registerSession(sessionId, agent);
|
|
222
|
-
}
|
|
223
|
-
/**
|
|
224
|
-
* Cleanup a session from the registry
|
|
225
|
-
*/
|
|
226
|
-
cleanupSession(sessionId) {
|
|
227
|
-
this.sessionRegistry.unregisterSession(sessionId);
|
|
228
|
-
}
|
|
229
|
-
/**
|
|
230
|
-
* Build a custom prompt for AI review with XML-formatted data
|
|
231
|
-
*/
|
|
232
|
-
async buildCustomPrompt(prInfo, customInstructions, _schema) {
|
|
233
|
-
const prContext = this.formatPRContext(prInfo);
|
|
234
|
-
const analysisType = prInfo.isIncremental ? 'INCREMENTAL' : 'FULL';
|
|
235
|
-
return `You are a senior code reviewer.
|
|
236
|
-
|
|
237
|
-
ANALYSIS TYPE: ${analysisType}
|
|
238
|
-
${analysisType === 'INCREMENTAL'
|
|
239
|
-
? '- You are analyzing a NEW COMMIT added to an existing PR. Focus on the <commit_diff> section for changes made in this specific commit.'
|
|
240
|
-
: '- You are analyzing the COMPLETE PR. Review all changes in the <full_diff> section.'}
|
|
241
|
-
|
|
242
|
-
REVIEW INSTRUCTIONS:
|
|
243
|
-
${customInstructions}
|
|
244
|
-
|
|
245
|
-
Analyze the following structured pull request data:
|
|
246
|
-
|
|
247
|
-
${prContext}
|
|
248
|
-
|
|
249
|
-
XML Data Structure Guide:
|
|
250
|
-
- <pull_request>: Root element containing all PR information
|
|
251
|
-
- <metadata>: PR metadata (number, title, author, branches, statistics)
|
|
252
|
-
- <description>: PR description text if provided
|
|
253
|
-
- <full_diff>: Complete unified diff of all changes (for FULL analysis)
|
|
254
|
-
- <commit_diff>: Diff of only the latest commit (for INCREMENTAL analysis)
|
|
255
|
-
- <files_summary>: List of all files changed with statistics
|
|
256
|
-
|
|
257
|
-
IMPORTANT RULES:
|
|
258
|
-
1. Only analyze code that appears with + (additions) or - (deletions) in the diff
|
|
259
|
-
2. Ignore unchanged code unless it's directly relevant to understanding a change
|
|
260
|
-
3. Line numbers in your response should match the actual file line numbers
|
|
261
|
-
4. Focus on real issues, not nitpicks
|
|
262
|
-
5. Provide actionable, specific feedback
|
|
263
|
-
6. For INCREMENTAL analysis, ONLY review changes in <commit_diff>
|
|
264
|
-
7. For FULL analysis, review all changes in <full_diff>`;
|
|
265
|
-
}
|
|
266
|
-
// REMOVED: Built-in prompts - only use custom prompts from .visor.yaml
|
|
267
|
-
// REMOVED: getFocusInstructions - only use custom prompts from .visor.yaml
|
|
268
|
-
/**
|
|
269
|
-
* Format PR context for the AI using XML structure
|
|
270
|
-
*/
|
|
271
|
-
formatPRContext(prInfo) {
|
|
272
|
-
let context = `<pull_request>
|
|
273
|
-
<metadata>
|
|
274
|
-
<number>${prInfo.number}</number>
|
|
275
|
-
<title>${this.escapeXml(prInfo.title)}</title>
|
|
276
|
-
<author>${prInfo.author}</author>
|
|
277
|
-
<base_branch>${prInfo.base}</base_branch>
|
|
278
|
-
<target_branch>${prInfo.head}</target_branch>
|
|
279
|
-
<total_additions>${prInfo.totalAdditions}</total_additions>
|
|
280
|
-
<total_deletions>${prInfo.totalDeletions}</total_deletions>
|
|
281
|
-
<files_changed_count>${prInfo.files.length}</files_changed_count>
|
|
282
|
-
</metadata>`;
|
|
283
|
-
// Add PR description if available
|
|
284
|
-
if (prInfo.body) {
|
|
285
|
-
context += `
|
|
286
|
-
<description>
|
|
287
|
-
${this.escapeXml(prInfo.body)}
|
|
288
|
-
</description>`;
|
|
289
|
-
}
|
|
290
|
-
// Add full diff if available (for complete PR review)
|
|
291
|
-
if (prInfo.fullDiff) {
|
|
292
|
-
context += `
|
|
293
|
-
<full_diff>
|
|
294
|
-
${this.escapeXml(prInfo.fullDiff)}
|
|
295
|
-
</full_diff>`;
|
|
296
|
-
}
|
|
297
|
-
// Add incremental commit diff if available (for new commit analysis)
|
|
298
|
-
if (prInfo.isIncremental) {
|
|
299
|
-
if (prInfo.commitDiff && prInfo.commitDiff.length > 0) {
|
|
300
|
-
context += `
|
|
301
|
-
<commit_diff>
|
|
302
|
-
${this.escapeXml(prInfo.commitDiff)}
|
|
303
|
-
</commit_diff>`;
|
|
304
|
-
}
|
|
305
|
-
else {
|
|
306
|
-
context += `
|
|
307
|
-
<commit_diff>
|
|
308
|
-
<!-- Commit diff could not be retrieved - falling back to full diff analysis -->
|
|
309
|
-
${prInfo.fullDiff ? this.escapeXml(prInfo.fullDiff) : ''}
|
|
310
|
-
</commit_diff>`;
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
// Add file summary for context
|
|
314
|
-
if (prInfo.files.length > 0) {
|
|
315
|
-
context += `
|
|
316
|
-
<files_summary>`;
|
|
317
|
-
prInfo.files.forEach((file, index) => {
|
|
318
|
-
context += `
|
|
319
|
-
<file index="${index + 1}">
|
|
320
|
-
<filename>${this.escapeXml(file.filename)}</filename>
|
|
321
|
-
<status>${file.status}</status>
|
|
322
|
-
<additions>${file.additions}</additions>
|
|
323
|
-
<deletions>${file.deletions}</deletions>
|
|
324
|
-
</file>`;
|
|
325
|
-
});
|
|
326
|
-
context += `
|
|
327
|
-
</files_summary>`;
|
|
328
|
-
}
|
|
329
|
-
context += `
|
|
330
|
-
</pull_request>`;
|
|
331
|
-
return context;
|
|
332
|
-
}
|
|
333
|
-
/**
|
|
334
|
-
* No longer escaping XML - returning text as-is
|
|
335
|
-
*/
|
|
336
|
-
escapeXml(text) {
|
|
337
|
-
return text;
|
|
338
|
-
}
|
|
339
|
-
/**
|
|
340
|
-
* Call ProbeAgent with an existing session
|
|
341
|
-
*/
|
|
342
|
-
async callProbeAgentWithExistingSession(agent, prompt, schema, debugInfo, _checkName) {
|
|
343
|
-
// Handle mock model/provider for testing
|
|
344
|
-
if (this.config.model === 'mock' || this.config.provider === 'mock') {
|
|
345
|
-
log('š Using mock AI model/provider for testing (session reuse)');
|
|
346
|
-
const response = await this.generateMockResponse(prompt);
|
|
347
|
-
return { response, effectiveSchema: schema };
|
|
348
|
-
}
|
|
349
|
-
log('š Reusing existing ProbeAgent session for AI review...');
|
|
350
|
-
log(`š Prompt length: ${prompt.length} characters`);
|
|
351
|
-
log(`āļø Model: ${this.config.model || 'default'}, Provider: ${this.config.provider || 'auto'}`);
|
|
352
|
-
try {
|
|
353
|
-
log('š Calling existing ProbeAgent with answer()...');
|
|
354
|
-
// Load and pass the actual schema content if provided (skip for plain schema)
|
|
355
|
-
let schemaString = undefined;
|
|
356
|
-
let effectiveSchema = schema;
|
|
357
|
-
if (schema && schema !== 'plain') {
|
|
358
|
-
try {
|
|
359
|
-
schemaString = await this.loadSchemaContent(schema);
|
|
360
|
-
log(`š Loaded schema content for: ${schema}`);
|
|
361
|
-
log(`š Raw schema JSON:\n${schemaString}`);
|
|
362
|
-
}
|
|
363
|
-
catch (error) {
|
|
364
|
-
log(`ā ļø Failed to load schema ${schema}, proceeding without schema:`, error);
|
|
365
|
-
schemaString = undefined;
|
|
366
|
-
effectiveSchema = undefined; // Schema loading failed, treat as no schema
|
|
367
|
-
if (debugInfo && debugInfo.errors) {
|
|
368
|
-
debugInfo.errors.push(`Failed to load schema: ${error}`);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
else if (schema === 'plain') {
|
|
373
|
-
log(`š Using plain schema - no JSON validation will be applied`);
|
|
374
|
-
}
|
|
375
|
-
// Pass schema in options object with 'schema' property
|
|
376
|
-
const schemaOptions = schemaString ? { schema: schemaString } : undefined;
|
|
377
|
-
// Store the exact schema options being passed to ProbeAgent in debug info
|
|
378
|
-
if (debugInfo && schemaOptions) {
|
|
379
|
-
debugInfo.schema = JSON.stringify(schemaOptions, null, 2);
|
|
380
|
-
}
|
|
381
|
-
// Log the schema options being passed to ProbeAgent
|
|
382
|
-
if (schemaOptions) {
|
|
383
|
-
log(`šÆ Schema options passed to ProbeAgent.answer() (session reuse):`);
|
|
384
|
-
log(JSON.stringify(schemaOptions, null, 2));
|
|
385
|
-
}
|
|
386
|
-
// Use existing agent's answer method - this reuses the conversation context
|
|
387
|
-
const response = await agent.answer(prompt, undefined, schemaOptions);
|
|
388
|
-
log('ā
ProbeAgent session reuse completed successfully');
|
|
389
|
-
log(`š¤ Response length: ${response.length} characters`);
|
|
390
|
-
return { response, effectiveSchema };
|
|
391
|
-
}
|
|
392
|
-
catch (error) {
|
|
393
|
-
console.error('ā ProbeAgent session reuse failed:', error);
|
|
394
|
-
throw new Error(`ProbeAgent session reuse failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
/**
|
|
398
|
-
* Call ProbeAgent SDK with built-in schema validation
|
|
399
|
-
*/
|
|
400
|
-
async callProbeAgent(prompt, schema, debugInfo, _checkName, providedSessionId) {
|
|
401
|
-
// Handle mock model/provider for testing
|
|
402
|
-
if (this.config.model === 'mock' || this.config.provider === 'mock') {
|
|
403
|
-
log('š Using mock AI model/provider for testing');
|
|
404
|
-
const response = await this.generateMockResponse(prompt);
|
|
405
|
-
return { response, effectiveSchema: schema };
|
|
406
|
-
}
|
|
407
|
-
// Create ProbeAgent instance with proper options
|
|
408
|
-
const sessionId = providedSessionId ||
|
|
409
|
-
(() => {
|
|
410
|
-
const timestamp = new Date().toISOString();
|
|
411
|
-
return `visor-${timestamp.replace(/[:.]/g, '-')}-${_checkName || 'unknown'}`;
|
|
412
|
-
})();
|
|
413
|
-
log('š¤ Creating ProbeAgent for AI review...');
|
|
414
|
-
log(`š Session ID: ${sessionId}`);
|
|
415
|
-
log(`š Prompt length: ${prompt.length} characters`);
|
|
416
|
-
log(`āļø Model: ${this.config.model || 'default'}, Provider: ${this.config.provider || 'auto'}`);
|
|
417
|
-
// Store original env vars to restore later
|
|
418
|
-
const originalEnv = {
|
|
419
|
-
GOOGLE_API_KEY: process.env.GOOGLE_API_KEY,
|
|
420
|
-
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
|
|
421
|
-
OPENAI_API_KEY: process.env.OPENAI_API_KEY,
|
|
422
|
-
};
|
|
423
|
-
try {
|
|
424
|
-
// Set environment variables for ProbeAgent
|
|
425
|
-
// ProbeAgent SDK expects these to be in the environment
|
|
426
|
-
if (this.config.provider === 'google' && this.config.apiKey) {
|
|
427
|
-
process.env.GOOGLE_API_KEY = this.config.apiKey;
|
|
428
|
-
}
|
|
429
|
-
else if (this.config.provider === 'anthropic' && this.config.apiKey) {
|
|
430
|
-
process.env.ANTHROPIC_API_KEY = this.config.apiKey;
|
|
431
|
-
}
|
|
432
|
-
else if (this.config.provider === 'openai' && this.config.apiKey) {
|
|
433
|
-
process.env.OPENAI_API_KEY = this.config.apiKey;
|
|
434
|
-
}
|
|
435
|
-
const options = {
|
|
436
|
-
sessionId: sessionId,
|
|
437
|
-
promptType: schema ? 'code-review-template' : undefined,
|
|
438
|
-
allowEdit: false, // We don't want the agent to modify files
|
|
439
|
-
debug: this.config.debug || false,
|
|
440
|
-
};
|
|
441
|
-
// Add provider-specific options if configured
|
|
442
|
-
if (this.config.provider) {
|
|
443
|
-
options.provider = this.config.provider;
|
|
444
|
-
}
|
|
445
|
-
if (this.config.model) {
|
|
446
|
-
options.model = this.config.model;
|
|
447
|
-
}
|
|
448
|
-
const agent = new probe_1.ProbeAgent(options);
|
|
449
|
-
log('š Calling ProbeAgent...');
|
|
450
|
-
// Load and pass the actual schema content if provided (skip for plain schema)
|
|
451
|
-
let schemaString = undefined;
|
|
452
|
-
let effectiveSchema = schema;
|
|
453
|
-
if (schema && schema !== 'plain') {
|
|
454
|
-
try {
|
|
455
|
-
schemaString = await this.loadSchemaContent(schema);
|
|
456
|
-
log(`š Loaded schema content for: ${schema}`);
|
|
457
|
-
log(`š Raw schema JSON:\n${schemaString}`);
|
|
458
|
-
}
|
|
459
|
-
catch (error) {
|
|
460
|
-
log(`ā ļø Failed to load schema ${schema}, proceeding without schema:`, error);
|
|
461
|
-
schemaString = undefined;
|
|
462
|
-
effectiveSchema = undefined; // Schema loading failed, treat as no schema
|
|
463
|
-
if (debugInfo && debugInfo.errors) {
|
|
464
|
-
debugInfo.errors.push(`Failed to load schema: ${error}`);
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
else if (schema === 'plain') {
|
|
469
|
-
log(`š Using plain schema - no JSON validation will be applied`);
|
|
470
|
-
}
|
|
471
|
-
// ProbeAgent now handles schema formatting internally!
|
|
472
|
-
// Pass schema in options object with 'schema' property
|
|
473
|
-
const schemaOptions = schemaString ? { schema: schemaString } : undefined;
|
|
474
|
-
// Store the exact schema options being passed to ProbeAgent in debug info
|
|
475
|
-
if (debugInfo && schemaOptions) {
|
|
476
|
-
debugInfo.schema = JSON.stringify(schemaOptions, null, 2);
|
|
477
|
-
}
|
|
478
|
-
// Log the schema options being passed to ProbeAgent
|
|
479
|
-
if (schemaOptions) {
|
|
480
|
-
log(`šÆ Schema options passed to ProbeAgent.answer():`);
|
|
481
|
-
log(JSON.stringify(schemaOptions, null, 2));
|
|
482
|
-
}
|
|
483
|
-
// Log the equivalent CLI command for local reproduction
|
|
484
|
-
const provider = this.config.provider || 'auto';
|
|
485
|
-
const model = this.config.model || 'default';
|
|
486
|
-
// Save prompt to a temp file for easier reproduction
|
|
487
|
-
try {
|
|
488
|
-
const fs = require('fs');
|
|
489
|
-
const path = require('path');
|
|
490
|
-
const os = require('os');
|
|
491
|
-
const tempDir = os.tmpdir();
|
|
492
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
493
|
-
const promptFile = path.join(tempDir, `visor-prompt-${timestamp}.txt`);
|
|
494
|
-
fs.writeFileSync(promptFile, prompt, 'utf-8');
|
|
495
|
-
log(`\nš¾ Prompt saved to: ${promptFile}`);
|
|
496
|
-
log(`\nš To reproduce locally, run:`);
|
|
497
|
-
let cliCommand = `npx @probelabs/probe@latest agent`;
|
|
498
|
-
cliCommand += ` --provider ${provider}`;
|
|
499
|
-
if (model !== 'default') {
|
|
500
|
-
cliCommand += ` --model ${model}`;
|
|
501
|
-
}
|
|
502
|
-
if (schema) {
|
|
503
|
-
cliCommand += ` --schema output/${schema}/schema.json`;
|
|
504
|
-
}
|
|
505
|
-
cliCommand += ` "${promptFile}"`;
|
|
506
|
-
log(`\n$ ${cliCommand}\n`);
|
|
507
|
-
}
|
|
508
|
-
catch (error) {
|
|
509
|
-
log(`ā ļø Could not save prompt file: ${error}`);
|
|
510
|
-
}
|
|
511
|
-
const response = await agent.answer(prompt, undefined, schemaOptions);
|
|
512
|
-
log('ā
ProbeAgent completed successfully');
|
|
513
|
-
log(`š¤ Response length: ${response.length} characters`);
|
|
514
|
-
// Register the session for potential reuse by dependent checks
|
|
515
|
-
if (_checkName) {
|
|
516
|
-
this.registerSession(sessionId, agent);
|
|
517
|
-
log(`š§ Debug: Registered AI session for potential reuse: ${sessionId}`);
|
|
518
|
-
}
|
|
519
|
-
return { response, effectiveSchema };
|
|
520
|
-
}
|
|
521
|
-
catch (error) {
|
|
522
|
-
console.error('ā ProbeAgent failed:', error);
|
|
523
|
-
throw new Error(`ProbeAgent execution failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
524
|
-
}
|
|
525
|
-
finally {
|
|
526
|
-
// Restore original environment variables
|
|
527
|
-
Object.keys(originalEnv).forEach(key => {
|
|
528
|
-
if (originalEnv[key] === undefined) {
|
|
529
|
-
delete process.env[key];
|
|
530
|
-
}
|
|
531
|
-
else {
|
|
532
|
-
process.env[key] = originalEnv[key];
|
|
533
|
-
}
|
|
534
|
-
});
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
/**
|
|
538
|
-
* Load schema content from schema files
|
|
539
|
-
*/
|
|
540
|
-
async loadSchemaContent(schemaName) {
|
|
541
|
-
const fs = require('fs').promises;
|
|
542
|
-
const path = require('path');
|
|
543
|
-
// Sanitize schema name to prevent path traversal attacks
|
|
544
|
-
const sanitizedSchemaName = schemaName.replace(/[^a-zA-Z0-9-]/g, '');
|
|
545
|
-
if (!sanitizedSchemaName || sanitizedSchemaName !== schemaName) {
|
|
546
|
-
throw new Error('Invalid schema name');
|
|
547
|
-
}
|
|
548
|
-
// Construct path to schema file using sanitized name
|
|
549
|
-
const schemaPath = path.join(process.cwd(), 'output', sanitizedSchemaName, 'schema.json');
|
|
550
|
-
try {
|
|
551
|
-
// Return the schema as a string, not parsed JSON
|
|
552
|
-
const schemaContent = await fs.readFile(schemaPath, 'utf-8');
|
|
553
|
-
return schemaContent.trim();
|
|
554
|
-
}
|
|
555
|
-
catch (error) {
|
|
556
|
-
throw new Error(`Failed to load schema from ${schemaPath}: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
/**
|
|
560
|
-
* Parse AI response JSON
|
|
561
|
-
*/
|
|
562
|
-
parseAIResponse(response, debugInfo, _schema) {
|
|
563
|
-
log('š Parsing AI response...');
|
|
564
|
-
log(`š Raw response length: ${response.length} characters`);
|
|
565
|
-
// Log first and last 200 chars for debugging
|
|
566
|
-
if (response.length > 400) {
|
|
567
|
-
log('š Response preview (first 200 chars):', response.substring(0, 200));
|
|
568
|
-
log('š Response preview (last 200 chars):', response.substring(response.length - 200));
|
|
569
|
-
}
|
|
570
|
-
else {
|
|
571
|
-
log('š Full response preview:', response);
|
|
572
|
-
}
|
|
573
|
-
try {
|
|
574
|
-
// Handle different schema types differently
|
|
575
|
-
let reviewData;
|
|
576
|
-
// Handle plain schema or no schema - no JSON parsing, return response as-is
|
|
577
|
-
if (_schema === 'plain' || !_schema) {
|
|
578
|
-
log(`š ${_schema === 'plain' ? 'Plain' : 'No'} schema detected - returning raw response without JSON parsing`);
|
|
579
|
-
return {
|
|
580
|
-
issues: [],
|
|
581
|
-
suggestions: [response.trim()],
|
|
582
|
-
debug: debugInfo,
|
|
583
|
-
};
|
|
584
|
-
}
|
|
585
|
-
{
|
|
586
|
-
// For other schemas (code-review, etc.), extract and parse JSON with boundary detection
|
|
587
|
-
log('š Extracting JSON from AI response...');
|
|
588
|
-
// Try direct parsing first - if AI returned pure JSON
|
|
589
|
-
try {
|
|
590
|
-
reviewData = JSON.parse(response.trim());
|
|
591
|
-
log('ā
Successfully parsed direct JSON response');
|
|
592
|
-
if (debugInfo)
|
|
593
|
-
debugInfo.jsonParseSuccess = true;
|
|
594
|
-
}
|
|
595
|
-
catch {
|
|
596
|
-
log('š Direct parsing failed, trying to extract JSON from response...');
|
|
597
|
-
// If the response starts with "I cannot" or similar, it's likely a refusal
|
|
598
|
-
if (response.toLowerCase().includes('i cannot') ||
|
|
599
|
-
response.toLowerCase().includes('unable to')) {
|
|
600
|
-
console.error('š« AI refused to analyze - returning empty result');
|
|
601
|
-
return {
|
|
602
|
-
issues: [],
|
|
603
|
-
suggestions: [
|
|
604
|
-
'AI was unable to analyze this code. Please check the content or try again.',
|
|
605
|
-
],
|
|
606
|
-
};
|
|
607
|
-
}
|
|
608
|
-
// Try to extract JSON using improved method with proper bracket matching
|
|
609
|
-
const jsonString = this.extractJsonFromResponse(response);
|
|
610
|
-
if (jsonString) {
|
|
611
|
-
try {
|
|
612
|
-
reviewData = JSON.parse(jsonString);
|
|
613
|
-
log('ā
Successfully parsed extracted JSON');
|
|
614
|
-
if (debugInfo)
|
|
615
|
-
debugInfo.jsonParseSuccess = true;
|
|
616
|
-
}
|
|
617
|
-
catch {
|
|
618
|
-
log('š§ Extracted JSON parsing failed, falling back to plain text handling...');
|
|
619
|
-
// Check if response is plain text and doesn't contain structured data
|
|
620
|
-
if (!response.includes('{') && !response.includes('}')) {
|
|
621
|
-
log('š§ Plain text response detected, creating structured fallback...');
|
|
622
|
-
const isNoChanges = response.toLowerCase().includes('no') &&
|
|
623
|
-
(response.toLowerCase().includes('changes') ||
|
|
624
|
-
response.toLowerCase().includes('code'));
|
|
625
|
-
reviewData = {
|
|
626
|
-
issues: [],
|
|
627
|
-
suggestions: isNoChanges
|
|
628
|
-
? ['No code changes detected in this analysis']
|
|
629
|
-
: [
|
|
630
|
-
`AI response: ${response.substring(0, 200)}${response.length > 200 ? '...' : ''}`,
|
|
631
|
-
],
|
|
632
|
-
};
|
|
633
|
-
}
|
|
634
|
-
else {
|
|
635
|
-
// Fallback: treat the entire response as a suggestion
|
|
636
|
-
log('š§ Creating fallback response from non-JSON content...');
|
|
637
|
-
reviewData = {
|
|
638
|
-
issues: [],
|
|
639
|
-
suggestions: [response.trim()],
|
|
640
|
-
};
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
else {
|
|
645
|
-
// No JSON found at all - treat as plain text response
|
|
646
|
-
log('š§ No JSON found in response, treating as plain text...');
|
|
647
|
-
reviewData = {
|
|
648
|
-
issues: [],
|
|
649
|
-
suggestions: [response.trim()],
|
|
650
|
-
};
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
// Standard code-review schema processing
|
|
655
|
-
log('š Validating parsed review data...');
|
|
656
|
-
log(`š Overall score: ${0}`);
|
|
657
|
-
log(`š Total issues: ${reviewData.issues?.length || 0}`);
|
|
658
|
-
log(`šØ Critical issues: ${reviewData.issues?.filter((i) => i.severity === 'critical').length || 0}`);
|
|
659
|
-
log(`š” Suggestions count: ${Array.isArray(reviewData.suggestions) ? reviewData.suggestions.length : 0}`);
|
|
660
|
-
log(`š¬ Comments count: ${Array.isArray(reviewData.issues) ? reviewData.issues.length : 0}`);
|
|
661
|
-
// Process issues from the simplified format
|
|
662
|
-
const processedIssues = Array.isArray(reviewData.issues)
|
|
663
|
-
? reviewData.issues.map((issue, index) => {
|
|
664
|
-
log(`š Processing issue ${index + 1}:`, issue);
|
|
665
|
-
return {
|
|
666
|
-
file: issue.file || 'unknown',
|
|
667
|
-
line: issue.line || 1,
|
|
668
|
-
endLine: issue.endLine,
|
|
669
|
-
ruleId: issue.ruleId || `${issue.category || 'general'}/unknown`,
|
|
670
|
-
message: issue.message || '',
|
|
671
|
-
severity: issue.severity,
|
|
672
|
-
category: issue.category,
|
|
673
|
-
suggestion: issue.suggestion,
|
|
674
|
-
replacement: issue.replacement,
|
|
675
|
-
};
|
|
676
|
-
})
|
|
677
|
-
: [];
|
|
678
|
-
// Validate and convert to ReviewSummary format
|
|
679
|
-
const result = {
|
|
680
|
-
issues: processedIssues,
|
|
681
|
-
suggestions: Array.isArray(reviewData.suggestions) ? reviewData.suggestions : [],
|
|
682
|
-
};
|
|
683
|
-
// Log issue counts
|
|
684
|
-
const criticalCount = (result.issues || []).filter(i => i.severity === 'critical').length;
|
|
685
|
-
if (criticalCount > 0) {
|
|
686
|
-
log(`šØ Found ${criticalCount} critical severity issue(s)`);
|
|
687
|
-
}
|
|
688
|
-
log(`š Total issues: ${(result.issues || []).length}`);
|
|
689
|
-
log('ā
Successfully created ReviewSummary');
|
|
690
|
-
return result;
|
|
691
|
-
}
|
|
692
|
-
catch (error) {
|
|
693
|
-
console.error('ā Failed to parse AI response:', error);
|
|
694
|
-
console.error('š FULL RAW RESPONSE:');
|
|
695
|
-
console.error('='.repeat(80));
|
|
696
|
-
console.error(response);
|
|
697
|
-
console.error('='.repeat(80));
|
|
698
|
-
console.error(`š Response length: ${response.length} characters`);
|
|
699
|
-
// Try to provide more helpful error information
|
|
700
|
-
if (error instanceof SyntaxError) {
|
|
701
|
-
console.error('š JSON parsing error - the response may not be valid JSON');
|
|
702
|
-
console.error('š Error details:', error.message);
|
|
703
|
-
// Try to identify where the parsing failed
|
|
704
|
-
const errorMatch = error.message.match(/position (\d+)/);
|
|
705
|
-
if (errorMatch) {
|
|
706
|
-
const position = parseInt(errorMatch[1]);
|
|
707
|
-
console.error(`š Error at position ${position}:`);
|
|
708
|
-
const start = Math.max(0, position - 50);
|
|
709
|
-
const end = Math.min(response.length, position + 50);
|
|
710
|
-
console.error(`š Context: "${response.substring(start, end)}"`);
|
|
711
|
-
// Show the first 100 characters to understand what format the AI returned
|
|
712
|
-
console.error(`š Response beginning: "${response.substring(0, 100)}"`);
|
|
713
|
-
}
|
|
714
|
-
// Check if response contains common non-JSON patterns
|
|
715
|
-
if (response.includes('I cannot')) {
|
|
716
|
-
console.error('š Response appears to be a refusal/explanation rather than JSON');
|
|
717
|
-
}
|
|
718
|
-
if (response.includes('```')) {
|
|
719
|
-
console.error('š Response appears to contain markdown code blocks');
|
|
720
|
-
}
|
|
721
|
-
if (response.startsWith('<')) {
|
|
722
|
-
console.error('š Response appears to start with XML/HTML');
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
throw new Error(`Invalid AI response format: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
/**
|
|
729
|
-
* Extract JSON from a response that might contain surrounding text
|
|
730
|
-
* Uses proper bracket matching to find valid JSON objects or arrays
|
|
731
|
-
*/
|
|
732
|
-
extractJsonFromResponse(response) {
|
|
733
|
-
const text = response.trim();
|
|
734
|
-
// Try to find JSON objects first (higher priority)
|
|
735
|
-
let bestJson = this.findJsonWithBracketMatching(text, '{', '}');
|
|
736
|
-
// If no object found, try arrays
|
|
737
|
-
if (!bestJson) {
|
|
738
|
-
bestJson = this.findJsonWithBracketMatching(text, '[', ']');
|
|
739
|
-
}
|
|
740
|
-
return bestJson;
|
|
741
|
-
}
|
|
742
|
-
/**
|
|
743
|
-
* Find JSON with proper bracket matching to avoid false positives
|
|
744
|
-
*/
|
|
745
|
-
findJsonWithBracketMatching(text, openChar, closeChar) {
|
|
746
|
-
const firstIndex = text.indexOf(openChar);
|
|
747
|
-
if (firstIndex === -1)
|
|
748
|
-
return null;
|
|
749
|
-
let depth = 0;
|
|
750
|
-
let inString = false;
|
|
751
|
-
let escaping = false;
|
|
752
|
-
for (let i = firstIndex; i < text.length; i++) {
|
|
753
|
-
const char = text[i];
|
|
754
|
-
if (escaping) {
|
|
755
|
-
escaping = false;
|
|
756
|
-
continue;
|
|
757
|
-
}
|
|
758
|
-
if (char === '\\' && inString) {
|
|
759
|
-
escaping = true;
|
|
760
|
-
continue;
|
|
761
|
-
}
|
|
762
|
-
if (char === '"' && !escaping) {
|
|
763
|
-
inString = !inString;
|
|
764
|
-
continue;
|
|
765
|
-
}
|
|
766
|
-
if (!inString) {
|
|
767
|
-
if (char === openChar) {
|
|
768
|
-
depth++;
|
|
769
|
-
}
|
|
770
|
-
else if (char === closeChar) {
|
|
771
|
-
depth--;
|
|
772
|
-
if (depth === 0) {
|
|
773
|
-
// Found matching closing bracket
|
|
774
|
-
const candidate = text.substring(firstIndex, i + 1);
|
|
775
|
-
try {
|
|
776
|
-
JSON.parse(candidate); // Validate it's actually valid JSON
|
|
777
|
-
return candidate;
|
|
778
|
-
}
|
|
779
|
-
catch {
|
|
780
|
-
// This wasn't valid JSON, keep looking
|
|
781
|
-
break;
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
return null;
|
|
788
|
-
}
|
|
789
|
-
/**
|
|
790
|
-
* Generate mock response for testing
|
|
791
|
-
*/
|
|
792
|
-
async generateMockResponse(_prompt) {
|
|
793
|
-
// Simulate some processing time
|
|
794
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
795
|
-
// Generate mock response based on prompt content
|
|
796
|
-
const mockResponse = {
|
|
797
|
-
content: JSON.stringify({
|
|
798
|
-
issues: [
|
|
799
|
-
{
|
|
800
|
-
file: 'test.ts',
|
|
801
|
-
line: 7,
|
|
802
|
-
endLine: 11,
|
|
803
|
-
ruleId: 'security/sql-injection',
|
|
804
|
-
message: 'SQL injection vulnerability detected in dynamic query construction',
|
|
805
|
-
severity: 'critical',
|
|
806
|
-
category: 'security',
|
|
807
|
-
suggestion: 'Use parameterized queries or ORM methods to prevent SQL injection',
|
|
808
|
-
},
|
|
809
|
-
{
|
|
810
|
-
file: 'test.ts',
|
|
811
|
-
line: 14,
|
|
812
|
-
endLine: 23,
|
|
813
|
-
ruleId: 'performance/nested-loops',
|
|
814
|
-
message: 'Inefficient nested loops with O(n²) complexity',
|
|
815
|
-
severity: 'warning',
|
|
816
|
-
category: 'performance',
|
|
817
|
-
suggestion: 'Consider using more efficient algorithms or caching mechanisms',
|
|
818
|
-
},
|
|
819
|
-
{
|
|
820
|
-
file: 'test.ts',
|
|
821
|
-
line: 28,
|
|
822
|
-
ruleId: 'style/inconsistent-naming',
|
|
823
|
-
message: 'Inconsistent variable naming and formatting',
|
|
824
|
-
severity: 'info',
|
|
825
|
-
category: 'style',
|
|
826
|
-
suggestion: 'Use consistent camelCase naming and proper spacing',
|
|
827
|
-
},
|
|
828
|
-
],
|
|
829
|
-
summary: {
|
|
830
|
-
totalIssues: 3,
|
|
831
|
-
criticalIssues: 1,
|
|
832
|
-
},
|
|
833
|
-
}),
|
|
834
|
-
};
|
|
835
|
-
return JSON.stringify(mockResponse);
|
|
836
|
-
}
|
|
837
|
-
/**
|
|
838
|
-
* Get the API key source for debugging (without revealing the key)
|
|
839
|
-
*/
|
|
840
|
-
getApiKeySource() {
|
|
841
|
-
if (process.env.GOOGLE_API_KEY && this.config.provider === 'google') {
|
|
842
|
-
return 'GOOGLE_API_KEY';
|
|
843
|
-
}
|
|
844
|
-
if (process.env.ANTHROPIC_API_KEY && this.config.provider === 'anthropic') {
|
|
845
|
-
return 'ANTHROPIC_API_KEY';
|
|
846
|
-
}
|
|
847
|
-
if (process.env.OPENAI_API_KEY && this.config.provider === 'openai') {
|
|
848
|
-
return 'OPENAI_API_KEY';
|
|
849
|
-
}
|
|
850
|
-
return 'unknown';
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
exports.AIReviewService = AIReviewService;
|
|
854
|
-
//# sourceMappingURL=ai-review-service.js.map
|