@probelabs/visor 0.1.93 → 0.1.95

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 (50) hide show
  1. package/README.md +4 -4
  2. package/defaults/.visor.yaml +86 -6
  3. package/dist/ai-review-service.d.ts +1 -1
  4. package/dist/ai-review-service.d.ts.map +1 -1
  5. package/dist/check-execution-engine.d.ts +5 -0
  6. package/dist/check-execution-engine.d.ts.map +1 -1
  7. package/dist/cli.d.ts +1 -0
  8. package/dist/cli.d.ts.map +1 -1
  9. package/dist/commands.d.ts.map +1 -1
  10. package/dist/config.d.ts +9 -2
  11. package/dist/config.d.ts.map +1 -1
  12. package/dist/defaults/.visor.yaml +86 -6
  13. package/dist/failure-condition-evaluator.d.ts.map +1 -1
  14. package/dist/footer.d.ts +25 -0
  15. package/dist/footer.d.ts.map +1 -0
  16. package/dist/github-check-service.d.ts.map +1 -1
  17. package/dist/github-comments.d.ts.map +1 -1
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +1074 -472
  20. package/dist/output/code-review/schema.json +0 -23
  21. package/dist/providers/command-check-provider.d.ts.map +1 -1
  22. package/dist/providers/log-check-provider.d.ts.map +1 -1
  23. package/dist/providers/memory-check-provider.d.ts.map +1 -1
  24. package/dist/reviewer.d.ts +11 -0
  25. package/dist/reviewer.d.ts.map +1 -1
  26. package/dist/sdk/{check-execution-engine-RORGGGGP.mjs → check-execution-engine-NMPXJ7FQ.mjs} +2 -2
  27. package/dist/sdk/{chunk-Z47UECAT.mjs → chunk-Q4S5A5TO.mjs} +314 -111
  28. package/dist/sdk/chunk-Q4S5A5TO.mjs.map +1 -0
  29. package/dist/sdk/sdk.d.mts +11 -2
  30. package/dist/sdk/sdk.d.ts +11 -2
  31. package/dist/sdk/sdk.js +398 -152
  32. package/dist/sdk/sdk.js.map +1 -1
  33. package/dist/sdk/sdk.mjs +57 -24
  34. package/dist/sdk/sdk.mjs.map +1 -1
  35. package/dist/sdk.d.ts +11 -2
  36. package/dist/sdk.d.ts.map +1 -1
  37. package/dist/traces/{run-2025-10-15T11-54-04-087Z.ndjson → run-2025-10-18T18-27-25-085Z.ndjson} +8 -1
  38. package/dist/traces/{run-2025-10-15T11-54-14-046Z.ndjson → run-2025-10-18T18-27-35-400Z.ndjson} +8 -1
  39. package/dist/traces/{run-2025-10-15T11-54-14-575Z.ndjson → run-2025-10-18T18-27-35-937Z.ndjson} +8 -1
  40. package/dist/traces/{run-2025-10-15T11-54-15-082Z.ndjson → run-2025-10-18T18-27-36-428Z.ndjson} +8 -1
  41. package/dist/types/cli.d.ts +3 -2
  42. package/dist/types/cli.d.ts.map +1 -1
  43. package/dist/types/config.d.ts +0 -2
  44. package/dist/types/config.d.ts.map +1 -1
  45. package/dist/utils/diff-processor.d.ts +6 -0
  46. package/dist/utils/diff-processor.d.ts.map +1 -0
  47. package/package.json +2 -2
  48. package/dist/sdk/chunk-Z47UECAT.mjs.map +0 -1
  49. /package/dist/sdk/{check-execution-engine-RORGGGGP.mjs.map → check-execution-engine-NMPXJ7FQ.mjs.map} +0 -0
  50. /package/dist/traces/{run-2025-10-15T11-54-15-561Z.ndjson → run-2025-10-18T18-27-36-917Z.ndjson} +0 -0
