@probelabs/visor 0.1.10 → 0.1.18
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/README.md +78 -0
- package/defaults/.visor.yaml +54 -0
- package/dist/ai-review-service.d.ts +13 -0
- package/dist/ai-review-service.d.ts.map +1 -1
- package/dist/ai-review-service.js +142 -73
- package/dist/ai-review-service.js.map +1 -1
- package/dist/check-execution-engine.d.ts +41 -1
- package/dist/check-execution-engine.d.ts.map +1 -1
- package/dist/check-execution-engine.js +376 -19
- package/dist/check-execution-engine.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +19 -3
- package/dist/config.js.map +1 -1
- package/dist/event-mapper.d.ts.map +1 -1
- package/dist/event-mapper.js +3 -5
- package/dist/event-mapper.js.map +1 -1
- package/dist/failure-condition-evaluator.d.ts +11 -3
- package/dist/failure-condition-evaluator.d.ts.map +1 -1
- package/dist/failure-condition-evaluator.js +41 -5
- package/dist/failure-condition-evaluator.js.map +1 -1
- package/dist/github-check-service.d.ts.map +1 -1
- package/dist/github-check-service.js +26 -39
- package/dist/github-check-service.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +332 -681
- package/dist/index.js.map +1 -1
- package/dist/licenses.txt +2300 -0
- package/dist/output/code-review/schema.json +84 -0
- package/dist/output/code-review/template.liquid +32 -0
- package/dist/output/plain/schema.json +14 -0
- package/dist/output/plain/template.liquid +1 -0
- package/dist/output-formatters.d.ts.map +1 -1
- package/dist/output-formatters.js +14 -14
- package/dist/output-formatters.js.map +1 -1
- package/dist/pr-analyzer.d.ts +10 -1
- package/dist/pr-analyzer.d.ts.map +1 -1
- package/dist/pr-analyzer.js +2 -1
- package/dist/pr-analyzer.js.map +1 -1
- package/dist/pr-detector.d.ts +14 -4
- package/dist/pr-detector.d.ts.map +1 -1
- package/dist/pr-detector.js.map +1 -1
- package/dist/providers/ai-check-provider.d.ts.map +1 -1
- package/dist/providers/ai-check-provider.js +27 -23
- package/dist/providers/ai-check-provider.js.map +1 -1
- package/dist/providers/check-provider-registry.js +2 -2
- package/dist/providers/check-provider-registry.js.map +1 -1
- package/dist/providers/check-provider.interface.d.ts +3 -1
- package/dist/providers/check-provider.interface.d.ts.map +1 -1
- package/dist/providers/check-provider.interface.js.map +1 -1
- package/dist/providers/index.d.ts +1 -1
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +3 -3
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/noop-check-provider.d.ts +25 -0
- package/dist/providers/noop-check-provider.d.ts.map +1 -0
- package/dist/providers/noop-check-provider.js +55 -0
- package/dist/providers/noop-check-provider.js.map +1 -0
- package/dist/providers/tool-check-provider.d.ts +4 -1
- package/dist/providers/tool-check-provider.d.ts.map +1 -1
- package/dist/providers/tool-check-provider.js +64 -15
- package/dist/providers/tool-check-provider.js.map +1 -1
- package/dist/providers/webhook-check-provider.js +3 -3
- package/dist/providers/webhook-check-provider.js.map +1 -1
- package/dist/reviewer.d.ts +19 -37
- package/dist/reviewer.d.ts.map +1 -1
- package/dist/reviewer.js +85 -596
- package/dist/reviewer.js.map +1 -1
- package/dist/tiktoken_bg.wasm +0 -0
- package/dist/types/config.d.ts +79 -6
- package/dist/types/config.d.ts.map +1 -1
- package/package.json +3 -3
- package/dist/providers/script-check-provider.d.ts +0 -23
- package/dist/providers/script-check-provider.d.ts.map +0 -1
- package/dist/providers/script-check-provider.js +0 -163
- package/dist/providers/script-check-provider.js.map +0 -1
package/README.md
CHANGED
|
@@ -1011,6 +1011,84 @@ checks:
|
|
|
1011
1011
|
- **Structured Output**: JSON Schema validation ensures consistent data
|
|
1012
1012
|
- **Flexible Rendering**: Different templates for different output types
|
|
1013
1013
|
|
|
1014
|
+
### GitHub Integration Schema Requirements
|
|
1015
|
+
|
|
1016
|
+
Visor is **fully schema-agnostic** - checks can return any structure and templates handle all formatting logic. However, for GitHub Checks API integration (status checks, outputs), specific structure may be required:
|
|
1017
|
+
|
|
1018
|
+
#### Unstructured Checks (No Schema / Plain Schema)
|
|
1019
|
+
```yaml
|
|
1020
|
+
# ✅ No-schema and plain schema behave identically
|
|
1021
|
+
overview:
|
|
1022
|
+
type: ai
|
|
1023
|
+
# No schema - returns raw markdown directly to PR comments
|
|
1024
|
+
prompt: "Analyze this PR and provide an overview"
|
|
1025
|
+
|
|
1026
|
+
documentation:
|
|
1027
|
+
type: ai
|
|
1028
|
+
schema: plain # Equivalent to no schema
|
|
1029
|
+
prompt: "Generate documentation for these changes"
|
|
1030
|
+
```
|
|
1031
|
+
|
|
1032
|
+
**Behavior**: AI returns raw text/markdown → Posted as-is to PR comments → GitHub integration reports 0 issues
|
|
1033
|
+
|
|
1034
|
+
#### Structured Checks (GitHub Checks API Compatible)
|
|
1035
|
+
```yaml
|
|
1036
|
+
security:
|
|
1037
|
+
type: ai
|
|
1038
|
+
schema: code-review # Built-in schema, works out of the box
|
|
1039
|
+
prompt: "Review for security issues and return findings as JSON"
|
|
1040
|
+
|
|
1041
|
+
# Custom schema example
|
|
1042
|
+
custom-check:
|
|
1043
|
+
type: ai
|
|
1044
|
+
schema: |
|
|
1045
|
+
{
|
|
1046
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
1047
|
+
"type": "object",
|
|
1048
|
+
"required": ["issues"],
|
|
1049
|
+
"properties": {
|
|
1050
|
+
"issues": {
|
|
1051
|
+
"type": "array",
|
|
1052
|
+
"items": {
|
|
1053
|
+
"type": "object",
|
|
1054
|
+
"required": ["file", "line", "message", "severity"],
|
|
1055
|
+
"properties": {
|
|
1056
|
+
"file": { "type": "string" },
|
|
1057
|
+
"line": { "type": "integer" },
|
|
1058
|
+
"ruleId": { "type": "string" },
|
|
1059
|
+
"message": { "type": "string" },
|
|
1060
|
+
"severity": { "enum": ["critical", "error", "warning", "info"] },
|
|
1061
|
+
"category": { "type": "string" }
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
prompt: "Review the code and return JSON matching the schema"
|
|
1068
|
+
```
|
|
1069
|
+
|
|
1070
|
+
**Required Structure for GitHub Checks API Integration**:
|
|
1071
|
+
- `issues`: Array of issue objects (required for GitHub status checks)
|
|
1072
|
+
- `issues[].severity`: Must be `"critical"`, `"error"`, `"warning"`, or `"info"`
|
|
1073
|
+
- `issues[].file`: File path (required for GitHub annotations)
|
|
1074
|
+
- `issues[].line`: Line number (required for GitHub annotations)
|
|
1075
|
+
- `issues[].message`: Issue description (required for GitHub annotations)
|
|
1076
|
+
|
|
1077
|
+
#### GitHub Checks API Features
|
|
1078
|
+
When checks return the structured format above:
|
|
1079
|
+
- ✅ **GitHub Status Checks**: Pass/fail based on severity thresholds
|
|
1080
|
+
- ✅ **GitHub Annotations**: Issues appear as file annotations in PR
|
|
1081
|
+
- ✅ **Action Outputs**: `issues-found`, `critical-issues-found` outputs
|
|
1082
|
+
- ✅ **PR Comments**: Structured table format with issue details
|
|
1083
|
+
|
|
1084
|
+
#### Schema Behavior Summary
|
|
1085
|
+
| Schema Type | AI Output | Comment Rendering | GitHub Checks API |
|
|
1086
|
+
|-------------|-----------|-------------------|-------------------|
|
|
1087
|
+
| **None/Plain** | Raw text/markdown | ✅ Posted as-is | ❌ No status checks, 0 issues |
|
|
1088
|
+
| **Structured** | JSON with `issues[]` | ✅ Table format | ✅ Full GitHub integration |
|
|
1089
|
+
|
|
1090
|
+
**Key Design**: Use unstructured (none/plain) for narrative content like overviews and documentation. Use structured schemas for actionable code review findings that integrate with GitHub's checking system.
|
|
1091
|
+
|
|
1014
1092
|
## 🧠 Advanced AI Features
|
|
1015
1093
|
|
|
1016
1094
|
### XML-Formatted Analysis
|
package/defaults/.visor.yaml
CHANGED
|
@@ -22,6 +22,49 @@ max_parallelism: 1
|
|
|
22
22
|
fail_if: "output.issues && output.issues.some(i => i.severity === 'critical')"
|
|
23
23
|
|
|
24
24
|
checks:
|
|
25
|
+
# AI-powered release notes generation - manual execution only for release workflows
|
|
26
|
+
release-notes:
|
|
27
|
+
type: ai
|
|
28
|
+
group: release
|
|
29
|
+
schema: plain
|
|
30
|
+
prompt: |
|
|
31
|
+
Generate professional release notes for version {{ env.TAG_NAME }} of this project.
|
|
32
|
+
|
|
33
|
+
Analyze the git commits since the last release:
|
|
34
|
+
```
|
|
35
|
+
{{ env.GIT_LOG }}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
And the file changes summary:
|
|
39
|
+
```
|
|
40
|
+
{{ env.GIT_DIFF_STAT }}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Create release notes with these sections:
|
|
44
|
+
|
|
45
|
+
## 🚀 What's New in {{ env.TAG_NAME }}
|
|
46
|
+
|
|
47
|
+
### ✨ New Features
|
|
48
|
+
List any new features added (look for feat: commits)
|
|
49
|
+
|
|
50
|
+
### 🐛 Bug Fixes
|
|
51
|
+
List any bugs fixed (look for fix: commits)
|
|
52
|
+
|
|
53
|
+
### 📈 Improvements
|
|
54
|
+
List any improvements or refactoring (look for refactor:, perf:, chore:, build: commits)
|
|
55
|
+
|
|
56
|
+
### 🔥 Breaking Changes
|
|
57
|
+
List any breaking changes if present (look for BREAKING CHANGE or ! in commits)
|
|
58
|
+
|
|
59
|
+
### 📊 Statistics
|
|
60
|
+
- Number of commits since last release
|
|
61
|
+
- Number of contributors involved
|
|
62
|
+
- Number of files changed
|
|
63
|
+
|
|
64
|
+
Keep descriptions concise and user-friendly. Focus on what changed from a user perspective, not implementation details.
|
|
65
|
+
Use present tense and action-oriented language. Group similar changes together.
|
|
66
|
+
on: [manual]
|
|
67
|
+
|
|
25
68
|
# PR overview with intelligent analysis - runs first to establish context
|
|
26
69
|
overview:
|
|
27
70
|
type: ai
|
|
@@ -198,10 +241,21 @@ checks:
|
|
|
198
241
|
reuse_ai_session: true # 🔄 Reuses the performance check's AI session for context continuity
|
|
199
242
|
on: [pr_opened, pr_updated]
|
|
200
243
|
|
|
244
|
+
# Command orchestrator - demonstrates noop type for triggering multiple checks
|
|
245
|
+
review-all:
|
|
246
|
+
type: noop
|
|
247
|
+
command: '/review'
|
|
248
|
+
depends_on: [overview, security, performance, quality]
|
|
249
|
+
on: [issue_comment]
|
|
250
|
+
if: "event.isPullRequest" # Only trigger on PR comments, not issues
|
|
251
|
+
group: orchestrator
|
|
252
|
+
|
|
201
253
|
# Intelligent Issue Assistant - provides sophisticated issue triage and assistance
|
|
202
254
|
issue-assistant:
|
|
203
255
|
type: ai
|
|
204
256
|
group: issue-support
|
|
257
|
+
command: "ask"
|
|
258
|
+
if: "event.name === 'issues' && event.action === 'opened' || (event.name === 'issue_comment' && event.comment && event.comment.body && event.comment.body.trim().startsWith('/ask'))"
|
|
205
259
|
prompt: |
|
|
206
260
|
You are an intelligent GitHub issue assistant for the {{ event.repository.fullName }} repository. Your role is to provide professional, knowledgeable assistance based on the trigger event.
|
|
207
261
|
|
|
@@ -33,6 +33,10 @@ export interface AIDebugInfo {
|
|
|
33
33
|
schema?: string;
|
|
34
34
|
/** Schema name/type requested */
|
|
35
35
|
schemaName?: string;
|
|
36
|
+
/** Checks executed during this review */
|
|
37
|
+
checksExecuted?: string[];
|
|
38
|
+
/** Whether parallel execution was used */
|
|
39
|
+
parallelExecution?: boolean;
|
|
36
40
|
/** Timestamp when request was made */
|
|
37
41
|
timestamp: string;
|
|
38
42
|
/** Total API calls made */
|
|
@@ -94,6 +98,15 @@ export declare class AIReviewService {
|
|
|
94
98
|
* Parse AI response JSON
|
|
95
99
|
*/
|
|
96
100
|
private parseAIResponse;
|
|
101
|
+
/**
|
|
102
|
+
* Extract JSON from a response that might contain surrounding text
|
|
103
|
+
* Uses proper bracket matching to find valid JSON objects or arrays
|
|
104
|
+
*/
|
|
105
|
+
private extractJsonFromResponse;
|
|
106
|
+
/**
|
|
107
|
+
* Find JSON with proper bracket matching to avoid false positives
|
|
108
|
+
*/
|
|
109
|
+
private findJsonWithBracketMatching;
|
|
97
110
|
/**
|
|
98
111
|
* Generate mock response for testing
|
|
99
112
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-review-service.d.ts","sourceRoot":"","sources":["../src/ai-review-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACvC,OAAO,EAAE,aAAa,EAAe,MAAM,YAAY,CAAC;AAcxD,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,QAAQ,GAAG,WAAW,GAAG,QAAQ,GAAG,MAAM,CAAC;IACtD,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,gCAAgC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,WAAW,EAAE,MAAM,CAAC;IACpB,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,4DAA4D;IAC5D,YAAY,EAAE,MAAM,CAAC;IACrB,sCAAsC;IACtC,cAAc,EAAE,MAAM,CAAC;IACvB,kCAAkC;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB,oCAAoC;IACpC,cAAc,EAAE,MAAM,CAAC;IACvB,6BAA6B;IAC7B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,qCAAqC;IACrC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,0CAA0C;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iCAAiC;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sCAAsC;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,2BAA2B;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,mCAAmC;IACnC,cAAc,CAAC,EAAE,KAAK,CAAC;QACrB,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,cAAc,EAAE,MAAM,CAAC;QACvB,OAAO,EAAE,OAAO,CAAC;KAClB,CAAC,CAAC;CACJ;AAoBD,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,eAAe,CAAkB;gBAE7B,MAAM,GAAE,cAAmB;IA4BvC;;OAEG;IACG,aAAa,CACjB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,MAAM,CAAC,EAAE,MAAM,EACf,UAAU,CAAC,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"ai-review-service.d.ts","sourceRoot":"","sources":["../src/ai-review-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -104,14 +104,14 @@ class AIReviewService {
|
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
try {
|
|
107
|
-
const response = await this.callProbeAgent(prompt, schema, debugInfo, _checkName, sessionId);
|
|
107
|
+
const { response, effectiveSchema } = await this.callProbeAgent(prompt, schema, debugInfo, _checkName, sessionId);
|
|
108
108
|
const processingTime = Date.now() - startTime;
|
|
109
109
|
if (debugInfo) {
|
|
110
110
|
debugInfo.rawResponse = response;
|
|
111
111
|
debugInfo.responseLength = response.length;
|
|
112
112
|
debugInfo.processingTime = processingTime;
|
|
113
113
|
}
|
|
114
|
-
const result = this.parseAIResponse(response, debugInfo,
|
|
114
|
+
const result = this.parseAIResponse(response, debugInfo, effectiveSchema);
|
|
115
115
|
if (debugInfo) {
|
|
116
116
|
result.debug = debugInfo;
|
|
117
117
|
}
|
|
@@ -176,14 +176,14 @@ class AIReviewService {
|
|
|
176
176
|
}
|
|
177
177
|
try {
|
|
178
178
|
// Use existing agent's answer method instead of creating new agent
|
|
179
|
-
const response = await this.callProbeAgentWithExistingSession(existingAgent, prompt, schema, debugInfo, checkName);
|
|
179
|
+
const { response, effectiveSchema } = await this.callProbeAgentWithExistingSession(existingAgent, prompt, schema, debugInfo, checkName);
|
|
180
180
|
const processingTime = Date.now() - startTime;
|
|
181
181
|
if (debugInfo) {
|
|
182
182
|
debugInfo.rawResponse = response;
|
|
183
183
|
debugInfo.responseLength = response.length;
|
|
184
184
|
debugInfo.processingTime = processingTime;
|
|
185
185
|
}
|
|
186
|
-
const result = this.parseAIResponse(response, debugInfo,
|
|
186
|
+
const result = this.parseAIResponse(response, debugInfo, effectiveSchema);
|
|
187
187
|
if (debugInfo) {
|
|
188
188
|
result.debug = debugInfo;
|
|
189
189
|
}
|
|
@@ -343,16 +343,18 @@ ${prInfo.fullDiff ? this.escapeXml(prInfo.fullDiff) : ''}
|
|
|
343
343
|
// Handle mock model/provider for testing
|
|
344
344
|
if (this.config.model === 'mock' || this.config.provider === 'mock') {
|
|
345
345
|
log('🎭 Using mock AI model/provider for testing (session reuse)');
|
|
346
|
-
|
|
346
|
+
const response = await this.generateMockResponse(prompt);
|
|
347
|
+
return { response, effectiveSchema: schema };
|
|
347
348
|
}
|
|
348
349
|
log('🔄 Reusing existing ProbeAgent session for AI review...');
|
|
349
350
|
log(`📝 Prompt length: ${prompt.length} characters`);
|
|
350
351
|
log(`⚙️ Model: ${this.config.model || 'default'}, Provider: ${this.config.provider || 'auto'}`);
|
|
351
352
|
try {
|
|
352
353
|
log('🚀 Calling existing ProbeAgent with answer()...');
|
|
353
|
-
// Load and pass the actual schema content if provided
|
|
354
|
+
// Load and pass the actual schema content if provided (skip for plain schema)
|
|
354
355
|
let schemaString = undefined;
|
|
355
|
-
|
|
356
|
+
let effectiveSchema = schema;
|
|
357
|
+
if (schema && schema !== 'plain') {
|
|
356
358
|
try {
|
|
357
359
|
schemaString = await this.loadSchemaContent(schema);
|
|
358
360
|
log(`📋 Loaded schema content for: ${schema}`);
|
|
@@ -361,11 +363,15 @@ ${prInfo.fullDiff ? this.escapeXml(prInfo.fullDiff) : ''}
|
|
|
361
363
|
catch (error) {
|
|
362
364
|
log(`⚠️ Failed to load schema ${schema}, proceeding without schema:`, error);
|
|
363
365
|
schemaString = undefined;
|
|
366
|
+
effectiveSchema = undefined; // Schema loading failed, treat as no schema
|
|
364
367
|
if (debugInfo && debugInfo.errors) {
|
|
365
368
|
debugInfo.errors.push(`Failed to load schema: ${error}`);
|
|
366
369
|
}
|
|
367
370
|
}
|
|
368
371
|
}
|
|
372
|
+
else if (schema === 'plain') {
|
|
373
|
+
log(`📋 Using plain schema - no JSON validation will be applied`);
|
|
374
|
+
}
|
|
369
375
|
// Pass schema in options object with 'schema' property
|
|
370
376
|
const schemaOptions = schemaString ? { schema: schemaString } : undefined;
|
|
371
377
|
// Store the exact schema options being passed to ProbeAgent in debug info
|
|
@@ -381,7 +387,7 @@ ${prInfo.fullDiff ? this.escapeXml(prInfo.fullDiff) : ''}
|
|
|
381
387
|
const response = await agent.answer(prompt, undefined, schemaOptions);
|
|
382
388
|
log('✅ ProbeAgent session reuse completed successfully');
|
|
383
389
|
log(`📤 Response length: ${response.length} characters`);
|
|
384
|
-
return response;
|
|
390
|
+
return { response, effectiveSchema };
|
|
385
391
|
}
|
|
386
392
|
catch (error) {
|
|
387
393
|
console.error('❌ ProbeAgent session reuse failed:', error);
|
|
@@ -395,7 +401,8 @@ ${prInfo.fullDiff ? this.escapeXml(prInfo.fullDiff) : ''}
|
|
|
395
401
|
// Handle mock model/provider for testing
|
|
396
402
|
if (this.config.model === 'mock' || this.config.provider === 'mock') {
|
|
397
403
|
log('🎭 Using mock AI model/provider for testing');
|
|
398
|
-
|
|
404
|
+
const response = await this.generateMockResponse(prompt);
|
|
405
|
+
return { response, effectiveSchema: schema };
|
|
399
406
|
}
|
|
400
407
|
// Create ProbeAgent instance with proper options
|
|
401
408
|
const sessionId = providedSessionId ||
|
|
@@ -440,9 +447,10 @@ ${prInfo.fullDiff ? this.escapeXml(prInfo.fullDiff) : ''}
|
|
|
440
447
|
}
|
|
441
448
|
const agent = new probe_1.ProbeAgent(options);
|
|
442
449
|
log('🚀 Calling ProbeAgent...');
|
|
443
|
-
// Load and pass the actual schema content if provided
|
|
450
|
+
// Load and pass the actual schema content if provided (skip for plain schema)
|
|
444
451
|
let schemaString = undefined;
|
|
445
|
-
|
|
452
|
+
let effectiveSchema = schema;
|
|
453
|
+
if (schema && schema !== 'plain') {
|
|
446
454
|
try {
|
|
447
455
|
schemaString = await this.loadSchemaContent(schema);
|
|
448
456
|
log(`📋 Loaded schema content for: ${schema}`);
|
|
@@ -451,11 +459,15 @@ ${prInfo.fullDiff ? this.escapeXml(prInfo.fullDiff) : ''}
|
|
|
451
459
|
catch (error) {
|
|
452
460
|
log(`⚠️ Failed to load schema ${schema}, proceeding without schema:`, error);
|
|
453
461
|
schemaString = undefined;
|
|
462
|
+
effectiveSchema = undefined; // Schema loading failed, treat as no schema
|
|
454
463
|
if (debugInfo && debugInfo.errors) {
|
|
455
464
|
debugInfo.errors.push(`Failed to load schema: ${error}`);
|
|
456
465
|
}
|
|
457
466
|
}
|
|
458
467
|
}
|
|
468
|
+
else if (schema === 'plain') {
|
|
469
|
+
log(`📋 Using plain schema - no JSON validation will be applied`);
|
|
470
|
+
}
|
|
459
471
|
// ProbeAgent now handles schema formatting internally!
|
|
460
472
|
// Pass schema in options object with 'schema' property
|
|
461
473
|
const schemaOptions = schemaString ? { schema: schemaString } : undefined;
|
|
@@ -504,7 +516,7 @@ ${prInfo.fullDiff ? this.escapeXml(prInfo.fullDiff) : ''}
|
|
|
504
516
|
this.registerSession(sessionId, agent);
|
|
505
517
|
log(`🔧 Debug: Registered AI session for potential reuse: ${sessionId}`);
|
|
506
518
|
}
|
|
507
|
-
return response;
|
|
519
|
+
return { response, effectiveSchema };
|
|
508
520
|
}
|
|
509
521
|
catch (error) {
|
|
510
522
|
console.error('❌ ProbeAgent failed:', error);
|
|
@@ -561,43 +573,27 @@ ${prInfo.fullDiff ? this.escapeXml(prInfo.fullDiff) : ''}
|
|
|
561
573
|
try {
|
|
562
574
|
// Handle different schema types differently
|
|
563
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
|
+
}
|
|
564
585
|
{
|
|
565
586
|
// For other schemas (code-review, etc.), extract and parse JSON with boundary detection
|
|
566
587
|
log('🔍 Extracting JSON from AI response...');
|
|
567
|
-
//
|
|
568
|
-
let jsonString = response.trim();
|
|
569
|
-
// Find the first occurrence of { or [
|
|
570
|
-
const firstBrace = jsonString.indexOf('{');
|
|
571
|
-
const firstBracket = jsonString.indexOf('[');
|
|
572
|
-
let startIndex = -1;
|
|
573
|
-
let endChar = '';
|
|
574
|
-
// Determine which comes first (or if only one exists), {} takes priority
|
|
575
|
-
if (firstBrace !== -1 && (firstBracket === -1 || firstBrace < firstBracket)) {
|
|
576
|
-
// Object comes first or only objects exist
|
|
577
|
-
startIndex = firstBrace;
|
|
578
|
-
endChar = '}';
|
|
579
|
-
}
|
|
580
|
-
else if (firstBracket !== -1) {
|
|
581
|
-
// Array comes first or only arrays exist
|
|
582
|
-
startIndex = firstBracket;
|
|
583
|
-
endChar = ']';
|
|
584
|
-
}
|
|
585
|
-
if (startIndex !== -1) {
|
|
586
|
-
// Find the last occurrence of the matching end character
|
|
587
|
-
const lastEndIndex = jsonString.lastIndexOf(endChar);
|
|
588
|
-
if (lastEndIndex !== -1 && lastEndIndex > startIndex) {
|
|
589
|
-
jsonString = jsonString.substring(startIndex, lastEndIndex + 1);
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
// Parse the extracted JSON
|
|
588
|
+
// Try direct parsing first - if AI returned pure JSON
|
|
593
589
|
try {
|
|
594
|
-
reviewData = JSON.parse(
|
|
595
|
-
log('✅ Successfully parsed
|
|
590
|
+
reviewData = JSON.parse(response.trim());
|
|
591
|
+
log('✅ Successfully parsed direct JSON response');
|
|
596
592
|
if (debugInfo)
|
|
597
593
|
debugInfo.jsonParseSuccess = true;
|
|
598
594
|
}
|
|
599
|
-
catch
|
|
600
|
-
log('🔍
|
|
595
|
+
catch {
|
|
596
|
+
log('🔍 Direct parsing failed, trying to extract JSON from response...');
|
|
601
597
|
// If the response starts with "I cannot" or similar, it's likely a refusal
|
|
602
598
|
if (response.toLowerCase().includes('i cannot') ||
|
|
603
599
|
response.toLowerCase().includes('unable to')) {
|
|
@@ -609,38 +605,50 @@ ${prInfo.fullDiff ? this.escapeXml(prInfo.fullDiff) : ''}
|
|
|
609
605
|
],
|
|
610
606
|
};
|
|
611
607
|
}
|
|
612
|
-
// Try to
|
|
613
|
-
const
|
|
614
|
-
if (
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
log('✅ Successfully parsed extracted JSON');
|
|
621
|
-
if (debugInfo)
|
|
622
|
-
debugInfo.jsonParseSuccess = true;
|
|
623
|
-
}
|
|
624
|
-
else {
|
|
625
|
-
// Check if response is plain text and doesn't contain structured data
|
|
626
|
-
if (!response.includes('{') && !response.includes('}')) {
|
|
627
|
-
log('🔧 Plain text response detected, creating structured fallback...');
|
|
628
|
-
const isNoChanges = response.toLowerCase().includes('no') &&
|
|
629
|
-
(response.toLowerCase().includes('changes') ||
|
|
630
|
-
response.toLowerCase().includes('code'));
|
|
631
|
-
reviewData = {
|
|
632
|
-
issues: [],
|
|
633
|
-
suggestions: isNoChanges
|
|
634
|
-
? ['No code changes detected in this analysis']
|
|
635
|
-
: [
|
|
636
|
-
`AI response: ${response.substring(0, 200)}${response.length > 200 ? '...' : ''}`,
|
|
637
|
-
],
|
|
638
|
-
};
|
|
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;
|
|
639
616
|
}
|
|
640
|
-
|
|
641
|
-
|
|
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
642
|
}
|
|
643
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
|
+
}
|
|
644
652
|
}
|
|
645
653
|
}
|
|
646
654
|
// Standard code-review schema processing
|
|
@@ -673,11 +681,11 @@ ${prInfo.fullDiff ? this.escapeXml(prInfo.fullDiff) : ''}
|
|
|
673
681
|
suggestions: Array.isArray(reviewData.suggestions) ? reviewData.suggestions : [],
|
|
674
682
|
};
|
|
675
683
|
// Log issue counts
|
|
676
|
-
const criticalCount = result.issues.filter(i => i.severity === 'critical').length;
|
|
684
|
+
const criticalCount = (result.issues || []).filter(i => i.severity === 'critical').length;
|
|
677
685
|
if (criticalCount > 0) {
|
|
678
686
|
log(`🚨 Found ${criticalCount} critical severity issue(s)`);
|
|
679
687
|
}
|
|
680
|
-
log(`📈 Total issues: ${result.issues.length}`);
|
|
688
|
+
log(`📈 Total issues: ${(result.issues || []).length}`);
|
|
681
689
|
log('✅ Successfully created ReviewSummary');
|
|
682
690
|
return result;
|
|
683
691
|
}
|
|
@@ -717,6 +725,67 @@ ${prInfo.fullDiff ? this.escapeXml(prInfo.fullDiff) : ''}
|
|
|
717
725
|
throw new Error(`Invalid AI response format: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
718
726
|
}
|
|
719
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
|
+
}
|
|
720
789
|
/**
|
|
721
790
|
* Generate mock response for testing
|
|
722
791
|
*/
|