@probelabs/visor 0.1.90 → 0.1.92

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 (131) hide show
  1. package/README.md +68 -2
  2. package/action.yml +1 -1
  3. package/defaults/.visor.yaml +120 -154
  4. package/dist/13.index.js +82 -0
  5. package/dist/159.index.js +38 -0
  6. package/dist/168.index.js +82 -0
  7. package/dist/201.index.js +82 -0
  8. package/dist/262.index.js +48 -0
  9. package/dist/272.index.js +82 -0
  10. package/dist/273.index.js +48 -0
  11. package/dist/320.index.js +38 -0
  12. package/dist/34.index.js +81 -0
  13. package/dist/421.index.js +48 -0
  14. package/dist/437.index.js +82 -0
  15. package/dist/441.index.js +81 -0
  16. package/dist/450.index.js +82 -0
  17. package/dist/54.index.js +81 -0
  18. package/dist/544.index.js +38 -0
  19. package/dist/558.index.js +82 -0
  20. package/dist/715.index.js +38 -0
  21. package/dist/737.index.js +82 -0
  22. package/dist/834.index.js +48 -0
  23. package/dist/85.index.js +82 -0
  24. package/dist/861.index.js +48 -0
  25. package/dist/878.index.js +81 -0
  26. package/dist/940.index.js +38 -0
  27. package/dist/989.index.js +81 -0
  28. package/dist/996.index.js +82 -0
  29. package/dist/ai-review-service.d.ts +11 -1
  30. package/dist/ai-review-service.d.ts.map +1 -1
  31. package/dist/check-execution-engine.d.ts +9 -2
  32. package/dist/check-execution-engine.d.ts.map +1 -1
  33. package/dist/cli-main.d.ts.map +1 -1
  34. package/dist/cli.d.ts.map +1 -1
  35. package/dist/config.d.ts.map +1 -1
  36. package/dist/defaults/.visor.yaml +120 -154
  37. package/dist/failure-condition-evaluator.d.ts +3 -2
  38. package/dist/failure-condition-evaluator.d.ts.map +1 -1
  39. package/dist/generated/config-schema.d.ts +89 -2
  40. package/dist/generated/config-schema.d.ts.map +1 -1
  41. package/dist/generated/config-schema.json +109 -1
  42. package/dist/git-repository-analyzer.d.ts +17 -1
  43. package/dist/git-repository-analyzer.d.ts.map +1 -1
  44. package/dist/github-comments.d.ts.map +1 -1
  45. package/dist/github-reactions.d.ts +36 -0
  46. package/dist/github-reactions.d.ts.map +1 -0
  47. package/dist/index.d.ts +2 -0
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +172824 -45634
  50. package/dist/liquid-extensions.d.ts +3 -0
  51. package/dist/liquid-extensions.d.ts.map +1 -1
  52. package/dist/memory-store.d.ts +129 -0
  53. package/dist/memory-store.d.ts.map +1 -0
  54. package/dist/output/issue-assistant/schema.json +29 -0
  55. package/dist/output/issue-assistant/template.liquid +1 -0
  56. package/dist/output/overview/schema.json +33 -0
  57. package/dist/output/overview/template.liquid +16 -0
  58. package/dist/output-formatters.d.ts +1 -0
  59. package/dist/output-formatters.d.ts.map +1 -1
  60. package/dist/pr-analyzer.d.ts +2 -0
  61. package/dist/pr-analyzer.d.ts.map +1 -1
  62. package/dist/proto/channelz.proto +564 -0
  63. package/dist/providers/check-provider-registry.d.ts.map +1 -1
  64. package/dist/providers/command-check-provider.d.ts.map +1 -1
  65. package/dist/providers/github-ops-provider.d.ts +18 -0
  66. package/dist/providers/github-ops-provider.d.ts.map +1 -0
  67. package/dist/providers/memory-check-provider.d.ts +56 -0
  68. package/dist/providers/memory-check-provider.d.ts.map +1 -0
  69. package/dist/reviewer.d.ts +2 -0
  70. package/dist/reviewer.d.ts.map +1 -1
  71. package/dist/sdk/check-execution-engine-L73PFZQY.mjs +11 -0
  72. package/dist/sdk/chunk-2U6BIWSY.mjs +748 -0
  73. package/dist/sdk/chunk-2U6BIWSY.mjs.map +1 -0
  74. package/dist/sdk/chunk-KVHVCGY6.mjs +103 -0
  75. package/dist/sdk/chunk-KVHVCGY6.mjs.map +1 -0
  76. package/dist/sdk/{chunk-N2PPFOSF.mjs → chunk-LJHRU3WQ.mjs} +2577 -250
  77. package/dist/sdk/chunk-LJHRU3WQ.mjs.map +1 -0
  78. package/dist/sdk/chunk-TWJKAYT6.mjs +1124 -0
  79. package/dist/sdk/chunk-TWJKAYT6.mjs.map +1 -0
  80. package/dist/sdk/liquid-extensions-AFKRYROF.mjs +14 -0
  81. package/dist/sdk/mermaid-telemetry-LZGDD35I.mjs +61 -0
  82. package/dist/sdk/mermaid-telemetry-LZGDD35I.mjs.map +1 -0
  83. package/dist/sdk/sdk.d.mts +48 -2
  84. package/dist/sdk/sdk.d.ts +48 -2
  85. package/dist/sdk/sdk.js +5352 -767
  86. package/dist/sdk/sdk.js.map +1 -1
  87. package/dist/sdk/sdk.mjs +112 -7
  88. package/dist/sdk/sdk.mjs.map +1 -1
  89. package/dist/sdk/tracer-init-O7RLXMJ3.mjs +10 -0
  90. package/dist/sdk/tracer-init-O7RLXMJ3.mjs.map +1 -0
  91. package/dist/session-registry.d.ts +19 -5
  92. package/dist/session-registry.d.ts.map +1 -1
  93. package/dist/telemetry/fallback-ndjson.d.ts +7 -0
  94. package/dist/telemetry/fallback-ndjson.d.ts.map +1 -0
  95. package/dist/telemetry/file-span-exporter.d.ts +17 -0
  96. package/dist/telemetry/file-span-exporter.d.ts.map +1 -0
  97. package/dist/telemetry/metrics.d.ts +13 -0
  98. package/dist/telemetry/metrics.d.ts.map +1 -0
  99. package/dist/telemetry/opentelemetry.d.ts +26 -0
  100. package/dist/telemetry/opentelemetry.d.ts.map +1 -0
  101. package/dist/telemetry/trace-helpers.d.ts +9 -0
  102. package/dist/telemetry/trace-helpers.d.ts.map +1 -0
  103. package/dist/telemetry/trace-report-exporter.d.ts +17 -0
  104. package/dist/telemetry/trace-report-exporter.d.ts.map +1 -0
  105. package/dist/traces/run-2025-10-15T07-21-47-696Z.ndjson +17 -0
  106. package/dist/traces/run-2025-10-15T07-21-58-106Z.ndjson +17 -0
  107. package/dist/traces/run-2025-10-15T07-21-58-693Z.ndjson +17 -0
  108. package/dist/traces/run-2025-10-15T07-21-59-167Z.ndjson +17 -0
  109. package/dist/traces/run-2025-10-15T07-21-59-629Z.ndjson +8 -0
  110. package/dist/types/cli.d.ts +4 -0
  111. package/dist/types/cli.d.ts.map +1 -1
  112. package/dist/types/config.d.ts +56 -2
  113. package/dist/types/config.d.ts.map +1 -1
  114. package/dist/utils/author-permissions.d.ts +74 -0
  115. package/dist/utils/author-permissions.d.ts.map +1 -0
  116. package/dist/utils/head-sha.d.ts +8 -0
  117. package/dist/utils/head-sha.d.ts.map +1 -0
  118. package/dist/utils/mermaid-telemetry.d.ts +3 -0
  119. package/dist/utils/mermaid-telemetry.d.ts.map +1 -0
  120. package/dist/utils/tracer-init.d.ts +12 -0
  121. package/dist/utils/tracer-init.d.ts.map +1 -0
  122. package/dist/utils/ui-helpers.d.ts +3 -0
  123. package/dist/utils/ui-helpers.d.ts.map +1 -0
  124. package/package.json +2 -2
  125. package/dist/sdk/check-execution-engine-Z2USLMN5.mjs +0 -9
  126. package/dist/sdk/chunk-FIL2OGF6.mjs +0 -68
  127. package/dist/sdk/chunk-FIL2OGF6.mjs.map +0 -1
  128. package/dist/sdk/chunk-N2PPFOSF.mjs.map +0 -1
  129. package/dist/sdk/liquid-extensions-KDECAJTV.mjs +0 -12
  130. /package/dist/sdk/{check-execution-engine-Z2USLMN5.mjs.map → check-execution-engine-L73PFZQY.mjs.map} +0 -0
  131. /package/dist/sdk/{liquid-extensions-KDECAJTV.mjs.map → liquid-extensions-AFKRYROF.mjs.map} +0 -0