@@ -217,6 +217,30 @@ var init_session_registry = __esm({
217
217
  // src/github-comments.ts
218
218
  init_logger();
219
219
  import { v4 as uuidv4 } from "uuid";
220
+
221
+ // src/footer.ts
222
+ function generateFooter(options = {}) {
223
+ const { includeMetadata, includeSeparator = true } = options;
224
+ const parts = [];
225
+ if (includeSeparator) {
226
+ parts.push("---");
227
+ parts.push("");
228
+ }
229
+ parts.push(
230
+ "*Powered by [Visor](https://probelabs.com/visor) from [Probelabs](https://probelabs.com)*"
231
+ );
232
+ if (includeMetadata) {
233
+ const { lastUpdated, triggeredBy, commitSha } = includeMetadata;
234
+ const commitInfo = commitSha ? ` | Commit: ${commitSha.substring(0, 7)}` : "";
235
+ parts.push("");
236
+ parts.push(`*Last updated: ${lastUpdated} | Triggered by: ${triggeredBy}${commitInfo}*`);
237
+ }
238
+ parts.push("");
239
+ parts.push("\u{1F4A1} **TIP:** You can chat with Visor using `/visor ask <your question>`");
240
+ return parts.join("\n");
241
+ }
242
+
243
+ // src/github-comments.ts
220
244
  var CommentManager = class {
221
245
  octokit;
222
246
  retryConfig;
@@ -318,15 +342,17 @@ var CommentManager = class {
318
342
  */
319
343
  formatCommentWithMetadata(content, metadata) {
320
344
  const { commentId, lastUpdated, triggeredBy, commitSha } = metadata;
321
- const commitInfo = commitSha ? ` | Commit: ${commitSha.substring(0, 7)}` : "";
345
+ const footer = generateFooter({
346
+ includeMetadata: {
347
+ lastUpdated,
348
+ triggeredBy,
349
+ commitSha
350
+ }
351
+ });
322
352
  return `<!-- visor-comment-id:${commentId} -->
323
353
  ${content}
324
354
 
325
- ---
326
-
327
- *Powered by [Visor](https://probelabs.com/visor) from [Probelabs](https://probelabs.com)*
328
-
329
- *Last updated: ${lastUpdated} | Triggered by: ${triggeredBy}${commitInfo}*
355
+ ${footer}
330
356
  <!-- /visor-comment-id:${commentId} -->`;
331
357
  }
332
358
  /**
@@ -508,6 +534,64 @@ init_session_registry();
508
534
  init_logger();
509
535
  init_tracer_init();
510
536
  import { ProbeAgent } from "@probelabs/probe";
537
+
538
+ // src/utils/diff-processor.ts
539
+ import { extract } from "@probelabs/probe";
540
+ import * as path from "path";
541
+ async function processDiffWithOutline(diffContent) {
542
+ if (!diffContent || diffContent.trim().length === 0) {
543
+ return diffContent;
544
+ }
545
+ try {
546
+ const originalProbePath = process.env.PROBE_PATH;
547
+ const fs5 = __require("fs");
548
+ const possiblePaths = [
549
+ // Relative to current working directory (most common in production)
550
+ path.join(process.cwd(), "node_modules/@probelabs/probe/bin/probe-binary"),
551
+ // Relative to __dirname (for unbundled development)
552
+ path.join(__dirname, "../..", "node_modules/@probelabs/probe/bin/probe-binary"),
553
+ // Relative to dist directory (for bundled CLI)
554
+ path.join(__dirname, "node_modules/@probelabs/probe/bin/probe-binary")
555
+ ];
556
+ let probeBinaryPath;
557
+ for (const candidatePath of possiblePaths) {
558
+ if (fs5.existsSync(candidatePath)) {
559
+ probeBinaryPath = candidatePath;
560
+ break;
561
+ }
562
+ }
563
+ if (!probeBinaryPath) {
564
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
565
+ console.error("Probe binary not found. Tried:", possiblePaths);
566
+ }
567
+ return diffContent;
568
+ }
569
+ process.env.PROBE_PATH = probeBinaryPath;
570
+ const extractPromise = extract({
571
+ content: diffContent,
572
+ format: "outline-diff",
573
+ allowTests: true
574
+ // Allow test files and test code blocks in extraction results
575
+ });
576
+ const timeoutPromise = new Promise((_, reject) => {
577
+ setTimeout(() => reject(new Error("Extract timeout after 30s")), 3e4);
578
+ });
579
+ const result = await Promise.race([extractPromise, timeoutPromise]);
580
+ if (originalProbePath !== void 0) {
581
+ process.env.PROBE_PATH = originalProbePath;
582
+ } else {
583
+ delete process.env.PROBE_PATH;
584
+ }
585
+ return typeof result === "string" ? result : JSON.stringify(result);
586
+ } catch (error) {
587
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
588
+ console.error("Failed to process diff with outline-diff format:", error);
589
+ }
590
+ return diffContent;
591
+ }
592
+ }
593
+
594
+ // src/ai-review-service.ts
511
595
  function log(...args) {
512
596
  logger.debug(args.join(" "));
513
597
  }
@@ -549,7 +633,7 @@ var AIReviewService = class {
549
633
  /**
550
634
  * Execute AI review using probe agent
551
635
  */
552
- async executeReview(prInfo, customPrompt, schema, _checkName, sessionId) {
636
+ async executeReview(prInfo, customPrompt, schema, checkName, sessionId) {
553
637
  const startTime = Date.now();
554
638
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
555
639
  const prompt = await this.buildCustomPrompt(prInfo, customPrompt, schema);
@@ -606,7 +690,7 @@ var AIReviewService = class {
606
690
  prompt,
607
691
  schema,
608
692
  debugInfo,
609
- _checkName,
693
+ checkName,
610
694
  sessionId
611
695
  );
612
696
  const processingTime = Date.now() - startTime;
@@ -766,9 +850,9 @@ var AIReviewService = class {
766
850
  */
767
851
  async buildCustomPrompt(prInfo, customInstructions, schema, options) {
768
852
  const skipPRContext = options?.skipPRContext === true;
769
- const prContext = skipPRContext ? "" : this.formatPRContext(prInfo);
770
- const isIssue = prInfo.isIssue === true;
771
853
  const isCodeReviewSchema = schema === "code-review";
854
+ const prContext = skipPRContext ? "" : await this.formatPRContext(prInfo, isCodeReviewSchema);
855
+ const isIssue = prInfo.isIssue === true;
772
856
  if (isIssue) {
773
857
  if (skipPRContext) {
774
858
  return `<instructions>
@@ -857,7 +941,7 @@ ${prContext}
857
941
  /**
858
942
  * Format PR or Issue context for the AI using XML structure
859
943
  */
860
- formatPRContext(prInfo) {
944
+ async formatPRContext(prInfo, isCodeReviewSchema) {
861
945
  const prContextInfo = prInfo;
862
946
  const isIssue = prContextInfo.isIssue === true;
863
947
  const isPRContext = prContextInfo.isPRContext === true;
@@ -939,7 +1023,12 @@ ${this.escapeXml(prInfo.body)}
939
1023
  }
940
1024
  const issueComments = prInfo.comments;
941
1025
  if (issueComments && issueComments.length > 0) {
942
- const historicalComments = triggeringComment2 ? issueComments.filter((c) => c.id !== triggeringComment2.id) : issueComments;
1026
+ let historicalComments = triggeringComment2 ? issueComments.filter((c) => c.id !== triggeringComment2.id) : issueComments;
1027
+ if (isCodeReviewSchema) {
1028
+ historicalComments = historicalComments.filter(
1029
+ (c) => !c.body || !c.body.includes("visor-comment-id:pr-review-")
1030
+ );
1031
+ }
943
1032
  if (historicalComments.length > 0) {
944
1033
  context2 += `
945
1034
  <!-- Previous comments in chronological order (excluding triggering comment) -->
@@ -981,24 +1070,27 @@ ${this.escapeXml(prInfo.body)}
981
1070
  }
982
1071
  if (includeCodeContext) {
983
1072
  if (prInfo.fullDiff) {
1073
+ const processedFullDiff = await processDiffWithOutline(prInfo.fullDiff);
984
1074
  context += `
985
- <!-- Complete unified diff showing all changes in the pull request -->
1075
+ <!-- Complete unified diff showing all changes in the pull request (processed with outline-diff) -->
986
1076
  <full_diff>
987
- ${this.escapeXml(prInfo.fullDiff)}
1077
+ ${this.escapeXml(processedFullDiff)}
988
1078
  </full_diff>`;
989
1079
  }
990
1080
  if (prInfo.isIncremental) {
991
1081
  if (prInfo.commitDiff && prInfo.commitDiff.length > 0) {
1082
+ const processedCommitDiff = await processDiffWithOutline(prInfo.commitDiff);
992
1083
  context += `
993
- <!-- Diff of only the latest commit for incremental analysis -->
1084
+ <!-- Diff of only the latest commit for incremental analysis (processed with outline-diff) -->
994
1085
  <commit_diff>
995
- ${this.escapeXml(prInfo.commitDiff)}
1086
+ ${this.escapeXml(processedCommitDiff)}
996
1087
  </commit_diff>`;
997
1088
  } else {
1089
+ const processedFallbackDiff = prInfo.fullDiff ? await processDiffWithOutline(prInfo.fullDiff) : "";
998
1090
  context += `
999
- <!-- Commit diff could not be retrieved - falling back to full diff analysis -->
1091
+ <!-- Commit diff could not be retrieved - falling back to full diff analysis (processed with outline-diff) -->
1000
1092
  <commit_diff>
1001
- ${prInfo.fullDiff ? this.escapeXml(prInfo.fullDiff) : ""}
1093
+ ${this.escapeXml(processedFallbackDiff)}
1002
1094
  </commit_diff>`;
1003
1095
  }
1004
1096
  }
@@ -1034,7 +1126,12 @@ ${prInfo.fullDiff ? this.escapeXml(prInfo.fullDiff) : ""}
1034
1126
  }
1035
1127
  const prComments = prInfo.comments;
1036
1128
  if (prComments && prComments.length > 0) {
1037
- const historicalComments = triggeringComment ? prComments.filter((c) => c.id !== triggeringComment.id) : prComments;
1129
+ let historicalComments = triggeringComment ? prComments.filter((c) => c.id !== triggeringComment.id) : prComments;
1130
+ if (isCodeReviewSchema) {
1131
+ historicalComments = historicalComments.filter(
1132
+ (c) => !c.body || !c.body.includes("visor-comment-id:pr-review-")
1133
+ );
1134
+ }
1038
1135
  if (historicalComments.length > 0) {
1039
1136
  context += `
1040
1137
  <!-- Previous PR comments in chronological order (excluding triggering comment) -->
@@ -1105,7 +1202,7 @@ ${schemaString}`);
1105
1202
  if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
1106
1203
  try {
1107
1204
  const fs5 = __require("fs");
1108
- const path5 = __require("path");
1205
+ const path6 = __require("path");
1109
1206
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1110
1207
  const provider = this.config.provider || "auto";
1111
1208
  const model = this.config.model || "default";
@@ -1219,16 +1316,16 @@ ${"=".repeat(60)}
1219
1316
  `;
1220
1317
  readableVersion += `${"=".repeat(60)}
1221
1318
  `;
1222
- const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path5.join(process.cwd(), "debug-artifacts");
1319
+ const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path6.join(process.cwd(), "debug-artifacts");
1223
1320
  if (!fs5.existsSync(debugArtifactsDir)) {
1224
1321
  fs5.mkdirSync(debugArtifactsDir, { recursive: true });
1225
1322
  }
1226
- const debugFile = path5.join(
1323
+ const debugFile = path6.join(
1227
1324
  debugArtifactsDir,
1228
1325
  `prompt-${_checkName || "unknown"}-${timestamp}.json`
1229
1326
  );
1230
1327
  fs5.writeFileSync(debugFile, debugJson, "utf-8");
1231
- const readableFile = path5.join(
1328
+ const readableFile = path6.join(
1232
1329
  debugArtifactsDir,
1233
1330
  `prompt-${_checkName || "unknown"}-${timestamp}.txt`
1234
1331
  );
@@ -1265,7 +1362,7 @@ ${"=".repeat(60)}
1265
1362
  if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
1266
1363
  try {
1267
1364
  const fs5 = __require("fs");
1268
- const path5 = __require("path");
1365
+ const path6 = __require("path");
1269
1366
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1270
1367
  const agentAny2 = agent;
1271
1368
  let fullHistory = [];
@@ -1276,8 +1373,8 @@ ${"=".repeat(60)}
1276
1373
  } else if (agentAny2._messages) {
1277
1374
  fullHistory = agentAny2._messages;
1278
1375
  }
1279
- const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path5.join(process.cwd(), "debug-artifacts");
1280
- const sessionFile = path5.join(
1376
+ const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path6.join(process.cwd(), "debug-artifacts");
1377
+ const sessionFile = path6.join(
1281
1378
  debugArtifactsDir,
1282
1379
  `session-${_checkName || "unknown"}-${timestamp}.json`
1283
1380
  );
@@ -1292,7 +1389,7 @@ ${"=".repeat(60)}
1292
1389
  latestResponse: response
1293
1390
  };
1294
1391
  fs5.writeFileSync(sessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
1295
- const sessionTxtFile = path5.join(
1392
+ const sessionTxtFile = path6.join(
1296
1393
  debugArtifactsDir,
1297
1394
  `session-${_checkName || "unknown"}-${timestamp}.txt`
1298
1395
  );
@@ -1336,10 +1433,10 @@ ${"=".repeat(60)}
1336
1433
  if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
1337
1434
  try {
1338
1435
  const fs5 = __require("fs");
1339
- const path5 = __require("path");
1436
+ const path6 = __require("path");
1340
1437
  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(
1438
+ const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path6.join(process.cwd(), "debug-artifacts");
1439
+ const responseFile = path6.join(
1343
1440
  debugArtifactsDir,
1344
1441
  `response-${_checkName || "unknown"}-${timestamp}.txt`
1345
1442
  );
@@ -1511,7 +1608,7 @@ ${schemaString}`);
1511
1608
  if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
1512
1609
  try {
1513
1610
  const fs5 = __require("fs");
1514
- const path5 = __require("path");
1611
+ const path6 = __require("path");
1515
1612
  const os = __require("os");
1516
1613
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1517
1614
  const debugData = {
@@ -1585,21 +1682,21 @@ ${"=".repeat(60)}
1585
1682
  readableVersion += `${"=".repeat(60)}
1586
1683
  `;
1587
1684
  const tempDir = os.tmpdir();
1588
- const promptFile = path5.join(tempDir, `visor-prompt-${timestamp}.txt`);
1685
+ const promptFile = path6.join(tempDir, `visor-prompt-${timestamp}.txt`);
1589
1686
  fs5.writeFileSync(promptFile, prompt, "utf-8");
1590
1687
  log(`
1591
1688
  \u{1F4BE} Prompt saved to: ${promptFile}`);
1592
- const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path5.join(process.cwd(), "debug-artifacts");
1689
+ const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path6.join(process.cwd(), "debug-artifacts");
1593
1690
  try {
1594
1691
  if (!fs5.existsSync(debugArtifactsDir)) {
1595
1692
  fs5.mkdirSync(debugArtifactsDir, { recursive: true });
1596
1693
  }
1597
- const debugFile = path5.join(
1694
+ const debugFile = path6.join(
1598
1695
  debugArtifactsDir,
1599
1696
  `prompt-${_checkName || "unknown"}-${timestamp}.json`
1600
1697
  );
1601
1698
  fs5.writeFileSync(debugFile, debugJson, "utf-8");
1602
- const readableFile = path5.join(
1699
+ const readableFile = path6.join(
1603
1700
  debugArtifactsDir,
1604
1701
  `prompt-${_checkName || "unknown"}-${timestamp}.txt`
1605
1702
  );
@@ -1652,7 +1749,7 @@ $ ${cliCommand}
1652
1749
  if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
1653
1750
  try {
1654
1751
  const fs5 = __require("fs");
1655
- const path5 = __require("path");
1752
+ const path6 = __require("path");
1656
1753
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1657
1754
  const agentAny = agent;
1658
1755
  let fullHistory = [];
@@ -1663,8 +1760,8 @@ $ ${cliCommand}
1663
1760
  } else if (agentAny._messages) {
1664
1761
  fullHistory = agentAny._messages;
1665
1762
  }
1666
- const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path5.join(process.cwd(), "debug-artifacts");
1667
- const sessionFile = path5.join(
1763
+ const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path6.join(process.cwd(), "debug-artifacts");
1764
+ const sessionFile = path6.join(
1668
1765
  debugArtifactsDir,
1669
1766
  `session-${_checkName || "unknown"}-${timestamp}.json`
1670
1767
  );
@@ -1679,7 +1776,7 @@ $ ${cliCommand}
1679
1776
  latestResponse: response
1680
1777
  };
1681
1778
  fs5.writeFileSync(sessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
1682
- const sessionTxtFile = path5.join(
1779
+ const sessionTxtFile = path6.join(
1683
1780
  debugArtifactsDir,
1684
1781
  `session-${_checkName || "unknown"}-${timestamp}.txt`
1685
1782
  );
@@ -1723,10 +1820,10 @@ ${"=".repeat(60)}
1723
1820
  if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
1724
1821
  try {
1725
1822
  const fs5 = __require("fs");
1726
- const path5 = __require("path");
1823
+ const path6 = __require("path");
1727
1824
  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(
1825
+ const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path6.join(process.cwd(), "debug-artifacts");
1826
+ const responseFile = path6.join(
1730
1827
  debugArtifactsDir,
1731
1828
  `response-${_checkName || "unknown"}-${timestamp}.txt`
1732
1829
  );
@@ -1816,7 +1913,7 @@ ${"=".repeat(60)}
1816
1913
  */
1817
1914
  async loadSchemaContent(schema) {
1818
1915
  const fs5 = __require("fs").promises;
1819
- const path5 = __require("path");
1916
+ const path6 = __require("path");
1820
1917
  if (typeof schema === "object" && schema !== null) {
1821
1918
  log("\u{1F4CB} Using inline schema object from configuration");
1822
1919
  return JSON.stringify(schema);
@@ -1829,12 +1926,12 @@ ${"=".repeat(60)}
1829
1926
  }
1830
1927
  } catch {
1831
1928
  }
1832
- if ((schema.startsWith("./") || schema.includes(".json")) && !path5.isAbsolute(schema)) {
1929
+ if ((schema.startsWith("./") || schema.includes(".json")) && !path6.isAbsolute(schema)) {
1833
1930
  if (schema.includes("..") || schema.includes("\0")) {
1834
1931
  throw new Error("Invalid schema path: path traversal not allowed");
1835
1932
  }
1836
1933
  try {
1837
- const schemaPath2 = path5.resolve(process.cwd(), schema);
1934
+ const schemaPath2 = path6.resolve(process.cwd(), schema);
1838
1935
  log(`\u{1F4CB} Loading custom schema from file: ${schemaPath2}`);
1839
1936
  const schemaContent = await fs5.readFile(schemaPath2, "utf-8");
1840
1937
  return schemaContent.trim();
@@ -1848,7 +1945,7 @@ ${"=".repeat(60)}
1848
1945
  if (!sanitizedSchemaName || sanitizedSchemaName !== schema) {
1849
1946
  throw new Error("Invalid schema name");
1850
1947
  }
1851
- const schemaPath = path5.join(process.cwd(), "output", sanitizedSchemaName, "schema.json");
1948
+ const schemaPath = path6.join(process.cwd(), "output", sanitizedSchemaName, "schema.json");
1852
1949
  try {
1853
1950
  const schemaContent = await fs5.readFile(schemaPath, "utf-8");
1854
1951
  return schemaContent.trim();
@@ -2178,7 +2275,7 @@ var PRReviewer = class {
2178
2275
  async reviewPR(owner, repo, prNumber, prInfo, options = {}) {
2179
2276
  const { debug = false, config, checks } = options;
2180
2277
  if (config && checks && checks.length > 0) {
2181
- const { CheckExecutionEngine: CheckExecutionEngine2 } = await import("./check-execution-engine-RORGGGGP.mjs");
2278
+ const { CheckExecutionEngine: CheckExecutionEngine2 } = await import("./check-execution-engine-NMPXJ7FQ.mjs");
2182
2279
  const engine = new CheckExecutionEngine2();
2183
2280
  const { results } = await engine.executeGroupedChecks(
2184
2281
  prInfo,
@@ -2197,14 +2294,66 @@ var PRReviewer = class {
2197
2294
  "No configuration provided. Please create a .visor.yaml file with check definitions. Built-in prompts have been removed - all checks must be explicitly configured."
2198
2295
  );
2199
2296
  }
2297
+ /**
2298
+ * Helper to check if a schema is comment-generating
2299
+ * Comment-generating schemas include:
2300
+ * - Built-in schemas: code-review, overview, plain, text
2301
+ * - Custom schemas with a "text" field in properties
2302
+ */
2303
+ async isCommentGeneratingSchema(schema) {
2304
+ try {
2305
+ if (typeof schema === "string") {
2306
+ if (["code-review", "overview", "plain", "text"].includes(schema)) {
2307
+ return true;
2308
+ }
2309
+ const fs5 = __require("fs").promises;
2310
+ const path6 = __require("path");
2311
+ const sanitizedSchemaName = schema.replace(/[^a-zA-Z0-9-]/g, "");
2312
+ if (!sanitizedSchemaName || sanitizedSchemaName !== schema) {
2313
+ return false;
2314
+ }
2315
+ const schemaPath = path6.join(process.cwd(), "output", sanitizedSchemaName, "schema.json");
2316
+ try {
2317
+ const schemaContent = await fs5.readFile(schemaPath, "utf-8");
2318
+ const schemaObj = JSON.parse(schemaContent);
2319
+ const properties = schemaObj.properties;
2320
+ return !!(properties && "text" in properties);
2321
+ } catch {
2322
+ return false;
2323
+ }
2324
+ } else {
2325
+ const properties = schema.properties;
2326
+ return !!(properties && "text" in properties);
2327
+ }
2328
+ } catch {
2329
+ return false;
2330
+ }
2331
+ }
2332
+ /**
2333
+ * Filter check results to only include those that should post GitHub comments
2334
+ */
2335
+ async filterCommentGeneratingChecks(checkResults, config) {
2336
+ const filtered = [];
2337
+ for (const r of checkResults) {
2338
+ const cfg = config.checks?.[r.checkName];
2339
+ const type = cfg?.type || "ai";
2340
+ const schema = cfg?.schema;
2341
+ let shouldPostComment = false;
2342
+ const isAICheck = type === "ai" || type === "claude-code";
2343
+ if (!schema || schema === "") {
2344
+ shouldPostComment = isAICheck;
2345
+ } else {
2346
+ shouldPostComment = await this.isCommentGeneratingSchema(schema);
2347
+ }
2348
+ if (shouldPostComment) {
2349
+ filtered.push(r);
2350
+ }
2351
+ }
2352
+ return filtered;
2353
+ }
2200
2354
  async postReviewComment(owner, repo, prNumber, groupedResults, options = {}) {
2201
2355
  for (const [groupName, checkResults] of Object.entries(groupedResults)) {
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;
2356
+ const filteredResults = options.config ? await this.filterCommentGeneratingChecks(checkResults, options.config) : checkResults;
2208
2357
  if (!filteredResults || filteredResults.length === 0) {
2209
2358
  continue;
2210
2359
  }
@@ -2337,14 +2486,14 @@ var PRReviewer = class {
2337
2486
  saveDebugArtifact(debug) {
2338
2487
  try {
2339
2488
  const fs5 = __require("fs");
2340
- const path5 = __require("path");
2341
- const debugDir = path5.join(process.cwd(), "debug-artifacts");
2489
+ const path6 = __require("path");
2490
+ const debugDir = path6.join(process.cwd(), "debug-artifacts");
2342
2491
  if (!fs5.existsSync(debugDir)) {
2343
2492
  fs5.mkdirSync(debugDir, { recursive: true });
2344
2493
  }
2345
2494
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
2346
2495
  const filename = `visor-debug-${timestamp}.md`;
2347
- const filepath = path5.join(debugDir, filename);
2496
+ const filepath = path6.join(debugDir, filename);
2348
2497
  const content = [
2349
2498
  `# Visor Debug Information`,
2350
2499
  ``,
@@ -2376,7 +2525,7 @@ var PRReviewer = class {
2376
2525
 
2377
2526
  // src/git-repository-analyzer.ts
2378
2527
  import { simpleGit } from "simple-git";
2379
- import * as path from "path";
2528
+ import * as path2 from "path";
2380
2529
  import * as fs from "fs";
2381
2530
  var MAX_PATCH_SIZE = 50 * 1024;
2382
2531
  var GitRepositoryAnalyzer = class {
@@ -2591,7 +2740,7 @@ ${file.patch}`).join("\n\n");
2591
2740
  console.error(`\u23ED\uFE0F Skipping excluded file: ${file}`);
2592
2741
  continue;
2593
2742
  }
2594
- const filePath = path.join(this.cwd, file);
2743
+ const filePath = path2.join(this.cwd, file);
2595
2744
  const fileChange = await this.analyzeFileChange(file, status2, filePath, includeContext);
2596
2745
  changes.push(fileChange);
2597
2746
  }
@@ -3052,7 +3201,7 @@ var EnvironmentResolver = class {
3052
3201
 
3053
3202
  // src/issue-filter.ts
3054
3203
  import * as fs2 from "fs";
3055
- import * as path2 from "path";
3204
+ import * as path3 from "path";
3056
3205
  var IssueFilter = class {
3057
3206
  fileCache = /* @__PURE__ */ new Map();
3058
3207
  suppressionEnabled;
@@ -3120,7 +3269,7 @@ var IssueFilter = class {
3120
3269
  return this.fileCache.get(filePath);
3121
3270
  }
3122
3271
  try {
3123
- const resolvedPath = path2.isAbsolute(filePath) ? filePath : path2.join(workingDir, filePath);
3272
+ const resolvedPath = path3.isAbsolute(filePath) ? filePath : path3.join(workingDir, filePath);
3124
3273
  if (!fs2.existsSync(resolvedPath)) {
3125
3274
  if (fs2.existsSync(filePath)) {
3126
3275
  const content2 = fs2.readFileSync(filePath, "utf8");
@@ -3148,7 +3297,7 @@ var IssueFilter = class {
3148
3297
 
3149
3298
  // src/providers/ai-check-provider.ts
3150
3299
  import fs3 from "fs/promises";
3151
- import path3 from "path";
3300
+ import path4 from "path";
3152
3301
  var AICheckProvider = class extends CheckProvider {
3153
3302
  aiReviewService;
3154
3303
  liquidEngine;
@@ -3265,7 +3414,7 @@ var AICheckProvider = class extends CheckProvider {
3265
3414
  const hasFileExtension = /\.[a-zA-Z0-9]{1,10}$/i.test(str);
3266
3415
  const hasPathSeparators = /[\/\\]/.test(str);
3267
3416
  const isRelativePath = /^\.{1,2}\//.test(str);
3268
- const isAbsolutePath = path3.isAbsolute(str);
3417
+ const isAbsolutePath = path4.isAbsolute(str);
3269
3418
  const hasTypicalFileChars = /^[a-zA-Z0-9._\-\/\\:~]+$/.test(str);
3270
3419
  if (!(hasFileExtension || isRelativePath || isAbsolutePath || hasPathSeparators)) {
3271
3420
  return false;
@@ -3275,10 +3424,10 @@ var AICheckProvider = class extends CheckProvider {
3275
3424
  }
3276
3425
  try {
3277
3426
  let resolvedPath;
3278
- if (path3.isAbsolute(str)) {
3279
- resolvedPath = path3.normalize(str);
3427
+ if (path4.isAbsolute(str)) {
3428
+ resolvedPath = path4.normalize(str);
3280
3429
  } else {
3281
- resolvedPath = path3.resolve(process.cwd(), str);
3430
+ resolvedPath = path4.resolve(process.cwd(), str);
3282
3431
  }
3283
3432
  const fs5 = __require("fs").promises;
3284
3433
  try {
@@ -3299,14 +3448,14 @@ var AICheckProvider = class extends CheckProvider {
3299
3448
  throw new Error("Prompt file must have .liquid extension");
3300
3449
  }
3301
3450
  let resolvedPath;
3302
- if (path3.isAbsolute(promptPath)) {
3451
+ if (path4.isAbsolute(promptPath)) {
3303
3452
  resolvedPath = promptPath;
3304
3453
  } else {
3305
- resolvedPath = path3.resolve(process.cwd(), promptPath);
3454
+ resolvedPath = path4.resolve(process.cwd(), promptPath);
3306
3455
  }
3307
- if (!path3.isAbsolute(promptPath)) {
3308
- const normalizedPath = path3.normalize(resolvedPath);
3309
- const currentDir = path3.resolve(process.cwd());
3456
+ if (!path4.isAbsolute(promptPath)) {
3457
+ const normalizedPath = path4.normalize(resolvedPath);
3458
+ const currentDir = path4.resolve(process.cwd());
3310
3459
  if (!normalizedPath.startsWith(currentDir)) {
3311
3460
  throw new Error("Invalid prompt file path: path traversal detected");
3312
3461
  }
@@ -4167,7 +4316,8 @@ var LogCheckProvider = class extends CheckProvider {
4167
4316
  dependencyResults,
4168
4317
  includePrContext,
4169
4318
  includeDependencies,
4170
- includeMetadata
4319
+ includeMetadata,
4320
+ config.__outputHistory
4171
4321
  );
4172
4322
  const renderedMessage = await this.liquid.parseAndRender(message, templateContext);
4173
4323
  const logOutput = this.formatLogOutput(
@@ -4188,7 +4338,7 @@ var LogCheckProvider = class extends CheckProvider {
4188
4338
  logOutput
4189
4339
  };
4190
4340
  }
4191
- buildTemplateContext(prInfo, dependencyResults, _includePrContext = true, _includeDependencies = true, includeMetadata = true) {
4341
+ buildTemplateContext(prInfo, dependencyResults, _includePrContext = true, _includeDependencies = true, includeMetadata = true, outputHistory) {
4192
4342
  const context = {};
4193
4343
  context.pr = {
4194
4344
  number: prInfo.number,
@@ -4212,6 +4362,7 @@ var LogCheckProvider = class extends CheckProvider {
4212
4362
  if (dependencyResults) {
4213
4363
  const dependencies = {};
4214
4364
  const outputs = {};
4365
+ const history = {};
4215
4366
  context.dependencyCount = dependencyResults.size;
4216
4367
  for (const [checkName, result] of dependencyResults.entries()) {
4217
4368
  dependencies[checkName] = {
@@ -4222,6 +4373,12 @@ var LogCheckProvider = class extends CheckProvider {
4222
4373
  const summary = result;
4223
4374
  outputs[checkName] = summary.output !== void 0 ? summary.output : summary;
4224
4375
  }
4376
+ if (outputHistory) {
4377
+ for (const [checkName, historyArray] of outputHistory) {
4378
+ history[checkName] = historyArray;
4379
+ }
4380
+ }
4381
+ outputs.history = history;
4225
4382
  context.dependencies = dependencies;
4226
4383
  context.outputs = outputs;
4227
4384
  }
@@ -4581,7 +4738,7 @@ ${cfg.value_js}
4581
4738
 
4582
4739
  // src/providers/claude-code-check-provider.ts
4583
4740
  import fs4 from "fs/promises";
4584
- import path4 from "path";
4741
+ import path5 from "path";
4585
4742
 
4586
4743
  // src/providers/claude-code-types.ts
4587
4744
  async function safeImport(moduleName) {
@@ -4742,7 +4899,7 @@ var ClaudeCodeCheckProvider = class extends CheckProvider {
4742
4899
  const hasFileExtension = /\.[a-zA-Z0-9]{1,10}$/i.test(str);
4743
4900
  const hasPathSeparators = /[\/\\]/.test(str);
4744
4901
  const isRelativePath = /^\.{1,2}\//.test(str);
4745
- const isAbsolutePath = path4.isAbsolute(str);
4902
+ const isAbsolutePath = path5.isAbsolute(str);
4746
4903
  const hasTypicalFileChars = /^[a-zA-Z0-9._\-\/\\:~]+$/.test(str);
4747
4904
  if (!(hasFileExtension || isRelativePath || isAbsolutePath || hasPathSeparators)) {
4748
4905
  return false;
@@ -4752,10 +4909,10 @@ var ClaudeCodeCheckProvider = class extends CheckProvider {
4752
4909
  }
4753
4910
  try {
4754
4911
  let resolvedPath;
4755
- if (path4.isAbsolute(str)) {
4756
- resolvedPath = path4.normalize(str);
4912
+ if (path5.isAbsolute(str)) {
4913
+ resolvedPath = path5.normalize(str);
4757
4914
  } else {
4758
- resolvedPath = path4.resolve(process.cwd(), str);
4915
+ resolvedPath = path5.resolve(process.cwd(), str);
4759
4916
  }
4760
4917
  try {
4761
4918
  const stat = await fs4.stat(resolvedPath);
@@ -4775,14 +4932,14 @@ var ClaudeCodeCheckProvider = class extends CheckProvider {
4775
4932
  throw new Error("Prompt file must have .liquid extension");
4776
4933
  }
4777
4934
  let resolvedPath;
4778
- if (path4.isAbsolute(promptPath)) {
4935
+ if (path5.isAbsolute(promptPath)) {
4779
4936
  resolvedPath = promptPath;
4780
4937
  } else {
4781
- resolvedPath = path4.resolve(process.cwd(), promptPath);
4938
+ resolvedPath = path5.resolve(process.cwd(), promptPath);
4782
4939
  }
4783
- if (!path4.isAbsolute(promptPath)) {
4784
- const normalizedPath = path4.normalize(resolvedPath);
4785
- const currentDir = path4.resolve(process.cwd());
4940
+ if (!path5.isAbsolute(promptPath)) {
4941
+ const normalizedPath = path5.normalize(resolvedPath);
4942
+ const currentDir = path5.resolve(process.cwd());
4786
4943
  if (!normalizedPath.startsWith(currentDir)) {
4787
4944
  throw new Error("Invalid prompt file path: path traversal detected");
4788
4945
  }
@@ -5093,7 +5250,10 @@ var CommandCheckProvider = class extends CheckProvider {
5093
5250
  },
5094
5251
  files: prInfo.files,
5095
5252
  fileCount: prInfo.files.length,
5096
- outputs: this.buildOutputContext(dependencyResults),
5253
+ outputs: this.buildOutputContext(
5254
+ dependencyResults,
5255
+ config.__outputHistory
5256
+ ),
5097
5257
  env: this.getSafeEnvironmentVariables()
5098
5258
  };
5099
5259
  logger.debug(
@@ -5819,16 +5979,23 @@ ${stderrOutput}` : `Command execution failed: ${errorMessage}`;
5819
5979
  };
5820
5980
  }
5821
5981
  }
5822
- buildOutputContext(dependencyResults) {
5982
+ buildOutputContext(dependencyResults, outputHistory) {
5823
5983
  if (!dependencyResults) {
5824
5984
  return {};
5825
5985
  }
5826
5986
  const outputs = {};
5987
+ const history = {};
5827
5988
  for (const [checkName, result] of dependencyResults) {
5828
5989
  const summary = result;
5829
5990
  const value = summary.output !== void 0 ? summary.output : summary;
5830
5991
  outputs[checkName] = this.makeJsonSmart(value);
5831
5992
  }
5993
+ if (outputHistory) {
5994
+ for (const [checkName, historyArray] of outputHistory) {
5995
+ history[checkName] = historyArray.map((val) => this.makeJsonSmart(val));
5996
+ }
5997
+ }
5998
+ outputs.history = history;
5832
5999
  return outputs;
5833
6000
  }
5834
6001
  /**
@@ -6377,7 +6544,12 @@ var MemoryCheckProvider = class extends CheckProvider {
6377
6544
  const key = config.key;
6378
6545
  const namespace = config.namespace;
6379
6546
  const memoryStore = MemoryStore.getInstance();
6380
- const templateContext = this.buildTemplateContext(prInfo, dependencyResults, memoryStore);
6547
+ const templateContext = this.buildTemplateContext(
6548
+ prInfo,
6549
+ dependencyResults,
6550
+ memoryStore,
6551
+ config.__outputHistory
6552
+ );
6381
6553
  let result;
6382
6554
  try {
6383
6555
  switch (operation) {
@@ -6660,7 +6832,7 @@ var MemoryCheckProvider = class extends CheckProvider {
6660
6832
  /**
6661
6833
  * Build template context for Liquid and JS evaluation
6662
6834
  */
6663
- buildTemplateContext(prInfo, dependencyResults, memoryStore) {
6835
+ buildTemplateContext(prInfo, dependencyResults, memoryStore, outputHistory) {
6664
6836
  const context = {};
6665
6837
  context.pr = {
6666
6838
  number: prInfo.number,
@@ -6679,14 +6851,21 @@ var MemoryCheckProvider = class extends CheckProvider {
6679
6851
  changes: f.changes
6680
6852
  }))
6681
6853
  };
6854
+ const outputs = {};
6855
+ const history = {};
6682
6856
  if (dependencyResults) {
6683
- const outputs = {};
6684
6857
  for (const [checkName, result] of dependencyResults.entries()) {
6685
6858
  const summary = result;
6686
6859
  outputs[checkName] = summary.output !== void 0 ? summary.output : summary;
6687
6860
  }
6688
- context.outputs = outputs;
6689
6861
  }
6862
+ if (outputHistory) {
6863
+ for (const [checkName, historyArray] of outputHistory) {
6864
+ history[checkName] = historyArray;
6865
+ }
6866
+ }
6867
+ outputs.history = history;
6868
+ context.outputs = outputs;
6690
6869
  if (memoryStore) {
6691
6870
  context.memory = {
6692
6871
  get: (key, ns) => memoryStore.get(key, ns),
@@ -7424,10 +7603,6 @@ var FailureConditionEvaluator = class _FailureConditionEvaluator {
7424
7603
  if (!Array.isArray(issues2)) return false;
7425
7604
  return issues2.some((issue) => issue.file?.includes(pattern));
7426
7605
  };
7427
- const hasSuggestion = (suggestions2, text) => {
7428
- if (!Array.isArray(suggestions2)) return false;
7429
- return suggestions2.some((s) => s.toLowerCase().includes(text.toLowerCase()));
7430
- };
7431
7606
  const hasIssueWith = hasIssue;
7432
7607
  const hasFileWith = hasFileMatching;
7433
7608
  const permissionHelpers = createPermissionHelpers(
@@ -7442,7 +7617,6 @@ var FailureConditionEvaluator = class _FailureConditionEvaluator {
7442
7617
  const isFirstTimer = permissionHelpers.isFirstTimer;
7443
7618
  const output = context.output || {};
7444
7619
  const issues = output.issues || [];
7445
- const suggestions = [];
7446
7620
  const metadata = context.metadata || {
7447
7621
  checkName: context.checkName || "",
7448
7622
  schema: context.schema || "",
@@ -7486,7 +7660,6 @@ var FailureConditionEvaluator = class _FailureConditionEvaluator {
7486
7660
  memory: memoryAccessor,
7487
7661
  // Legacy compatibility variables
7488
7662
  issues,
7489
- suggestions,
7490
7663
  metadata,
7491
7664
  criticalIssues,
7492
7665
  errorIssues,
@@ -7515,7 +7688,6 @@ var FailureConditionEvaluator = class _FailureConditionEvaluator {
7515
7688
  hasIssue,
7516
7689
  countIssues,
7517
7690
  hasFileMatching,
7518
- hasSuggestion,
7519
7691
  hasIssueWith,
7520
7692
  hasFileWith,
7521
7693
  // Permission helpers
@@ -8019,11 +8191,7 @@ Please check your configuration and try again.`
8019
8191
  });
8020
8192
  }
8021
8193
  sections.push("");
8022
- sections.push("---");
8023
- sections.push("");
8024
- sections.push(
8025
- "*Powered by [Visor](https://probelabs.com/visor) from [Probelabs](https://probelabs.com)*"
8026
- );
8194
+ sections.push(generateFooter());
8027
8195
  return sections.join("\n");
8028
8196
  }
8029
8197
  /**
@@ -8263,6 +8431,8 @@ var CheckExecutionEngine = class _CheckExecutionEngine {
8263
8431
  webhookContext;
8264
8432
  routingSandbox;
8265
8433
  executionStats = /* @__PURE__ */ new Map();
8434
+ // Track history of all outputs for each check (useful for loops and goto)
8435
+ outputHistory = /* @__PURE__ */ new Map();
8266
8436
  // Event override to simulate alternate event (used during routing goto)
8267
8437
  routingEventOverride;
8268
8438
  // Cached GitHub context for context elevation when running in Actions
@@ -8506,6 +8676,8 @@ ${expr}
8506
8676
  transform_js: targetCfg.transform_js,
8507
8677
  env: targetCfg.env,
8508
8678
  forEach: targetCfg.forEach,
8679
+ // Pass output history for loop/goto scenarios
8680
+ __outputHistory: this.outputHistory,
8509
8681
  // Include provider-specific keys (e.g., op/values for github)
8510
8682
  ...targetCfg,
8511
8683
  ai: {
@@ -8573,6 +8745,10 @@ ${expr}
8573
8745
  timestamp: Date.now()
8574
8746
  }));
8575
8747
  const enriched = { ...r, issues: enrichedIssues };
8748
+ const enrichedWithOutput = enriched;
8749
+ if (enrichedWithOutput.output !== void 0) {
8750
+ this.trackOutputHistory(target, enrichedWithOutput.output);
8751
+ }
8576
8752
  resultsMap?.set(target, enriched);
8577
8753
  if (debug) log2(`\u{1F527} Debug: inline executed '${target}', issues: ${enrichedIssues.length}`);
8578
8754
  return enriched;
@@ -9341,6 +9517,8 @@ ${expr}
9341
9517
  ai_model: checkConfig.ai_model || config.ai_model,
9342
9518
  // Pass claude_code config if present
9343
9519
  claude_code: checkConfig.claude_code,
9520
+ // Pass output history for loop/goto scenarios
9521
+ __outputHistory: this.outputHistory,
9344
9522
  // Pass any provider-specific config
9345
9523
  ...checkConfig
9346
9524
  };
@@ -9377,10 +9555,14 @@ ${expr}
9377
9555
  }
9378
9556
  }
9379
9557
  const content = await this.renderCheckContent(checkName, result, checkConfig, prInfo);
9558
+ let group = checkConfig.group || "default";
9559
+ if (config?.output?.pr_comment?.group_by === "check" && !checkConfig.group) {
9560
+ group = checkName;
9561
+ }
9380
9562
  return {
9381
9563
  checkName,
9382
9564
  content,
9383
- group: checkConfig.group || "default",
9565
+ group,
9384
9566
  output: result.output,
9385
9567
  debug: result.debug,
9386
9568
  issues: result.issues
@@ -9519,16 +9701,19 @@ ${expr}
9519
9701
  }
9520
9702
  ];
9521
9703
  }
9704
+ let group = checkConfig.group || "default";
9705
+ if (config?.output?.pr_comment?.group_by === "check" && !checkConfig.group) {
9706
+ group = checkName;
9707
+ }
9522
9708
  const checkResult = {
9523
9709
  checkName,
9524
9710
  content,
9525
- group: checkConfig.group || "default",
9711
+ group,
9526
9712
  output: checkSummary.output,
9527
9713
  debug: reviewSummary.debug,
9528
9714
  issues: issuesForCheck
9529
9715
  // Include structured issues + rendering error if any
9530
9716
  };
9531
- const group = checkResult.group;
9532
9717
  if (!groupedResults[group]) {
9533
9718
  groupedResults[group] = [];
9534
9719
  }
@@ -9546,7 +9731,7 @@ ${expr}
9546
9731
  * - Enforcing .liquid file extension
9547
9732
  */
9548
9733
  async validateTemplatePath(templatePath) {
9549
- const path5 = await import("path");
9734
+ const path6 = await import("path");
9550
9735
  if (!templatePath || typeof templatePath !== "string" || templatePath.trim() === "") {
9551
9736
  throw new Error("Template path must be a non-empty string");
9552
9737
  }
@@ -9556,7 +9741,7 @@ ${expr}
9556
9741
  if (!templatePath.endsWith(".liquid")) {
9557
9742
  throw new Error("Template file must have .liquid extension");
9558
9743
  }
9559
- if (path5.isAbsolute(templatePath)) {
9744
+ if (path6.isAbsolute(templatePath)) {
9560
9745
  throw new Error("Template path must be relative to project directory");
9561
9746
  }
9562
9747
  if (templatePath.includes("..")) {
@@ -9570,14 +9755,14 @@ ${expr}
9570
9755
  if (!projectRoot || typeof projectRoot !== "string") {
9571
9756
  throw new Error("Unable to determine project root directory");
9572
9757
  }
9573
- const resolvedPath = path5.resolve(projectRoot, templatePath);
9574
- const resolvedProjectRoot = path5.resolve(projectRoot);
9758
+ const resolvedPath = path6.resolve(projectRoot, templatePath);
9759
+ const resolvedProjectRoot = path6.resolve(projectRoot);
9575
9760
  if (!resolvedPath || !resolvedProjectRoot || resolvedPath === "" || resolvedProjectRoot === "") {
9576
9761
  throw new Error(
9577
9762
  `Unable to resolve template path: projectRoot="${projectRoot}", templatePath="${templatePath}", resolvedPath="${resolvedPath}", resolvedProjectRoot="${resolvedProjectRoot}"`
9578
9763
  );
9579
9764
  }
9580
- if (!resolvedPath.startsWith(resolvedProjectRoot + path5.sep) && resolvedPath !== resolvedProjectRoot) {
9765
+ if (!resolvedPath.startsWith(resolvedProjectRoot + path6.sep) && resolvedPath !== resolvedProjectRoot) {
9581
9766
  throw new Error("Template path escapes project directory");
9582
9767
  }
9583
9768
  return resolvedPath;
@@ -9622,7 +9807,7 @@ ${expr}
9622
9807
  }
9623
9808
  const { createExtendedLiquid: createExtendedLiquid2 } = await import("./liquid-extensions-GMEGEGC3.mjs");
9624
9809
  const fs5 = await import("fs/promises");
9625
- const path5 = await import("path");
9810
+ const path6 = await import("path");
9626
9811
  const liquid = createExtendedLiquid2({
9627
9812
  trimTagLeft: false,
9628
9813
  trimTagRight: false,
@@ -9656,7 +9841,7 @@ ${expr}
9656
9841
  if (!sanitizedSchema) {
9657
9842
  throw new Error("Invalid schema name");
9658
9843
  }
9659
- const templatePath = path5.join(__dirname, `../output/${sanitizedSchema}/template.liquid`);
9844
+ const templatePath = path6.join(__dirname, `../output/${sanitizedSchema}/template.liquid`);
9660
9845
  templateContent = await fs5.readFile(templatePath, "utf-8");
9661
9846
  if (sanitizedSchema === "issue-assistant") {
9662
9847
  enrichAssistantContext = true;
@@ -10429,6 +10614,10 @@ ${expr}
10429
10614
  itemResult.issues || [],
10430
10615
  itemResult.output
10431
10616
  );
10617
+ const itemOutput = itemResult.output;
10618
+ if (itemOutput !== void 0) {
10619
+ this.trackOutputHistory(checkName, itemOutput);
10620
+ }
10432
10621
  const descendantSet = (() => {
10433
10622
  const visited = /* @__PURE__ */ new Set();
10434
10623
  const stack = [checkName];
@@ -11059,6 +11248,10 @@ ${error.stack || ""}` : String(error);
11059
11248
  ]);
11060
11249
  } catch {
11061
11250
  }
11251
+ const reviewResultWithOutput = reviewResult;
11252
+ if (reviewResultWithOutput.output !== void 0) {
11253
+ this.trackOutputHistory(checkName, reviewResultWithOutput.output);
11254
+ }
11062
11255
  results.set(checkName, reviewResult);
11063
11256
  } else {
11064
11257
  const errorSummary = {
@@ -12302,6 +12495,16 @@ ${result.value.result.debug.rawResponse}`;
12302
12495
  stats.outputsProduced = (stats.outputsProduced || 0) + 1;
12303
12496
  }
12304
12497
  }
12498
+ /**
12499
+ * Track output in history for loop/goto scenarios
12500
+ */
12501
+ trackOutputHistory(checkName, output) {
12502
+ if (output === void 0) return;
12503
+ if (!this.outputHistory.has(checkName)) {
12504
+ this.outputHistory.set(checkName, []);
12505
+ }
12506
+ this.outputHistory.get(checkName).push(output);
12507
+ }
12305
12508
  /**
12306
12509
  * Record that a check was skipped
12307
12510
  */
@@ -12494,4 +12697,4 @@ ${result.value.result.debug.rawResponse}`;
12494
12697
  export {
12495
12698
  CheckExecutionEngine
12496
12699
  };
12497
- //# sourceMappingURL=chunk-Z47UECAT.mjs.map
12700
+ //# sourceMappingURL=chunk-Q4S5A5TO.mjs.map