@probelabs/visor 0.1.90 → 0.1.93

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 +133 -168
  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 +15 -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 +133 -168
  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 +173112 -46133
  50. package/dist/liquid-extensions.d.ts +15 -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 +42 -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-RORGGGGP.mjs +11 -0
  72. package/dist/sdk/chunk-I3GQJIR7.mjs +752 -0
  73. package/dist/sdk/chunk-I3GQJIR7.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-TWJKAYT6.mjs +1124 -0
  77. package/dist/sdk/chunk-TWJKAYT6.mjs.map +1 -0
  78. package/dist/sdk/{chunk-N2PPFOSF.mjs → chunk-Z47UECAT.mjs} +2593 -264
  79. package/dist/sdk/chunk-Z47UECAT.mjs.map +1 -0
  80. package/dist/sdk/liquid-extensions-GMEGEGC3.mjs +18 -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 +5372 -781
  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-15T11-54-04-087Z.ndjson +33 -0
  106. package/dist/traces/run-2025-10-15T11-54-14-046Z.ndjson +33 -0
  107. package/dist/traces/run-2025-10-15T11-54-14-575Z.ndjson +33 -0
  108. package/dist/traces/run-2025-10-15T11-54-15-082Z.ndjson +33 -0
  109. package/dist/traces/run-2025-10-15T11-54-15-561Z.ndjson +12 -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-RORGGGGP.mjs.map} +0 -0
  131. /package/dist/sdk/{liquid-extensions-KDECAJTV.mjs.map → liquid-extensions-GMEGEGC3.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-I3GQJIR7.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-RORGGGGP.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,27 +8263,50 @@ 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
  }
8285
+ /**
8286
+ * Enrich event context with authenticated octokit instance
8287
+ * @param eventContext - The event context to enrich
8288
+ * @returns Enriched event context with octokit if available
8289
+ */
8290
+ enrichEventContext(eventContext) {
8291
+ const baseContext = eventContext || {};
8292
+ if (this.actionContext?.octokit) {
8293
+ return { ...baseContext, octokit: this.actionContext.octokit };
8294
+ }
8295
+ return baseContext;
8296
+ }
6529
8297
  /**
6530
8298
  * Lazily create a secure sandbox for routing JS (goto_js, run_js)
6531
8299
  */
6532
8300
  getRoutingSandbox() {
6533
8301
  if (this.routingSandbox) return this.routingSandbox;
6534
8302
  const globals = {
6535
- ...Sandbox3.SAFE_GLOBALS,
8303
+ ...Sandbox5.SAFE_GLOBALS,
6536
8304
  Math,
6537
8305
  JSON,
6538
8306
  console: { log: console.log }
6539
8307
  };
6540
- const prototypeWhitelist = new Map(Sandbox3.SAFE_PROTOTYPES);
6541
- this.routingSandbox = new Sandbox3({ globals, prototypeWhitelist });
8308
+ const prototypeWhitelist = new Map(Sandbox5.SAFE_PROTOTYPES);
8309
+ this.routingSandbox = new Sandbox5({ globals, prototypeWhitelist });
6542
8310
  return this.routingSandbox;
6543
8311
  }