@@ -1,6 +1,25 @@
1
1
  import {
2
- createExtendedLiquid
3
- } from "./chunk-FIL2OGF6.mjs";
2
+ init_tracer_init,
3
+ initializeTracer
4
+ } from "./chunk-KVHVCGY6.mjs";
5
+ import {
6
+ MemoryStore,
7
+ createExtendedLiquid,
8
+ createPermissionHelpers,
9
+ detectLocalMode,
10
+ init_logger,
11
+ logger,
12
+ logger_exports,
13
+ resolveAssociationFromEvent
14
+ } from "./chunk-2U6BIWSY.mjs";
15
+ import {
16
+ addEvent,
17
+ addFailIfTriggered,
18
+ emitNdjsonFallback,
19
+ emitNdjsonSpanWithEvents,
20
+ fallback_ndjson_exports,
21
+ init_fallback_ndjson
22
+ } from "./chunk-TWJKAYT6.mjs";
4
23
  import {
5
24
  __esm,
6
25
  __export,
@@ -95,43 +114,61 @@ var init_session_registry = __esm({
95
114
  return this.sessions.has(sessionId);
96
115
  }
97
116
  /**
98
- * Clone a session with a new session ID
99
- * Creates a new ProbeAgent with a copy of the conversation history
117
+ * Clone a session with a new session ID using ProbeAgent's official clone() method
118
+ * This uses ProbeAgent's built-in cloning which automatically handles:
119
+ * - Intelligent filtering of internal messages (schema reminders, tool prompts, etc.)
120
+ * - Preserving system message for cache efficiency
121
+ * - Deep copying conversation history
122
+ * - Copying agent configuration
100
123
  */
101
- async cloneSession(sourceSessionId, newSessionId) {
124
+ async cloneSession(sourceSessionId, newSessionId, checkName) {
102
125
  const sourceAgent = this.sessions.get(sourceSessionId);
103
126
  if (!sourceAgent) {
104
127
  console.error(`\u26A0\uFE0F Cannot clone session: ${sourceSessionId} not found`);
105
128
  return void 0;
106
129
  }
107
130
  try {
108
- const sourceHistory = sourceAgent.conversationHistory || [];
109
- const sourceOptions = sourceAgent.options || {};
110
- const { ProbeAgent: ProbeAgentClass } = await import("@probelabs/probe");
111
- const clonedAgent = new ProbeAgentClass({
112
- ...sourceOptions,
113
- sessionId: newSessionId
131
+ const clonedAgent = sourceAgent.clone({
132
+ sessionId: newSessionId,
133
+ stripInternalMessages: true,
134
+ // Remove schema reminders, tool prompts, etc.
135
+ keepSystemMessage: true,
136
+ // Keep for cache efficiency
137
+ deepCopy: true
138
+ // Safe deep copy of history
114
139
  });
115
- if (sourceHistory.length > 0) {
140
+ if (sourceAgent.debug && checkName) {
116
141
  try {
117
- const deepClonedHistory = JSON.parse(JSON.stringify(sourceHistory));
118
- clonedAgent.conversationHistory = deepClonedHistory;
119
- console.error(
120
- `\u{1F4CB} Cloned session ${sourceSessionId} \u2192 ${newSessionId} (${sourceHistory.length} messages, deep copy)`
121
- );
122
- } catch (cloneError) {
142
+ const { initializeTracer: initializeTracer2 } = await import("./tracer-init-O7RLXMJ3.mjs");
143
+ const tracerResult = await initializeTracer2(newSessionId, checkName);
144
+ if (tracerResult) {
145
+ clonedAgent.tracer = tracerResult.tracer;
146
+ clonedAgent._telemetryConfig = tracerResult.telemetryConfig;
147
+ clonedAgent._traceFilePath = tracerResult.filePath;
148
+ }
149
+ } catch (traceError) {
123
150
  console.error(
124
- `\u26A0\uFE0F Warning: Deep clone failed for session ${sourceSessionId}, using shallow copy: ${cloneError}`
151
+ "\u26A0\uFE0F Warning: Failed to initialize tracing for cloned session:",
152
+ traceError
125
153
  );
126
- clonedAgent.conversationHistory = [...sourceHistory];
127
154
  }
128
- } else {
129
- console.error(`\u{1F4CB} Cloned session ${sourceSessionId} \u2192 ${newSessionId} (no history)`);
130
155
  }
156
+ if (sourceAgent._mcpInitialized && typeof clonedAgent.initialize === "function") {
157
+ try {
158
+ await clonedAgent.initialize();
159
+ console.error(`\u{1F527} Initialized MCP tools for cloned session`);
160
+ } catch (initError) {
161
+ console.error(`\u26A0\uFE0F Warning: Failed to initialize cloned agent: ${initError}`);
162
+ }
163
+ }
164
+ const historyLength = clonedAgent.history?.length || 0;
165
+ console.error(
166
+ `\u{1F4CB} Cloned session ${sourceSessionId} \u2192 ${newSessionId} using ProbeAgent.clone() (${historyLength} messages, internal messages filtered)`
167
+ );
131
168
  this.registerSession(newSessionId, clonedAgent);
132
169
  return clonedAgent;
133
170
  } catch (error) {
134
- console.error(`\u26A0\uFE0F Failed to clone session ${sourceSessionId}: ${error}`);
171
+ console.error(`\u26A0\uFE0F Failed to clone session ${sourceSessionId}:`, error);
135
172
  return void 0;
136
173
  }
137
174
  }
@@ -177,113 +214,8 @@ var init_session_registry = __esm({
177
214
  }
178
215
  });
179
216
 
180
- // src/logger.ts
181
- var logger_exports = {};
182
- __export(logger_exports, {
183
- configureLoggerFromCli: () => configureLoggerFromCli,
184
- logger: () => logger
185
- });
186
- function levelToNumber(level) {
187
- switch (level) {
188
- case "silent":
189
- return 0;
190
- case "error":
191
- return 10;
192
- case "warn":
193
- return 20;
194
- case "info":
195
- return 30;
196
- case "verbose":
197
- return 40;
198
- case "debug":
199
- return 50;
200
- }
201
- }
202
- function configureLoggerFromCli(options) {
203
- logger.configure({
204
- outputFormat: options.output,
205
- debug: options.debug,
206
- verbose: options.verbose,
207
- quiet: options.quiet
208
- });
209
- try {
210
- if (options.output) process.env.VISOR_OUTPUT_FORMAT = String(options.output);
211
- if (typeof options.debug === "boolean") {
212
- process.env.VISOR_DEBUG = options.debug ? "true" : "false";
213
- }
214
- } catch {
215
- }
216
- }
217
- var Logger, logger;
218
- var init_logger = __esm({
219
- "src/logger.ts"() {
220
- "use strict";
221
- Logger = class {
222
- level = "info";
223
- isJsonLike = false;
224
- isTTY = typeof process !== "undefined" ? !!process.stderr.isTTY : false;
225
- configure(opts = {}) {
226
- let lvl = "info";
227
- if (opts.debug || process.env.VISOR_DEBUG === "true") {
228
- lvl = "debug";
229
- } else if (opts.verbose || process.env.VISOR_LOG_LEVEL === "verbose") {
230
- lvl = "verbose";
231
- } else if (opts.quiet || process.env.VISOR_LOG_LEVEL === "quiet") {
232
- lvl = "warn";
233
- } else if (opts.level) {
234
- lvl = opts.level;
235
- } else if (process.env.VISOR_LOG_LEVEL) {
236
- const envLvl = process.env.VISOR_LOG_LEVEL;
237
- if (["silent", "error", "warn", "info", "verbose", "debug"].includes(envLvl)) {
238
- lvl = envLvl;
239
- }
240
- }
241
- this.level = lvl;
242
- const output = opts.outputFormat || process.env.VISOR_OUTPUT_FORMAT || "table";
243
- this.isJsonLike = output === "json" || output === "sarif";
244
- }
245
- shouldLog(level) {
246
- const desired = levelToNumber(level);
247
- const current = levelToNumber(this.level);
248
- if (desired > current) return false;
249
- if (this.isJsonLike && desired < levelToNumber("error") && this.level !== "debug" && this.level !== "verbose") {
250
- return false;
251
- }
252
- return true;
253
- }
254
- write(msg) {
255
- try {
256
- process.stderr.write(msg + "\n");
257
- } catch {
258
- }
259
- }
260
- info(msg) {
261
- if (this.shouldLog("info")) this.write(msg);
262
- }
263
- warn(msg) {
264
- if (this.shouldLog("warn")) this.write(msg);
265
- }
266
- error(msg) {
267
- if (this.shouldLog("error")) this.write(msg);
268
- }
269
- verbose(msg) {
270
- if (this.shouldLog("verbose")) this.write(msg);
271
- }
272
- debug(msg) {
273
- if (this.shouldLog("debug")) this.write(msg);
274
- }
275
- step(msg) {
276
- if (this.shouldLog("info")) this.write(`\u25B6 ${msg}`);
277
- }
278
- success(msg) {
279
- if (this.shouldLog("info")) this.write(`\u2714 ${msg}`);
280
- }
281
- };
282
- logger = new Logger();
283
- }
284
- });
285
-
286
217
  // src/github-comments.ts
218
+ init_logger();
287
219
  import { v4 as uuidv4 } from "uuid";
288
220
  var CommentManager = class {
289
221
  octokit;
@@ -363,6 +295,9 @@ var CommentManager = class {
363
295
  comment_id: existingComment.id,
364
296
  body: formattedContent
365
297
  });
298
+ logger.info(
299
+ `\u2705 Successfully updated comment (ID: ${commentId}, GitHub ID: ${existingComment.id}) on PR #${prNumber} in ${owner}/${repo}`
300
+ );
366
301
  return updatedComment.data;
367
302
  } else {
368
303
  const newComment = await this.octokit.rest.issues.createComment({
@@ -371,6 +306,9 @@ var CommentManager = class {
371
306
  issue_number: prNumber,
372
307
  body: formattedContent
373
308
  });
309
+ logger.info(
310
+ `\u2705 Successfully created comment (ID: ${commentId}, GitHub ID: ${newComment.data.id}) on PR #${prNumber} in ${owner}/${repo}`
311
+ );
374
312
  return newComment.data;
375
313
  }
376
314
  });
@@ -384,6 +322,10 @@ var CommentManager = class {
384
322
  return `<!-- visor-comment-id:${commentId} -->
385
323
  ${content}
386
324
 
325
+ ---
326
+
327
+ *Powered by [Visor](https://probelabs.com/visor) from [Probelabs](https://probelabs.com)*
328
+
387
329
  *Last updated: ${lastUpdated} | Triggered by: ${triggeredBy}${commitInfo}*
388
330
  <!-- /visor-comment-id:${commentId} -->`;
389
331
  }
@@ -564,6 +506,7 @@ ${content}
564
506
  // src/ai-review-service.ts
565
507
  init_session_registry();
566
508
  init_logger();
509
+ init_tracer_init();
567
510
  import { ProbeAgent } from "@probelabs/probe";
568
511
  function log(...args) {
569
512
  logger.debug(args.join(" "));
@@ -711,18 +654,24 @@ var AIReviewService = class {
711
654
  `Session not found for reuse: ${parentSessionId}. Ensure the parent check completed successfully.`
712
655
  );
713
656
  }
714
- const prompt = await this.buildCustomPrompt(prInfo, customPrompt, schema);
657
+ const prompt = await this.buildCustomPrompt(prInfo, customPrompt, schema, {
658
+ skipPRContext: true
659
+ });
715
660
  let agentToUse;
716
661
  let currentSessionId;
717
662
  if (sessionMode === "clone") {
718
- currentSessionId = `${parentSessionId}-clone-${Date.now()}`;
719
- log(`\u{1F4CB} Cloning AI session ${parentSessionId} \u2192 ${currentSessionId}...`);
663
+ currentSessionId = `${checkName}-session-${Date.now()}`;
664
+ log(
665
+ `\u{1F4CB} Cloning AI session ${parentSessionId} \u2192 ${currentSessionId} for ${checkName} check...`
666
+ );
720
667
  const clonedAgent = await this.sessionRegistry.cloneSession(
721
668
  parentSessionId,
722
- currentSessionId
669
+ currentSessionId,
670
+ checkName
671
+ // Pass checkName for tracing
723
672
  );
724
673
  if (!clonedAgent) {
725
- throw new Error(`Failed to clone session ${parentSessionId}. Falling back to append mode.`);
674
+ throw new Error(`Failed to clone session ${parentSessionId}`);
726
675
  }
727
676
  agentToUse = clonedAgent;
728
677
  } else {
@@ -731,7 +680,13 @@ var AIReviewService = class {
731
680
  currentSessionId = parentSessionId;
732
681
  }
733
682
  log(`\u{1F527} Debug: Raw schema parameter: ${JSON.stringify(schema)} (type: ${typeof schema})`);
734
- log(`Schema type: ${schema || "none (no schema)"}`);
683
+ log(`\u{1F4CB} Schema for this check: ${schema || "none (no schema)"}`);
684
+ if (sessionMode === "clone") {
685
+ log(`\u2705 Cloned agent will use NEW schema (${schema}) - parent schema does not persist`);
686
+ log(`\u{1F504} Clone operation ensures fresh agent with copied history but new configuration`);
687
+ } else {
688
+ log(`\u{1F504} Append mode - using existing agent instance with shared history and configuration`);
689
+ }
735
690
  let debugInfo;
736
691
  if (this.config.debug) {
737
692
  debugInfo = {
@@ -809,11 +764,17 @@ var AIReviewService = class {
809
764
  /**
810
765
  * Build a custom prompt for AI review with XML-formatted data
811
766
  */
812
- async buildCustomPrompt(prInfo, customInstructions, schema) {
813
- const prContext = this.formatPRContext(prInfo);
767
+ async buildCustomPrompt(prInfo, customInstructions, schema, options) {
768
+ const skipPRContext = options?.skipPRContext === true;
769
+ const prContext = skipPRContext ? "" : this.formatPRContext(prInfo);
814
770
  const isIssue = prInfo.isIssue === true;
815
771
  const isCodeReviewSchema = schema === "code-review";
816
772
  if (isIssue) {
773
+ if (skipPRContext) {
774
+ return `<instructions>
775
+ ${customInstructions}
776
+ </instructions>`;
777
+ }
817
778
  return `<review_request>
818
779
  <instructions>
819
780
  ${customInstructions}
@@ -836,6 +797,19 @@ ${prContext}
836
797
  }
837
798
  if (isCodeReviewSchema) {
838
799
  const analysisType = prInfo.isIncremental ? "INCREMENTAL" : "FULL";
800
+ if (skipPRContext) {
801
+ return `<instructions>
802
+ ${customInstructions}
803
+ </instructions>
804
+
805
+ <reminder>
806
+ <rule>The code context and diff were provided in the previous message</rule>
807
+ <rule>Focus on the new analysis instructions above</rule>
808
+ <rule>Only analyze code that appears with + (additions) or - (deletions) in the diff sections</rule>
809
+ <rule>STRICT OUTPUT POLICY: Report only actual problems, risks, or deficiencies</rule>
810
+ <rule>SEVERITY ASSIGNMENT: Assign severity ONLY to problems introduced or left unresolved by this change</rule>
811
+ </reminder>`;
812
+ }
839
813
  return `<review_request>
840
814
  <analysis_type>${analysisType}</analysis_type>
841
815
 
@@ -864,6 +838,11 @@ ${prContext}
864
838
  <rule>SEVERITY ASSIGNMENT: Assign severity ONLY to problems introduced or left unresolved by this change (critical/error/warning/info as appropriate). Do NOT create issue entries solely to acknowledge improvements; if no problems exist, return zero issues.</rule>
865
839
  </rules>
866
840
  </review_request>`;
841
+ }
842
+ if (skipPRContext) {
843
+ return `<instructions>
844
+ ${customInstructions}
845
+ </instructions>`;
867
846
  }
868
847
  return `<instructions>
869
848
  ${customInstructions}
@@ -1123,9 +1102,308 @@ ${schemaString}`);
1123
1102
  log(`\u{1F3AF} Schema options passed to ProbeAgent.answer() (session reuse):`);
1124
1103
  log(JSON.stringify(schemaOptions, null, 2));
1125
1104
  }
1126
- const response = await agent.answer(prompt, void 0, schemaOptions);
1105
+ if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
1106
+ try {
1107
+ const fs5 = __require("fs");
1108
+ const path5 = __require("path");
1109
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1110
+ const provider = this.config.provider || "auto";
1111
+ const model = this.config.model || "default";
1112
+ let conversationHistory = [];
1113
+ try {
1114
+ const agentAny2 = agent;
1115
+ if (agentAny2.history) {
1116
+ conversationHistory = agentAny2.history;
1117
+ } else if (agentAny2.messages) {
1118
+ conversationHistory = agentAny2.messages;
1119
+ } else if (agentAny2._messages) {
1120
+ conversationHistory = agentAny2._messages;
1121
+ }
1122
+ } catch {
1123
+ }
1124
+ const debugData = {
1125
+ timestamp,
1126
+ checkName: _checkName || "unknown",
1127
+ provider,
1128
+ model,
1129
+ schema: effectiveSchema,
1130
+ schemaOptions: schemaOptions || "none",
1131
+ sessionInfo: {
1132
+ isSessionReuse: true,
1133
+ historyMessageCount: conversationHistory.length
1134
+ },
1135
+ currentPromptLength: prompt.length,
1136
+ currentPrompt: prompt,
1137
+ conversationHistory
1138
+ };
1139
+ const debugJson = JSON.stringify(debugData, null, 2);
1140
+ let readableVersion = `=============================================================
1141
+ `;
1142
+ readableVersion += `VISOR DEBUG REPORT - SESSION REUSE
1143
+ `;
1144
+ readableVersion += `=============================================================
1145
+ `;
1146
+ readableVersion += `Timestamp: ${timestamp}
1147
+ `;
1148
+ readableVersion += `Check Name: ${_checkName || "unknown"}
1149
+ `;
1150
+ readableVersion += `Provider: ${provider}
1151
+ `;
1152
+ readableVersion += `Model: ${model}
1153
+ `;
1154
+ readableVersion += `Schema: ${effectiveSchema}
1155
+ `;
1156
+ readableVersion += `Schema Options: ${schemaOptions ? "provided" : "none"}
1157
+ `;
1158
+ readableVersion += `History Messages: ${conversationHistory.length}
1159
+ `;
1160
+ readableVersion += `=============================================================
1161
+
1162
+ `;
1163
+ if (schemaOptions) {
1164
+ readableVersion += `
1165
+ ${"=".repeat(60)}
1166
+ `;
1167
+ readableVersion += `SCHEMA CONFIGURATION
1168
+ `;
1169
+ readableVersion += `${"=".repeat(60)}
1170
+ `;
1171
+ readableVersion += JSON.stringify(schemaOptions, null, 2);
1172
+ readableVersion += `
1173
+ `;
1174
+ }
1175
+ if (conversationHistory.length > 0) {
1176
+ readableVersion += `
1177
+ ${"=".repeat(60)}
1178
+ `;
1179
+ readableVersion += `CONVERSATION HISTORY (${conversationHistory.length} messages)
1180
+ `;
1181
+ readableVersion += `${"=".repeat(60)}
1182
+ `;
1183
+ conversationHistory.forEach((msg, index) => {
1184
+ readableVersion += `
1185
+ ${"-".repeat(60)}
1186
+ `;
1187
+ readableVersion += `MESSAGE #${index + 1}
1188
+ `;
1189
+ readableVersion += `Role: ${msg.role || "unknown"}
1190
+ `;
1191
+ if (msg.content) {
1192
+ const contentStr = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content, null, 2);
1193
+ readableVersion += `Length: ${contentStr.length} characters
1194
+ `;
1195
+ readableVersion += `${"-".repeat(60)}
1196
+ `;
1197
+ readableVersion += `${contentStr}
1198
+ `;
1199
+ }
1200
+ });
1201
+ }
1202
+ readableVersion += `
1203
+ ${"=".repeat(60)}
1204
+ `;
1205
+ readableVersion += `CURRENT PROMPT (NEW MESSAGE)
1206
+ `;
1207
+ readableVersion += `${"=".repeat(60)}
1208
+ `;
1209
+ readableVersion += `Length: ${prompt.length} characters
1210
+ `;
1211
+ readableVersion += `${"-".repeat(60)}
1212
+ `;
1213
+ readableVersion += `${prompt}
1214
+ `;
1215
+ readableVersion += `
1216
+ ${"=".repeat(60)}
1217
+ `;
1218
+ readableVersion += `END OF DEBUG REPORT
1219
+ `;
1220
+ readableVersion += `${"=".repeat(60)}
1221
+ `;
1222
+ const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path5.join(process.cwd(), "debug-artifacts");
1223
+ if (!fs5.existsSync(debugArtifactsDir)) {
1224
+ fs5.mkdirSync(debugArtifactsDir, { recursive: true });
1225
+ }
1226
+ const debugFile = path5.join(
1227
+ debugArtifactsDir,
1228
+ `prompt-${_checkName || "unknown"}-${timestamp}.json`
1229
+ );
1230
+ fs5.writeFileSync(debugFile, debugJson, "utf-8");
1231
+ const readableFile = path5.join(
1232
+ debugArtifactsDir,
1233
+ `prompt-${_checkName || "unknown"}-${timestamp}.txt`
1234
+ );
1235
+ fs5.writeFileSync(readableFile, readableVersion, "utf-8");
1236
+ log(`
1237
+ \u{1F4BE} Full debug info saved to:`);
1238
+ log(` JSON: ${debugFile}`);
1239
+ log(` TXT: ${readableFile}`);
1240
+ log(` - Includes: full conversation history, schema, current prompt`);
1241
+ } catch (error) {
1242
+ log(`\u26A0\uFE0F Could not save debug file: ${error}`);
1243
+ }
1244
+ }
1245
+ const agentAny = agent;
1246
+ let response;
1247
+ if (agentAny.tracer && typeof agentAny.tracer.withSpan === "function") {
1248
+ response = await agentAny.tracer.withSpan(
1249
+ "visor.ai_check_reuse",
1250
+ async () => {
1251
+ return await agent.answer(prompt, void 0, schemaOptions);
1252
+ },
1253
+ {
1254
+ "check.name": _checkName || "unknown",
1255
+ "check.mode": "session_reuse",
1256
+ "prompt.length": prompt.length,
1257
+ "schema.type": effectiveSchema || "none"
1258
+ }
1259
+ );
1260
+ } else {
1261
+ response = await agent.answer(prompt, void 0, schemaOptions);
1262
+ }
1127
1263
  log("\u2705 ProbeAgent session reuse completed successfully");
1128
1264
  log(`\u{1F4E4} Response length: ${response.length} characters`);
1265
+ if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
1266
+ try {
1267
+ const fs5 = __require("fs");
1268
+ const path5 = __require("path");
1269
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1270
+ const agentAny2 = agent;
1271
+ let fullHistory = [];
1272
+ if (agentAny2.history) {
1273
+ fullHistory = agentAny2.history;
1274
+ } else if (agentAny2.messages) {
1275
+ fullHistory = agentAny2.messages;
1276
+ } else if (agentAny2._messages) {
1277
+ fullHistory = agentAny2._messages;
1278
+ }
1279
+ const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path5.join(process.cwd(), "debug-artifacts");
1280
+ const sessionFile = path5.join(
1281
+ debugArtifactsDir,
1282
+ `session-${_checkName || "unknown"}-${timestamp}.json`
1283
+ );
1284
+ const sessionData = {
1285
+ timestamp,
1286
+ checkName: _checkName || "unknown",
1287
+ provider: this.config.provider || "auto",
1288
+ model: this.config.model || "default",
1289
+ schema: effectiveSchema,
1290
+ fullConversationHistory: fullHistory,
1291
+ totalMessages: fullHistory.length,
1292
+ latestResponse: response
1293
+ };
1294
+ fs5.writeFileSync(sessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
1295
+ const sessionTxtFile = path5.join(
1296
+ debugArtifactsDir,
1297
+ `session-${_checkName || "unknown"}-${timestamp}.txt`
1298
+ );
1299
+ let readable = `=============================================================
1300
+ `;
1301
+ readable += `COMPLETE AI SESSION HISTORY (AFTER RESPONSE)
1302
+ `;
1303
+ readable += `=============================================================
1304
+ `;
1305
+ readable += `Timestamp: ${timestamp}
1306
+ `;
1307
+ readable += `Check: ${_checkName || "unknown"}
1308
+ `;
1309
+ readable += `Total Messages: ${fullHistory.length}
1310
+ `;
1311
+ readable += `=============================================================
1312
+
1313
+ `;
1314
+ fullHistory.forEach((msg, idx) => {
1315
+ readable += `
1316
+ ${"=".repeat(60)}
1317
+ `;
1318
+ readable += `MESSAGE ${idx + 1}/${fullHistory.length}
1319
+ `;
1320
+ readable += `Role: ${msg.role || "unknown"}
1321
+ `;
1322
+ readable += `${"=".repeat(60)}
1323
+ `;
1324
+ const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content, null, 2);
1325
+ readable += content + "\n";
1326
+ });
1327
+ fs5.writeFileSync(sessionTxtFile, readable, "utf-8");
1328
+ log(`\u{1F4BE} Complete session history saved:`);
1329
+ log(` JSON: ${sessionFile}`);
1330
+ log(` TXT: ${sessionTxtFile}`);
1331
+ log(` - Contains ALL ${fullHistory.length} messages (prompts + responses)`);
1332
+ } catch (error) {
1333
+ log(`\u26A0\uFE0F Could not save complete session history: ${error}`);
1334
+ }
1335
+ }
1336
+ if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
1337
+ try {
1338
+ const fs5 = __require("fs");
1339
+ const path5 = __require("path");
1340
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1341
+ const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path5.join(process.cwd(), "debug-artifacts");
1342
+ const responseFile = path5.join(
1343
+ debugArtifactsDir,
1344
+ `response-${_checkName || "unknown"}-${timestamp}.txt`
1345
+ );
1346
+ let responseContent = `=============================================================
1347
+ `;
1348
+ responseContent += `VISOR AI RESPONSE - SESSION REUSE
1349
+ `;
1350
+ responseContent += `=============================================================
1351
+ `;
1352
+ responseContent += `Timestamp: ${timestamp}
1353
+ `;
1354
+ responseContent += `Check Name: ${_checkName || "unknown"}
1355
+ `;
1356
+ responseContent += `Response Length: ${response.length} characters
1357
+ `;
1358
+ responseContent += `=============================================================
1359
+
1360
+ `;
1361
+ responseContent += `${"=".repeat(60)}
1362
+ `;
1363
+ responseContent += `AI RESPONSE
1364
+ `;
1365
+ responseContent += `${"=".repeat(60)}
1366
+ `;
1367
+ responseContent += response;
1368
+ responseContent += `
1369
+ ${"=".repeat(60)}
1370
+ `;
1371
+ responseContent += `END OF RESPONSE
1372
+ `;
1373
+ responseContent += `${"=".repeat(60)}
1374
+ `;
1375
+ fs5.writeFileSync(responseFile, responseContent, "utf-8");
1376
+ log(`\u{1F4BE} Response saved to: ${responseFile}`);
1377
+ } catch (error) {
1378
+ log(`\u26A0\uFE0F Could not save response file: ${error}`);
1379
+ }
1380
+ }
1381
+ if (agentAny._traceFilePath && agentAny._telemetryConfig) {
1382
+ try {
1383
+ if (agentAny.tracer && typeof agentAny.tracer.flush === "function") {
1384
+ await agentAny.tracer.flush();
1385
+ log(`\u{1F504} Flushed tracer spans for cloned session`);
1386
+ }
1387
+ if (agentAny._telemetryConfig && typeof agentAny._telemetryConfig.shutdown === "function") {
1388
+ await agentAny._telemetryConfig.shutdown();
1389
+ log(`\u{1F4CA} OpenTelemetry trace saved to: ${agentAny._traceFilePath}`);
1390
+ if (process.env.GITHUB_ACTIONS) {
1391
+ const fs5 = __require("fs");
1392
+ if (fs5.existsSync(agentAny._traceFilePath)) {
1393
+ const stats = fs5.statSync(agentAny._traceFilePath);
1394
+ console.log(
1395
+ `::notice title=AI Trace Saved::${agentAny._traceFilePath} (${stats.size} bytes)`
1396
+ );
1397
+ }
1398
+ }
1399
+ } else if (agentAny.tracer && typeof agentAny.tracer.shutdown === "function") {
1400
+ await agentAny.tracer.shutdown();
1401
+ log(`\u{1F4CA} Trace saved to: ${agentAny._traceFilePath}`);
1402
+ }
1403
+ } catch (exportError) {
1404
+ console.error("\u26A0\uFE0F Warning: Failed to export trace for cloned session:", exportError);
1405
+ }
1406
+ }
1129
1407
  return { response, effectiveSchema };
1130
1408
  } catch (error) {
1131
1409
  console.error("\u274C ProbeAgent session reuse failed:", error);
@@ -1176,6 +1454,16 @@ ${schemaString}`);
1176
1454
  // We don't want the agent to modify files
1177
1455
  debug: this.config.debug || false
1178
1456
  };
1457
+ let traceFilePath = "";
1458
+ let telemetryConfig = null;
1459
+ if (this.config.debug) {
1460
+ const tracerResult = await initializeTracer(sessionId, _checkName);
1461
+ if (tracerResult) {
1462
+ options.tracer = tracerResult.tracer;
1463
+ telemetryConfig = tracerResult.telemetryConfig;
1464
+ traceFilePath = tracerResult.filePath;
1465
+ }
1466
+ }
1179
1467
  if (this.config.mcpServers && Object.keys(this.config.mcpServers).length > 0) {
1180
1468
  options.enableMcp = true;
1181
1469
  options.mcpConfig = { mcpServers: this.config.mcpServers };
@@ -1220,36 +1508,289 @@ ${schemaString}`);
1220
1508
  }
1221
1509
  const provider = this.config.provider || "auto";
1222
1510
  const model = this.config.model || "default";
1223
- try {
1224
- const fs5 = __require("fs");
1225
- const path5 = __require("path");
1226
- const os = __require("os");
1227
- const tempDir = os.tmpdir();
1228
- const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1229
- const promptFile = path5.join(tempDir, `visor-prompt-${timestamp}.txt`);
1230
- fs5.writeFileSync(promptFile, prompt, "utf-8");
1231
- log(`
1511
+ if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
1512
+ try {
1513
+ const fs5 = __require("fs");
1514
+ const path5 = __require("path");
1515
+ const os = __require("os");
1516
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1517
+ const debugData = {
1518
+ timestamp,
1519
+ checkName: _checkName || "unknown",
1520
+ provider,
1521
+ model,
1522
+ schema: effectiveSchema,
1523
+ schemaOptions: schemaOptions || "none",
1524
+ sessionInfo: {
1525
+ isSessionReuse: false,
1526
+ isNewSession: true
1527
+ },
1528
+ promptLength: prompt.length,
1529
+ prompt
1530
+ };
1531
+ const debugJson = JSON.stringify(debugData, null, 2);
1532
+ let readableVersion = `=============================================================
1533
+ `;
1534
+ readableVersion += `VISOR DEBUG REPORT - NEW SESSION
1535
+ `;
1536
+ readableVersion += `=============================================================
1537
+ `;
1538
+ readableVersion += `Timestamp: ${timestamp}
1539
+ `;
1540
+ readableVersion += `Check Name: ${_checkName || "unknown"}
1541
+ `;
1542
+ readableVersion += `Provider: ${provider}
1543
+ `;
1544
+ readableVersion += `Model: ${model}
1545
+ `;
1546
+ readableVersion += `Schema: ${effectiveSchema}
1547
+ `;
1548
+ readableVersion += `Schema Options: ${schemaOptions ? "provided" : "none"}
1549
+ `;
1550
+ readableVersion += `Session Type: New Session (no history)
1551
+ `;
1552
+ readableVersion += `=============================================================
1553
+
1554
+ `;
1555
+ if (schemaOptions) {
1556
+ readableVersion += `
1557
+ ${"=".repeat(60)}
1558
+ `;
1559
+ readableVersion += `SCHEMA CONFIGURATION
1560
+ `;
1561
+ readableVersion += `${"=".repeat(60)}
1562
+ `;
1563
+ readableVersion += JSON.stringify(schemaOptions, null, 2);
1564
+ readableVersion += `
1565
+ `;
1566
+ }
1567
+ readableVersion += `
1568
+ ${"=".repeat(60)}
1569
+ `;
1570
+ readableVersion += `PROMPT
1571
+ `;
1572
+ readableVersion += `${"=".repeat(60)}
1573
+ `;
1574
+ readableVersion += `Length: ${prompt.length} characters
1575
+ `;
1576
+ readableVersion += `${"-".repeat(60)}
1577
+ `;
1578
+ readableVersion += `${prompt}
1579
+ `;
1580
+ readableVersion += `
1581
+ ${"=".repeat(60)}
1582
+ `;
1583
+ readableVersion += `END OF DEBUG REPORT
1584
+ `;
1585
+ readableVersion += `${"=".repeat(60)}
1586
+ `;
1587
+ const tempDir = os.tmpdir();
1588
+ const promptFile = path5.join(tempDir, `visor-prompt-${timestamp}.txt`);
1589
+ fs5.writeFileSync(promptFile, prompt, "utf-8");
1590
+ log(`
1232
1591
  \u{1F4BE} Prompt saved to: ${promptFile}`);
1233
- log(`
1592
+ const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path5.join(process.cwd(), "debug-artifacts");
1593
+ try {
1594
+ if (!fs5.existsSync(debugArtifactsDir)) {
1595
+ fs5.mkdirSync(debugArtifactsDir, { recursive: true });
1596
+ }
1597
+ const debugFile = path5.join(
1598
+ debugArtifactsDir,
1599
+ `prompt-${_checkName || "unknown"}-${timestamp}.json`
1600
+ );
1601
+ fs5.writeFileSync(debugFile, debugJson, "utf-8");
1602
+ const readableFile = path5.join(
1603
+ debugArtifactsDir,
1604
+ `prompt-${_checkName || "unknown"}-${timestamp}.txt`
1605
+ );
1606
+ fs5.writeFileSync(readableFile, readableVersion, "utf-8");
1607
+ log(`
1608
+ \u{1F4BE} Full debug info saved to:`);
1609
+ log(` JSON: ${debugFile}`);
1610
+ log(` TXT: ${readableFile}`);
1611
+ log(` - Includes: prompt, schema, provider, model, and schema options`);
1612
+ } catch {
1613
+ }
1614
+ log(`
1234
1615
  \u{1F4DD} To reproduce locally, run:`);
1235
- let cliCommand = `npx @probelabs/probe@latest agent`;
1236
- cliCommand += ` --provider ${provider}`;
1237
- if (model !== "default") {
1238
- cliCommand += ` --model ${model}`;
1239
- }
1240
- if (schema) {
1241
- cliCommand += ` --schema output/${schema}/schema.json`;
1242
- }
1243
- cliCommand += ` "${promptFile}"`;
1244
- log(`
1616
+ let cliCommand = `npx @probelabs/probe@latest agent`;
1617
+ cliCommand += ` --provider ${provider}`;
1618
+ if (model !== "default") {
1619
+ cliCommand += ` --model ${model}`;
1620
+ }
1621
+ if (schema) {
1622
+ cliCommand += ` --schema output/${schema}/schema.json`;
1623
+ }
1624
+ cliCommand += ` "${promptFile}"`;
1625
+ log(`
1245
1626
  $ ${cliCommand}
1246
1627
  `);
1247
- } catch (error) {
1248
- log(`\u26A0\uFE0F Could not save prompt file: ${error}`);
1628
+ } catch (error) {
1629
+ log(`\u26A0\uFE0F Could not save prompt file: ${error}`);
1630
+ }
1631
+ }
1632
+ let response;
1633
+ const tracer = options.tracer;
1634
+ if (tracer && typeof tracer.withSpan === "function") {
1635
+ response = await tracer.withSpan(
1636
+ "visor.ai_check",
1637
+ async () => {
1638
+ return await agent.answer(prompt, void 0, schemaOptions);
1639
+ },
1640
+ {
1641
+ "check.name": _checkName || "unknown",
1642
+ "check.session_id": sessionId,
1643
+ "prompt.length": prompt.length,
1644
+ "schema.type": effectiveSchema || "none"
1645
+ }
1646
+ );
1647
+ } else {
1648
+ response = await agent.answer(prompt, void 0, schemaOptions);
1249
1649
  }
1250
- const response = await agent.answer(prompt, void 0, schemaOptions);
1251
1650
  log("\u2705 ProbeAgent completed successfully");
1252
1651
  log(`\u{1F4E4} Response length: ${response.length} characters`);
1652
+ if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
1653
+ try {
1654
+ const fs5 = __require("fs");
1655
+ const path5 = __require("path");
1656
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1657
+ const agentAny = agent;
1658
+ let fullHistory = [];
1659
+ if (agentAny.history) {
1660
+ fullHistory = agentAny.history;
1661
+ } else if (agentAny.messages) {
1662
+ fullHistory = agentAny.messages;
1663
+ } else if (agentAny._messages) {
1664
+ fullHistory = agentAny._messages;
1665
+ }
1666
+ const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path5.join(process.cwd(), "debug-artifacts");
1667
+ const sessionFile = path5.join(
1668
+ debugArtifactsDir,
1669
+ `session-${_checkName || "unknown"}-${timestamp}.json`
1670
+ );
1671
+ const sessionData = {
1672
+ timestamp,
1673
+ checkName: _checkName || "unknown",
1674
+ provider: this.config.provider || "auto",
1675
+ model: this.config.model || "default",
1676
+ schema: effectiveSchema,
1677
+ fullConversationHistory: fullHistory,
1678
+ totalMessages: fullHistory.length,
1679
+ latestResponse: response
1680
+ };
1681
+ fs5.writeFileSync(sessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
1682
+ const sessionTxtFile = path5.join(
1683
+ debugArtifactsDir,
1684
+ `session-${_checkName || "unknown"}-${timestamp}.txt`
1685
+ );
1686
+ let readable = `=============================================================
1687
+ `;
1688
+ readable += `COMPLETE AI SESSION HISTORY (AFTER RESPONSE)
1689
+ `;
1690
+ readable += `=============================================================
1691
+ `;
1692
+ readable += `Timestamp: ${timestamp}
1693
+ `;
1694
+ readable += `Check: ${_checkName || "unknown"}
1695
+ `;
1696
+ readable += `Total Messages: ${fullHistory.length}
1697
+ `;
1698
+ readable += `=============================================================
1699
+
1700
+ `;
1701
+ fullHistory.forEach((msg, idx) => {
1702
+ readable += `
1703
+ ${"=".repeat(60)}
1704
+ `;
1705
+ readable += `MESSAGE ${idx + 1}/${fullHistory.length}
1706
+ `;
1707
+ readable += `Role: ${msg.role || "unknown"}
1708
+ `;
1709
+ readable += `${"=".repeat(60)}
1710
+ `;
1711
+ const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content, null, 2);
1712
+ readable += content + "\n";
1713
+ });
1714
+ fs5.writeFileSync(sessionTxtFile, readable, "utf-8");
1715
+ log(`\u{1F4BE} Complete session history saved:`);
1716
+ log(` JSON: ${sessionFile}`);
1717
+ log(` TXT: ${sessionTxtFile}`);
1718
+ log(` - Contains ALL ${fullHistory.length} messages (prompts + responses)`);
1719
+ } catch (error) {
1720
+ log(`\u26A0\uFE0F Could not save complete session history: ${error}`);
1721
+ }
1722
+ }
1723
+ if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
1724
+ try {
1725
+ const fs5 = __require("fs");
1726
+ const path5 = __require("path");
1727
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1728
+ const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path5.join(process.cwd(), "debug-artifacts");
1729
+ const responseFile = path5.join(
1730
+ debugArtifactsDir,
1731
+ `response-${_checkName || "unknown"}-${timestamp}.txt`
1732
+ );
1733
+ let responseContent = `=============================================================
1734
+ `;
1735
+ responseContent += `VISOR AI RESPONSE - NEW SESSION
1736
+ `;
1737
+ responseContent += `=============================================================
1738
+ `;
1739
+ responseContent += `Timestamp: ${timestamp}
1740
+ `;
1741
+ responseContent += `Check Name: ${_checkName || "unknown"}
1742
+ `;
1743
+ responseContent += `Response Length: ${response.length} characters
1744
+ `;
1745
+ responseContent += `=============================================================
1746
+
1747
+ `;
1748
+ responseContent += `${"=".repeat(60)}
1749
+ `;
1750
+ responseContent += `AI RESPONSE
1751
+ `;
1752
+ responseContent += `${"=".repeat(60)}
1753
+ `;
1754
+ responseContent += response;
1755
+ responseContent += `
1756
+ ${"=".repeat(60)}
1757
+ `;
1758
+ responseContent += `END OF RESPONSE
1759
+ `;
1760
+ responseContent += `${"=".repeat(60)}
1761
+ `;
1762
+ fs5.writeFileSync(responseFile, responseContent, "utf-8");
1763
+ log(`\u{1F4BE} Response saved to: ${responseFile}`);
1764
+ } catch (error) {
1765
+ log(`\u26A0\uFE0F Could not save response file: ${error}`);
1766
+ }
1767
+ }
1768
+ if (traceFilePath && telemetryConfig) {
1769
+ try {
1770
+ if (tracer && typeof tracer.flush === "function") {
1771
+ await tracer.flush();
1772
+ log(`\u{1F504} Flushed tracer spans`);
1773
+ }
1774
+ if (telemetryConfig && typeof telemetryConfig.shutdown === "function") {
1775
+ await telemetryConfig.shutdown();
1776
+ log(`\u{1F4CA} OpenTelemetry trace saved to: ${traceFilePath}`);
1777
+ if (process.env.GITHUB_ACTIONS) {
1778
+ const fs5 = __require("fs");
1779
+ if (fs5.existsSync(traceFilePath)) {
1780
+ const stats = fs5.statSync(traceFilePath);
1781
+ console.log(
1782
+ `::notice title=AI Trace Saved::OpenTelemetry trace file size: ${stats.size} bytes`
1783
+ );
1784
+ }
1785
+ }
1786
+ } else if (tracer && typeof tracer.shutdown === "function") {
1787
+ await tracer.shutdown();
1788
+ log(`\u{1F4CA} Trace saved to: ${traceFilePath}`);
1789
+ }
1790
+ } catch (exportError) {
1791
+ console.error("\u26A0\uFE0F Warning: Failed to export trace:", exportError);
1792
+ }
1793
+ }
1253
1794
  if (_checkName) {
1254
1795
  this.registerSession(sessionId, agent);
1255
1796
  log(`\u{1F527} Debug: Registered AI session for potential reuse: ${sessionId}`);
@@ -1637,7 +2178,7 @@ var PRReviewer = class {
1637
2178
  async reviewPR(owner, repo, prNumber, prInfo, options = {}) {
1638
2179
  const { debug = false, config, checks } = options;
1639
2180
  if (config && checks && checks.length > 0) {
1640
- const { CheckExecutionEngine: CheckExecutionEngine2 } = await import("./check-execution-engine-Z2USLMN5.mjs");
2181
+ const { CheckExecutionEngine: CheckExecutionEngine2 } = await import("./check-execution-engine-L73PFZQY.mjs");
1641
2182
  const engine = new CheckExecutionEngine2();
1642
2183
  const { results } = await engine.executeGroupedChecks(
1643
2184
  prInfo,
@@ -1645,7 +2186,10 @@ var PRReviewer = class {
1645
2186
  void 0,
1646
2187
  config,
1647
2188
  void 0,
1648
- debug
2189
+ debug,
2190
+ void 0,
2191
+ void 0,
2192
+ options.tagFilter
1649
2193
  );
1650
2194
  return results;
1651
2195
  }
@@ -1655,7 +2199,16 @@ var PRReviewer = class {
1655
2199
  }
1656
2200
  async postReviewComment(owner, repo, prNumber, groupedResults, options = {}) {
1657
2201
  for (const [groupName, checkResults] of Object.entries(groupedResults)) {
1658
- const comment = await this.formatGroupComment(checkResults, options, {
2202
+ const filteredResults = options.config ? checkResults.filter((r) => {
2203
+ const cfg = options.config.checks?.[r.checkName];
2204
+ const t = cfg?.type || "";
2205
+ const isGitHubOps = t === "github" || r.group === "github" || t === "noop" || t === "command";
2206
+ return !isGitHubOps;
2207
+ }) : checkResults;
2208
+ if (!filteredResults || filteredResults.length === 0) {
2209
+ continue;
2210
+ }
2211
+ const comment = await this.formatGroupComment(filteredResults, options, {
1659
2212
  owner,
1660
2213
  repo,
1661
2214
  prNumber,
@@ -1668,6 +2221,7 @@ var PRReviewer = class {
1668
2221
  } else {
1669
2222
  commentId = options.commentId ? `${options.commentId}-${groupName}` : `visor-review-${groupName}`;
1670
2223
  }
2224
+ if (!comment || !comment.trim()) continue;
1671
2225
  await this.commentManager.updateOrCreateComment(owner, repo, prNumber, comment, {
1672
2226
  commentId,
1673
2227
  triggeredBy: options.triggeredBy || "unknown",
@@ -1677,22 +2231,33 @@ var PRReviewer = class {
1677
2231
  }
1678
2232
  }
1679
2233
  async formatGroupComment(checkResults, _options, _githubContext) {
2234
+ const normalize = (s) => s.replace(/\\n/g, "\n");
2235
+ const checkContents = checkResults.map((result) => {
2236
+ const trimmed = result.content?.trim();
2237
+ if (trimmed) return normalize(trimmed);
2238
+ const out = result.output;
2239
+ if (out) {
2240
+ if (typeof out === "string" && out.trim()) return normalize(out.trim());
2241
+ if (typeof out === "object") {
2242
+ const txt = out.text || out.response || out.message;
2243
+ if (typeof txt === "string" && txt.trim()) return normalize(txt.trim());
2244
+ }
2245
+ }
2246
+ return "";
2247
+ }).filter((content) => content && content.trim());
2248
+ const debugInfo = checkResults.find((result) => result.debug)?.debug;
2249
+ if (checkContents.length === 0 && !debugInfo) {
2250
+ return "";
2251
+ }
1680
2252
  let comment = "";
1681
2253
  comment += `## \u{1F50D} Code Analysis Results
1682
2254
 
1683
2255
  `;
1684
- const checkContents = checkResults.map((result) => result.content).filter((content) => content.trim());
1685
2256
  comment += checkContents.join("\n\n");
1686
- const debugInfo = checkResults.find((result) => result.debug)?.debug;
1687
2257
  if (debugInfo) {
1688
2258
  comment += "\n\n" + this.formatDebugSection(debugInfo);
1689
2259
  comment += "\n\n";
1690
2260
  }
1691
- comment += `
1692
-
1693
- ---
1694
-
1695
- *Powered by [Visor](https://probelabs.com/visor) from [Probelabs](https://probelabs.com)*`;
1696
2261
  return comment;
1697
2262
  }
1698
2263
  formatDebugSection(debug) {
@@ -1813,6 +2378,7 @@ var PRReviewer = class {
1813
2378
  import { simpleGit } from "simple-git";
1814
2379
  import * as path from "path";
1815
2380
  import * as fs from "fs";
2381
+ var MAX_PATCH_SIZE = 50 * 1024;
1816
2382
  var GitRepositoryAnalyzer = class {
1817
2383
  git;
1818
2384
  cwd;
@@ -1823,30 +2389,48 @@ var GitRepositoryAnalyzer = class {
1823
2389
  /**
1824
2390
  * Analyze the current git repository state and return data compatible with PRInfo interface
1825
2391
  */
1826
- async analyzeRepository(includeContext = true) {
2392
+ async analyzeRepository(includeContext = true, enableBranchDiff = false) {
1827
2393
  const isRepo = await this.isGitRepository();
1828
2394
  if (!isRepo) {
1829
2395
  return this.createEmptyRepositoryInfo("Not a git repository");
1830
2396
  }
1831
2397
  try {
1832
- const [status, currentBranch] = await Promise.all([
2398
+ const [status, currentBranch, baseBranch] = await Promise.all([
1833
2399
  this.git.status(),
1834
- this.getCurrentBranch()
2400
+ this.getCurrentBranch(),
2401
+ this.getBaseBranch()
1835
2402
  ]);
1836
- const uncommittedFiles = await this.getUncommittedChanges(includeContext);
2403
+ const isFeatureBranch = currentBranch !== baseBranch && currentBranch !== "main" && currentBranch !== "master";
2404
+ let uncommittedFiles = await this.getUncommittedChanges(includeContext);
2405
+ if (isFeatureBranch && includeContext && enableBranchDiff) {
2406
+ if (uncommittedFiles.length > 0) {
2407
+ console.error(`\u{1F4CA} Feature branch detected: ${currentBranch}`);
2408
+ console.error(
2409
+ `\u26A0\uFE0F Ignoring ${uncommittedFiles.length} uncommitted file(s) due to --analyze-branch-diff flag`
2410
+ );
2411
+ } else {
2412
+ console.error(`\u{1F4CA} Feature branch detected: ${currentBranch}`);
2413
+ }
2414
+ console.error(
2415
+ `\u{1F4C2} Analyzing diff vs ${baseBranch} (${uncommittedFiles.length > 0 ? "forced by --analyze-branch-diff" : "auto-enabled for code-review schemas"})`
2416
+ );
2417
+ uncommittedFiles = await this.getBranchDiff(baseBranch, includeContext);
2418
+ } else if (uncommittedFiles.length > 0) {
2419
+ console.error(`\u{1F4DD} Analyzing uncommitted changes (${uncommittedFiles.length} files)`);
2420
+ }
1837
2421
  let lastCommit = null;
1838
2422
  try {
1839
2423
  const recentCommits = await this.git.log({ maxCount: 1 });
1840
2424
  lastCommit = recentCommits.latest;
1841
2425
  } catch {
1842
- console.log("\u{1F4DD} Repository has no commits yet, analyzing uncommitted changes");
2426
+ console.error("\u{1F4DD} Repository has no commits yet, analyzing uncommitted changes");
1843
2427
  }
1844
2428
  let author = lastCommit?.author_name;
1845
2429
  if (!author) {
1846
2430
  try {
1847
2431
  const [userName, userEmail] = await Promise.all([
1848
- this.git.raw(["config", "user.name"]).catch(() => null),
1849
- this.git.raw(["config", "user.email"]).catch(() => null)
2432
+ this.git.raw(["config", "--local", "user.name"]).catch(() => null),
2433
+ this.git.raw(["config", "--local", "user.email"]).catch(() => null)
1850
2434
  ]);
1851
2435
  author = userName?.trim() || userEmail?.trim() || "unknown";
1852
2436
  } catch {
@@ -1857,7 +2441,7 @@ var GitRepositoryAnalyzer = class {
1857
2441
  title: this.generateTitle(status, currentBranch),
1858
2442
  body: this.generateDescription(status, lastCommit),
1859
2443
  author,
1860
- base: await this.getBaseBranch(),
2444
+ base: baseBranch,
1861
2445
  head: currentBranch,
1862
2446
  files: uncommittedFiles,
1863
2447
  totalAdditions: uncommittedFiles.reduce((sum, file) => sum + file.additions, 0),
@@ -1935,6 +2519,51 @@ ${file.patch}`).join("\n\n");
1935
2519
  return "main";
1936
2520
  }
1937
2521
  }
2522
+ /**
2523
+ * Check if a file should be excluded from analysis
2524
+ * This includes:
2525
+ * - Files in .gitignore (even if force-added)
2526
+ * - Built/generated files (dist/, build/, .next/, etc.)
2527
+ */
2528
+ async shouldExcludeFile(filename) {
2529
+ const excludePatterns = [
2530
+ /^dist\//,
2531
+ /^build\//,
2532
+ /^\.next\//,
2533
+ /^out\//,
2534
+ /^node_modules\//,
2535
+ /^coverage\//,
2536
+ /^\.turbo\//
2537
+ ];
2538
+ for (const pattern of excludePatterns) {
2539
+ if (pattern.test(filename)) {
2540
+ return true;
2541
+ }
2542
+ }
2543
+ try {
2544
+ const result = await this.git.raw(["check-ignore", filename]);
2545
+ return result.trim().length > 0;
2546
+ } catch {
2547
+ return false;
2548
+ }
2549
+ }
2550
+ /**
2551
+ * Truncate a patch if it exceeds MAX_PATCH_SIZE
2552
+ */
2553
+ truncatePatch(patch, filename) {
2554
+ const patchSize = Buffer.byteLength(patch, "utf8");
2555
+ if (patchSize <= MAX_PATCH_SIZE) {
2556
+ return { patch, truncated: false };
2557
+ }
2558
+ const truncated = patch.substring(0, MAX_PATCH_SIZE);
2559
+ const truncatedPatch = `${truncated}
2560
+
2561
+ ... [TRUNCATED: Diff too large (${(patchSize / 1024).toFixed(1)}KB), showing first ${(MAX_PATCH_SIZE / 1024).toFixed(0)}KB] ...`;
2562
+ console.error(
2563
+ `\u26A0\uFE0F Truncated diff for ${filename} (${(patchSize / 1024).toFixed(1)}KB \u2192 ${(MAX_PATCH_SIZE / 1024).toFixed(0)}KB)`
2564
+ );
2565
+ return { patch: truncatedPatch, truncated: true };
2566
+ }
1938
2567
  async getRemoteInfo() {
1939
2568
  try {
1940
2569
  const remotes = await this.git.getRemotes(true);
@@ -1958,6 +2587,10 @@ ${file.patch}`).join("\n\n");
1958
2587
  }))
1959
2588
  ];
1960
2589
  for (const { file, status: status2 } of fileChanges) {
2590
+ if (await this.shouldExcludeFile(file)) {
2591
+ console.error(`\u23ED\uFE0F Skipping excluded file: ${file}`);
2592
+ continue;
2593
+ }
1961
2594
  const filePath = path.join(this.cwd, file);
1962
2595
  const fileChange = await this.analyzeFileChange(file, status2, filePath, includeContext);
1963
2596
  changes.push(fileChange);
@@ -1968,16 +2601,78 @@ ${file.patch}`).join("\n\n");
1968
2601
  return [];
1969
2602
  }
1970
2603
  }
2604
+ /**
2605
+ * Get diff between current branch and base branch (for feature branch analysis)
2606
+ */
2607
+ async getBranchDiff(baseBranch, includeContext = true) {
2608
+ try {
2609
+ const diffSummary = await this.git.diffSummary([baseBranch]);
2610
+ const changes = [];
2611
+ if (!diffSummary || !diffSummary.files) {
2612
+ return [];
2613
+ }
2614
+ for (const file of diffSummary.files) {
2615
+ if (await this.shouldExcludeFile(file.file)) {
2616
+ console.error(`\u23ED\uFE0F Skipping excluded file: ${file.file}`);
2617
+ continue;
2618
+ }
2619
+ const isBinary = "binary" in file && file.binary;
2620
+ const insertions = "insertions" in file ? file.insertions : 0;
2621
+ const deletions = "deletions" in file ? file.deletions : 0;
2622
+ const fileChanges = "changes" in file ? file.changes : 0;
2623
+ let status;
2624
+ if (isBinary) {
2625
+ status = "modified";
2626
+ } else if (insertions > 0 && deletions === 0) {
2627
+ status = "added";
2628
+ } else if (insertions === 0 && deletions > 0) {
2629
+ status = "removed";
2630
+ } else {
2631
+ status = "modified";
2632
+ }
2633
+ let patch;
2634
+ let truncated = false;
2635
+ if (includeContext && !isBinary) {
2636
+ try {
2637
+ const rawPatch = await this.git.diff([baseBranch, "--", file.file]);
2638
+ if (rawPatch) {
2639
+ const result = this.truncatePatch(rawPatch, file.file);
2640
+ patch = result.patch;
2641
+ truncated = result.truncated;
2642
+ }
2643
+ } catch {
2644
+ }
2645
+ }
2646
+ const fileChange = {
2647
+ filename: file.file,
2648
+ additions: insertions,
2649
+ deletions,
2650
+ changes: fileChanges,
2651
+ status,
2652
+ patch,
2653
+ truncated
2654
+ };
2655
+ changes.push(fileChange);
2656
+ }
2657
+ return changes;
2658
+ } catch (error) {
2659
+ console.error("Error getting branch diff:", error);
2660
+ return [];
2661
+ }
2662
+ }
1971
2663
  async analyzeFileChange(filename, status, filePath, includeContext = true) {
1972
2664
  let additions = 0;
1973
2665
  let deletions = 0;
1974
2666
  let patch;
1975
2667
  let content;
2668
+ let truncated = false;
1976
2669
  try {
1977
2670
  if (includeContext && status !== "added" && fs.existsSync(filePath)) {
1978
2671
  const diff = await this.git.diff(["--", filename]).catch(() => "");
1979
2672
  if (diff) {
1980
- patch = diff;
2673
+ const result = this.truncatePatch(diff, filename);
2674
+ patch = result.patch;
2675
+ truncated = result.truncated;
1981
2676
  const lines = diff.split("\n");
1982
2677
  additions = lines.filter((line) => line.startsWith("+")).length;
1983
2678
  deletions = lines.filter((line) => line.startsWith("-")).length;
@@ -1996,7 +2691,9 @@ ${file.patch}`).join("\n\n");
1996
2691
  if (stats.isFile() && stats.size < 1024 * 1024) {
1997
2692
  if (includeContext) {
1998
2693
  content = fs.readFileSync(filePath, "utf8");
1999
- patch = content;
2694
+ const result = this.truncatePatch(content, filename);
2695
+ patch = result.patch;
2696
+ truncated = result.truncated;
2000
2697
  }
2001
2698
  const fileContent = includeContext ? content : fs.readFileSync(filePath, "utf8");
2002
2699
  additions = fileContent.split("\n").length;
@@ -2017,7 +2714,8 @@ ${file.patch}`).join("\n\n");
2017
2714
  deletions,
2018
2715
  changes: additions + deletions,
2019
2716
  content,
2020
- patch
2717
+ patch,
2718
+ truncated
2021
2719
  };
2022
2720
  }
2023
2721
  generateTitle(status, branch) {
@@ -2079,6 +2777,172 @@ ${file.patch}`).join("\n\n");
2079
2777
  }
2080
2778
  };
2081
2779
 
2780
+ // src/pr-analyzer.ts
2781
+ var PRAnalyzer = class {
2782
+ constructor(octokit, maxRetries = 3) {
2783
+ this.octokit = octokit;
2784
+ this.maxRetries = maxRetries;
2785
+ }
2786
+ /**
2787
+ * Fetch commit diff for incremental analysis
2788
+ */
2789
+ async fetchCommitDiff(owner, repo, commitSha) {
2790
+ try {
2791
+ const { data: commit } = await this.withRetry(
2792
+ () => this.octokit.rest.repos.getCommit({
2793
+ owner,
2794
+ repo,
2795
+ ref: commitSha
2796
+ })
2797
+ );
2798
+ const patches = commit.files?.filter((file) => file.patch).map((file) => `--- ${file.filename}
2799
+ ${file.patch}`).join("\n\n") || "";
2800
+ return patches;
2801
+ } catch (error) {
2802
+ console.warn(`Failed to fetch commit diff for ${commitSha}:`, error);
2803
+ return "";
2804
+ }
2805
+ }
2806
+ /**
2807
+ * Generate unified diff for all PR files
2808
+ */
2809
+ generateFullDiff(files) {
2810
+ return files.filter((file) => file.patch).map((file) => `--- ${file.filename}
2811
+ ${file.patch}`).join("\n\n");
2812
+ }
2813
+ async fetchPRDiff(owner, repo, prNumber, commitSha, eventType) {
2814
+ const [prData, filesData] = await Promise.all([
2815
+ this.withRetry(
2816
+ () => this.octokit.rest.pulls.get({
2817
+ owner,
2818
+ repo,
2819
+ pull_number: prNumber
2820
+ })
2821
+ ),
2822
+ this.withRetry(
2823
+ () => this.octokit.rest.pulls.listFiles({
2824
+ owner,
2825
+ repo,
2826
+ pull_number: prNumber
2827
+ })
2828
+ )
2829
+ ]);
2830
+ const pr = prData?.data;
2831
+ const files = filesData?.data || [];
2832
+ if (!pr) {
2833
+ throw new Error("Invalid or missing pull request data");
2834
+ }
2835
+ const title = typeof pr.title === "string" ? pr.title : pr.title ? String(pr.title) : "MISSING";
2836
+ const body = typeof pr.body === "string" ? pr.body : pr.body ? String(pr.body) : "";
2837
+ const author = pr.user && typeof pr.user === "object" && pr.user.login ? typeof pr.user.login === "string" ? pr.user.login : String(pr.user.login) : "unknown";
2838
+ const authorAssociation = pr.author_association && typeof pr.author_association === "string" ? pr.author_association : void 0;
2839
+ const base = pr.base && typeof pr.base === "object" && pr.base.ref ? typeof pr.base.ref === "string" ? pr.base.ref : String(pr.base.ref) : "main";
2840
+ const head = pr.head && typeof pr.head === "object" && pr.head.ref ? typeof pr.head.ref === "string" ? pr.head.ref : String(pr.head.ref) : "feature";
2841
+ const validFiles = files ? files.filter((file) => file && typeof file === "object" && file.filename).map((file) => ({
2842
+ filename: typeof file.filename === "string" ? file.filename : String(file.filename || "unknown"),
2843
+ additions: typeof file.additions === "number" ? Math.max(0, file.additions) : 0,
2844
+ deletions: typeof file.deletions === "number" ? Math.max(0, file.deletions) : 0,
2845
+ changes: typeof file.changes === "number" ? Math.max(0, file.changes) : 0,
2846
+ patch: typeof file.patch === "string" ? file.patch : void 0,
2847
+ status: ["added", "removed", "modified", "renamed"].includes(file.status) ? file.status : "modified"
2848
+ })).filter((file) => file.filename.length > 0) : [];
2849
+ const prInfo = {
2850
+ number: typeof pr.number === "number" ? pr.number : parseInt(String(pr.number || 1), 10),
2851
+ title,
2852
+ body,
2853
+ author,
2854
+ authorAssociation,
2855
+ base,
2856
+ head,
2857
+ files: validFiles,
2858
+ totalAdditions: validFiles.reduce((sum, file) => sum + file.additions, 0),
2859
+ totalDeletions: validFiles.reduce((sum, file) => sum + file.deletions, 0),
2860
+ fullDiff: this.generateFullDiff(validFiles),
2861
+ eventType
2862
+ };
2863
+ try {
2864
+ console.log(`\u{1F4AC} Fetching comment history for PR #${prInfo.number}`);
2865
+ const comments = await this.fetchPRComments(owner, repo, prInfo.number);
2866
+ prInfo.comments = comments;
2867
+ console.log(`\u2705 Retrieved ${comments.length} comments`);
2868
+ } catch (error) {
2869
+ console.warn(
2870
+ `\u26A0\uFE0F Could not fetch comments: ${error instanceof Error ? error.message : "Unknown error"}`
2871
+ );
2872
+ prInfo.comments = [];
2873
+ }
2874
+ if (commitSha) {
2875
+ console.log(`\u{1F527} Fetching incremental diff for commit: ${commitSha}`);
2876
+ prInfo.commitDiff = await this.fetchCommitDiff(owner, repo, commitSha);
2877
+ prInfo.isIncremental = true;
2878
+ if (!prInfo.commitDiff || prInfo.commitDiff.length === 0) {
2879
+ console.warn(
2880
+ `\u26A0\uFE0F No commit diff retrieved for ${commitSha}, will use full diff as fallback`
2881
+ );
2882
+ } else {
2883
+ console.log(`\u2705 Incremental diff retrieved (${prInfo.commitDiff.length} chars)`);
2884
+ }
2885
+ } else {
2886
+ prInfo.isIncremental = false;
2887
+ }
2888
+ return prInfo;
2889
+ }
2890
+ async fetchPRComments(owner, repo, prNumber) {
2891
+ const { data: comments } = await this.withRetry(
2892
+ () => this.octokit.rest.issues.listComments({
2893
+ owner,
2894
+ repo,
2895
+ issue_number: prNumber
2896
+ })
2897
+ );
2898
+ return comments.map((comment) => ({
2899
+ id: comment.id,
2900
+ author: comment.user?.login || "unknown",
2901
+ body: comment.body || "",
2902
+ createdAt: comment.created_at,
2903
+ updatedAt: comment.updated_at
2904
+ }));
2905
+ }
2906
+ async withRetry(operation) {
2907
+ let lastError = new Error("Unknown error");
2908
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
2909
+ try {
2910
+ return await operation();
2911
+ } catch (error) {
2912
+ if (error instanceof Error) {
2913
+ lastError = error;
2914
+ } else if (typeof error === "object" && error !== null) {
2915
+ const errorObj = error;
2916
+ const message = errorObj.message || errorObj.code || "Unknown error";
2917
+ lastError = new Error(String(message));
2918
+ Object.assign(lastError, error);
2919
+ } else {
2920
+ lastError = new Error(String(error));
2921
+ }
2922
+ if (attempt === this.maxRetries) {
2923
+ break;
2924
+ }
2925
+ if (this.isRetryableError(error)) {
2926
+ const delay = Math.min(1e3 * Math.pow(2, attempt), 5e3);
2927
+ await new Promise((resolve) => setTimeout(resolve, delay));
2928
+ } else {
2929
+ throw error;
2930
+ }
2931
+ }
2932
+ }
2933
+ throw lastError;
2934
+ }
2935
+ isRetryableError(error) {
2936
+ const retryableErrors = ["ETIMEDOUT", "ECONNRESET", "ECONNREFUSED", "ENOTFOUND", "EAI_AGAIN"];
2937
+ const retryableStatuses = [408, 429, 500, 502, 503, 504];
2938
+ if (typeof error !== "object" || error === null) {
2939
+ return false;
2940
+ }
2941
+ const err = error;
2942
+ return err.code !== void 0 && retryableErrors.includes(err.code) || err.status !== void 0 && retryableStatuses.includes(err.status) || err.response?.status !== void 0 && retryableStatuses.includes(err.response.status);
2943
+ }
2944
+ };
2945
+
2082
2946
  // src/providers/check-provider.interface.ts
2083
2947
  var CheckProvider = class {
2084
2948
  };
@@ -3451,6 +4315,270 @@ var LogCheckProvider = class extends CheckProvider {
3451
4315
  }
3452
4316
  };
3453
4317
 
4318
+ // src/providers/github-ops-provider.ts
4319
+ import Sandbox from "@nyariv/sandboxjs";
4320
+ var GitHubOpsProvider = class extends CheckProvider {
4321
+ sandbox;
4322
+ getName() {
4323
+ return "github";
4324
+ }
4325
+ getDescription() {
4326
+ return "Native GitHub operations (labels, comments, reviewers) executed via Octokit";
4327
+ }
4328
+ async validateConfig(config) {
4329
+ if (!config || typeof config !== "object") return false;
4330
+ const cfg = config;
4331
+ return typeof cfg.op === "string" && cfg.op.length > 0;
4332
+ }
4333
+ getSupportedConfigKeys() {
4334
+ return ["op", "values", "value", "value_js"];
4335
+ }
4336
+ async isAvailable() {
4337
+ return Boolean(
4338
+ process.env.GITHUB_TOKEN || process.env["INPUT_GITHUB-TOKEN"] || process.env.GITHUB_REPOSITORY
4339
+ );
4340
+ }
4341
+ getRequirements() {
4342
+ return ["GITHUB_TOKEN or INPUT_GITHUB-TOKEN", "GITHUB_REPOSITORY"];
4343
+ }
4344
+ async execute(prInfo, config, dependencyResults) {
4345
+ const cfg = config;
4346
+ let octokit = config.eventContext?.octokit;
4347
+ if (!octokit) {
4348
+ const token = process.env["INPUT_GITHUB-TOKEN"] || process.env["GITHUB_TOKEN"];
4349
+ if (!token) {
4350
+ return {
4351
+ issues: [
4352
+ {
4353
+ file: "system",
4354
+ line: 0,
4355
+ ruleId: "github/missing_token",
4356
+ message: "No GitHub token available; set GITHUB_TOKEN or pass github-token input for native GitHub operations",
4357
+ severity: "error",
4358
+ category: "logic"
4359
+ }
4360
+ ]
4361
+ };
4362
+ }
4363
+ const { Octokit } = await import("@octokit/rest");
4364
+ octokit = new Octokit({ auth: token });
4365
+ }
4366
+ const repoEnv = process.env.GITHUB_REPOSITORY || "";
4367
+ const [owner, repo] = repoEnv.split("/");
4368
+ if (!owner || !repo || !prInfo?.number) {
4369
+ return {
4370
+ issues: [
4371
+ {
4372
+ file: "system",
4373
+ line: 0,
4374
+ ruleId: "github/missing_context",
4375
+ message: "Missing owner/repo or PR number; GitHub operations require Action context",
4376
+ severity: "error",
4377
+ category: "logic"
4378
+ }
4379
+ ]
4380
+ };
4381
+ }
4382
+ let valuesRaw = [];
4383
+ if (Array.isArray(cfg.values)) valuesRaw = cfg.values.map((v) => String(v));
4384
+ else if (typeof cfg.values === "string") valuesRaw = [cfg.values];
4385
+ else if (typeof cfg.value === "string") valuesRaw = [cfg.value];
4386
+ const renderValues = async (arr) => {
4387
+ if (!arr || arr.length === 0) return [];
4388
+ const liq = createExtendedLiquid({
4389
+ cache: false,
4390
+ strictFilters: false,
4391
+ strictVariables: false
4392
+ });
4393
+ const outputs = {};
4394
+ if (dependencyResults) {
4395
+ for (const [name, result] of dependencyResults.entries()) {
4396
+ const summary = result;
4397
+ outputs[name] = summary.output !== void 0 ? summary.output : summary;
4398
+ }
4399
+ }
4400
+ const ctx = {
4401
+ pr: {
4402
+ number: prInfo.number,
4403
+ title: prInfo.title,
4404
+ author: prInfo.author,
4405
+ branch: prInfo.head,
4406
+ base: prInfo.base,
4407
+ authorAssociation: prInfo.authorAssociation
4408
+ },
4409
+ outputs
4410
+ };
4411
+ const out = [];
4412
+ for (const item of arr) {
4413
+ if (typeof item === "string" && (item.includes("{{") || item.includes("{%"))) {
4414
+ try {
4415
+ const rendered = await liq.parseAndRender(item, ctx);
4416
+ out.push(rendered);
4417
+ } catch (e) {
4418
+ const msg = e instanceof Error ? e.message : String(e);
4419
+ return Promise.reject({
4420
+ issues: [
4421
+ {
4422
+ file: "system",
4423
+ line: 0,
4424
+ ruleId: "github/liquid_render_error",
4425
+ message: `Failed to render template: ${msg}`,
4426
+ severity: "error",
4427
+ category: "logic"
4428
+ }
4429
+ ]
4430
+ });
4431
+ }
4432
+ } else {
4433
+ out.push(String(item));
4434
+ }
4435
+ }
4436
+ return out;
4437
+ };
4438
+ let values = await renderValues(valuesRaw);
4439
+ if (cfg.value_js && cfg.value_js.trim()) {
4440
+ try {
4441
+ const sandbox = this.getSecureSandbox();
4442
+ const code = `
4443
+ const __fn = () => {
4444
+ ${cfg.value_js}
4445
+ };
4446
+ return __fn();
4447
+ `;
4448
+ const exec = sandbox.compile(code);
4449
+ const res = exec({ pr: prInfo, values });
4450
+ if (typeof res === "string") values = [res];
4451
+ else if (Array.isArray(res)) values = res.map((v) => String(v));
4452
+ } catch (e) {
4453
+ const msg = e instanceof Error ? e.message : String(e);
4454
+ return {
4455
+ issues: [
4456
+ {
4457
+ file: "system",
4458
+ line: 0,
4459
+ ruleId: "github/value_js_error",
4460
+ message: `value_js evaluation failed: ${msg}`,
4461
+ severity: "error",
4462
+ category: "logic"
4463
+ }
4464
+ ]
4465
+ };
4466
+ }
4467
+ }
4468
+ values = values.map((v) => v.trim()).filter((v) => v.length > 0);
4469
+ values = Array.from(new Set(values));
4470
+ try {
4471
+ switch (cfg.op) {
4472
+ case "labels.add": {
4473
+ if (values.length === 0) break;
4474
+ await octokit.rest.issues.addLabels({
4475
+ owner,
4476
+ repo,
4477
+ issue_number: prInfo.number,
4478
+ labels: values
4479
+ });
4480
+ break;
4481
+ }
4482
+ case "labels.remove": {
4483
+ for (const l of values) {
4484
+ await octokit.rest.issues.removeLabel({
4485
+ owner,
4486
+ repo,
4487
+ issue_number: prInfo.number,
4488
+ name: l
4489
+ });
4490
+ }
4491
+ break;
4492
+ }
4493
+ case "comment.create": {
4494
+ const body = values.join("\n").trim();
4495
+ if (body)
4496
+ await octokit.rest.issues.createComment({
4497
+ owner,
4498
+ repo,
4499
+ issue_number: prInfo.number,
4500
+ body
4501
+ });
4502
+ break;
4503
+ }
4504
+ default:
4505
+ return {
4506
+ issues: [
4507
+ {
4508
+ file: "system",
4509
+ line: 0,
4510
+ ruleId: "github/unsupported_op",
4511
+ message: `Unsupported GitHub op: ${cfg.op}`,
4512
+ severity: "error",
4513
+ category: "logic"
4514
+ }
4515
+ ]
4516
+ };
4517
+ }
4518
+ return { issues: [] };
4519
+ } catch (e) {
4520
+ const msg = e instanceof Error ? e.message : String(e);
4521
+ return {
4522
+ issues: [
4523
+ {
4524
+ file: "system",
4525
+ line: 0,
4526
+ ruleId: "github/op_failed",
4527
+ message: `GitHub operation failed (${cfg.op}): ${msg}`,
4528
+ severity: "error",
4529
+ category: "logic"
4530
+ }
4531
+ ]
4532
+ };
4533
+ }
4534
+ }
4535
+ /**
4536
+ * Create a secure sandbox for evaluating small expressions without access to process/env
4537
+ */
4538
+ getSecureSandbox() {
4539
+ if (this.sandbox) return this.sandbox;
4540
+ const globals = {
4541
+ ...Sandbox.SAFE_GLOBALS,
4542
+ Math
4543
+ };
4544
+ const prototypeWhitelist = new Map(Sandbox.SAFE_PROTOTYPES);
4545
+ const arrayMethods = /* @__PURE__ */ new Set([
4546
+ "some",
4547
+ "every",
4548
+ "filter",
4549
+ "map",
4550
+ "reduce",
4551
+ "find",
4552
+ "includes",
4553
+ "indexOf",
4554
+ "length",
4555
+ "slice",
4556
+ "concat",
4557
+ "join"
4558
+ ]);
4559
+ prototypeWhitelist.set(Array.prototype, arrayMethods);
4560
+ const stringMethods = /* @__PURE__ */ new Set([
4561
+ "toLowerCase",
4562
+ "toUpperCase",
4563
+ "includes",
4564
+ "indexOf",
4565
+ "startsWith",
4566
+ "endsWith",
4567
+ "slice",
4568
+ "substring",
4569
+ "length",
4570
+ "trim",
4571
+ "split",
4572
+ "replace"
4573
+ ]);
4574
+ prototypeWhitelist.set(String.prototype, stringMethods);
4575
+ const objectMethods = /* @__PURE__ */ new Set(["hasOwnProperty", "toString", "valueOf"]);
4576
+ prototypeWhitelist.set(Object.prototype, objectMethods);
4577
+ this.sandbox = new Sandbox({ globals, prototypeWhitelist });
4578
+ return this.sandbox;
4579
+ }
4580
+ };
4581
+
3454
4582
  // src/providers/claude-code-check-provider.ts
3455
4583
  import fs4 from "fs/promises";
3456
4584
  import path4 from "path";
@@ -3905,7 +5033,7 @@ var ClaudeCodeCheckProvider = class extends CheckProvider {
3905
5033
  };
3906
5034
 
3907
5035
  // src/providers/command-check-provider.ts
3908
- import Sandbox from "@nyariv/sandboxjs";
5036
+ import Sandbox2 from "@nyariv/sandboxjs";
3909
5037
  init_logger();
3910
5038
  var CommandCheckProvider = class extends CheckProvider {
3911
5039
  liquid;
@@ -3920,12 +5048,12 @@ var CommandCheckProvider = class extends CheckProvider {
3920
5048
  }
3921
5049
  createSecureSandbox() {
3922
5050
  const globals = {
3923
- ...Sandbox.SAFE_GLOBALS,
5051
+ ...Sandbox2.SAFE_GLOBALS,
3924
5052
  console,
3925
5053
  JSON
3926
5054
  };
3927
- const prototypeWhitelist = new Map(Sandbox.SAFE_PROTOTYPES);
3928
- return new Sandbox({ globals, prototypeWhitelist });
5055
+ const prototypeWhitelist = new Map(Sandbox2.SAFE_PROTOTYPES);
5056
+ return new Sandbox2({ globals, prototypeWhitelist });
3929
5057
  }
3930
5058
  getName() {
3931
5059
  return "command";
@@ -3995,7 +5123,20 @@ var CommandCheckProvider = class extends CheckProvider {
3995
5123
  const execAsync = promisify(exec);
3996
5124
  const timeoutSeconds = config.timeout || 60;
3997
5125
  const timeoutMs = timeoutSeconds * 1e3;
3998
- const { stdout, stderr } = await execAsync(renderedCommand, {
5126
+ const normalizeNodeEval = (cmd) => {
5127
+ const re = /^(?<prefix>\s*(?:\/usr\/bin\/env\s+)?node(?:\.exe)?\s+(?:-e|--eval)\s+)(['"])([\s\S]*?)\2(?<suffix>\s|$)/;
5128
+ const m = cmd.match(re);
5129
+ if (!m || !m.groups) return cmd;
5130
+ const prefix = m.groups.prefix;
5131
+ const quote = m[2];
5132
+ const code = m[3];
5133
+ const suffix = m.groups.suffix || "";
5134
+ if (!code.includes("\n")) return cmd;
5135
+ const escaped = code.replace(/\n/g, "\\n");
5136
+ return cmd.replace(re, `${prefix}${quote}${escaped}${quote}${suffix}`);
5137
+ };
5138
+ const safeCommand = normalizeNodeEval(renderedCommand);
5139
+ const { stdout, stderr } = await execAsync(safeCommand, {
3999
5140
  env: scriptEnv,
4000
5141
  timeout: timeoutMs,
4001
5142
  maxBuffer: 10 * 1024 * 1024
@@ -4077,7 +5218,11 @@ var CommandCheckProvider = class extends CheckProvider {
4077
5218
  pr: templateContext.pr,
4078
5219
  files: templateContext.files,
4079
5220
  outputs: this.makeOutputsJsonSmart(templateContext.outputs),
4080
- env: templateContext.env
5221
+ env: templateContext.env,
5222
+ permissions: createPermissionHelpers(
5223
+ resolveAssociationFromEvent(prInfo.eventContext, prInfo.authorAssociation),
5224
+ detectLocalMode()
5225
+ )
4081
5226
  };
4082
5227
  const trimmedTransform = transformJs.trim();
4083
5228
  const buildBodyWithReturn = (raw) => {
@@ -4104,6 +5249,12 @@ return (${lastExpr});`;
4104
5249
  const outputs = scope.outputs;
4105
5250
  const env = scope.env;
4106
5251
  const log = (...args) => { console.log('\u{1F50D} Debug:', ...args); };
5252
+ const hasMinPermission = scope.permissions.hasMinPermission;
5253
+ const isOwner = scope.permissions.isOwner;
5254
+ const isMember = scope.permissions.isMember;
5255
+ const isCollaborator = scope.permissions.isCollaborator;
5256
+ const isContributor = scope.permissions.isContributor;
5257
+ const isFirstTimer = scope.permissions.isFirstTimer;
4107
5258
  const __result = (function(){
4108
5259
  ${bodyWithReturn}
4109
5260
  })();
@@ -4121,6 +5272,12 @@ ${bodyWithReturn}
4121
5272
  const outputs = scope.outputs;
4122
5273
  const env = scope.env;
4123
5274
  const log = (...args) => { console.log('\u{1F50D} Debug:', ...args); };
5275
+ const hasMinPermission = scope.permissions.hasMinPermission;
5276
+ const isOwner = scope.permissions.isOwner;
5277
+ const isMember = scope.permissions.isMember;
5278
+ const isCollaborator = scope.permissions.isCollaborator;
5279
+ const isContributor = scope.permissions.isContributor;
5280
+ const isFirstTimer = scope.permissions.isFirstTimer;
4124
5281
  const __ret = (function(){
4125
5282
  ${bodyWithReturn}
4126
5283
  })();
@@ -5103,6 +6260,475 @@ ${stderrOutput}` : `Command execution failed: ${errorMessage}`;
5103
6260
  }
5104
6261
  };
5105
6262
 
6263
+ // src/providers/memory-check-provider.ts
6264
+ init_logger();
6265
+ import Sandbox3 from "@nyariv/sandboxjs";
6266
+ var MemoryCheckProvider = class extends CheckProvider {
6267
+ liquid;
6268
+ sandbox;
6269
+ constructor() {
6270
+ super();
6271
+ this.liquid = createExtendedLiquid({
6272
+ strictVariables: false,
6273
+ strictFilters: false
6274
+ });
6275
+ }
6276
+ /**
6277
+ * Create a secure sandbox for JavaScript execution
6278
+ */
6279
+ createSecureSandbox() {
6280
+ const globals = {
6281
+ ...Sandbox3.SAFE_GLOBALS,
6282
+ Math,
6283
+ console: {
6284
+ log: console.log,
6285
+ warn: console.warn,
6286
+ error: console.error
6287
+ }
6288
+ };
6289
+ const prototypeWhitelist = new Map(Sandbox3.SAFE_PROTOTYPES);
6290
+ const arrayMethods = /* @__PURE__ */ new Set([
6291
+ "some",
6292
+ "every",
6293
+ "filter",
6294
+ "map",
6295
+ "reduce",
6296
+ "find",
6297
+ "includes",
6298
+ "indexOf",
6299
+ "length",
6300
+ "slice",
6301
+ "concat",
6302
+ "join",
6303
+ "push",
6304
+ "pop",
6305
+ "shift",
6306
+ "unshift",
6307
+ "sort",
6308
+ "reverse"
6309
+ ]);
6310
+ prototypeWhitelist.set(Array.prototype, arrayMethods);
6311
+ const stringMethods = /* @__PURE__ */ new Set([
6312
+ "toLowerCase",
6313
+ "toUpperCase",
6314
+ "includes",
6315
+ "indexOf",
6316
+ "startsWith",
6317
+ "endsWith",
6318
+ "slice",
6319
+ "substring",
6320
+ "length",
6321
+ "trim",
6322
+ "split",
6323
+ "replace",
6324
+ "match",
6325
+ "padStart",
6326
+ "padEnd"
6327
+ ]);
6328
+ prototypeWhitelist.set(String.prototype, stringMethods);
6329
+ const objectMethods = /* @__PURE__ */ new Set(["hasOwnProperty", "toString", "valueOf"]);
6330
+ prototypeWhitelist.set(Object.prototype, objectMethods);
6331
+ return new Sandbox3({
6332
+ globals,
6333
+ prototypeWhitelist
6334
+ });
6335
+ }
6336
+ getName() {
6337
+ return "memory";
6338
+ }
6339
+ getDescription() {
6340
+ return "Memory/state management provider for persistent key-value storage across checks";
6341
+ }
6342
+ async validateConfig(config) {
6343
+ if (!config || typeof config !== "object") {
6344
+ return false;
6345
+ }
6346
+ const cfg = config;
6347
+ if (cfg.type !== "memory") {
6348
+ return false;
6349
+ }
6350
+ if (!cfg.operation || typeof cfg.operation !== "string") {
6351
+ return false;
6352
+ }
6353
+ const operation = cfg.operation;
6354
+ const validOps = ["get", "set", "append", "increment", "delete", "clear", "list", "exec_js"];
6355
+ if (!validOps.includes(operation)) {
6356
+ return false;
6357
+ }
6358
+ if (["get", "set", "append", "increment", "delete"].includes(operation)) {
6359
+ if (!cfg.key || typeof cfg.key !== "string") {
6360
+ return false;
6361
+ }
6362
+ }
6363
+ if (["set", "append"].includes(operation)) {
6364
+ if (cfg.value === void 0 && !cfg.value_js) {
6365
+ return false;
6366
+ }
6367
+ }
6368
+ if (operation === "exec_js") {
6369
+ if (!cfg.memory_js || typeof cfg.memory_js !== "string") {
6370
+ return false;
6371
+ }
6372
+ }
6373
+ return true;
6374
+ }
6375
+ async execute(prInfo, config, dependencyResults, _sessionInfo) {
6376
+ const operation = config.operation;
6377
+ const key = config.key;
6378
+ const namespace = config.namespace;
6379
+ const memoryStore = MemoryStore.getInstance();
6380
+ const templateContext = this.buildTemplateContext(prInfo, dependencyResults, memoryStore);
6381
+ let result;
6382
+ try {
6383
+ switch (operation) {
6384
+ case "get":
6385
+ result = await this.handleGet(memoryStore, key, namespace);
6386
+ break;
6387
+ case "set":
6388
+ result = await this.handleSet(memoryStore, key, config, namespace, templateContext);
6389
+ break;
6390
+ case "append":
6391
+ result = await this.handleAppend(memoryStore, key, config, namespace, templateContext);
6392
+ break;
6393
+ case "increment":
6394
+ result = await this.handleIncrement(
6395
+ memoryStore,
6396
+ key,
6397
+ config,
6398
+ namespace,
6399
+ templateContext
6400
+ );
6401
+ break;
6402
+ case "delete":
6403
+ result = await this.handleDelete(memoryStore, key, namespace);
6404
+ break;
6405
+ case "clear":
6406
+ result = await this.handleClear(memoryStore, namespace);
6407
+ break;
6408
+ case "list":
6409
+ result = await this.handleList(memoryStore, namespace);
6410
+ break;
6411
+ case "exec_js":
6412
+ result = await this.handleExecJs(memoryStore, config, templateContext);
6413
+ break;
6414
+ default:
6415
+ throw new Error(`Unknown memory operation: ${operation}`);
6416
+ }
6417
+ return {
6418
+ issues: [],
6419
+ output: result
6420
+ };
6421
+ } catch (error) {
6422
+ const errorMsg = error instanceof Error ? error.message : "Unknown error in memory operation";
6423
+ logger.error(`Memory operation failed: ${errorMsg}`);
6424
+ return {
6425
+ issues: [],
6426
+ output: null,
6427
+ error: errorMsg
6428
+ };
6429
+ }
6430
+ }
6431
+ async handleGet(store, key, namespace) {
6432
+ const value = store.get(key, namespace);
6433
+ logger.debug(
6434
+ `Memory GET: ${namespace || store.getDefaultNamespace()}.${key} = ${JSON.stringify(value)}`
6435
+ );
6436
+ return value;
6437
+ }
6438
+ async handleSet(store, key, config, namespace, context) {
6439
+ const value = await this.computeValue(config, context);
6440
+ await store.set(key, value, namespace);
6441
+ logger.debug(
6442
+ `Memory SET: ${namespace || store.getDefaultNamespace()}.${key} = ${JSON.stringify(value)}`
6443
+ );
6444
+ return value;
6445
+ }
6446
+ async handleAppend(store, key, config, namespace, context) {
6447
+ const value = await this.computeValue(config, context);
6448
+ await store.append(key, value, namespace);
6449
+ const result = store.get(key, namespace);
6450
+ logger.debug(
6451
+ `Memory APPEND: ${namespace || store.getDefaultNamespace()}.${key} += ${JSON.stringify(value)} (now: ${JSON.stringify(result)})`
6452
+ );
6453
+ return result;
6454
+ }
6455
+ async handleIncrement(store, key, config, namespace, context) {
6456
+ let amount = 1;
6457
+ if (config.value !== void 0 || config.value_js) {
6458
+ const computedValue = await this.computeValue(config, context);
6459
+ if (typeof computedValue === "number") {
6460
+ amount = computedValue;
6461
+ } else {
6462
+ throw new Error(`Increment amount must be a number, got ${typeof computedValue}`);
6463
+ }
6464
+ }
6465
+ const result = await store.increment(key, amount, namespace);
6466
+ logger.debug(
6467
+ `Memory INCREMENT: ${namespace || store.getDefaultNamespace()}.${key} += ${amount} (now: ${result})`
6468
+ );
6469
+ return result;
6470
+ }
6471
+ async handleDelete(store, key, namespace) {
6472
+ const deleted = await store.delete(key, namespace);
6473
+ logger.debug(
6474
+ `Memory DELETE: ${namespace || store.getDefaultNamespace()}.${key} (deleted: ${deleted})`
6475
+ );
6476
+ return deleted;
6477
+ }
6478
+ async handleClear(store, namespace) {
6479
+ await store.clear(namespace);
6480
+ logger.debug(`Memory CLEAR: ${namespace ? `namespace ${namespace}` : "all namespaces"}`);
6481
+ }
6482
+ async handleList(store, namespace) {
6483
+ const keys = store.list(namespace);
6484
+ logger.debug(`Memory LIST: ${namespace || store.getDefaultNamespace()} (${keys.length} keys)`);
6485
+ return keys;
6486
+ }
6487
+ async handleExecJs(store, config, context) {
6488
+ const script = config.memory_js;
6489
+ const pendingOps = [];
6490
+ const enhancedContext = {
6491
+ ...context,
6492
+ memory: {
6493
+ get: (key, ns) => store.get(key, ns),
6494
+ set: (key, value, ns) => {
6495
+ const nsName = ns || store.getDefaultNamespace();
6496
+ if (!store["data"].has(nsName)) {
6497
+ store["data"].set(nsName, /* @__PURE__ */ new Map());
6498
+ }
6499
+ store["data"].get(nsName).set(key, value);
6500
+ pendingOps.push(async () => {
6501
+ if (store.getConfig().storage === "file" && store.getConfig().auto_save) {
6502
+ await store.save();
6503
+ }
6504
+ });
6505
+ return value;
6506
+ },
6507
+ append: (key, value, ns) => {
6508
+ const existing = store.get(key, ns);
6509
+ let newValue;
6510
+ if (existing === void 0) {
6511
+ newValue = [value];
6512
+ } else if (Array.isArray(existing)) {
6513
+ newValue = [...existing, value];
6514
+ } else {
6515
+ newValue = [existing, value];
6516
+ }
6517
+ const nsName = ns || store.getDefaultNamespace();
6518
+ if (!store["data"].has(nsName)) {
6519
+ store["data"].set(nsName, /* @__PURE__ */ new Map());
6520
+ }
6521
+ store["data"].get(nsName).set(key, newValue);
6522
+ pendingOps.push(async () => {
6523
+ if (store.getConfig().storage === "file" && store.getConfig().auto_save) {
6524
+ await store.save();
6525
+ }
6526
+ });
6527
+ return newValue;
6528
+ },
6529
+ increment: (key, amount = 1, ns) => {
6530
+ const existing = store.get(key, ns);
6531
+ let newValue;
6532
+ if (existing === void 0 || existing === null) {
6533
+ newValue = amount;
6534
+ } else if (typeof existing === "number") {
6535
+ newValue = existing + amount;
6536
+ } else {
6537
+ throw new Error(
6538
+ `Cannot increment non-numeric value at key '${key}' (type: ${typeof existing})`
6539
+ );
6540
+ }
6541
+ const nsName = ns || store.getDefaultNamespace();
6542
+ if (!store["data"].has(nsName)) {
6543
+ store["data"].set(nsName, /* @__PURE__ */ new Map());
6544
+ }
6545
+ store["data"].get(nsName).set(key, newValue);
6546
+ pendingOps.push(async () => {
6547
+ if (store.getConfig().storage === "file" && store.getConfig().auto_save) {
6548
+ await store.save();
6549
+ }
6550
+ });
6551
+ return newValue;
6552
+ },
6553
+ delete: (key, ns) => {
6554
+ const nsName = ns || store.getDefaultNamespace();
6555
+ const nsData = store["data"].get(nsName);
6556
+ const deleted = nsData?.delete(key) || false;
6557
+ if (deleted) {
6558
+ pendingOps.push(async () => {
6559
+ if (store.getConfig().storage === "file" && store.getConfig().auto_save) {
6560
+ await store.save();
6561
+ }
6562
+ });
6563
+ }
6564
+ return deleted;
6565
+ },
6566
+ clear: (ns) => {
6567
+ if (ns) {
6568
+ store["data"].delete(ns);
6569
+ } else {
6570
+ store["data"].clear();
6571
+ }
6572
+ pendingOps.push(async () => {
6573
+ if (store.getConfig().storage === "file" && store.getConfig().auto_save) {
6574
+ await store.save();
6575
+ }
6576
+ });
6577
+ },
6578
+ list: (ns) => store.list(ns),
6579
+ has: (key, ns) => store.has(key, ns),
6580
+ getAll: (ns) => store.getAll(ns),
6581
+ listNamespaces: () => store.listNamespaces()
6582
+ }
6583
+ };
6584
+ const result = this.evaluateJavaScriptBlock(script, enhancedContext);
6585
+ if (pendingOps.length > 0 && store.getConfig().storage === "file" && store.getConfig().auto_save) {
6586
+ await store.save();
6587
+ }
6588
+ logger.debug(`Memory EXEC_JS: Executed custom script with ${pendingOps.length} operations`);
6589
+ return result;
6590
+ }
6591
+ /**
6592
+ * Compute value from config using value, value_js, transform, or transform_js
6593
+ */
6594
+ async computeValue(config, context) {
6595
+ let value;
6596
+ if (config.value_js && typeof config.value_js === "string") {
6597
+ value = this.evaluateJavaScript(config.value_js, context);
6598
+ } else {
6599
+ value = config.value;
6600
+ }
6601
+ if (config.transform && typeof config.transform === "string") {
6602
+ const rendered = await this.liquid.parseAndRender(config.transform, {
6603
+ ...context,
6604
+ value
6605
+ });
6606
+ value = rendered;
6607
+ }
6608
+ if (config.transform_js && typeof config.transform_js === "string") {
6609
+ value = this.evaluateJavaScript(config.transform_js, { ...context, value });
6610
+ }
6611
+ return value;
6612
+ }
6613
+ /**
6614
+ * Evaluate JavaScript expression in context using SandboxJS for secure execution
6615
+ */
6616
+ evaluateJavaScript(expression, context) {
6617
+ if (!this.sandbox) {
6618
+ this.sandbox = this.createSecureSandbox();
6619
+ }
6620
+ try {
6621
+ const log2 = (...args) => {
6622
+ logger.info(`\u{1F50D} [memory-js] ${args.map((a) => JSON.stringify(a)).join(" ")}`);
6623
+ };
6624
+ const scope = {
6625
+ ...context,
6626
+ log: log2
6627
+ };
6628
+ const exec = this.sandbox.compile(`return (${expression});`);
6629
+ return exec(scope).run();
6630
+ } catch (error) {
6631
+ const errorMsg = error instanceof Error ? error.message : "Unknown error";
6632
+ throw new Error(`Failed to evaluate value_js: ${errorMsg}`);
6633
+ }
6634
+ }
6635
+ /**
6636
+ * Evaluate JavaScript block (multi-line script) using SandboxJS for secure execution
6637
+ * Unlike evaluateJavaScript, this supports full scripts with statements, not just expressions
6638
+ */
6639
+ evaluateJavaScriptBlock(script, context) {
6640
+ if (!this.sandbox) {
6641
+ this.sandbox = this.createSecureSandbox();
6642
+ }
6643
+ try {
6644
+ const log2 = (...args) => {
6645
+ logger.info(`\u{1F50D} [memory-js] ${args.map((a) => JSON.stringify(a)).join(" ")}`);
6646
+ };
6647
+ const scope = {
6648
+ ...context,
6649
+ log: log2
6650
+ };
6651
+ const exec = this.sandbox.compile(script);
6652
+ const result = exec(scope).run();
6653
+ return result;
6654
+ } catch (error) {
6655
+ const errorMsg = error instanceof Error ? error.message : "Unknown error";
6656
+ logger.error(`[memory-js] Script execution error: ${errorMsg}`);
6657
+ throw new Error(`Failed to execute memory_js: ${errorMsg}`);
6658
+ }
6659
+ }
6660
+ /**
6661
+ * Build template context for Liquid and JS evaluation
6662
+ */
6663
+ buildTemplateContext(prInfo, dependencyResults, memoryStore) {
6664
+ const context = {};
6665
+ context.pr = {
6666
+ number: prInfo.number,
6667
+ title: prInfo.title,
6668
+ body: prInfo.body,
6669
+ author: prInfo.author,
6670
+ base: prInfo.base,
6671
+ head: prInfo.head,
6672
+ totalAdditions: prInfo.totalAdditions,
6673
+ totalDeletions: prInfo.totalDeletions,
6674
+ files: prInfo.files.map((f) => ({
6675
+ filename: f.filename,
6676
+ status: f.status,
6677
+ additions: f.additions,
6678
+ deletions: f.deletions,
6679
+ changes: f.changes
6680
+ }))
6681
+ };
6682
+ if (dependencyResults) {
6683
+ const outputs = {};
6684
+ for (const [checkName, result] of dependencyResults.entries()) {
6685
+ const summary = result;
6686
+ outputs[checkName] = summary.output !== void 0 ? summary.output : summary;
6687
+ }
6688
+ context.outputs = outputs;
6689
+ }
6690
+ if (memoryStore) {
6691
+ context.memory = {
6692
+ get: (key, ns) => memoryStore.get(key, ns),
6693
+ has: (key, ns) => memoryStore.has(key, ns),
6694
+ list: (ns) => memoryStore.list(ns),
6695
+ getAll: (ns) => memoryStore.getAll(ns)
6696
+ };
6697
+ }
6698
+ return context;
6699
+ }
6700
+ getSupportedConfigKeys() {
6701
+ return [
6702
+ "type",
6703
+ "operation",
6704
+ "key",
6705
+ "value",
6706
+ "value_js",
6707
+ "memory_js",
6708
+ "transform",
6709
+ "transform_js",
6710
+ "namespace",
6711
+ "depends_on",
6712
+ "group",
6713
+ "command",
6714
+ "on",
6715
+ "if",
6716
+ "fail_if",
6717
+ "on_fail",
6718
+ "on_success"
6719
+ ];
6720
+ }
6721
+ async isAvailable() {
6722
+ return true;
6723
+ }
6724
+ getRequirements() {
6725
+ return [
6726
+ "No external dependencies required",
6727
+ "Used for state management and persistent storage across checks"
6728
+ ];
6729
+ }
6730
+ };
6731
+
5106
6732
  // src/providers/check-provider-registry.ts
5107
6733
  var CheckProviderRegistry = class _CheckProviderRegistry {
5108
6734
  providers = /* @__PURE__ */ new Map();
@@ -5130,6 +6756,8 @@ var CheckProviderRegistry = class _CheckProviderRegistry {
5130
6756
  this.register(new HttpClientProvider());
5131
6757
  this.register(new NoopCheckProvider());
5132
6758
  this.register(new LogCheckProvider());
6759
+ this.register(new MemoryCheckProvider());
6760
+ this.register(new GitHubOpsProvider());
5133
6761
  try {
5134
6762
  this.register(new ClaudeCodeCheckProvider());
5135
6763
  } catch (error) {
@@ -5427,7 +7055,7 @@ var DependencyResolver = class {
5427
7055
  };
5428
7056
 
5429
7057
  // src/failure-condition-evaluator.ts
5430
- import Sandbox2 from "@nyariv/sandboxjs";
7058
+ import Sandbox4 from "@nyariv/sandboxjs";
5431
7059
  var FailureConditionEvaluator = class _FailureConditionEvaluator {
5432
7060
  sandbox;
5433
7061
  constructor() {
@@ -5437,7 +7065,7 @@ var FailureConditionEvaluator = class _FailureConditionEvaluator {
5437
7065
  */
5438
7066
  createSecureSandbox() {
5439
7067
  const globals = {
5440
- ...Sandbox2.SAFE_GLOBALS,
7068
+ ...Sandbox4.SAFE_GLOBALS,
5441
7069
  // Allow Math for calculations
5442
7070
  Math,
5443
7071
  // Allow console for debugging (in controlled environment)
@@ -5447,7 +7075,7 @@ var FailureConditionEvaluator = class _FailureConditionEvaluator {
5447
7075
  error: console.error
5448
7076
  }
5449
7077
  };
5450
- const prototypeWhitelist = new Map(Sandbox2.SAFE_PROTOTYPES);
7078
+ const prototypeWhitelist = new Map(Sandbox4.SAFE_PROTOTYPES);
5451
7079
  const arrayMethods = /* @__PURE__ */ new Set([
5452
7080
  "some",
5453
7081
  "every",
@@ -5480,7 +7108,7 @@ var FailureConditionEvaluator = class _FailureConditionEvaluator {
5480
7108
  prototypeWhitelist.set(String.prototype, stringMethods);
5481
7109
  const objectMethods = /* @__PURE__ */ new Set(["hasOwnProperty", "toString", "valueOf"]);
5482
7110
  prototypeWhitelist.set(Object.prototype, objectMethods);
5483
- return new Sandbox2({
7111
+ return new Sandbox4({
5484
7112
  globals,
5485
7113
  prototypeWhitelist
5486
7114
  });
@@ -5488,13 +7116,14 @@ var FailureConditionEvaluator = class _FailureConditionEvaluator {
5488
7116
  /**
5489
7117
  * Evaluate simple fail_if condition
5490
7118
  */
5491
- async evaluateSimpleCondition(checkName, checkSchema, checkGroup, reviewSummary, expression, previousOutputs) {
7119
+ async evaluateSimpleCondition(checkName, checkSchema, checkGroup, reviewSummary, expression, previousOutputs, authorAssociation) {
5492
7120
  const context = this.buildEvaluationContext(
5493
7121
  checkName,
5494
7122
  checkSchema,
5495
7123
  checkGroup,
5496
7124
  reviewSummary,
5497
- previousOutputs
7125
+ previousOutputs,
7126
+ authorAssociation
5498
7127
  );
5499
7128
  try {
5500
7129
  try {
@@ -5509,6 +7138,38 @@ var FailureConditionEvaluator = class _FailureConditionEvaluator {
5509
7138
  } catch {
5510
7139
  }
5511
7140
  const res = this.evaluateExpression(expression, context);
7141
+ if (res === true) {
7142
+ try {
7143
+ addEvent("fail_if.triggered", {
7144
+ check: checkName,
7145
+ scope: "check",
7146
+ name: `${checkName}_fail_if`,
7147
+ expression,
7148
+ severity: "error"
7149
+ });
7150
+ } catch {
7151
+ }
7152
+ try {
7153
+ const { emitNdjsonSpanWithEvents: emitNdjsonSpanWithEvents2 } = (init_fallback_ndjson(), __toCommonJS(fallback_ndjson_exports));
7154
+ emitNdjsonSpanWithEvents2(
7155
+ "visor.fail_if",
7156
+ { check: checkName, scope: "check", name: `${checkName}_fail_if` },
7157
+ [
7158
+ {
7159
+ name: "fail_if.triggered",
7160
+ attrs: {
7161
+ check: checkName,
7162
+ scope: "check",
7163
+ name: `${checkName}_fail_if`,
7164
+ expression,
7165
+ severity: "error"
7166
+ }
7167
+ }
7168
+ ]
7169
+ );
7170
+ } catch {
7171
+ }
7172
+ }
5512
7173
  return res;
5513
7174
  } catch (error) {
5514
7175
  console.warn(`Failed to evaluate fail_if expression: ${error}`);
@@ -5566,6 +7227,8 @@ var FailureConditionEvaluator = class _FailureConditionEvaluator {
5566
7227
  output: {
5567
7228
  issues: []
5568
7229
  },
7230
+ // Author association (used by permission helpers)
7231
+ authorAssociation: contextData?.authorAssociation,
5569
7232
  // Utility metadata
5570
7233
  metadata: {
5571
7234
  checkName,
@@ -5591,13 +7254,14 @@ var FailureConditionEvaluator = class _FailureConditionEvaluator {
5591
7254
  /**
5592
7255
  * Evaluate all failure conditions for a check result
5593
7256
  */
5594
- async evaluateConditions(checkName, checkSchema, checkGroup, reviewSummary, globalConditions, checkConditions, previousOutputs) {
7257
+ async evaluateConditions(checkName, checkSchema, checkGroup, reviewSummary, globalConditions, checkConditions, previousOutputs, authorAssociation) {
5595
7258
  const context = this.buildEvaluationContext(
5596
7259
  checkName,
5597
7260
  checkSchema,
5598
7261
  checkGroup,
5599
7262
  reviewSummary,
5600
- previousOutputs
7263
+ previousOutputs,
7264
+ authorAssociation
5601
7265
  );
5602
7266
  const results = [];
5603
7267
  if (globalConditions) {
@@ -5631,9 +7295,54 @@ var FailureConditionEvaluator = class _FailureConditionEvaluator {
5631
7295
  async evaluateConditionSet(conditions, context, source) {
5632
7296
  const results = [];
5633
7297
  for (const [conditionName, condition] of Object.entries(conditions)) {
7298
+ try {
7299
+ addEvent("fail_if.evaluated", {
7300
+ check: context.checkName,
7301
+ scope: source,
7302
+ name: conditionName,
7303
+ expression: this.extractExpression(condition)
7304
+ });
7305
+ } catch {
7306
+ }
7307
+ try {
7308
+ const { emitNdjsonSpanWithEvents: emitNdjsonSpanWithEvents2 } = (init_fallback_ndjson(), __toCommonJS(fallback_ndjson_exports));
7309
+ emitNdjsonSpanWithEvents2(
7310
+ "visor.fail_if",
7311
+ { check: context.checkName || "unknown", scope: source, name: conditionName },
7312
+ [
7313
+ {
7314
+ name: "fail_if.evaluated",
7315
+ attrs: {
7316
+ check: context.checkName,
7317
+ scope: source,
7318
+ name: conditionName,
7319
+ expression: this.extractExpression(condition)
7320
+ }
7321
+ }
7322
+ ]
7323
+ );
7324
+ } catch {
7325
+ }
5634
7326
  try {
5635
7327
  const result = await this.evaluateSingleCondition(conditionName, condition, context);
5636
7328
  results.push(result);
7329
+ if (result.failed) {
7330
+ try {
7331
+ addEvent("fail_if.triggered", {
7332
+ check: context.checkName,
7333
+ scope: source,
7334
+ name: conditionName,
7335
+ expression: result.expression,
7336
+ severity: result.severity,
7337
+ halt_execution: result.haltExecution
7338
+ });
7339
+ } catch {
7340
+ }
7341
+ try {
7342
+ addFailIfTriggered(context.checkName || "unknown", source);
7343
+ } catch {
7344
+ }
7345
+ }
5637
7346
  } catch (error) {
5638
7347
  results.push({
5639
7348
  conditionName,
@@ -5721,6 +7430,16 @@ var FailureConditionEvaluator = class _FailureConditionEvaluator {
5721
7430
  };
5722
7431
  const hasIssueWith = hasIssue;
5723
7432
  const hasFileWith = hasFileMatching;
7433
+ const permissionHelpers = createPermissionHelpers(
7434
+ context.authorAssociation,
7435
+ detectLocalMode()
7436
+ );
7437
+ const hasMinPermission = permissionHelpers.hasMinPermission;
7438
+ const isOwner = permissionHelpers.isOwner;
7439
+ const isMember = permissionHelpers.isMember;
7440
+ const isCollaborator = permissionHelpers.isCollaborator;
7441
+ const isContributor = permissionHelpers.isContributor;
7442
+ const isFirstTimer = permissionHelpers.isFirstTimer;
5724
7443
  const output = context.output || {};
5725
7444
  const issues = output.issues || [];
5726
7445
  const suggestions = [];
@@ -5751,11 +7470,20 @@ var FailureConditionEvaluator = class _FailureConditionEvaluator {
5751
7470
  const env = context.env || {};
5752
7471
  const outputs = context.outputs || {};
5753
7472
  const debugData = context.debug || null;
7473
+ const memoryStore = MemoryStore.getInstance();
7474
+ const memoryAccessor = {
7475
+ get: (key, ns) => memoryStore.get(key, ns),
7476
+ has: (key, ns) => memoryStore.has(key, ns),
7477
+ list: (ns) => memoryStore.list(ns),
7478
+ getAll: (ns) => memoryStore.getAll(ns)
7479
+ };
5754
7480
  const scope = {
5755
7481
  // Primary context variables
5756
7482
  output,
5757
7483
  outputs,
5758
7484
  debug: debugData,
7485
+ // Memory accessor for fail_if expressions
7486
+ memory: memoryAccessor,
5759
7487
  // Legacy compatibility variables
5760
7488
  issues,
5761
7489
  suggestions,
@@ -5789,7 +7517,14 @@ var FailureConditionEvaluator = class _FailureConditionEvaluator {
5789
7517
  hasFileMatching,
5790
7518
  hasSuggestion,
5791
7519
  hasIssueWith,
5792
- hasFileWith
7520
+ hasFileWith,
7521
+ // Permission helpers
7522
+ hasMinPermission,
7523
+ isOwner,
7524
+ isMember,
7525
+ isCollaborator,
7526
+ isContributor,
7527
+ isFirstTimer
5793
7528
  };
5794
7529
  const raw = condition.trim();
5795
7530
  if (!this.sandbox) {
@@ -5838,7 +7573,7 @@ var FailureConditionEvaluator = class _FailureConditionEvaluator {
5838
7573
  /**
5839
7574
  * Build the evaluation context for expressions
5840
7575
  */
5841
- buildEvaluationContext(checkName, checkSchema, checkGroup, reviewSummary, previousOutputs) {
7576
+ buildEvaluationContext(checkName, checkSchema, checkGroup, reviewSummary, previousOutputs, authorAssociation) {
5842
7577
  const { issues, debug } = reviewSummary;
5843
7578
  const reviewSummaryWithOutput = reviewSummary;
5844
7579
  const {
@@ -5936,6 +7671,7 @@ var FailureConditionEvaluator = class _FailureConditionEvaluator {
5936
7671
  }
5937
7672
  } catch {
5938
7673
  }
7674
+ const memoryStore = MemoryStore.getInstance();
5939
7675
  const context = {
5940
7676
  output: aggregatedOutput,
5941
7677
  outputs: (() => {
@@ -5947,10 +7683,18 @@ var FailureConditionEvaluator = class _FailureConditionEvaluator {
5947
7683
  }
5948
7684
  return outputs;
5949
7685
  })(),
7686
+ // Add memory accessor for fail_if expressions
7687
+ memory: {
7688
+ get: (key, ns) => memoryStore.get(key, ns),
7689
+ has: (key, ns) => memoryStore.has(key, ns),
7690
+ list: (ns) => memoryStore.list(ns),
7691
+ getAll: (ns) => memoryStore.getAll(ns)
7692
+ },
5950
7693
  // Add basic context info for failure conditions
5951
7694
  checkName,
5952
7695
  schema: checkSchema,
5953
- group: checkGroup
7696
+ group: checkGroup,
7697
+ authorAssociation
5954
7698
  };
5955
7699
  if (debug) {
5956
7700
  context.debug = {
@@ -6278,7 +8022,7 @@ Please check your configuration and try again.`
6278
8022
  sections.push("---");
6279
8023
  sections.push("");
6280
8024
  sections.push(
6281
- "*Generated by [Visor](https://github.com/probelabs/visor) - AI-powered code review*"
8025
+ "*Powered by [Visor](https://probelabs.com/visor) from [Probelabs](https://probelabs.com)*"
6282
8026
  );
6283
8027
  return sections.join("\n");
6284
8028
  }
@@ -6480,7 +8224,8 @@ Please check your configuration and try again.`
6480
8224
 
6481
8225
  // src/check-execution-engine.ts
6482
8226
  init_logger();
6483
- import Sandbox3 from "@nyariv/sandboxjs";
8227
+ import Sandbox5 from "@nyariv/sandboxjs";
8228
+ init_fallback_ndjson();
6484
8229
  function getSafeEnvironmentVariables() {
6485
8230
  const safeEnvVars = [
6486
8231
  "CI",
@@ -6518,11 +8263,22 @@ var CheckExecutionEngine = class _CheckExecutionEngine {
6518
8263
  webhookContext;
6519
8264
  routingSandbox;
6520
8265
  executionStats = /* @__PURE__ */ new Map();
6521
- constructor(workingDirectory) {
8266
+ // Event override to simulate alternate event (used during routing goto)
8267
+ routingEventOverride;
8268
+ // Cached GitHub context for context elevation when running in Actions
8269
+ actionContext;
8270
+ constructor(workingDirectory, octokit) {
6522
8271
  this.workingDirectory = workingDirectory || process.cwd();
6523
8272
  this.gitAnalyzer = new GitRepositoryAnalyzer(this.workingDirectory);
6524
8273
  this.providerRegistry = CheckProviderRegistry.getInstance();
6525
8274
  this.failureEvaluator = new FailureConditionEvaluator();
8275
+ if (octokit) {
8276
+ const repoEnv = process.env.GITHUB_REPOSITORY || "";
8277
+ const [owner, repo] = repoEnv.split("/");
8278
+ if (owner && repo) {
8279
+ this.actionContext = { owner, repo, octokit };
8280
+ }
8281
+ }
6526
8282
  this.mockOctokit = this.createMockOctokit();
6527
8283
  this.reviewer = new PRReviewer(this.mockOctokit);
6528
8284
  }
@@ -6532,13 +8288,13 @@ var CheckExecutionEngine = class _CheckExecutionEngine {
6532
8288
  getRoutingSandbox() {
6533
8289
  if (this.routingSandbox) return this.routingSandbox;
6534
8290
  const globals = {
6535
- ...Sandbox3.SAFE_GLOBALS,
8291
+ ...Sandbox5.SAFE_GLOBALS,
6536
8292
  Math,
6537
8293
  JSON,
6538
8294
  console: { log: console.log }
6539
8295
  };
6540
- const prototypeWhitelist = new Map(Sandbox3.SAFE_PROTOTYPES);
6541
- this.routingSandbox = new Sandbox3({ globals, prototypeWhitelist });
8296
+ const prototypeWhitelist = new Map(Sandbox5.SAFE_PROTOTYPES);
8297
+ this.routingSandbox = new Sandbox5({ globals, prototypeWhitelist });
6542
8298
  return this.routingSandbox;
6543
8299
  }
6544
8300
  redact(str, limit = 200) {
@@ -6578,10 +8334,12 @@ var CheckExecutionEngine = class _CheckExecutionEngine {
6578
8334
  let loopCount = 0;
6579
8335
  const seed = `${checkName}-${prInfo.number || "local"}`;
6580
8336
  const allAncestors = DependencyResolver.getAllDependencies(checkName, dependencyGraph.nodes);
8337
+ let currentRouteOutput = void 0;
6581
8338
  const evalRunJs = async (expr, error) => {
6582
8339
  if (!expr) return [];
6583
8340
  try {
6584
8341
  const sandbox = this.getRoutingSandbox();
8342
+ const eventObj = { name: prInfo.eventType || "manual" };
6585
8343
  const scope = {
6586
8344
  step: { id: checkName, tags: checkConfig.tags || [], group: checkConfig.group },
6587
8345
  attempt,
@@ -6593,6 +8351,7 @@ var CheckExecutionEngine = class _CheckExecutionEngine {
6593
8351
  parent: foreachContext.parent
6594
8352
  } : null,
6595
8353
  outputs: Object.fromEntries((dependencyResults || /* @__PURE__ */ new Map()).entries()),
8354
+ output: currentRouteOutput,
6596
8355
  pr: {
6597
8356
  number: prInfo.number,
6598
8357
  title: prInfo.title,
@@ -6601,10 +8360,15 @@ var CheckExecutionEngine = class _CheckExecutionEngine {
6601
8360
  base: prInfo.base
6602
8361
  },
6603
8362
  files: prInfo.files,
6604
- env: getSafeEnvironmentVariables()
8363
+ env: getSafeEnvironmentVariables(),
8364
+ permissions: createPermissionHelpers(
8365
+ resolveAssociationFromEvent(prInfo.eventContext, prInfo.authorAssociation),
8366
+ detectLocalMode()
8367
+ ),
8368
+ event: eventObj
6605
8369
  };
6606
8370
  const code = `
6607
- const step = scope.step; const attempt = scope.attempt; const loop = scope.loop; const error = scope.error; const foreach = scope.foreach; const outputs = scope.outputs; const pr = scope.pr; const files = scope.files; const env = scope.env; const log = (...a)=>console.log('\u{1F50D} Debug:',...a);
8371
+ const step = scope.step; const attempt = scope.attempt; const loop = scope.loop; const error = scope.error; const foreach = scope.foreach; const outputs = scope.outputs; const output = scope.output; const pr = scope.pr; const files = scope.files; const env = scope.env; const event = scope.event; const log = (...a)=>console.log('\u{1F50D} Debug:',...a); const hasMinPermission = scope.permissions.hasMinPermission; const isOwner = scope.permissions.isOwner; const isMember = scope.permissions.isMember; const isCollaborator = scope.permissions.isCollaborator; const isContributor = scope.permissions.isContributor; const isFirstTimer = scope.permissions.isFirstTimer;
6608
8372
  const __fn = () => {
6609
8373
  ${expr}
6610
8374
  };
@@ -6628,6 +8392,7 @@ ${expr}
6628
8392
  if (!expr) return null;
6629
8393
  try {
6630
8394
  const sandbox = this.getRoutingSandbox();
8395
+ const eventObj = { name: prInfo.eventType || "manual" };
6631
8396
  const scope = {
6632
8397
  step: { id: checkName, tags: checkConfig.tags || [], group: checkConfig.group },
6633
8398
  attempt,
@@ -6639,6 +8404,7 @@ ${expr}
6639
8404
  parent: foreachContext.parent
6640
8405
  } : null,
6641
8406
  outputs: Object.fromEntries((dependencyResults || /* @__PURE__ */ new Map()).entries()),
8407
+ output: currentRouteOutput,
6642
8408
  pr: {
6643
8409
  number: prInfo.number,
6644
8410
  title: prInfo.title,
@@ -6647,10 +8413,15 @@ ${expr}
6647
8413
  base: prInfo.base
6648
8414
  },
6649
8415
  files: prInfo.files,
6650
- env: getSafeEnvironmentVariables()
8416
+ env: getSafeEnvironmentVariables(),
8417
+ permissions: createPermissionHelpers(
8418
+ resolveAssociationFromEvent(prInfo.eventContext, prInfo.authorAssociation),
8419
+ detectLocalMode()
8420
+ ),
8421
+ event: eventObj
6651
8422
  };
6652
8423
  const code = `
6653
- const step = scope.step; const attempt = scope.attempt; const loop = scope.loop; const error = scope.error; const foreach = scope.foreach; const outputs = scope.outputs; const pr = scope.pr; const files = scope.files; const env = scope.env; const log = (...a)=>console.log('\u{1F50D} Debug:',...a);
8424
+ const step = scope.step; const attempt = scope.attempt; const loop = scope.loop; const error = scope.error; const foreach = scope.foreach; const outputs = scope.outputs; const output = scope.output; const pr = scope.pr; const files = scope.files; const env = scope.env; const event = scope.event; const log = (...a)=>console.log('\u{1F50D} Debug:',...a); const hasMinPermission = scope.permissions.hasMinPermission; const isOwner = scope.permissions.isOwner; const isMember = scope.permissions.isMember; const isCollaborator = scope.permissions.isCollaborator; const isContributor = scope.permissions.isContributor; const isFirstTimer = scope.permissions.isFirstTimer;
6654
8425
  const __fn = () => {
6655
8426
  ${expr}
6656
8427
  };
@@ -6686,7 +8457,7 @@ ${expr}
6686
8457
  dfs(name);
6687
8458
  return Array.from(new Set(acc));
6688
8459
  };
6689
- const executeNamedCheckInline = async (target) => {
8460
+ const executeNamedCheckInline = async (target, opts) => {
6690
8461
  const targetCfg = config?.checks?.[target];
6691
8462
  if (!targetCfg) {
6692
8463
  throw new Error(`on_* referenced unknown check '${target}'`);
@@ -6710,6 +8481,10 @@ ${expr}
6710
8481
  const providerType = targetCfg.type || "ai";
6711
8482
  const prov = this.providerRegistry.getProviderOrThrow(providerType);
6712
8483
  this.setProviderWebhookContext(prov);
8484
+ const enrichedEventContext = {
8485
+ ...prInfo.eventContext,
8486
+ ...this.actionContext?.octokit ? { octokit: this.actionContext.octokit } : {}
8487
+ };
6713
8488
  const provCfg = {
6714
8489
  type: providerType,
6715
8490
  prompt: targetCfg.prompt,
@@ -6718,15 +8493,17 @@ ${expr}
6718
8493
  schema: targetCfg.schema,
6719
8494
  group: targetCfg.group,
6720
8495
  checkName: target,
6721
- eventContext: prInfo.eventContext,
8496
+ eventContext: enrichedEventContext,
6722
8497
  transform: targetCfg.transform,
6723
8498
  transform_js: targetCfg.transform_js,
6724
8499
  env: targetCfg.env,
6725
8500
  forEach: targetCfg.forEach,
8501
+ // Include provider-specific keys (e.g., op/values for github)
8502
+ ...targetCfg,
6726
8503
  ai: {
8504
+ ...targetCfg.ai || {},
6727
8505
  timeout: providerConfig.ai?.timeout || 6e5,
6728
- debug: !!debug,
6729
- ...targetCfg.ai || {}
8506
+ debug: !!debug
6730
8507
  }
6731
8508
  };
6732
8509
  const targetDeps = getAllDepsFromConfig(target);
@@ -6750,7 +8527,34 @@ ${expr}
6750
8527
  const execStr = provCfg.exec;
6751
8528
  if (execStr) log2(`\u{1F527} Debug: inline exec '${target}' command: ${execStr}`);
6752
8529
  }
6753
- const r = await prov.execute(prInfo, provCfg, depResults, sessionInfo);
8530
+ let prInfoForInline = prInfo;
8531
+ const prevEventOverride = this.routingEventOverride;
8532
+ if (opts?.eventOverride) {
8533
+ const elevated = await this.elevateContextToPullRequest(
8534
+ { ...prInfo, eventType: opts.eventOverride },
8535
+ opts.eventOverride,
8536
+ log2,
8537
+ debug
8538
+ );
8539
+ if (elevated) {
8540
+ prInfoForInline = elevated;
8541
+ } else {
8542
+ prInfoForInline = { ...prInfo, eventType: opts.eventOverride };
8543
+ }
8544
+ this.routingEventOverride = opts.eventOverride;
8545
+ const msg = `\u21AA goto_event: inline '${target}' with event=${opts.eventOverride}${elevated ? " (elevated to PR context)" : ""}`;
8546
+ if (debug) log2(`\u{1F527} Debug: ${msg}`);
8547
+ try {
8548
+ (init_logger(), __toCommonJS(logger_exports)).logger.info(msg);
8549
+ } catch {
8550
+ }
8551
+ }
8552
+ let r;
8553
+ try {
8554
+ r = await prov.execute(prInfoForInline, provCfg, depResults, sessionInfo);
8555
+ } finally {
8556
+ this.routingEventOverride = prevEventOverride;
8557
+ }
6754
8558
  const enrichedIssues = (r.issues || []).map((issue) => ({
6755
8559
  ...issue,
6756
8560
  checkName: target,
@@ -6767,7 +8571,18 @@ ${expr}
6767
8571
  };
6768
8572
  while (true) {
6769
8573
  try {
8574
+ try {
8575
+ emitNdjsonFallback("visor.provider", {
8576
+ "visor.check.id": checkName,
8577
+ "visor.provider.type": providerConfig.type || "ai"
8578
+ });
8579
+ } catch {
8580
+ }
6770
8581
  const res = await provider.execute(prInfo, providerConfig, dependencyResults, sessionInfo);
8582
+ try {
8583
+ currentRouteOutput = res?.output;
8584
+ } catch {
8585
+ }
6771
8586
  const hasSoftFailure = (res.issues || []).some(
6772
8587
  (i) => i.severity === "error" || i.severity === "critical"
6773
8588
  );
@@ -6786,6 +8601,12 @@ ${expr}
6786
8601
  runList = Array.from(new Set(runList));
6787
8602
  if (debug) log2(`\u{1F527} Debug: on_fail.run (soft) list = [${runList.join(", ")}]`);
6788
8603
  if (runList.length > 0) {
8604
+ try {
8605
+ (init_logger(), __toCommonJS(logger_exports)).logger.info(
8606
+ `\u25B6 on_fail.run: scheduling [${runList.join(", ")}] after '${checkName}'`
8607
+ );
8608
+ } catch {
8609
+ }
6789
8610
  loopCount++;
6790
8611
  if (loopCount > maxLoops) {
6791
8612
  throw new Error(
@@ -6801,6 +8622,12 @@ ${expr}
6801
8622
  if (!target && onFail.goto) target = onFail.goto;
6802
8623
  if (debug) log2(`\u{1F527} Debug: on_fail.goto (soft) target = ${target}`);
6803
8624
  if (target) {
8625
+ try {
8626
+ (init_logger(), __toCommonJS(logger_exports)).logger.info(
8627
+ `\u21AA on_fail.goto: jumping to '${target}' from '${checkName}'`
8628
+ );
8629
+ } catch {
8630
+ }
6804
8631
  if (!allAncestors.includes(target)) {
6805
8632
  if (debug)
6806
8633
  log2(
@@ -6813,7 +8640,7 @@ ${expr}
6813
8640
  `Routing loop budget exceeded (max_loops=${maxLoops}) during on_fail goto`
6814
8641
  );
6815
8642
  }
6816
- await executeNamedCheckInline(target);
8643
+ await executeNamedCheckInline(target, { eventOverride: onFail.goto_event });
6817
8644
  }
6818
8645
  }
6819
8646
  const retryMax = onFail.retry?.max ?? 0;
@@ -6836,10 +8663,17 @@ ${expr}
6836
8663
  return res;
6837
8664
  }
6838
8665
  let needRerun = false;
8666
+ let rerunEventOverride;
6839
8667
  if (onSuccess) {
6840
8668
  const dynamicRun = await evalRunJs(onSuccess.run_js);
6841
8669
  const runList = [...onSuccess.run || [], ...dynamicRun].filter(Boolean);
6842
8670
  if (runList.length > 0) {
8671
+ try {
8672
+ (init_logger(), __toCommonJS(logger_exports)).logger.info(
8673
+ `\u25B6 on_success.run: scheduling [${Array.from(new Set(runList)).join(", ")}] after '${checkName}'`
8674
+ );
8675
+ } catch {
8676
+ }
6843
8677
  loopCount++;
6844
8678
  if (loopCount > maxLoops) {
6845
8679
  throw new Error(
@@ -6849,15 +8683,106 @@ ${expr}
6849
8683
  for (const stepId of Array.from(new Set(runList))) {
6850
8684
  await executeNamedCheckInline(stepId);
6851
8685
  }
8686
+ } else {
8687
+ try {
8688
+ const assoc = resolveAssociationFromEvent(
8689
+ prInfo?.eventContext,
8690
+ prInfo.authorAssociation
8691
+ );
8692
+ const perms = createPermissionHelpers(assoc, detectLocalMode());
8693
+ const allowedMember = perms.hasMinPermission("MEMBER");
8694
+ let intent;
8695
+ try {
8696
+ intent = res?.output?.intent;
8697
+ } catch {
8698
+ }
8699
+ (init_logger(), __toCommonJS(logger_exports)).logger.info(
8700
+ `\u23ED on_success.run: none after '${checkName}' (event=${prInfo.eventType || "manual"}, intent=${intent || "n/a"}, assoc=${assoc || "unknown"}, memberOrHigher=${allowedMember})`
8701
+ );
8702
+ } catch {
8703
+ }
6852
8704
  }
6853
8705
  let target = await evalGotoJs(onSuccess.goto_js);
6854
8706
  if (!target && onSuccess.goto) target = onSuccess.goto;
6855
8707
  if (target) {
8708
+ try {
8709
+ (init_logger(), __toCommonJS(logger_exports)).logger.info(
8710
+ `\u21AA on_success.goto: jumping to '${target}' from '${checkName}'`
8711
+ );
8712
+ } catch {
8713
+ }
6856
8714
  if (!allAncestors.includes(target)) {
6857
- if (debug)
6858
- log2(
6859
- `\u26A0\uFE0F Debug: on_success.goto '${target}' is not an ancestor of '${checkName}' \u2014 skipping`
6860
- );
8715
+ const prevEventOverride2 = this.routingEventOverride;
8716
+ if (onSuccess.goto_event) {
8717
+ this.routingEventOverride = onSuccess.goto_event;
8718
+ }
8719
+ try {
8720
+ const cfgChecks = config?.checks || {};
8721
+ const forwardSet = /* @__PURE__ */ new Set();
8722
+ if (cfgChecks[target]) forwardSet.add(target);
8723
+ const dependsOn = (name, root) => {
8724
+ const seen = /* @__PURE__ */ new Set();
8725
+ const dfs = (n) => {
8726
+ if (seen.has(n)) return false;
8727
+ seen.add(n);
8728
+ const deps = cfgChecks[n]?.depends_on || [];
8729
+ if (deps.includes(root)) return true;
8730
+ return deps.some((d) => dfs(d));
8731
+ };
8732
+ return dfs(name);
8733
+ };
8734
+ const ev = onSuccess.goto_event || prInfo.eventType || "issue_comment";
8735
+ for (const name of Object.keys(cfgChecks)) {
8736
+ if (name === target) continue;
8737
+ const onArr = cfgChecks[name]?.on;
8738
+ const eventMatches = !onArr || Array.isArray(onArr) && onArr.includes(ev);
8739
+ if (!eventMatches) continue;
8740
+ if (dependsOn(name, target)) forwardSet.add(name);
8741
+ }
8742
+ const order = [];
8743
+ const inSet = (n) => forwardSet.has(n);
8744
+ const tempMarks = /* @__PURE__ */ new Set();
8745
+ const permMarks = /* @__PURE__ */ new Set();
8746
+ const stack = [];
8747
+ const visit = (n) => {
8748
+ if (permMarks.has(n)) return;
8749
+ if (tempMarks.has(n)) {
8750
+ const idx = stack.indexOf(n);
8751
+ const cyclePath = idx >= 0 ? [...stack.slice(idx), n] : [n];
8752
+ throw new Error(
8753
+ `Cycle detected in forward-run dependency subset: ${cyclePath.join(" -> ")}`
8754
+ );
8755
+ }
8756
+ tempMarks.add(n);
8757
+ stack.push(n);
8758
+ const deps = (cfgChecks[n]?.depends_on || []).filter(inSet);
8759
+ for (const d of deps) visit(d);
8760
+ stack.pop();
8761
+ tempMarks.delete(n);
8762
+ permMarks.add(n);
8763
+ order.push(n);
8764
+ };
8765
+ for (const n of forwardSet) visit(n);
8766
+ for (const stepId of order) {
8767
+ if (!this.executionStats.has(stepId)) this.initializeCheckStats(stepId);
8768
+ const childStart = this.recordIterationStart(stepId);
8769
+ const childRes = await executeNamedCheckInline(stepId, {
8770
+ eventOverride: onSuccess.goto_event
8771
+ });
8772
+ const childIssues = (childRes.issues || []).map((i) => ({ ...i }));
8773
+ const childSuccess = !this.hasFatal(childIssues);
8774
+ const childOutput = childRes?.output;
8775
+ this.recordIterationComplete(
8776
+ stepId,
8777
+ childStart,
8778
+ childSuccess,
8779
+ childIssues,
8780
+ childOutput
8781
+ );
8782
+ }
8783
+ } finally {
8784
+ this.routingEventOverride = prevEventOverride2;
8785
+ }
6861
8786
  } else {
6862
8787
  loopCount++;
6863
8788
  if (loopCount > maxLoops) {
@@ -6865,14 +8790,22 @@ ${expr}
6865
8790
  `Routing loop budget exceeded (max_loops=${maxLoops}) during on_success goto`
6866
8791
  );
6867
8792
  }
6868
- await executeNamedCheckInline(target);
8793
+ await executeNamedCheckInline(target, { eventOverride: onSuccess.goto_event });
6869
8794
  needRerun = true;
8795
+ rerunEventOverride = onSuccess.goto_event;
6870
8796
  }
6871
8797
  }
6872
8798
  }
6873
8799
  if (needRerun) {
6874
8800
  if (debug) log2(`\u{1F504} Debug: Re-running '${checkName}' after on_success.goto`);
8801
+ try {
8802
+ (init_logger(), __toCommonJS(logger_exports)).logger.info(`\u{1F501} Re-running '${checkName}' after goto`);
8803
+ } catch {
8804
+ }
8805
+ const prev = this.routingEventOverride;
8806
+ if (rerunEventOverride) this.routingEventOverride = rerunEventOverride;
6875
8807
  attempt++;
8808
+ this.routingEventOverride = prev;
6876
8809
  continue;
6877
8810
  }
6878
8811
  return res;
@@ -6885,6 +8818,12 @@ ${expr}
6885
8818
  let runList = [...onFail.run || [], ...dynamicRun].filter(Boolean);
6886
8819
  runList = Array.from(new Set(runList));
6887
8820
  if (runList.length > 0) {
8821
+ try {
8822
+ (init_logger(), __toCommonJS(logger_exports)).logger.info(
8823
+ `\u25B6 on_fail.run: scheduling [${runList.join(", ")}] after '${checkName}'`
8824
+ );
8825
+ } catch {
8826
+ }
6888
8827
  loopCount++;
6889
8828
  if (loopCount > maxLoops) {
6890
8829
  throw new Error(
@@ -6899,6 +8838,12 @@ ${expr}
6899
8838
  let target = await evalGotoJs(onFail.goto_js, lastError);
6900
8839
  if (!target && onFail.goto) target = onFail.goto;
6901
8840
  if (target) {
8841
+ try {
8842
+ (init_logger(), __toCommonJS(logger_exports)).logger.info(
8843
+ `\u21AA on_fail.goto: jumping to '${target}' from '${checkName}'`
8844
+ );
8845
+ } catch {
8846
+ }
6902
8847
  if (!allAncestors.includes(target)) {
6903
8848
  if (debug)
6904
8849
  log2(
@@ -6911,7 +8856,7 @@ ${expr}
6911
8856
  `Routing loop budget exceeded (max_loops=${maxLoops}) during on_fail goto`
6912
8857
  );
6913
8858
  }
6914
- await executeNamedCheckInline(target);
8859
+ await executeNamedCheckInline(target, { eventOverride: onFail.goto_event });
6915
8860
  }
6916
8861
  }
6917
8862
  const retryMax = onFail.retry?.max ?? 0;
@@ -6988,6 +8933,11 @@ ${expr}
6988
8933
  const startTime = Date.now();
6989
8934
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
6990
8935
  try {
8936
+ if (options.config?.memory) {
8937
+ const memoryStore = MemoryStore.getInstance(options.config.memory);
8938
+ await memoryStore.initialize();
8939
+ logger.debug("Memory store initialized");
8940
+ }
6991
8941
  this.webhookContext = options.webhookContext;
6992
8942
  const logFn = (msg) => logger.info(msg);
6993
8943
  if (options.githubChecks?.enabled && options.githubChecks.octokit) {
@@ -7284,6 +9234,21 @@ ${expr}
7284
9234
  );
7285
9235
  }
7286
9236
  checks = tagFilteredChecks;
9237
+ if (!this.actionContext) {
9238
+ try {
9239
+ const repoEnv = process.env.GITHUB_REPOSITORY || "";
9240
+ const [owner, repo] = repoEnv.split("/");
9241
+ const token = process.env["INPUT_GITHUB-TOKEN"] || process.env["GITHUB_TOKEN"];
9242
+ if (owner && repo) {
9243
+ this.actionContext = { owner, repo };
9244
+ if (token) {
9245
+ const { Octokit } = await import("@octokit/rest");
9246
+ this.actionContext.octokit = new Octokit({ auth: token });
9247
+ }
9248
+ }
9249
+ } catch {
9250
+ }
9251
+ }
7287
9252
  if (checks.length === 0) {
7288
9253
  logger.warn("\u26A0\uFE0F No checks remain after tag filtering");
7289
9254
  return {
@@ -7298,10 +9263,14 @@ ${expr}
7298
9263
  const checkConfig = config.checks[checkName];
7299
9264
  return checkConfig?.depends_on && checkConfig.depends_on.length > 0;
7300
9265
  });
7301
- if (checks.length > 1 || hasDependencies) {
9266
+ const hasRouting = checks.some((checkName) => {
9267
+ const c = config.checks[checkName];
9268
+ return Boolean(c?.on_success || c?.on_fail);
9269
+ });
9270
+ if (checks.length > 1 || hasDependencies || hasRouting) {
7302
9271
  if (debug) {
7303
9272
  logger.debug(
7304
- `\u{1F527} Debug: Using grouped dependency-aware execution for ${checks.length} checks (has dependencies: ${hasDependencies})`
9273
+ `\u{1F527} Debug: Using grouped dependency-aware execution for ${checks.length} checks (has dependencies: ${hasDependencies}, has routing: ${hasRouting})`
7305
9274
  );
7306
9275
  }
7307
9276
  return await this.executeGroupedDependencyAwareChecks(
@@ -7384,7 +9353,12 @@ ${expr}
7384
9353
  }
7385
9354
  }
7386
9355
  if (config && (config.fail_if || checkConfig.fail_if)) {
7387
- const failureResults = await this.evaluateFailureConditions(checkName, result, config);
9356
+ const failureResults = await this.evaluateFailureConditions(
9357
+ checkName,
9358
+ result,
9359
+ config,
9360
+ prInfo
9361
+ );
7388
9362
  if (failureResults.length > 0) {
7389
9363
  const failureIssues = failureResults.filter((f) => f.failed).map((f) => ({
7390
9364
  file: "system",
@@ -7402,6 +9376,7 @@ ${expr}
7402
9376
  checkName,
7403
9377
  content,
7404
9378
  group: checkConfig.group || "default",
9379
+ output: result.output,
7405
9380
  debug: result.debug,
7406
9381
  issues: result.issues
7407
9382
  // Include structured issues
@@ -7487,8 +9462,24 @@ ${expr}
7487
9462
  */
7488
9463
  async convertReviewSummaryToGroupedResults(reviewSummary, checks, config, prInfo) {
7489
9464
  const groupedResults = {};
7490
- const contentMap = reviewSummary.__contents;
7491
- for (const checkName of checks) {
9465
+ const agg = reviewSummary;
9466
+ const contentMap = agg.__contents;
9467
+ const outputMap = agg.__outputs;
9468
+ const allCheckNames = [];
9469
+ const seen = /* @__PURE__ */ new Set();
9470
+ const pushUnique = (n) => {
9471
+ if (!n) return;
9472
+ if (!seen.has(n)) {
9473
+ seen.add(n);
9474
+ allCheckNames.push(n);
9475
+ }
9476
+ };
9477
+ for (const n of checks) pushUnique(n);
9478
+ if (contentMap) for (const n of Object.keys(contentMap)) pushUnique(n);
9479
+ if (outputMap) for (const n of Object.keys(outputMap)) pushUnique(n);
9480
+ for (const issue of reviewSummary.issues || []) pushUnique(issue.checkName);
9481
+ if (Array.isArray(agg.__executed)) for (const n of agg.__executed) pushUnique(n);
9482
+ for (const checkName of allCheckNames) {
7492
9483
  const checkConfig = config?.checks?.[checkName];
7493
9484
  if (!checkConfig) continue;
7494
9485
  const checkIssues = (reviewSummary.issues || []).filter(
@@ -7499,17 +9490,38 @@ ${expr}
7499
9490
  debug: reviewSummary.debug
7500
9491
  };
7501
9492
  if (contentMap?.[checkName]) {
7502
- const summaryWithContent = checkSummary;
7503
- summaryWithContent.content = contentMap[checkName];
9493
+ checkSummary.content = contentMap[checkName];
9494
+ }
9495
+ if (outputMap && Object.prototype.hasOwnProperty.call(outputMap, checkName)) {
9496
+ checkSummary.output = outputMap[checkName];
9497
+ }
9498
+ let content = "";
9499
+ let issuesForCheck = [...checkIssues];
9500
+ try {
9501
+ content = await this.renderCheckContent(checkName, checkSummary, checkConfig, prInfo);
9502
+ } catch (e) {
9503
+ const msg = e instanceof Error ? e.message : String(e);
9504
+ console.error(`\u274C Failed to render content for check '${checkName}': ${msg}`);
9505
+ issuesForCheck = [
9506
+ ...issuesForCheck,
9507
+ {
9508
+ file: "system",
9509
+ line: 0,
9510
+ ruleId: `${checkName}/render-error`,
9511
+ message: `Template rendering failed: ${msg}`,
9512
+ severity: "error",
9513
+ category: "logic"
9514
+ }
9515
+ ];
7504
9516
  }
7505
- const content = await this.renderCheckContent(checkName, checkSummary, checkConfig, prInfo);
7506
9517
  const checkResult = {
7507
9518
  checkName,
7508
9519
  content,
7509
9520
  group: checkConfig.group || "default",
9521
+ output: checkSummary.output,
7510
9522
  debug: reviewSummary.debug,
7511
- issues: checkIssues
7512
- // Include structured issues
9523
+ issues: issuesForCheck
9524
+ // Include structured issues + rendering error if any
7513
9525
  };
7514
9526
  const group = checkResult.group;
7515
9527
  if (!groupedResults[group]) {
@@ -7575,13 +9587,20 @@ ${expr}
7575
9587
  * @returns true if the check should run, false if it should be skipped
7576
9588
  */
7577
9589
  async evaluateCheckCondition(checkName, condition, prInfo, results, debug) {
9590
+ const override = this.routingEventOverride;
9591
+ const eventName = override ? override.startsWith("pr_") ? "pull_request" : override === "issue_comment" ? "issue_comment" : override.startsWith("issue_") ? "issues" : "manual" : "issue_comment";
9592
+ const commenterAssoc = resolveAssociationFromEvent(
9593
+ prInfo?.eventContext,
9594
+ prInfo.authorAssociation
9595
+ );
7578
9596
  const shouldRun = await this.failureEvaluator.evaluateIfCondition(checkName, condition, {
7579
9597
  branch: prInfo.head,
7580
9598
  baseBranch: prInfo.base,
7581
9599
  filesChanged: prInfo.files.map((f) => f.filename),
7582
- event: "issue_comment",
9600
+ event: eventName,
7583
9601
  environment: getSafeEnvironmentVariables(),
7584
- previousResults: results
9602
+ previousResults: results,
9603
+ authorAssociation: commenterAssoc
7585
9604
  });
7586
9605
  if (!shouldRun && debug) {
7587
9606
  logger.debug(`\u{1F527} Debug: Skipping check '${checkName}' - if condition evaluated to false`);
@@ -7596,7 +9615,7 @@ ${expr}
7596
9615
  if (typeof directContent === "string" && directContent.trim()) {
7597
9616
  return directContent.trim();
7598
9617
  }
7599
- const { createExtendedLiquid: createExtendedLiquid2 } = await import("./liquid-extensions-KDECAJTV.mjs");
9618
+ const { createExtendedLiquid: createExtendedLiquid2 } = await import("./liquid-extensions-AFKRYROF.mjs");
7600
9619
  const fs5 = await import("fs/promises");
7601
9620
  const path5 = await import("path");
7602
9621
  const liquid = createExtendedLiquid2({
@@ -7615,6 +9634,7 @@ ${expr}
7615
9634
  schemaName = checkConfig.schema || "plain";
7616
9635
  }
7617
9636
  let templateContent;
9637
+ let enrichAssistantContext = false;
7618
9638
  if (checkConfig.template) {
7619
9639
  if (checkConfig.template.content) {
7620
9640
  templateContent = checkConfig.template.content;
@@ -7633,16 +9653,109 @@ ${expr}
7633
9653
  }
7634
9654
  const templatePath = path5.join(__dirname, `../output/${sanitizedSchema}/template.liquid`);
7635
9655
  templateContent = await fs5.readFile(templatePath, "utf-8");
9656
+ if (sanitizedSchema === "issue-assistant") {
9657
+ enrichAssistantContext = true;
9658
+ }
7636
9659
  }
7637
9660
  const filteredIssues = (reviewSummary.issues || []).filter(
7638
9661
  (issue) => !(issue.file === "system" && issue.line === 0)
7639
9662
  );
7640
9663
  const templateData = {
7641
9664
  issues: filteredIssues,
7642
- checkName
9665
+ checkName,
9666
+ // Expose structured output for custom schemas/templates (e.g., overview)
9667
+ // This allows templates to render fields like output.text or output.tags
9668
+ output: reviewSummary.output
7643
9669
  };
7644
- const rendered = await liquid.parseAndRender(templateContent, templateData);
7645
- return rendered.trim();
9670
+ if (enrichAssistantContext) {
9671
+ let authorAssociation;
9672
+ let eventName = "manual";
9673
+ let eventAction;
9674
+ try {
9675
+ const anyInfo = _prInfo;
9676
+ authorAssociation = resolveAssociationFromEvent(
9677
+ anyInfo?.eventContext,
9678
+ anyInfo?.authorAssociation
9679
+ );
9680
+ eventName = anyInfo?.eventContext?.event_name || anyInfo?.eventType || "manual";
9681
+ eventAction = anyInfo?.eventContext?.action;
9682
+ } catch {
9683
+ }
9684
+ templateData.authorAssociation = authorAssociation;
9685
+ templateData.event = { name: eventName, action: eventAction };
9686
+ }
9687
+ const { withPermissionsContext } = await import("./liquid-extensions-AFKRYROF.mjs");
9688
+ let authorAssociationForFilters;
9689
+ try {
9690
+ const anyInfo = _prInfo;
9691
+ authorAssociationForFilters = resolveAssociationFromEvent(
9692
+ anyInfo?.eventContext,
9693
+ anyInfo?.authorAssociation
9694
+ );
9695
+ } catch {
9696
+ }
9697
+ let rendered;
9698
+ if (typeof withPermissionsContext === "function") {
9699
+ rendered = await withPermissionsContext(
9700
+ { authorAssociation: authorAssociationForFilters },
9701
+ async () => await liquid.parseAndRender(templateContent, templateData)
9702
+ );
9703
+ if (rendered === void 0 || rendered === null) {
9704
+ rendered = await liquid.parseAndRender(templateContent, templateData);
9705
+ }
9706
+ } else {
9707
+ rendered = await liquid.parseAndRender(templateContent, templateData);
9708
+ }
9709
+ const finalRendered = rendered.trim();
9710
+ try {
9711
+ const { emitMermaidFromMarkdown } = await import("./mermaid-telemetry-LZGDD35I.mjs");
9712
+ emitMermaidFromMarkdown(checkName, finalRendered, "content");
9713
+ } catch {
9714
+ }
9715
+ return finalRendered;
9716
+ }
9717
+ /**
9718
+ * Attempt to elevate an issue/issue_comment context to full PR context when routing via goto_event.
9719
+ * Returns a new PRInfo with files/diff when possible; otherwise returns null.
9720
+ */
9721
+ async elevateContextToPullRequest(prInfo, targetEvent, log2, debug) {
9722
+ try {
9723
+ if (targetEvent !== "pr_opened" && targetEvent !== "pr_updated") return null;
9724
+ const isIssueContext = prInfo.isIssue === true;
9725
+ const ctx = prInfo.eventContext || {};
9726
+ const isPRThread = Boolean(ctx?.issue?.pull_request);
9727
+ if (!isIssueContext || !isPRThread) return null;
9728
+ let owner = this.actionContext?.owner;
9729
+ let repo = this.actionContext?.repo;
9730
+ if (!owner || !repo) {
9731
+ const repoEnv = process.env.GITHUB_REPOSITORY || "";
9732
+ [owner, repo] = repoEnv.split("/");
9733
+ }
9734
+ if (!owner || !repo) return null;
9735
+ const prNumber = ctx?.issue?.number || prInfo.number;
9736
+ if (!prNumber) return null;
9737
+ let octokit = this.actionContext?.octokit;
9738
+ if (!octokit) {
9739
+ const token = process.env["INPUT_GITHUB-TOKEN"] || process.env["GITHUB_TOKEN"];
9740
+ if (!token) return null;
9741
+ const { Octokit } = await import("@octokit/rest");
9742
+ octokit = new Octokit({ auth: token });
9743
+ }
9744
+ const analyzer = new PRAnalyzer(octokit);
9745
+ const elevated = await analyzer.fetchPRDiff(owner, repo, prNumber, void 0, targetEvent);
9746
+ elevated.eventContext = prInfo.eventContext || ctx;
9747
+ elevated.isPRContext = true;
9748
+ elevated.includeCodeContext = true;
9749
+ if (debug)
9750
+ log2?.(`\u{1F527} Debug: Elevated context to PR #${prNumber} for goto_event=${targetEvent}`);
9751
+ return elevated;
9752
+ } catch (e) {
9753
+ if (debug) {
9754
+ const msg = e instanceof Error ? e.message : String(e);
9755
+ log2?.(`\u26A0\uFE0F Debug: Context elevation to PR failed: ${msg}`);
9756
+ }
9757
+ return null;
9758
+ }
7646
9759
  }
7647
9760
  /**
7648
9761
  * Execute multiple checks with dependency awareness - intelligently parallel and sequential
@@ -7828,10 +9941,12 @@ ${expr}
7828
9941
  message: extendedCheckConfig.message,
7829
9942
  env: checkConfig.env,
7830
9943
  forEach: checkConfig.forEach,
9944
+ // Pass through any provider-specific keys (e.g., op/values for github provider)
9945
+ ...checkConfig,
7831
9946
  ai: {
9947
+ ...checkConfig.ai || {},
7832
9948
  timeout: timeout || 6e5,
7833
- debug,
7834
- ...checkConfig.ai || {}
9949
+ debug
7835
9950
  }
7836
9951
  };
7837
9952
  const dependencyResults = /* @__PURE__ */ new Map();
@@ -8008,12 +10123,21 @@ ${expr}
8008
10123
  transform_js: childCfg.transform_js,
8009
10124
  env: childCfg.env,
8010
10125
  forEach: childCfg.forEach,
10126
+ // Include provider-specific keys like op/values for non-AI providers
10127
+ ...childCfg,
8011
10128
  ai: {
10129
+ ...childCfg.ai || {},
8012
10130
  timeout: timeout || 6e5,
8013
- debug,
8014
- ...childCfg.ai || {}
10131
+ debug
8015
10132
  }
8016
10133
  };
10134
+ try {
10135
+ emitNdjsonSpanWithEvents("visor.check", { "visor.check.id": checkName }, [
10136
+ { name: "check.started" },
10137
+ { name: "check.completed" }
10138
+ ]);
10139
+ } catch {
10140
+ }
8017
10141
  const childDepResults = /* @__PURE__ */ new Map();
8018
10142
  const childAllDeps = DependencyResolver.getAllDependencies(
8019
10143
  childName,
@@ -8136,6 +10260,18 @@ ${expr}
8136
10260
  }
8137
10261
  };
8138
10262
  const itemTasks = forEachItems.map((item, itemIndex) => async () => {
10263
+ try {
10264
+ emitNdjsonSpanWithEvents(
10265
+ "visor.foreach.item",
10266
+ {
10267
+ "visor.check.id": checkName,
10268
+ "visor.foreach.index": itemIndex,
10269
+ "visor.foreach.total": forEachItems.length
10270
+ },
10271
+ []
10272
+ );
10273
+ } catch {
10274
+ }
8139
10275
  const forEachDependencyResults = /* @__PURE__ */ new Map();
8140
10276
  for (const [depName, depResult] of dependencyResults) {
8141
10277
  if (forEachParents.includes(depName)) {
@@ -8738,6 +10874,13 @@ ${expr}
8738
10874
  debug,
8739
10875
  results
8740
10876
  );
10877
+ try {
10878
+ emitNdjsonSpanWithEvents("visor.check", { "visor.check.id": checkName }, [
10879
+ { name: "check.started" },
10880
+ { name: "check.completed" }
10881
+ ]);
10882
+ } catch {
10883
+ }
8741
10884
  if (config && (config.fail_if || checkConfig.fail_if)) {
8742
10885
  const failureResults = await this.evaluateFailureConditions(
8743
10886
  checkName,
@@ -8905,6 +11048,13 @@ ${error.stack || ""}` : String(error);
8905
11048
  reviewSummaryWithOutput.forEachItems = normalizedOutput;
8906
11049
  reviewSummaryWithOutput.isForEach = true;
8907
11050
  }
11051
+ try {
11052
+ emitNdjsonSpanWithEvents("visor.check", { "visor.check.id": checkName }, [
11053
+ { name: "check.started" },
11054
+ { name: "check.completed" }
11055
+ ]);
11056
+ } catch {
11057
+ }
8908
11058
  results.set(checkName, reviewResult);
8909
11059
  } else {
8910
11060
  const errorSummary = {
@@ -9018,6 +11168,12 @@ ${error.stack || ""}` : String(error);
9018
11168
  `\u{1F527} Debug: Starting check: ${checkName} with prompt type: ${typeof checkConfig.prompt}`
9019
11169
  );
9020
11170
  if (checkConfig.if) {
11171
+ const override = this.routingEventOverride;
11172
+ const eventName = override ? override.startsWith("pr_") ? "pull_request" : override === "issue_comment" ? "issue_comment" : override.startsWith("issue_") ? "issues" : "manual" : "issue_comment";
11173
+ const commenterAssoc = resolveAssociationFromEvent(
11174
+ prInfo?.eventContext,
11175
+ prInfo.authorAssociation
11176
+ );
9021
11177
  const shouldRun = await this.failureEvaluator.evaluateIfCondition(
9022
11178
  checkName,
9023
11179
  checkConfig.if,
@@ -9025,11 +11181,12 @@ ${error.stack || ""}` : String(error);
9025
11181
  branch: prInfo.head,
9026
11182
  baseBranch: prInfo.base,
9027
11183
  filesChanged: prInfo.files.map((f) => f.filename),
9028
- event: "issue_comment",
9029
- // Command triggered from comment
11184
+ event: eventName,
11185
+ // honor routing override if present
9030
11186
  environment: getSafeEnvironmentVariables(),
9031
- previousResults: /* @__PURE__ */ new Map()
11187
+ previousResults: /* @__PURE__ */ new Map(),
9032
11188
  // No previous results in parallel execution
11189
+ authorAssociation: commenterAssoc
9033
11190
  }
9034
11191
  );
9035
11192
  if (!shouldRun) {
@@ -9171,6 +11328,7 @@ ${error.stack || ""}` : String(error);
9171
11328
  const aggregatedIssues = [];
9172
11329
  const debugInfo = [];
9173
11330
  const contentMap = {};
11331
+ const outputsMap = {};
9174
11332
  const stats = DependencyResolver.getExecutionStats(dependencyGraph);
9175
11333
  const executionInfo = [
9176
11334
  stoppedEarly ? `\u{1F6D1} Dependency-aware execution stopped early (fail-fast):` : `\u{1F50D} Dependency-aware execution completed:`,
@@ -9182,6 +11340,7 @@ ${error.stack || ""}` : String(error);
9182
11340
  stoppedEarly ? ` - Stopped early due to fail-fast behavior` : ``
9183
11341
  ].filter(Boolean);
9184
11342
  debugInfo.push(...executionInfo);
11343
+ const processed = /* @__PURE__ */ new Set();
9185
11344
  for (const executionGroup of dependencyGraph.executionOrder) {
9186
11345
  for (const checkName of executionGroup.parallel) {
9187
11346
  const result = results.get(checkName);
@@ -9199,6 +11358,7 @@ ${error.stack || ""}` : String(error);
9199
11358
  `\u2705 Check "${checkName}" completed: ${(result.issues || []).length} issues found (level ${executionGroup.level})`
9200
11359
  );
9201
11360
  }
11361
+ processed.add(checkName);
9202
11362
  const nonInternalIssues = (result.issues || []).filter(
9203
11363
  (issue) => !issue.ruleId?.endsWith("/__skipped")
9204
11364
  );
@@ -9208,7 +11368,29 @@ ${error.stack || ""}` : String(error);
9208
11368
  if (typeof resultContent === "string" && resultContent.trim()) {
9209
11369
  contentMap[checkName] = resultContent.trim();
9210
11370
  }
11371
+ if (resultSummary.output !== void 0) {
11372
+ outputsMap[checkName] = resultSummary.output;
11373
+ }
11374
+ }
11375
+ }
11376
+ for (const [checkName, result] of results.entries()) {
11377
+ if (processed.has(checkName)) continue;
11378
+ if (!result) continue;
11379
+ const nonInternalIssues = (result.issues || []).filter(
11380
+ (issue) => !issue.ruleId?.endsWith("/__skipped")
11381
+ );
11382
+ aggregatedIssues.push(...nonInternalIssues);
11383
+ const resultSummary = result;
11384
+ const resultContent = resultSummary.content;
11385
+ if (typeof resultContent === "string" && resultContent.trim()) {
11386
+ contentMap[checkName] = resultContent.trim();
11387
+ }
11388
+ if (resultSummary.output !== void 0) {
11389
+ outputsMap[checkName] = resultSummary.output;
9211
11390
  }
11391
+ debugInfo.push(
11392
+ `\u2705 (dynamic) Check "${checkName}" included: ${(result.issues || []).length} issues found`
11393
+ );
9212
11394
  }
9213
11395
  if (debug) {
9214
11396
  console.error(
@@ -9267,6 +11449,10 @@ ${result.debug.rawResponse}`).join("\n\n"),
9267
11449
  if (Object.keys(contentMap).length > 0) {
9268
11450
  summary.__contents = contentMap;
9269
11451
  }
11452
+ if (Object.keys(outputsMap).length > 0) {
11453
+ summary.__outputs = outputsMap;
11454
+ }
11455
+ summary.__executed = Array.from(results.keys());
9270
11456
  return summary;
9271
11457
  }
9272
11458
  /**
@@ -9590,7 +11776,7 @@ ${result.value.result.debug.rawResponse}`;
9590
11776
  /**
9591
11777
  * Evaluate failure conditions for a check result
9592
11778
  */
9593
- async evaluateFailureConditions(checkName, reviewSummary, config) {
11779
+ async evaluateFailureConditions(checkName, reviewSummary, config, prInfo) {
9594
11780
  if (!config) {
9595
11781
  return [];
9596
11782
  }
@@ -9609,7 +11795,50 @@ ${result.value.result.debug.rawResponse}`;
9609
11795
  reviewSummary,
9610
11796
  globalFailIf
9611
11797
  );
11798
+ try {
11799
+ addEvent("fail_if.evaluated", {
11800
+ check: checkName,
11801
+ scope: "global",
11802
+ name: "global_fail_if",
11803
+ expression: globalFailIf
11804
+ });
11805
+ } catch {
11806
+ }
9612
11807
  if (failed) {
11808
+ try {
11809
+ addEvent("fail_if.triggered", {
11810
+ check: checkName,
11811
+ scope: "global",
11812
+ name: "global_fail_if",
11813
+ expression: globalFailIf,
11814
+ severity: "error"
11815
+ });
11816
+ } catch {
11817
+ }
11818
+ try {
11819
+ addFailIfTriggered(checkName, "global");
11820
+ } catch {
11821
+ }
11822
+ try {
11823
+ const { emitNdjsonSpanWithEvents: emitNdjsonSpanWithEvents2 } = (init_fallback_ndjson(), __toCommonJS(fallback_ndjson_exports));
11824
+ emitNdjsonSpanWithEvents2(
11825
+ "visor.fail_if",
11826
+ { check: checkName, scope: "global", name: "global_fail_if" },
11827
+ [
11828
+ {
11829
+ name: "fail_if.triggered",
11830
+ attrs: {
11831
+ check: checkName,
11832
+ scope: "global",
11833
+ name: "global_fail_if",
11834
+ expression: globalFailIf,
11835
+ severity: "error"
11836
+ }
11837
+ }
11838
+ ]
11839
+ );
11840
+ } catch {
11841
+ }
9613
11842
  logger.warn(`\u26A0\uFE0F Check "${checkName}" - global fail_if condition met: ${globalFailIf}`);
9614
11843
  results.push({
9615
11844
  conditionName: "global_fail_if",
@@ -9631,7 +11860,78 @@ ${result.value.result.debug.rawResponse}`;
9631
11860
  reviewSummary,
9632
11861
  checkFailIf
9633
11862
  );
11863
+ try {
11864
+ addEvent("fail_if.evaluated", {
11865
+ check: checkName,
11866
+ scope: "check",
11867
+ name: `${checkName}_fail_if`,
11868
+ expression: checkFailIf
11869
+ });
11870
+ } catch {
11871
+ }
11872
+ try {
11873
+ const { emitNdjsonSpanWithEvents: emitNdjsonSpanWithEvents2 } = (init_fallback_ndjson(), __toCommonJS(fallback_ndjson_exports));
11874
+ emitNdjsonSpanWithEvents2(
11875
+ "visor.fail_if",
11876
+ { check: checkName, scope: "check", name: `${checkName}_fail_if` },
11877
+ [
11878
+ {
11879
+ name: "fail_if.evaluated",
11880
+ attrs: {
11881
+ check: checkName,
11882
+ scope: "check",
11883
+ name: `${checkName}_fail_if`,
11884
+ expression: checkFailIf
11885
+ }
11886
+ }
11887
+ ]
11888
+ );
11889
+ } catch {
11890
+ }
9634
11891
  if (failed) {
11892
+ try {
11893
+ addEvent("fail_if.triggered", {
11894
+ check: checkName,
11895
+ scope: "check",
11896
+ name: `${checkName}_fail_if`,
11897
+ expression: checkFailIf,
11898
+ severity: "error"
11899
+ });
11900
+ } catch {
11901
+ }
11902
+ try {
11903
+ addEvent("fail_if.evaluated", {
11904
+ check: checkName,
11905
+ scope: "check",
11906
+ name: `${checkName}_fail_if`,
11907
+ expression: checkFailIf
11908
+ });
11909
+ } catch {
11910
+ }
11911
+ try {
11912
+ addFailIfTriggered(checkName, "check");
11913
+ } catch {
11914
+ }
11915
+ try {
11916
+ const { emitNdjsonSpanWithEvents: emitNdjsonSpanWithEvents2 } = (init_fallback_ndjson(), __toCommonJS(fallback_ndjson_exports));
11917
+ emitNdjsonSpanWithEvents2(
11918
+ "visor.fail_if",
11919
+ { check: checkName, scope: "check", name: `${checkName}_fail_if` },
11920
+ [
11921
+ {
11922
+ name: "fail_if.triggered",
11923
+ attrs: {
11924
+ check: checkName,
11925
+ scope: "check",
11926
+ name: `${checkName}_fail_if`,
11927
+ expression: checkFailIf,
11928
+ severity: "error"
11929
+ }
11930
+ }
11931
+ ]
11932
+ );
11933
+ } catch {
11934
+ }
9635
11935
  logger.warn(`\u26A0\uFE0F Check "${checkName}" - fail_if condition met: ${checkFailIf}`);
9636
11936
  results.push({
9637
11937
  conditionName: `${checkName}_fail_if`,
@@ -9645,6 +11945,31 @@ ${result.value.result.debug.rawResponse}`;
9645
11945
  logger.debug(`\u2713 Check "${checkName}" - fail_if condition passed`);
9646
11946
  }
9647
11947
  }
11948
+ try {
11949
+ const { emitNdjsonSpanWithEvents: emitNdjsonSpanWithEvents2 } = (init_fallback_ndjson(), __toCommonJS(fallback_ndjson_exports));
11950
+ const hadTriggered = results.some((r) => r.failed === true);
11951
+ emitNdjsonSpanWithEvents2(
11952
+ "visor.fail_if",
11953
+ {
11954
+ check: checkName,
11955
+ scope: hadTriggered ? checkFailIf ? "check" : "global" : checkFailIf ? "check" : "global"
11956
+ },
11957
+ [
11958
+ {
11959
+ name: "fail_if.evaluated",
11960
+ attrs: { check: checkName, scope: checkFailIf ? "check" : "global" }
11961
+ }
11962
+ ].concat(
11963
+ hadTriggered ? [
11964
+ {
11965
+ name: "fail_if.triggered",
11966
+ attrs: { check: checkName, scope: checkFailIf ? "check" : "global" }
11967
+ }
11968
+ ] : []
11969
+ )
11970
+ );
11971
+ } catch {
11972
+ }
9648
11973
  return results;
9649
11974
  }
9650
11975
  const globalConditions = config.failure_conditions;
@@ -9655,7 +11980,10 @@ ${result.value.result.debug.rawResponse}`;
9655
11980
  checkGroup,
9656
11981
  reviewSummary,
9657
11982
  globalConditions,
9658
- checkConditions
11983
+ checkConditions,
11984
+ void 0,
11985
+ // previousOutputs
11986
+ prInfo?.authorAssociation
9659
11987
  );
9660
11988
  }
9661
11989
  /**
@@ -9778,6 +12106,7 @@ ${result.value.result.debug.rawResponse}`;
9778
12106
  { issues: checkIssues },
9779
12107
  options.config
9780
12108
  );
12109
+ const execErrorIssue = checkIssues.find((i) => i.ruleId?.startsWith("command/"));
9781
12110
  await this.githubCheckService.completeCheckRun(
9782
12111
  options.githubChecks.owner,
9783
12112
  options.githubChecks.repo,
@@ -9785,7 +12114,7 @@ ${result.value.result.debug.rawResponse}`;
9785
12114
  checkName,
9786
12115
  failureResults,
9787
12116
  checkIssues,
9788
- void 0,
12117
+ execErrorIssue ? execErrorIssue.message : void 0,
9789
12118
  // executionError
9790
12119
  prInfo.files.map((f) => f.filename),
9791
12120
  // filesChangedInCommit
@@ -10161,8 +12490,6 @@ ${result.value.result.debug.rawResponse}`;
10161
12490
  };
10162
12491
 
10163
12492
  export {
10164
- logger,
10165
- init_logger,
10166
12493
  CheckExecutionEngine
10167
12494
  };
10168
- //# sourceMappingURL=chunk-N2PPFOSF.mjs.map
12495
+ //# sourceMappingURL=chunk-LJHRU3WQ.mjs.map