@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.
Files changed (75) hide show
  1. package/README.md +78 -0
  2. package/defaults/.visor.yaml +54 -0
  3. package/dist/ai-review-service.d.ts +13 -0
  4. package/dist/ai-review-service.d.ts.map +1 -1
  5. package/dist/ai-review-service.js +142 -73
  6. package/dist/ai-review-service.js.map +1 -1
  7. package/dist/check-execution-engine.d.ts +41 -1
  8. package/dist/check-execution-engine.d.ts.map +1 -1
  9. package/dist/check-execution-engine.js +376 -19
  10. package/dist/check-execution-engine.js.map +1 -1
  11. package/dist/config.d.ts.map +1 -1
  12. package/dist/config.js +19 -3
  13. package/dist/config.js.map +1 -1
  14. package/dist/event-mapper.d.ts.map +1 -1
  15. package/dist/event-mapper.js +3 -5
  16. package/dist/event-mapper.js.map +1 -1
  17. package/dist/failure-condition-evaluator.d.ts +11 -3
  18. package/dist/failure-condition-evaluator.d.ts.map +1 -1
  19. package/dist/failure-condition-evaluator.js +41 -5
  20. package/dist/failure-condition-evaluator.js.map +1 -1
  21. package/dist/github-check-service.d.ts.map +1 -1
  22. package/dist/github-check-service.js +26 -39
  23. package/dist/github-check-service.js.map +1 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +332 -681
  26. package/dist/index.js.map +1 -1
  27. package/dist/licenses.txt +2300 -0
  28. package/dist/output/code-review/schema.json +84 -0
  29. package/dist/output/code-review/template.liquid +32 -0
  30. package/dist/output/plain/schema.json +14 -0
  31. package/dist/output/plain/template.liquid +1 -0
  32. package/dist/output-formatters.d.ts.map +1 -1
  33. package/dist/output-formatters.js +14 -14
  34. package/dist/output-formatters.js.map +1 -1
  35. package/dist/pr-analyzer.d.ts +10 -1
  36. package/dist/pr-analyzer.d.ts.map +1 -1
  37. package/dist/pr-analyzer.js +2 -1
  38. package/dist/pr-analyzer.js.map +1 -1
  39. package/dist/pr-detector.d.ts +14 -4
  40. package/dist/pr-detector.d.ts.map +1 -1
  41. package/dist/pr-detector.js.map +1 -1
  42. package/dist/providers/ai-check-provider.d.ts.map +1 -1
  43. package/dist/providers/ai-check-provider.js +27 -23
  44. package/dist/providers/ai-check-provider.js.map +1 -1
  45. package/dist/providers/check-provider-registry.js +2 -2
  46. package/dist/providers/check-provider-registry.js.map +1 -1
  47. package/dist/providers/check-provider.interface.d.ts +3 -1
  48. package/dist/providers/check-provider.interface.d.ts.map +1 -1
  49. package/dist/providers/check-provider.interface.js.map +1 -1
  50. package/dist/providers/index.d.ts +1 -1
  51. package/dist/providers/index.d.ts.map +1 -1
  52. package/dist/providers/index.js +3 -3
  53. package/dist/providers/index.js.map +1 -1
  54. package/dist/providers/noop-check-provider.d.ts +25 -0
  55. package/dist/providers/noop-check-provider.d.ts.map +1 -0
  56. package/dist/providers/noop-check-provider.js +55 -0
  57. package/dist/providers/noop-check-provider.js.map +1 -0
  58. package/dist/providers/tool-check-provider.d.ts +4 -1
  59. package/dist/providers/tool-check-provider.d.ts.map +1 -1
  60. package/dist/providers/tool-check-provider.js +64 -15
  61. package/dist/providers/tool-check-provider.js.map +1 -1
  62. package/dist/providers/webhook-check-provider.js +3 -3
  63. package/dist/providers/webhook-check-provider.js.map +1 -1
  64. package/dist/reviewer.d.ts +19 -37
  65. package/dist/reviewer.d.ts.map +1 -1
  66. package/dist/reviewer.js +85 -596
  67. package/dist/reviewer.js.map +1 -1
  68. package/dist/tiktoken_bg.wasm +0 -0
  69. package/dist/types/config.d.ts +79 -6
  70. package/dist/types/config.d.ts.map +1 -1
  71. package/package.json +3 -3
  72. package/dist/providers/script-check-provider.d.ts +0 -23
  73. package/dist/providers/script-check-provider.d.ts.map +0 -1
  74. package/dist/providers/script-check-provider.js +0 -163
  75. 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
@@ -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;IA6GzB;;OAEG;IACG,6BAA6B,CACjC,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,eAAe,EAAE,MAAM,EACvB,MAAM,CAAC,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,aAAa,CAAC;IAyFzB;;OAEG;IACH,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI;IAI3D;;OAEG;IACH,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAIvC;;OAEG;YACW,iBAAiB;IA8C/B;;OAEG;IACH,OAAO,CAAC,eAAe;IAoEvB;;OAEG;IACH,OAAO,CAAC,SAAS;IAIjB;;OAEG;YACW,iCAAiC;IAiE/C;;OAEG;YACW,cAAc;IA0J5B;;OAEG;YACW,iBAAiB;IAwB/B;;OAEG;IACH,OAAO,CAAC,eAAe;IAuMvB;;OAEG;YACW,oBAAoB;IAgDlC;;OAEG;IACH,OAAO,CAAC,eAAe;CAYxB"}
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, schema);
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, schema);
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
- return this.generateMockResponse(prompt);
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
- if (schema) {
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
- return this.generateMockResponse(prompt);
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
- if (schema) {
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
- // Simple JSON extraction: find first { or [ and last } or ], with {} taking priority
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(jsonString);
595
- log('✅ Successfully parsed probe agent JSON response');
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 (initialError) {
600
- log('🔍 Initial parsing failed, trying to extract JSON from response...');
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 find JSON within the response
613
- const jsonMatches = response.match(/\{[\s\S]*\}/g);
614
- if (jsonMatches && jsonMatches.length > 0) {
615
- log('🔧 Found potential JSON in response, attempting to parse...');
616
- // Try the largest JSON-like string (likely the complete response)
617
- const largestJson = jsonMatches.reduce((a, b) => (a.length > b.length ? a : b));
618
- log('🔧 Attempting to parse extracted JSON...');
619
- reviewData = JSON.parse(largestJson);
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
- else {
641
- throw initialError;
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
  */