6544
8312
  redact(str, limit = 200) {
@@ -6578,10 +8346,12 @@ var CheckExecutionEngine = class _CheckExecutionEngine {
6578
8346
  let loopCount = 0;
6579
8347
  const seed = `${checkName}-${prInfo.number || "local"}`;
6580
8348
  const allAncestors = DependencyResolver.getAllDependencies(checkName, dependencyGraph.nodes);
8349
+ let currentRouteOutput = void 0;
6581
8350
  const evalRunJs = async (expr, error) => {
6582
8351
  if (!expr) return [];
6583
8352
  try {
6584
8353
  const sandbox = this.getRoutingSandbox();
8354
+ const eventObj = { name: prInfo.eventType || "manual" };
6585
8355
  const scope = {
6586
8356
  step: { id: checkName, tags: checkConfig.tags || [], group: checkConfig.group },
6587
8357
  attempt,
@@ -6593,6 +8363,7 @@ var CheckExecutionEngine = class _CheckExecutionEngine {
6593
8363
  parent: foreachContext.parent
6594
8364
  } : null,
6595
8365
  outputs: Object.fromEntries((dependencyResults || /* @__PURE__ */ new Map()).entries()),
8366
+ output: currentRouteOutput,
6596
8367
  pr: {
6597
8368
  number: prInfo.number,
6598
8369
  title: prInfo.title,
@@ -6601,10 +8372,15 @@ var CheckExecutionEngine = class _CheckExecutionEngine {
6601
8372
  base: prInfo.base
6602
8373
  },
6603
8374
  files: prInfo.files,
6604
- env: getSafeEnvironmentVariables()
8375
+ env: getSafeEnvironmentVariables(),
8376
+ permissions: createPermissionHelpers(
8377
+ resolveAssociationFromEvent(prInfo.eventContext, prInfo.authorAssociation),
8378
+ detectLocalMode()
8379
+ ),
8380
+ event: eventObj
6605
8381
  };
6606
8382
  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);
8383
+ 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
8384
  const __fn = () => {
6609
8385
  ${expr}
6610
8386
  };
@@ -6628,6 +8404,7 @@ ${expr}
6628
8404
  if (!expr) return null;
6629
8405
  try {
6630
8406
  const sandbox = this.getRoutingSandbox();
8407
+ const eventObj = { name: prInfo.eventType || "manual" };
6631
8408
  const scope = {
6632
8409
  step: { id: checkName, tags: checkConfig.tags || [], group: checkConfig.group },
6633
8410
  attempt,
@@ -6639,6 +8416,7 @@ ${expr}
6639
8416
  parent: foreachContext.parent
6640
8417
  } : null,
6641
8418
  outputs: Object.fromEntries((dependencyResults || /* @__PURE__ */ new Map()).entries()),
8419
+ output: currentRouteOutput,
6642
8420
  pr: {
6643
8421
  number: prInfo.number,
6644
8422
  title: prInfo.title,
@@ -6647,10 +8425,15 @@ ${expr}
6647
8425
  base: prInfo.base
6648
8426
  },
6649
8427
  files: prInfo.files,
6650
- env: getSafeEnvironmentVariables()
8428
+ env: getSafeEnvironmentVariables(),
8429
+ permissions: createPermissionHelpers(
8430
+ resolveAssociationFromEvent(prInfo.eventContext, prInfo.authorAssociation),
8431
+ detectLocalMode()
8432
+ ),
8433
+ event: eventObj
6651
8434
  };
6652
8435
  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);
8436
+ 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
8437
  const __fn = () => {
6655
8438
  ${expr}
6656
8439
  };
@@ -6686,7 +8469,7 @@ ${expr}
6686
8469
  dfs(name);
6687
8470
  return Array.from(new Set(acc));
6688
8471
  };
6689
- const executeNamedCheckInline = async (target) => {
8472
+ const executeNamedCheckInline = async (target, opts) => {
6690
8473
  const targetCfg = config?.checks?.[target];
6691
8474
  if (!targetCfg) {
6692
8475
  throw new Error(`on_* referenced unknown check '${target}'`);
@@ -6718,15 +8501,17 @@ ${expr}
6718
8501
  schema: targetCfg.schema,
6719
8502
  group: targetCfg.group,
6720
8503
  checkName: target,
6721
- eventContext: prInfo.eventContext,
8504
+ eventContext: this.enrichEventContext(prInfo.eventContext),
6722
8505
  transform: targetCfg.transform,
6723
8506
  transform_js: targetCfg.transform_js,
6724
8507
  env: targetCfg.env,
6725
8508
  forEach: targetCfg.forEach,
8509
+ // Include provider-specific keys (e.g., op/values for github)
8510
+ ...targetCfg,
6726
8511
  ai: {
8512
+ ...targetCfg.ai || {},
6727
8513
  timeout: providerConfig.ai?.timeout || 6e5,
6728
- debug: !!debug,
6729
- ...targetCfg.ai || {}
8514
+ debug: !!debug
6730
8515
  }
6731
8516
  };
6732
8517
  const targetDeps = getAllDepsFromConfig(target);
@@ -6750,7 +8535,34 @@ ${expr}
6750
8535
  const execStr = provCfg.exec;
6751
8536
  if (execStr) log2(`\u{1F527} Debug: inline exec '${target}' command: ${execStr}`);
6752
8537
  }
6753
- const r = await prov.execute(prInfo, provCfg, depResults, sessionInfo);
8538
+ let prInfoForInline = prInfo;
8539
+ const prevEventOverride = this.routingEventOverride;
8540
+ if (opts?.eventOverride) {
8541
+ const elevated = await this.elevateContextToPullRequest(
8542
+ { ...prInfo, eventType: opts.eventOverride },
8543
+ opts.eventOverride,
8544
+ log2,
8545
+ debug
8546
+ );
8547
+ if (elevated) {
8548
+ prInfoForInline = elevated;
8549
+ } else {
8550
+ prInfoForInline = { ...prInfo, eventType: opts.eventOverride };
8551
+ }
8552
+ this.routingEventOverride = opts.eventOverride;
8553
+ const msg = `\u21AA goto_event: inline '${target}' with event=${opts.eventOverride}${elevated ? " (elevated to PR context)" : ""}`;
8554
+ if (debug) log2(`\u{1F527} Debug: ${msg}`);
8555
+ try {
8556
+ (init_logger(), __toCommonJS(logger_exports)).logger.info(msg);
8557
+ } catch {
8558
+ }
8559
+ }
8560
+ let r;
8561
+ try {
8562
+ r = await prov.execute(prInfoForInline, provCfg, depResults, sessionInfo);
8563
+ } finally {
8564
+ this.routingEventOverride = prevEventOverride;
8565
+ }
6754
8566
  const enrichedIssues = (r.issues || []).map((issue) => ({
6755
8567
  ...issue,
6756
8568
  checkName: target,
@@ -6767,7 +8579,18 @@ ${expr}
6767
8579
  };
6768
8580
  while (true) {
6769
8581
  try {
8582
+ try {
8583
+ emitNdjsonFallback("visor.provider", {
8584
+ "visor.check.id": checkName,
8585
+ "visor.provider.type": providerConfig.type || "ai"
8586
+ });
8587
+ } catch {
8588
+ }
6770
8589
  const res = await provider.execute(prInfo, providerConfig, dependencyResults, sessionInfo);
8590
+ try {
8591
+ currentRouteOutput = res?.output;
8592
+ } catch {
8593
+ }
6771
8594
  const hasSoftFailure = (res.issues || []).some(
6772
8595
  (i) => i.severity === "error" || i.severity === "critical"
6773
8596
  );
@@ -6786,6 +8609,12 @@ ${expr}
6786
8609
  runList = Array.from(new Set(runList));
6787
8610
  if (debug) log2(`\u{1F527} Debug: on_fail.run (soft) list = [${runList.join(", ")}]`);
6788
8611
  if (runList.length > 0) {
8612
+ try {
8613
+ (init_logger(), __toCommonJS(logger_exports)).logger.info(
8614
+ `\u25B6 on_fail.run: scheduling [${runList.join(", ")}] after '${checkName}'`
8615
+ );
8616
+ } catch {
8617
+ }
6789
8618
  loopCount++;
6790
8619
  if (loopCount > maxLoops) {
6791
8620
  throw new Error(
@@ -6801,6 +8630,12 @@ ${expr}
6801
8630
  if (!target && onFail.goto) target = onFail.goto;
6802
8631
  if (debug) log2(`\u{1F527} Debug: on_fail.goto (soft) target = ${target}`);
6803
8632
  if (target) {
8633
+ try {
8634
+ (init_logger(), __toCommonJS(logger_exports)).logger.info(
8635
+ `\u21AA on_fail.goto: jumping to '${target}' from '${checkName}'`
8636
+ );
8637
+ } catch {
8638
+ }
6804
8639
  if (!allAncestors.includes(target)) {
6805
8640
  if (debug)
6806
8641
  log2(
@@ -6813,7 +8648,7 @@ ${expr}
6813
8648
  `Routing loop budget exceeded (max_loops=${maxLoops}) during on_fail goto`
6814
8649
  );
6815
8650
  }
6816
- await executeNamedCheckInline(target);
8651
+ await executeNamedCheckInline(target, { eventOverride: onFail.goto_event });
6817
8652
  }
6818
8653
  }
6819
8654
  const retryMax = onFail.retry?.max ?? 0;
@@ -6836,10 +8671,17 @@ ${expr}
6836
8671
  return res;
6837
8672
  }
6838
8673
  let needRerun = false;
8674
+ let rerunEventOverride;
6839
8675
  if (onSuccess) {
6840
8676
  const dynamicRun = await evalRunJs(onSuccess.run_js);
6841
8677
  const runList = [...onSuccess.run || [], ...dynamicRun].filter(Boolean);
6842
8678
  if (runList.length > 0) {
8679
+ try {
8680
+ (init_logger(), __toCommonJS(logger_exports)).logger.info(
8681
+ `\u25B6 on_success.run: scheduling [${Array.from(new Set(runList)).join(", ")}] after '${checkName}'`
8682
+ );
8683
+ } catch {
8684
+ }
6843
8685
  loopCount++;
6844
8686
  if (loopCount > maxLoops) {
6845
8687
  throw new Error(
@@ -6849,15 +8691,106 @@ ${expr}
6849
8691
  for (const stepId of Array.from(new Set(runList))) {
6850
8692
  await executeNamedCheckInline(stepId);
6851
8693
  }
8694
+ } else {
8695
+ try {
8696
+ const assoc = resolveAssociationFromEvent(
8697
+ prInfo?.eventContext,
8698
+ prInfo.authorAssociation
8699
+ );
8700
+ const perms = createPermissionHelpers(assoc, detectLocalMode());
8701
+ const allowedMember = perms.hasMinPermission("MEMBER");
8702
+ let intent;
8703
+ try {
8704
+ intent = res?.output?.intent;
8705
+ } catch {
8706
+ }
8707
+ (init_logger(), __toCommonJS(logger_exports)).logger.info(
8708
+ `\u23ED on_success.run: none after '${checkName}' (event=${prInfo.eventType || "manual"}, intent=${intent || "n/a"}, assoc=${assoc || "unknown"}, memberOrHigher=${allowedMember})`
8709
+ );
8710
+ } catch {
8711
+ }
6852
8712
  }
6853
8713
  let target = await evalGotoJs(onSuccess.goto_js);
6854
8714
  if (!target && onSuccess.goto) target = onSuccess.goto;
6855
8715
  if (target) {
8716
+ try {
8717
+ (init_logger(), __toCommonJS(logger_exports)).logger.info(
8718
+ `\u21AA on_success.goto: jumping to '${target}' from '${checkName}'`
8719
+ );
8720
+ } catch {
8721
+ }
6856
8722
  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
- );
8723
+ const prevEventOverride2 = this.routingEventOverride;
8724
+ if (onSuccess.goto_event) {
8725
+ this.routingEventOverride = onSuccess.goto_event;
8726
+ }
8727
+ try {
8728
+ const cfgChecks = config?.checks || {};
8729
+ const forwardSet = /* @__PURE__ */ new Set();
8730
+ if (cfgChecks[target]) forwardSet.add(target);
8731
+ const dependsOn = (name, root) => {
8732
+ const seen = /* @__PURE__ */ new Set();
8733
+ const dfs = (n) => {
8734
+ if (seen.has(n)) return false;
8735
+ seen.add(n);
8736
+ const deps = cfgChecks[n]?.depends_on || [];
8737
+ if (deps.includes(root)) return true;
8738
+ return deps.some((d) => dfs(d));
8739
+ };
8740
+ return dfs(name);
8741
+ };
8742
+ const ev = onSuccess.goto_event || prInfo.eventType || "issue_comment";
8743
+ for (const name of Object.keys(cfgChecks)) {
8744
+ if (name === target) continue;
8745
+ const onArr = cfgChecks[name]?.on;
8746
+ const eventMatches = !onArr || Array.isArray(onArr) && onArr.includes(ev);
8747
+ if (!eventMatches) continue;
8748
+ if (dependsOn(name, target)) forwardSet.add(name);
8749
+ }
8750
+ const order = [];
8751
+ const inSet = (n) => forwardSet.has(n);
8752
+ const tempMarks = /* @__PURE__ */ new Set();
8753
+ const permMarks = /* @__PURE__ */ new Set();
8754
+ const stack = [];
8755
+ const visit = (n) => {
8756
+ if (permMarks.has(n)) return;
8757
+ if (tempMarks.has(n)) {
8758
+ const idx = stack.indexOf(n);
8759
+ const cyclePath = idx >= 0 ? [...stack.slice(idx), n] : [n];
8760
+ throw new Error(
8761
+ `Cycle detected in forward-run dependency subset: ${cyclePath.join(" -> ")}`
8762
+ );
8763
+ }
8764
+ tempMarks.add(n);
8765
+ stack.push(n);
8766
+ const deps = (cfgChecks[n]?.depends_on || []).filter(inSet);
8767
+ for (const d of deps) visit(d);
8768
+ stack.pop();
8769
+ tempMarks.delete(n);
8770
+ permMarks.add(n);
8771
+ order.push(n);
8772
+ };
8773
+ for (const n of forwardSet) visit(n);
8774
+ for (const stepId of order) {
8775
+ if (!this.executionStats.has(stepId)) this.initializeCheckStats(stepId);
8776
+ const childStart = this.recordIterationStart(stepId);
8777
+ const childRes = await executeNamedCheckInline(stepId, {
8778
+ eventOverride: onSuccess.goto_event
8779
+ });
8780
+ const childIssues = (childRes.issues || []).map((i) => ({ ...i }));
8781
+ const childSuccess = !this.hasFatal(childIssues);
8782
+ const childOutput = childRes?.output;
8783
+ this.recordIterationComplete(
8784
+ stepId,
8785
+ childStart,
8786
+ childSuccess,
8787
+ childIssues,
8788
+ childOutput
8789
+ );
8790
+ }
8791
+ } finally {
8792
+ this.routingEventOverride = prevEventOverride2;
8793
+ }
6861
8794
  } else {
6862
8795
  loopCount++;
6863
8796
  if (loopCount > maxLoops) {
@@ -6865,14 +8798,22 @@ ${expr}
6865
8798
  `Routing loop budget exceeded (max_loops=${maxLoops}) during on_success goto`
6866
8799
  );
6867
8800
  }
6868
- await executeNamedCheckInline(target);
8801
+ await executeNamedCheckInline(target, { eventOverride: onSuccess.goto_event });
6869
8802
  needRerun = true;
8803
+ rerunEventOverride = onSuccess.goto_event;
6870
8804
  }
6871
8805
  }
6872
8806
  }
6873
8807
  if (needRerun) {
6874
8808
  if (debug) log2(`\u{1F504} Debug: Re-running '${checkName}' after on_success.goto`);
8809
+ try {
8810
+ (init_logger(), __toCommonJS(logger_exports)).logger.info(`\u{1F501} Re-running '${checkName}' after goto`);
8811
+ } catch {
8812
+ }
8813
+ const prev = this.routingEventOverride;
8814
+ if (rerunEventOverride) this.routingEventOverride = rerunEventOverride;
6875
8815
  attempt++;
8816
+ this.routingEventOverride = prev;
6876
8817
  continue;
6877
8818
  }
6878
8819
  return res;
@@ -6885,6 +8826,12 @@ ${expr}
6885
8826
  let runList = [...onFail.run || [], ...dynamicRun].filter(Boolean);
6886
8827
  runList = Array.from(new Set(runList));
6887
8828
  if (runList.length > 0) {
8829
+ try {
8830
+ (init_logger(), __toCommonJS(logger_exports)).logger.info(
8831
+ `\u25B6 on_fail.run: scheduling [${runList.join(", ")}] after '${checkName}'`
8832
+ );
8833
+ } catch {
8834
+ }
6888
8835
  loopCount++;
6889
8836
  if (loopCount > maxLoops) {
6890
8837
  throw new Error(
@@ -6899,6 +8846,12 @@ ${expr}
6899
8846
  let target = await evalGotoJs(onFail.goto_js, lastError);
6900
8847
  if (!target && onFail.goto) target = onFail.goto;
6901
8848
  if (target) {
8849
+ try {
8850
+ (init_logger(), __toCommonJS(logger_exports)).logger.info(
8851
+ `\u21AA on_fail.goto: jumping to '${target}' from '${checkName}'`
8852
+ );
8853
+ } catch {
8854
+ }
6902
8855
  if (!allAncestors.includes(target)) {
6903
8856
  if (debug)
6904
8857
  log2(
@@ -6911,7 +8864,7 @@ ${expr}
6911
8864
  `Routing loop budget exceeded (max_loops=${maxLoops}) during on_fail goto`
6912
8865
  );
6913
8866
  }
6914
- await executeNamedCheckInline(target);
8867
+ await executeNamedCheckInline(target, { eventOverride: onFail.goto_event });
6915
8868
  }
6916
8869
  }
6917
8870
  const retryMax = onFail.retry?.max ?? 0;
@@ -6988,6 +8941,11 @@ ${expr}
6988
8941
  const startTime = Date.now();
6989
8942
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
6990
8943
  try {
8944
+ if (options.config?.memory) {
8945
+ const memoryStore = MemoryStore.getInstance(options.config.memory);
8946
+ await memoryStore.initialize();
8947
+ logger.debug("Memory store initialized");
8948
+ }
6991
8949
  this.webhookContext = options.webhookContext;
6992
8950
  const logFn = (msg) => logger.info(msg);
6993
8951
  if (options.githubChecks?.enabled && options.githubChecks.octokit) {
@@ -7184,8 +9142,7 @@ ${expr}
7184
9142
  const providerConfig = {
7185
9143
  type: checks[0],
7186
9144
  prompt: "all",
7187
- eventContext: prInfo.eventContext,
7188
- // Pass event context for templates
9145
+ eventContext: this.enrichEventContext(prInfo.eventContext),
7189
9146
  ai: timeout ? { timeout } : void 0
7190
9147
  };
7191
9148
  const result = await provider.execute(prInfo, providerConfig);
@@ -7219,8 +9176,7 @@ ${expr}
7219
9176
  type: "ai",
7220
9177
  prompt: focus2,
7221
9178
  focus: focus2,
7222
- eventContext: prInfo.eventContext,
7223
- // Pass event context for templates
9179
+ eventContext: this.enrichEventContext(prInfo.eventContext),
7224
9180
  ai: timeout ? { timeout } : void 0,
7225
9181
  // Inherit global AI provider and model settings if config is available
7226
9182
  ai_provider: config?.ai_provider,
@@ -7284,6 +9240,21 @@ ${expr}
7284
9240
  );
7285
9241
  }
7286
9242
  checks = tagFilteredChecks;
9243
+ if (!this.actionContext) {
9244
+ try {
9245
+ const repoEnv = process.env.GITHUB_REPOSITORY || "";
9246
+ const [owner, repo] = repoEnv.split("/");
9247
+ const token = process.env["INPUT_GITHUB-TOKEN"] || process.env["GITHUB_TOKEN"];
9248
+ if (owner && repo) {
9249
+ this.actionContext = { owner, repo };
9250
+ if (token) {
9251
+ const { Octokit } = await import("@octokit/rest");
9252
+ this.actionContext.octokit = new Octokit({ auth: token });
9253
+ }
9254
+ }
9255
+ } catch {
9256
+ }
9257
+ }
7287
9258
  if (checks.length === 0) {
7288
9259
  logger.warn("\u26A0\uFE0F No checks remain after tag filtering");
7289
9260
  return {
@@ -7298,10 +9269,14 @@ ${expr}
7298
9269
  const checkConfig = config.checks[checkName];
7299
9270
  return checkConfig?.depends_on && checkConfig.depends_on.length > 0;
7300
9271
  });
7301
- if (checks.length > 1 || hasDependencies) {
9272
+ const hasRouting = checks.some((checkName) => {
9273
+ const c = config.checks[checkName];
9274
+ return Boolean(c?.on_success || c?.on_fail);
9275
+ });
9276
+ if (checks.length > 1 || hasDependencies || hasRouting) {
7302
9277
  if (debug) {
7303
9278
  logger.debug(
7304
- `\u{1F527} Debug: Using grouped dependency-aware execution for ${checks.length} checks (has dependencies: ${hasDependencies})`
9279
+ `\u{1F527} Debug: Using grouped dependency-aware execution for ${checks.length} checks (has dependencies: ${hasDependencies}, has routing: ${hasRouting})`
7305
9280
  );
7306
9281
  }
7307
9282
  return await this.executeGroupedDependencyAwareChecks(
@@ -7356,8 +9331,7 @@ ${expr}
7356
9331
  focus: checkConfig.focus || this.mapCheckNameToFocus(checkName),
7357
9332
  schema: checkConfig.schema,
7358
9333
  group: checkConfig.group,
7359
- eventContext: prInfo.eventContext,
7360
- // Pass event context for templates
9334
+ eventContext: this.enrichEventContext(prInfo.eventContext),
7361
9335
  ai: {
7362
9336
  timeout: timeout || 6e5,
7363
9337
  debug,
@@ -7384,7 +9358,12 @@ ${expr}
7384
9358
  }
7385
9359
  }
7386
9360
  if (config && (config.fail_if || checkConfig.fail_if)) {
7387
- const failureResults = await this.evaluateFailureConditions(checkName, result, config);
9361
+ const failureResults = await this.evaluateFailureConditions(
9362
+ checkName,
9363
+ result,
9364
+ config,
9365
+ prInfo
9366
+ );
7388
9367
  if (failureResults.length > 0) {
7389
9368
  const failureIssues = failureResults.filter((f) => f.failed).map((f) => ({
7390
9369
  file: "system",
@@ -7402,6 +9381,7 @@ ${expr}
7402
9381
  checkName,
7403
9382
  content,
7404
9383
  group: checkConfig.group || "default",
9384
+ output: result.output,
7405
9385
  debug: result.debug,
7406
9386
  issues: result.issues
7407
9387
  // Include structured issues
@@ -7487,8 +9467,24 @@ ${expr}
7487
9467
  */
7488
9468
  async convertReviewSummaryToGroupedResults(reviewSummary, checks, config, prInfo) {
7489
9469
  const groupedResults = {};
7490
- const contentMap = reviewSummary.__contents;
7491
- for (const checkName of checks) {
9470
+ const agg = reviewSummary;
9471
+ const contentMap = agg.__contents;
9472
+ const outputMap = agg.__outputs;
9473
+ const allCheckNames = [];
9474
+ const seen = /* @__PURE__ */ new Set();
9475
+ const pushUnique = (n) => {
9476
+ if (!n) return;
9477
+ if (!seen.has(n)) {
9478
+ seen.add(n);
9479
+ allCheckNames.push(n);
9480
+ }
9481
+ };
9482
+ for (const n of checks) pushUnique(n);
9483
+ if (contentMap) for (const n of Object.keys(contentMap)) pushUnique(n);
9484
+ if (outputMap) for (const n of Object.keys(outputMap)) pushUnique(n);
9485
+ for (const issue of reviewSummary.issues || []) pushUnique(issue.checkName);
9486
+ if (Array.isArray(agg.__executed)) for (const n of agg.__executed) pushUnique(n);
9487
+ for (const checkName of allCheckNames) {
7492
9488
  const checkConfig = config?.checks?.[checkName];
7493
9489
  if (!checkConfig) continue;
7494
9490
  const checkIssues = (reviewSummary.issues || []).filter(
@@ -7499,17 +9495,38 @@ ${expr}
7499
9495
  debug: reviewSummary.debug
7500
9496
  };
7501
9497
  if (contentMap?.[checkName]) {
7502
- const summaryWithContent = checkSummary;
7503
- summaryWithContent.content = contentMap[checkName];
9498
+ checkSummary.content = contentMap[checkName];
9499
+ }
9500
+ if (outputMap && Object.prototype.hasOwnProperty.call(outputMap, checkName)) {
9501
+ checkSummary.output = outputMap[checkName];
9502
+ }
9503
+ let content = "";
9504
+ let issuesForCheck = [...checkIssues];
9505
+ try {
9506
+ content = await this.renderCheckContent(checkName, checkSummary, checkConfig, prInfo);
9507
+ } catch (e) {
9508
+ const msg = e instanceof Error ? e.message : String(e);
9509
+ console.error(`\u274C Failed to render content for check '${checkName}': ${msg}`);
9510
+ issuesForCheck = [
9511
+ ...issuesForCheck,
9512
+ {
9513
+ file: "system",
9514
+ line: 0,
9515
+ ruleId: `${checkName}/render-error`,
9516
+ message: `Template rendering failed: ${msg}`,
9517
+ severity: "error",
9518
+ category: "logic"
9519
+ }
9520
+ ];
7504
9521
  }
7505
- const content = await this.renderCheckContent(checkName, checkSummary, checkConfig, prInfo);
7506
9522
  const checkResult = {
7507
9523
  checkName,
7508
9524
  content,
7509
9525
  group: checkConfig.group || "default",
9526
+ output: checkSummary.output,
7510
9527
  debug: reviewSummary.debug,
7511
- issues: checkIssues
7512
- // Include structured issues
9528
+ issues: issuesForCheck
9529
+ // Include structured issues + rendering error if any
7513
9530
  };
7514
9531
  const group = checkResult.group;
7515
9532
  if (!groupedResults[group]) {
@@ -7575,13 +9592,20 @@ ${expr}
7575
9592
  * @returns true if the check should run, false if it should be skipped
7576
9593
  */
7577
9594
  async evaluateCheckCondition(checkName, condition, prInfo, results, debug) {
9595
+ const override = this.routingEventOverride;
9596
+ const eventName = override ? override.startsWith("pr_") ? "pull_request" : override === "issue_comment" ? "issue_comment" : override.startsWith("issue_") ? "issues" : "manual" : "issue_comment";
9597
+ const commenterAssoc = resolveAssociationFromEvent(
9598
+ prInfo?.eventContext,
9599
+ prInfo.authorAssociation
9600
+ );
7578
9601
  const shouldRun = await this.failureEvaluator.evaluateIfCondition(checkName, condition, {
7579
9602
  branch: prInfo.head,
7580
9603
  baseBranch: prInfo.base,
7581
9604
  filesChanged: prInfo.files.map((f) => f.filename),
7582
- event: "issue_comment",
9605
+ event: eventName,
7583
9606
  environment: getSafeEnvironmentVariables(),
7584
- previousResults: results
9607
+ previousResults: results,
9608
+ authorAssociation: commenterAssoc
7585
9609
  });
7586
9610
  if (!shouldRun && debug) {
7587
9611
  logger.debug(`\u{1F527} Debug: Skipping check '${checkName}' - if condition evaluated to false`);
@@ -7596,7 +9620,7 @@ ${expr}
7596
9620
  if (typeof directContent === "string" && directContent.trim()) {
7597
9621
  return directContent.trim();
7598
9622
  }
7599
- const { createExtendedLiquid: createExtendedLiquid2 } = await import("./liquid-extensions-KDECAJTV.mjs");
9623
+ const { createExtendedLiquid: createExtendedLiquid2 } = await import("./liquid-extensions-GMEGEGC3.mjs");
7600
9624
  const fs5 = await import("fs/promises");
7601
9625
  const path5 = await import("path");
7602
9626
  const liquid = createExtendedLiquid2({
@@ -7615,6 +9639,7 @@ ${expr}
7615
9639
  schemaName = checkConfig.schema || "plain";
7616
9640
  }
7617
9641
  let templateContent;
9642
+ let enrichAssistantContext = false;
7618
9643
  if (checkConfig.template) {
7619
9644
  if (checkConfig.template.content) {
7620
9645
  templateContent = checkConfig.template.content;
@@ -7633,16 +9658,109 @@ ${expr}
7633
9658
  }
7634
9659
  const templatePath = path5.join(__dirname, `../output/${sanitizedSchema}/template.liquid`);
7635
9660
  templateContent = await fs5.readFile(templatePath, "utf-8");
9661
+ if (sanitizedSchema === "issue-assistant") {
9662
+ enrichAssistantContext = true;
9663
+ }
7636
9664
  }
7637
9665
  const filteredIssues = (reviewSummary.issues || []).filter(
7638
9666
  (issue) => !(issue.file === "system" && issue.line === 0)
7639
9667
  );
7640
9668
  const templateData = {
7641
9669
  issues: filteredIssues,
7642
- checkName
9670
+ checkName,
9671
+ // Expose structured output for custom schemas/templates (e.g., overview)
9672
+ // This allows templates to render fields like output.text or output.tags
9673
+ output: reviewSummary.output
7643
9674
  };
7644
- const rendered = await liquid.parseAndRender(templateContent, templateData);
7645
- return rendered.trim();
9675
+ if (enrichAssistantContext) {
9676
+ let authorAssociation;
9677
+ let eventName = "manual";
9678
+ let eventAction;
9679
+ try {
9680
+ const anyInfo = _prInfo;
9681
+ authorAssociation = resolveAssociationFromEvent(
9682
+ anyInfo?.eventContext,
9683
+ anyInfo?.authorAssociation
9684
+ );
9685
+ eventName = anyInfo?.eventContext?.event_name || anyInfo?.eventType || "manual";
9686
+ eventAction = anyInfo?.eventContext?.action;
9687
+ } catch {
9688
+ }
9689
+ templateData.authorAssociation = authorAssociation;
9690
+ templateData.event = { name: eventName, action: eventAction };
9691
+ }
9692
+ const { withPermissionsContext } = await import("./liquid-extensions-GMEGEGC3.mjs");
9693
+ let authorAssociationForFilters;
9694
+ try {
9695
+ const anyInfo = _prInfo;
9696
+ authorAssociationForFilters = resolveAssociationFromEvent(
9697
+ anyInfo?.eventContext,
9698
+ anyInfo?.authorAssociation
9699
+ );
9700
+ } catch {
9701
+ }
9702
+ let rendered;
9703
+ if (typeof withPermissionsContext === "function") {
9704
+ rendered = await withPermissionsContext(
9705
+ { authorAssociation: authorAssociationForFilters },
9706
+ async () => await liquid.parseAndRender(templateContent, templateData)
9707
+ );
9708
+ if (rendered === void 0 || rendered === null) {
9709
+ rendered = await liquid.parseAndRender(templateContent, templateData);
9710
+ }
9711
+ } else {
9712
+ rendered = await liquid.parseAndRender(templateContent, templateData);
9713
+ }
9714
+ const finalRendered = rendered.trim();
9715
+ try {
9716
+ const { emitMermaidFromMarkdown } = await import("./mermaid-telemetry-LZGDD35I.mjs");
9717
+ emitMermaidFromMarkdown(checkName, finalRendered, "content");
9718
+ } catch {
9719
+ }
9720
+ return finalRendered;
9721
+ }
9722
+ /**
9723
+ * Attempt to elevate an issue/issue_comment context to full PR context when routing via goto_event.
9724
+ * Returns a new PRInfo with files/diff when possible; otherwise returns null.
9725
+ */
9726
+ async elevateContextToPullRequest(prInfo, targetEvent, log2, debug) {
9727
+ try {
9728
+ if (targetEvent !== "pr_opened" && targetEvent !== "pr_updated") return null;
9729
+ const isIssueContext = prInfo.isIssue === true;
9730
+ const ctx = prInfo.eventContext || {};
9731
+ const isPRThread = Boolean(ctx?.issue?.pull_request);
9732
+ if (!isIssueContext || !isPRThread) return null;
9733
+ let owner = this.actionContext?.owner;
9734
+ let repo = this.actionContext?.repo;
9735
+ if (!owner || !repo) {
9736
+ const repoEnv = process.env.GITHUB_REPOSITORY || "";
9737
+ [owner, repo] = repoEnv.split("/");
9738
+ }
9739
+ if (!owner || !repo) return null;
9740
+ const prNumber = ctx?.issue?.number || prInfo.number;
9741
+ if (!prNumber) return null;
9742
+ let octokit = this.actionContext?.octokit;
9743
+ if (!octokit) {
9744
+ const token = process.env["INPUT_GITHUB-TOKEN"] || process.env["GITHUB_TOKEN"];
9745
+ if (!token) return null;
9746
+ const { Octokit } = await import("@octokit/rest");
9747
+ octokit = new Octokit({ auth: token });
9748
+ }
9749
+ const analyzer = new PRAnalyzer(octokit);
9750
+ const elevated = await analyzer.fetchPRDiff(owner, repo, prNumber, void 0, targetEvent);
9751
+ elevated.eventContext = prInfo.eventContext || ctx;
9752
+ elevated.isPRContext = true;
9753
+ elevated.includeCodeContext = true;
9754
+ if (debug)
9755
+ log2?.(`\u{1F527} Debug: Elevated context to PR #${prNumber} for goto_event=${targetEvent}`);
9756
+ return elevated;
9757
+ } catch (e) {
9758
+ if (debug) {
9759
+ const msg = e instanceof Error ? e.message : String(e);
9760
+ log2?.(`\u26A0\uFE0F Debug: Context elevation to PR failed: ${msg}`);
9761
+ }
9762
+ return null;
9763
+ }
7646
9764
  }
7647
9765
  /**
7648
9766
  * Execute multiple checks with dependency awareness - intelligently parallel and sequential
@@ -7817,8 +9935,7 @@ ${expr}
7817
9935
  group: checkConfig.group,
7818
9936
  checkName,
7819
9937
  // Add checkName for sessionID
7820
- eventContext: prInfo.eventContext,
7821
- // Pass event context for templates
9938
+ eventContext: this.enrichEventContext(prInfo.eventContext),
7822
9939
  transform: checkConfig.transform,
7823
9940
  transform_js: checkConfig.transform_js,
7824
9941
  // Important: pass through provider-level timeout from check config
@@ -7828,10 +9945,12 @@ ${expr}
7828
9945
  message: extendedCheckConfig.message,
7829
9946
  env: checkConfig.env,
7830
9947
  forEach: checkConfig.forEach,
9948
+ // Pass through any provider-specific keys (e.g., op/values for github provider)
9949
+ ...checkConfig,
7831
9950
  ai: {
9951
+ ...checkConfig.ai || {},
7832
9952
  timeout: timeout || 6e5,
7833
- debug,
7834
- ...checkConfig.ai || {}
9953
+ debug
7835
9954
  }
7836
9955
  };
7837
9956
  const dependencyResults = /* @__PURE__ */ new Map();
@@ -8003,17 +10122,26 @@ ${expr}
8003
10122
  schema: childCfg.schema,
8004
10123
  group: childCfg.group,
8005
10124
  checkName: childName,
8006
- eventContext: prInfo.eventContext,
10125
+ eventContext: this.enrichEventContext(prInfo.eventContext),
8007
10126
  transform: childCfg.transform,
8008
10127
  transform_js: childCfg.transform_js,
8009
10128
  env: childCfg.env,
8010
10129
  forEach: childCfg.forEach,
10130
+ // Include provider-specific keys like op/values for non-AI providers
10131
+ ...childCfg,
8011
10132
  ai: {
10133
+ ...childCfg.ai || {},
8012
10134
  timeout: timeout || 6e5,
8013
- debug,
8014
- ...childCfg.ai || {}
10135
+ debug
8015
10136
  }
8016
10137
  };
10138
+ try {
10139
+ emitNdjsonSpanWithEvents("visor.check", { "visor.check.id": checkName }, [
10140
+ { name: "check.started" },
10141
+ { name: "check.completed" }
10142
+ ]);
10143
+ } catch {
10144
+ }
8017
10145
  const childDepResults = /* @__PURE__ */ new Map();
8018
10146
  const childAllDeps = DependencyResolver.getAllDependencies(
8019
10147
  childName,
@@ -8136,6 +10264,18 @@ ${expr}
8136
10264
  }
8137
10265
  };
8138
10266
  const itemTasks = forEachItems.map((item, itemIndex) => async () => {
10267
+ try {
10268
+ emitNdjsonSpanWithEvents(
10269
+ "visor.foreach.item",
10270
+ {
10271
+ "visor.check.id": checkName,
10272
+ "visor.foreach.index": itemIndex,
10273
+ "visor.foreach.total": forEachItems.length
10274
+ },
10275
+ []
10276
+ );
10277
+ } catch {
10278
+ }
8139
10279
  const forEachDependencyResults = /* @__PURE__ */ new Map();
8140
10280
  for (const [depName, depResult] of dependencyResults) {
8141
10281
  if (forEachParents.includes(depName)) {
@@ -8376,7 +10516,7 @@ ${expr}
8376
10516
  schema: nodeCfg.schema,
8377
10517
  group: nodeCfg.group,
8378
10518
  checkName: node,
8379
- eventContext: prInfo.eventContext,
10519
+ eventContext: this.enrichEventContext(prInfo.eventContext),
8380
10520
  transform: nodeCfg.transform,
8381
10521
  transform_js: nodeCfg.transform_js,
8382
10522
  env: nodeCfg.env,
@@ -8738,6 +10878,13 @@ ${expr}
8738
10878
  debug,
8739
10879
  results
8740
10880
  );
10881
+ try {
10882
+ emitNdjsonSpanWithEvents("visor.check", { "visor.check.id": checkName }, [
10883
+ { name: "check.started" },
10884
+ { name: "check.completed" }
10885
+ ]);
10886
+ } catch {
10887
+ }
8741
10888
  if (config && (config.fail_if || checkConfig.fail_if)) {
8742
10889
  const failureResults = await this.evaluateFailureConditions(
8743
10890
  checkName,
@@ -8905,6 +11052,13 @@ ${error.stack || ""}` : String(error);
8905
11052
  reviewSummaryWithOutput.forEachItems = normalizedOutput;
8906
11053
  reviewSummaryWithOutput.isForEach = true;
8907
11054
  }
11055
+ try {
11056
+ emitNdjsonSpanWithEvents("visor.check", { "visor.check.id": checkName }, [
11057
+ { name: "check.started" },
11058
+ { name: "check.completed" }
11059
+ ]);
11060
+ } catch {
11061
+ }
8908
11062
  results.set(checkName, reviewResult);
8909
11063
  } else {
8910
11064
  const errorSummary = {
@@ -9018,6 +11172,12 @@ ${error.stack || ""}` : String(error);
9018
11172
  `\u{1F527} Debug: Starting check: ${checkName} with prompt type: ${typeof checkConfig.prompt}`
9019
11173
  );
9020
11174
  if (checkConfig.if) {
11175
+ const override = this.routingEventOverride;
11176
+ const eventName = override ? override.startsWith("pr_") ? "pull_request" : override === "issue_comment" ? "issue_comment" : override.startsWith("issue_") ? "issues" : "manual" : "issue_comment";
11177
+ const commenterAssoc = resolveAssociationFromEvent(
11178
+ prInfo?.eventContext,
11179
+ prInfo.authorAssociation
11180
+ );
9021
11181
  const shouldRun = await this.failureEvaluator.evaluateIfCondition(
9022
11182
  checkName,
9023
11183
  checkConfig.if,
@@ -9025,11 +11185,12 @@ ${error.stack || ""}` : String(error);
9025
11185
  branch: prInfo.head,
9026
11186
  baseBranch: prInfo.base,
9027
11187
  filesChanged: prInfo.files.map((f) => f.filename),
9028
- event: "issue_comment",
9029
- // Command triggered from comment
11188
+ event: eventName,
11189
+ // honor routing override if present
9030
11190
  environment: getSafeEnvironmentVariables(),
9031
- previousResults: /* @__PURE__ */ new Map()
11191
+ previousResults: /* @__PURE__ */ new Map(),
9032
11192
  // No previous results in parallel execution
11193
+ authorAssociation: commenterAssoc
9033
11194
  }
9034
11195
  );
9035
11196
  if (!shouldRun) {
@@ -9051,8 +11212,7 @@ ${error.stack || ""}` : String(error);
9051
11212
  focus: checkConfig.focus || this.mapCheckNameToFocus(checkName),
9052
11213
  schema: checkConfig.schema,
9053
11214
  group: checkConfig.group,
9054
- eventContext: prInfo.eventContext,
9055
- // Pass event context for templates
11215
+ eventContext: this.enrichEventContext(prInfo.eventContext),
9056
11216
  ai: {
9057
11217
  timeout: timeout || 6e5,
9058
11218
  debug,
@@ -9128,8 +11288,7 @@ ${error.stack || ""}` : String(error);
9128
11288
  focus: checkConfig.focus || this.mapCheckNameToFocus(checkName),
9129
11289
  schema: checkConfig.schema,
9130
11290
  group: checkConfig.group,
9131
- eventContext: prInfo.eventContext,
9132
- // Pass event context for templates
11291
+ eventContext: this.enrichEventContext(prInfo.eventContext),
9133
11292
  ai: {
9134
11293
  timeout: timeout || 6e5,
9135
11294
  ...checkConfig.ai || {}
@@ -9171,6 +11330,7 @@ ${error.stack || ""}` : String(error);
9171
11330
  const aggregatedIssues = [];
9172
11331
  const debugInfo = [];
9173
11332
  const contentMap = {};
11333
+ const outputsMap = {};
9174
11334
  const stats = DependencyResolver.getExecutionStats(dependencyGraph);
9175
11335
  const executionInfo = [
9176
11336
  stoppedEarly ? `\u{1F6D1} Dependency-aware execution stopped early (fail-fast):` : `\u{1F50D} Dependency-aware execution completed:`,
@@ -9182,6 +11342,7 @@ ${error.stack || ""}` : String(error);
9182
11342
  stoppedEarly ? ` - Stopped early due to fail-fast behavior` : ``
9183
11343
  ].filter(Boolean);
9184
11344
  debugInfo.push(...executionInfo);
11345
+ const processed = /* @__PURE__ */ new Set();
9185
11346
  for (const executionGroup of dependencyGraph.executionOrder) {
9186
11347
  for (const checkName of executionGroup.parallel) {
9187
11348
  const result = results.get(checkName);
@@ -9199,6 +11360,7 @@ ${error.stack || ""}` : String(error);
9199
11360
  `\u2705 Check "${checkName}" completed: ${(result.issues || []).length} issues found (level ${executionGroup.level})`
9200
11361
  );
9201
11362
  }
11363
+ processed.add(checkName);
9202
11364
  const nonInternalIssues = (result.issues || []).filter(
9203
11365
  (issue) => !issue.ruleId?.endsWith("/__skipped")
9204
11366
  );
@@ -9208,7 +11370,29 @@ ${error.stack || ""}` : String(error);
9208
11370
  if (typeof resultContent === "string" && resultContent.trim()) {
9209
11371
  contentMap[checkName] = resultContent.trim();
9210
11372
  }
11373
+ if (resultSummary.output !== void 0) {
11374
+ outputsMap[checkName] = resultSummary.output;
11375
+ }
11376
+ }
11377
+ }
11378
+ for (const [checkName, result] of results.entries()) {
11379
+ if (processed.has(checkName)) continue;
11380
+ if (!result) continue;
11381
+ const nonInternalIssues = (result.issues || []).filter(
11382
+ (issue) => !issue.ruleId?.endsWith("/__skipped")
11383
+ );
11384
+ aggregatedIssues.push(...nonInternalIssues);
11385
+ const resultSummary = result;
11386
+ const resultContent = resultSummary.content;
11387
+ if (typeof resultContent === "string" && resultContent.trim()) {
11388
+ contentMap[checkName] = resultContent.trim();
11389
+ }
11390
+ if (resultSummary.output !== void 0) {
11391
+ outputsMap[checkName] = resultSummary.output;
9211
11392
  }
11393
+ debugInfo.push(
11394
+ `\u2705 (dynamic) Check "${checkName}" included: ${(result.issues || []).length} issues found`
11395
+ );
9212
11396
  }
9213
11397
  if (debug) {
9214
11398
  console.error(
@@ -9267,6 +11451,10 @@ ${result.debug.rawResponse}`).join("\n\n"),
9267
11451
  if (Object.keys(contentMap).length > 0) {
9268
11452
  summary.__contents = contentMap;
9269
11453
  }
11454
+ if (Object.keys(outputsMap).length > 0) {
11455
+ summary.__outputs = outputsMap;
11456
+ }
11457
+ summary.__executed = Array.from(results.keys());
9270
11458
  return summary;
9271
11459
  }
9272
11460
  /**
@@ -9590,7 +11778,7 @@ ${result.value.result.debug.rawResponse}`;
9590
11778
  /**
9591
11779
  * Evaluate failure conditions for a check result
9592
11780
  */
9593
- async evaluateFailureConditions(checkName, reviewSummary, config) {
11781
+ async evaluateFailureConditions(checkName, reviewSummary, config, prInfo) {
9594
11782
  if (!config) {
9595
11783
  return [];
9596
11784
  }
@@ -9609,7 +11797,50 @@ ${result.value.result.debug.rawResponse}`;
9609
11797
  reviewSummary,
9610
11798
  globalFailIf
9611
11799
  );
11800
+ try {
11801
+ addEvent("fail_if.evaluated", {
11802
+ check: checkName,
11803
+ scope: "global",
11804
+ name: "global_fail_if",
11805
+ expression: globalFailIf
11806
+ });
11807
+ } catch {
11808
+ }
9612
11809
  if (failed) {
11810
+ try {
11811
+ addEvent("fail_if.triggered", {
11812
+ check: checkName,
11813
+ scope: "global",
11814
+ name: "global_fail_if",
11815
+ expression: globalFailIf,
11816
+ severity: "error"
11817
+ });
11818
+ } catch {
11819
+ }
11820
+ try {
11821
+ addFailIfTriggered(checkName, "global");
11822
+ } catch {
11823
+ }
11824
+ try {
11825
+ const { emitNdjsonSpanWithEvents: emitNdjsonSpanWithEvents2 } = (init_fallback_ndjson(), __toCommonJS(fallback_ndjson_exports));
11826
+ emitNdjsonSpanWithEvents2(
11827
+ "visor.fail_if",
11828
+ { check: checkName, scope: "global", name: "global_fail_if" },
11829
+ [
11830
+ {
11831
+ name: "fail_if.triggered",
11832
+ attrs: {
11833
+ check: checkName,
11834
+ scope: "global",
11835
+ name: "global_fail_if",
11836
+ expression: globalFailIf,
11837
+ severity: "error"
11838
+ }
11839
+ }
11840
+ ]
11841
+ );
11842
+ } catch {
11843
+ }
9613
11844
  logger.warn(`\u26A0\uFE0F Check "${checkName}" - global fail_if condition met: ${globalFailIf}`);
9614
11845
  results.push({
9615
11846
  conditionName: "global_fail_if",
@@ -9631,7 +11862,78 @@ ${result.value.result.debug.rawResponse}`;
9631
11862
  reviewSummary,
9632
11863
  checkFailIf
9633
11864
  );
11865
+ try {
11866
+ addEvent("fail_if.evaluated", {
11867
+ check: checkName,
11868
+ scope: "check",
11869
+ name: `${checkName}_fail_if`,
11870
+ expression: checkFailIf
11871
+ });
11872
+ } catch {
11873
+ }
11874
+ try {
11875
+ const { emitNdjsonSpanWithEvents: emitNdjsonSpanWithEvents2 } = (init_fallback_ndjson(), __toCommonJS(fallback_ndjson_exports));
11876
+ emitNdjsonSpanWithEvents2(
11877
+ "visor.fail_if",
11878
+ { check: checkName, scope: "check", name: `${checkName}_fail_if` },
11879
+ [
11880
+ {
11881
+ name: "fail_if.evaluated",
11882
+ attrs: {
11883
+ check: checkName,
11884
+ scope: "check",
11885
+ name: `${checkName}_fail_if`,
11886
+ expression: checkFailIf
11887
+ }
11888
+ }
11889
+ ]
11890
+ );
11891
+ } catch {
11892
+ }
9634
11893
  if (failed) {
11894
+ try {
11895
+ addEvent("fail_if.triggered", {
11896
+ check: checkName,
11897
+ scope: "check",
11898
+ name: `${checkName}_fail_if`,
11899
+ expression: checkFailIf,
11900
+ severity: "error"
11901
+ });
11902
+ } catch {
11903
+ }
11904
+ try {
11905
+ addEvent("fail_if.evaluated", {
11906
+ check: checkName,
11907
+ scope: "check",
11908
+ name: `${checkName}_fail_if`,
11909
+ expression: checkFailIf
11910
+ });
11911
+ } catch {
11912
+ }
11913
+ try {
11914
+ addFailIfTriggered(checkName, "check");
11915
+ } catch {
11916
+ }
11917
+ try {
11918
+ const { emitNdjsonSpanWithEvents: emitNdjsonSpanWithEvents2 } = (init_fallback_ndjson(), __toCommonJS(fallback_ndjson_exports));
11919
+ emitNdjsonSpanWithEvents2(
11920
+ "visor.fail_if",
11921
+ { check: checkName, scope: "check", name: `${checkName}_fail_if` },
11922
+ [
11923
+ {
11924
+ name: "fail_if.triggered",
11925
+ attrs: {
11926
+ check: checkName,
11927
+ scope: "check",
11928
+ name: `${checkName}_fail_if`,
11929
+ expression: checkFailIf,
11930
+ severity: "error"
11931
+ }
11932
+ }
11933
+ ]
11934
+ );
11935
+ } catch {
11936
+ }
9635
11937
  logger.warn(`\u26A0\uFE0F Check "${checkName}" - fail_if condition met: ${checkFailIf}`);
9636
11938
  results.push({
9637
11939
  conditionName: `${checkName}_fail_if`,
@@ -9645,6 +11947,31 @@ ${result.value.result.debug.rawResponse}`;
9645
11947
  logger.debug(`\u2713 Check "${checkName}" - fail_if condition passed`);
9646
11948
  }
9647
11949
  }
11950
+ try {
11951
+ const { emitNdjsonSpanWithEvents: emitNdjsonSpanWithEvents2 } = (init_fallback_ndjson(), __toCommonJS(fallback_ndjson_exports));
11952
+ const hadTriggered = results.some((r) => r.failed === true);
11953
+ emitNdjsonSpanWithEvents2(
11954
+ "visor.fail_if",
11955
+ {
11956
+ check: checkName,
11957
+ scope: hadTriggered ? checkFailIf ? "check" : "global" : checkFailIf ? "check" : "global"
11958
+ },
11959
+ [
11960
+ {
11961
+ name: "fail_if.evaluated",
11962
+ attrs: { check: checkName, scope: checkFailIf ? "check" : "global" }
11963
+ }
11964
+ ].concat(
11965
+ hadTriggered ? [
11966
+ {
11967
+ name: "fail_if.triggered",
11968
+ attrs: { check: checkName, scope: checkFailIf ? "check" : "global" }
11969
+ }
11970
+ ] : []
11971
+ )
11972
+ );
11973
+ } catch {
11974
+ }
9648
11975
  return results;
9649
11976
  }
9650
11977
  const globalConditions = config.failure_conditions;
@@ -9655,7 +11982,10 @@ ${result.value.result.debug.rawResponse}`;
9655
11982
  checkGroup,
9656
11983
  reviewSummary,
9657
11984
  globalConditions,
9658
- checkConditions
11985
+ checkConditions,
11986
+ void 0,
11987
+ // previousOutputs
11988
+ prInfo?.authorAssociation
9659
11989
  );
9660
11990
  }
9661
11991
  /**
@@ -9778,6 +12108,7 @@ ${result.value.result.debug.rawResponse}`;
9778
12108
  { issues: checkIssues },
9779
12109
  options.config
9780
12110
  );
12111
+ const execErrorIssue = checkIssues.find((i) => i.ruleId?.startsWith("command/"));
9781
12112
  await this.githubCheckService.completeCheckRun(
9782
12113
  options.githubChecks.owner,
9783
12114
  options.githubChecks.repo,
@@ -9785,7 +12116,7 @@ ${result.value.result.debug.rawResponse}`;
9785
12116
  checkName,
9786
12117
  failureResults,
9787
12118
  checkIssues,
9788
- void 0,
12119
+ execErrorIssue ? execErrorIssue.message : void 0,
9789
12120
  // executionError
9790
12121
  prInfo.files.map((f) => f.filename),
9791
12122
  // filesChangedInCommit
@@ -10161,8 +12492,6 @@ ${result.value.result.debug.rawResponse}`;
10161
12492
  };
10162
12493
 
10163
12494
  export {
10164
- logger,
10165
- init_logger,
10166
12495
  CheckExecutionEngine
10167
12496
  };
10168
- //# sourceMappingURL=chunk-N2PPFOSF.mjs.map
12497
+ //# sourceMappingURL=chunk-Z47UECAT.mjs.map