@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
package/dist/sdk/sdk.js CHANGED
@@ -136,6 +136,33 @@ var init_logger = __esm({
136
136
  }
137
137
  });
138
138
 
139
+ // src/footer.ts
140
+ function generateFooter(options = {}) {
141
+ const { includeMetadata, includeSeparator = true } = options;
142
+ const parts = [];
143
+ if (includeSeparator) {
144
+ parts.push("---");
145
+ parts.push("");
146
+ }
147
+ parts.push(
148
+ "*Powered by [Visor](https://probelabs.com/visor) from [Probelabs](https://probelabs.com)*"
149
+ );
150
+ if (includeMetadata) {
151
+ const { lastUpdated, triggeredBy, commitSha } = includeMetadata;
152
+ const commitInfo = commitSha ? ` | Commit: ${commitSha.substring(0, 7)}` : "";
153
+ parts.push("");
154
+ parts.push(`*Last updated: ${lastUpdated} | Triggered by: ${triggeredBy}${commitInfo}*`);
155
+ }
156
+ parts.push("");
157
+ parts.push("\u{1F4A1} **TIP:** You can chat with Visor using `/visor ask <your question>`");
158
+ return parts.join("\n");
159
+ }
160
+ var init_footer = __esm({
161
+ "src/footer.ts"() {
162
+ "use strict";
163
+ }
164
+ });
165
+
139
166
  // src/github-comments.ts
140
167
  var import_uuid, CommentManager;
141
168
  var init_github_comments = __esm({
@@ -143,6 +170,7 @@ var init_github_comments = __esm({
143
170
  "use strict";
144
171
  import_uuid = require("uuid");
145
172
  init_logger();
173
+ init_footer();
146
174
  CommentManager = class {
147
175
  octokit;
148
176
  retryConfig;
@@ -244,15 +272,17 @@ var init_github_comments = __esm({
244
272
  */
245
273
  formatCommentWithMetadata(content, metadata) {
246
274
  const { commentId, lastUpdated, triggeredBy, commitSha } = metadata;
247
- const commitInfo = commitSha ? ` | Commit: ${commitSha.substring(0, 7)}` : "";
275
+ const footer = generateFooter({
276
+ includeMetadata: {
277
+ lastUpdated,
278
+ triggeredBy,
279
+ commitSha
280
+ }
281
+ });
248
282
  return `<!-- visor-comment-id:${commentId} -->
249
283
  ${content}
250
284
 
251
- ---
252
-
253
- *Powered by [Visor](https://probelabs.com/visor) from [Probelabs](https://probelabs.com)*
254
-
255
- *Last updated: ${lastUpdated} | Triggered by: ${triggeredBy}${commitInfo}*
285
+ ${footer}
256
286
  <!-- /visor-comment-id:${commentId} -->`;
257
287
  }
258
288
  /**
@@ -718,18 +748,81 @@ var init_session_registry = __esm({
718
748
  }
719
749
  });
720
750
 
751
+ // src/utils/diff-processor.ts
752
+ async function processDiffWithOutline(diffContent) {
753
+ if (!diffContent || diffContent.trim().length === 0) {
754
+ return diffContent;
755
+ }
756
+ try {
757
+ const originalProbePath = process.env.PROBE_PATH;
758
+ const fs12 = require("fs");
759
+ const possiblePaths = [
760
+ // Relative to current working directory (most common in production)
761
+ path2.join(process.cwd(), "node_modules/@probelabs/probe/bin/probe-binary"),
762
+ // Relative to __dirname (for unbundled development)
763
+ path2.join(__dirname, "../..", "node_modules/@probelabs/probe/bin/probe-binary"),
764
+ // Relative to dist directory (for bundled CLI)
765
+ path2.join(__dirname, "node_modules/@probelabs/probe/bin/probe-binary")
766
+ ];
767
+ let probeBinaryPath;
768
+ for (const candidatePath of possiblePaths) {
769
+ if (fs12.existsSync(candidatePath)) {
770
+ probeBinaryPath = candidatePath;
771
+ break;
772
+ }
773
+ }
774
+ if (!probeBinaryPath) {
775
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
776
+ console.error("Probe binary not found. Tried:", possiblePaths);
777
+ }
778
+ return diffContent;
779
+ }
780
+ process.env.PROBE_PATH = probeBinaryPath;
781
+ const extractPromise = (0, import_probe2.extract)({
782
+ content: diffContent,
783
+ format: "outline-diff",
784
+ allowTests: true
785
+ // Allow test files and test code blocks in extraction results
786
+ });
787
+ const timeoutPromise = new Promise((_, reject) => {
788
+ setTimeout(() => reject(new Error("Extract timeout after 30s")), 3e4);
789
+ });
790
+ const result = await Promise.race([extractPromise, timeoutPromise]);
791
+ if (originalProbePath !== void 0) {
792
+ process.env.PROBE_PATH = originalProbePath;
793
+ } else {
794
+ delete process.env.PROBE_PATH;
795
+ }
796
+ return typeof result === "string" ? result : JSON.stringify(result);
797
+ } catch (error) {
798
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
799
+ console.error("Failed to process diff with outline-diff format:", error);
800
+ }
801
+ return diffContent;
802
+ }
803
+ }
804
+ var import_probe2, path2;
805
+ var init_diff_processor = __esm({
806
+ "src/utils/diff-processor.ts"() {
807
+ "use strict";
808
+ import_probe2 = require("@probelabs/probe");
809
+ path2 = __toESM(require("path"));
810
+ }
811
+ });
812
+
721
813
  // src/ai-review-service.ts
722
814
  function log(...args) {
723
815
  logger.debug(args.join(" "));
724
816
  }
725
- var import_probe2, AIReviewService;
817
+ var import_probe3, AIReviewService;
726
818
  var init_ai_review_service = __esm({
727
819
  "src/ai-review-service.ts"() {
728
820
  "use strict";
729
- import_probe2 = require("@probelabs/probe");
821
+ import_probe3 = require("@probelabs/probe");
730
822
  init_session_registry();
731
823
  init_logger();
732
824
  init_tracer_init();
825
+ init_diff_processor();
733
826
  AIReviewService = class {
734
827
  config;
735
828
  sessionRegistry;
@@ -768,7 +861,7 @@ var init_ai_review_service = __esm({
768
861
  /**
769
862
  * Execute AI review using probe agent
770
863
  */
771
- async executeReview(prInfo, customPrompt, schema, _checkName, sessionId) {
864
+ async executeReview(prInfo, customPrompt, schema, checkName, sessionId) {
772
865
  const startTime = Date.now();
773
866
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
774
867
  const prompt = await this.buildCustomPrompt(prInfo, customPrompt, schema);
@@ -825,7 +918,7 @@ var init_ai_review_service = __esm({
825
918
  prompt,
826
919
  schema,
827
920
  debugInfo,
828
- _checkName,
921
+ checkName,
829
922
  sessionId
830
923
  );
831
924
  const processingTime = Date.now() - startTime;
@@ -985,9 +1078,9 @@ var init_ai_review_service = __esm({
985
1078
  */
986
1079
  async buildCustomPrompt(prInfo, customInstructions, schema, options) {
987
1080
  const skipPRContext = options?.skipPRContext === true;
988
- const prContext = skipPRContext ? "" : this.formatPRContext(prInfo);
989
- const isIssue = prInfo.isIssue === true;
990
1081
  const isCodeReviewSchema = schema === "code-review";
1082
+ const prContext = skipPRContext ? "" : await this.formatPRContext(prInfo, isCodeReviewSchema);
1083
+ const isIssue = prInfo.isIssue === true;
991
1084
  if (isIssue) {
992
1085
  if (skipPRContext) {
993
1086
  return `<instructions>
@@ -1076,7 +1169,7 @@ ${prContext}
1076
1169
  /**
1077
1170
  * Format PR or Issue context for the AI using XML structure
1078
1171
  */
1079
- formatPRContext(prInfo) {
1172
+ async formatPRContext(prInfo, isCodeReviewSchema) {
1080
1173
  const prContextInfo = prInfo;
1081
1174
  const isIssue = prContextInfo.isIssue === true;
1082
1175
  const isPRContext = prContextInfo.isPRContext === true;
@@ -1158,7 +1251,12 @@ ${this.escapeXml(prInfo.body)}
1158
1251
  }
1159
1252
  const issueComments = prInfo.comments;
1160
1253
  if (issueComments && issueComments.length > 0) {
1161
- const historicalComments = triggeringComment2 ? issueComments.filter((c) => c.id !== triggeringComment2.id) : issueComments;
1254
+ let historicalComments = triggeringComment2 ? issueComments.filter((c) => c.id !== triggeringComment2.id) : issueComments;
1255
+ if (isCodeReviewSchema) {
1256
+ historicalComments = historicalComments.filter(
1257
+ (c) => !c.body || !c.body.includes("visor-comment-id:pr-review-")
1258
+ );
1259
+ }
1162
1260
  if (historicalComments.length > 0) {
1163
1261
  context3 += `
1164
1262
  <!-- Previous comments in chronological order (excluding triggering comment) -->
@@ -1200,24 +1298,27 @@ ${this.escapeXml(prInfo.body)}
1200
1298
  }
1201
1299
  if (includeCodeContext) {
1202
1300
  if (prInfo.fullDiff) {
1301
+ const processedFullDiff = await processDiffWithOutline(prInfo.fullDiff);
1203
1302
  context2 += `
1204
- <!-- Complete unified diff showing all changes in the pull request -->
1303
+ <!-- Complete unified diff showing all changes in the pull request (processed with outline-diff) -->
1205
1304
  <full_diff>
1206
- ${this.escapeXml(prInfo.fullDiff)}
1305
+ ${this.escapeXml(processedFullDiff)}
1207
1306
  </full_diff>`;
1208
1307
  }
1209
1308
  if (prInfo.isIncremental) {
1210
1309
  if (prInfo.commitDiff && prInfo.commitDiff.length > 0) {
1310
+ const processedCommitDiff = await processDiffWithOutline(prInfo.commitDiff);
1211
1311
  context2 += `
1212
- <!-- Diff of only the latest commit for incremental analysis -->
1312
+ <!-- Diff of only the latest commit for incremental analysis (processed with outline-diff) -->
1213
1313
  <commit_diff>
1214
- ${this.escapeXml(prInfo.commitDiff)}
1314
+ ${this.escapeXml(processedCommitDiff)}
1215
1315
  </commit_diff>`;
1216
1316
  } else {
1317
+ const processedFallbackDiff = prInfo.fullDiff ? await processDiffWithOutline(prInfo.fullDiff) : "";
1217
1318
  context2 += `
1218
- <!-- Commit diff could not be retrieved - falling back to full diff analysis -->
1319
+ <!-- Commit diff could not be retrieved - falling back to full diff analysis (processed with outline-diff) -->
1219
1320
  <commit_diff>
1220
- ${prInfo.fullDiff ? this.escapeXml(prInfo.fullDiff) : ""}
1321
+ ${this.escapeXml(processedFallbackDiff)}
1221
1322
  </commit_diff>`;
1222
1323
  }
1223
1324
  }
@@ -1253,7 +1354,12 @@ ${prInfo.fullDiff ? this.escapeXml(prInfo.fullDiff) : ""}
1253
1354
  }
1254
1355
  const prComments = prInfo.comments;
1255
1356
  if (prComments && prComments.length > 0) {
1256
- const historicalComments = triggeringComment ? prComments.filter((c) => c.id !== triggeringComment.id) : prComments;
1357
+ let historicalComments = triggeringComment ? prComments.filter((c) => c.id !== triggeringComment.id) : prComments;
1358
+ if (isCodeReviewSchema) {
1359
+ historicalComments = historicalComments.filter(
1360
+ (c) => !c.body || !c.body.includes("visor-comment-id:pr-review-")
1361
+ );
1362
+ }
1257
1363
  if (historicalComments.length > 0) {
1258
1364
  context2 += `
1259
1365
  <!-- Previous PR comments in chronological order (excluding triggering comment) -->
@@ -1324,7 +1430,7 @@ ${schemaString}`);
1324
1430
  if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
1325
1431
  try {
1326
1432
  const fs12 = require("fs");
1327
- const path12 = require("path");
1433
+ const path13 = require("path");
1328
1434
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1329
1435
  const provider = this.config.provider || "auto";
1330
1436
  const model = this.config.model || "default";
@@ -1438,16 +1544,16 @@ ${"=".repeat(60)}
1438
1544
  `;
1439
1545
  readableVersion += `${"=".repeat(60)}
1440
1546
  `;
1441
- const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path12.join(process.cwd(), "debug-artifacts");
1547
+ const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path13.join(process.cwd(), "debug-artifacts");
1442
1548
  if (!fs12.existsSync(debugArtifactsDir)) {
1443
1549
  fs12.mkdirSync(debugArtifactsDir, { recursive: true });
1444
1550
  }
1445
- const debugFile = path12.join(
1551
+ const debugFile = path13.join(
1446
1552
  debugArtifactsDir,
1447
1553
  `prompt-${_checkName || "unknown"}-${timestamp}.json`
1448
1554
  );
1449
1555
  fs12.writeFileSync(debugFile, debugJson, "utf-8");
1450
- const readableFile = path12.join(
1556
+ const readableFile = path13.join(
1451
1557
  debugArtifactsDir,
1452
1558
  `prompt-${_checkName || "unknown"}-${timestamp}.txt`
1453
1559
  );
@@ -1484,7 +1590,7 @@ ${"=".repeat(60)}
1484
1590
  if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
1485
1591
  try {
1486
1592
  const fs12 = require("fs");
1487
- const path12 = require("path");
1593
+ const path13 = require("path");
1488
1594
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1489
1595
  const agentAny2 = agent;
1490
1596
  let fullHistory = [];
@@ -1495,8 +1601,8 @@ ${"=".repeat(60)}
1495
1601
  } else if (agentAny2._messages) {
1496
1602
  fullHistory = agentAny2._messages;
1497
1603
  }
1498
- const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path12.join(process.cwd(), "debug-artifacts");
1499
- const sessionFile = path12.join(
1604
+ const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path13.join(process.cwd(), "debug-artifacts");
1605
+ const sessionFile = path13.join(
1500
1606
  debugArtifactsDir,
1501
1607
  `session-${_checkName || "unknown"}-${timestamp}.json`
1502
1608
  );
@@ -1511,7 +1617,7 @@ ${"=".repeat(60)}
1511
1617
  latestResponse: response
1512
1618
  };
1513
1619
  fs12.writeFileSync(sessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
1514
- const sessionTxtFile = path12.join(
1620
+ const sessionTxtFile = path13.join(
1515
1621
  debugArtifactsDir,
1516
1622
  `session-${_checkName || "unknown"}-${timestamp}.txt`
1517
1623
  );
@@ -1555,10 +1661,10 @@ ${"=".repeat(60)}
1555
1661
  if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
1556
1662
  try {
1557
1663
  const fs12 = require("fs");
1558
- const path12 = require("path");
1664
+ const path13 = require("path");
1559
1665
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1560
- const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path12.join(process.cwd(), "debug-artifacts");
1561
- const responseFile = path12.join(
1666
+ const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path13.join(process.cwd(), "debug-artifacts");
1667
+ const responseFile = path13.join(
1562
1668
  debugArtifactsDir,
1563
1669
  `response-${_checkName || "unknown"}-${timestamp}.txt`
1564
1670
  );
@@ -1696,7 +1802,7 @@ ${"=".repeat(60)}
1696
1802
  if (this.config.model) {
1697
1803
  options.model = this.config.model;
1698
1804
  }
1699
- const agent = new import_probe2.ProbeAgent(options);
1805
+ const agent = new import_probe3.ProbeAgent(options);
1700
1806
  log("\u{1F680} Calling ProbeAgent...");
1701
1807
  let schemaString = void 0;
1702
1808
  let effectiveSchema = typeof schema === "object" ? "custom" : schema;
@@ -1730,7 +1836,7 @@ ${schemaString}`);
1730
1836
  if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
1731
1837
  try {
1732
1838
  const fs12 = require("fs");
1733
- const path12 = require("path");
1839
+ const path13 = require("path");
1734
1840
  const os = require("os");
1735
1841
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1736
1842
  const debugData = {
@@ -1804,21 +1910,21 @@ ${"=".repeat(60)}
1804
1910
  readableVersion += `${"=".repeat(60)}
1805
1911
  `;
1806
1912
  const tempDir = os.tmpdir();
1807
- const promptFile = path12.join(tempDir, `visor-prompt-${timestamp}.txt`);
1913
+ const promptFile = path13.join(tempDir, `visor-prompt-${timestamp}.txt`);
1808
1914
  fs12.writeFileSync(promptFile, prompt, "utf-8");
1809
1915
  log(`
1810
1916
  \u{1F4BE} Prompt saved to: ${promptFile}`);
1811
- const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path12.join(process.cwd(), "debug-artifacts");
1917
+ const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path13.join(process.cwd(), "debug-artifacts");
1812
1918
  try {
1813
1919
  if (!fs12.existsSync(debugArtifactsDir)) {
1814
1920
  fs12.mkdirSync(debugArtifactsDir, { recursive: true });
1815
1921
  }
1816
- const debugFile = path12.join(
1922
+ const debugFile = path13.join(
1817
1923
  debugArtifactsDir,
1818
1924
  `prompt-${_checkName || "unknown"}-${timestamp}.json`
1819
1925
  );
1820
1926
  fs12.writeFileSync(debugFile, debugJson, "utf-8");
1821
- const readableFile = path12.join(
1927
+ const readableFile = path13.join(
1822
1928
  debugArtifactsDir,
1823
1929
  `prompt-${_checkName || "unknown"}-${timestamp}.txt`
1824
1930
  );
@@ -1871,7 +1977,7 @@ $ ${cliCommand}
1871
1977
  if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
1872
1978
  try {
1873
1979
  const fs12 = require("fs");
1874
- const path12 = require("path");
1980
+ const path13 = require("path");
1875
1981
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1876
1982
  const agentAny = agent;
1877
1983
  let fullHistory = [];
@@ -1882,8 +1988,8 @@ $ ${cliCommand}
1882
1988
  } else if (agentAny._messages) {
1883
1989
  fullHistory = agentAny._messages;
1884
1990
  }
1885
- const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path12.join(process.cwd(), "debug-artifacts");
1886
- const sessionFile = path12.join(
1991
+ const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path13.join(process.cwd(), "debug-artifacts");
1992
+ const sessionFile = path13.join(
1887
1993
  debugArtifactsDir,
1888
1994
  `session-${_checkName || "unknown"}-${timestamp}.json`
1889
1995
  );
@@ -1898,7 +2004,7 @@ $ ${cliCommand}
1898
2004
  latestResponse: response
1899
2005
  };
1900
2006
  fs12.writeFileSync(sessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
1901
- const sessionTxtFile = path12.join(
2007
+ const sessionTxtFile = path13.join(
1902
2008
  debugArtifactsDir,
1903
2009
  `session-${_checkName || "unknown"}-${timestamp}.txt`
1904
2010
  );
@@ -1942,10 +2048,10 @@ ${"=".repeat(60)}
1942
2048
  if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
1943
2049
  try {
1944
2050
  const fs12 = require("fs");
1945
- const path12 = require("path");
2051
+ const path13 = require("path");
1946
2052
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1947
- const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path12.join(process.cwd(), "debug-artifacts");
1948
- const responseFile = path12.join(
2053
+ const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path13.join(process.cwd(), "debug-artifacts");
2054
+ const responseFile = path13.join(
1949
2055
  debugArtifactsDir,
1950
2056
  `response-${_checkName || "unknown"}-${timestamp}.txt`
1951
2057
  );
@@ -2035,7 +2141,7 @@ ${"=".repeat(60)}
2035
2141
  */
2036
2142
  async loadSchemaContent(schema) {
2037
2143
  const fs12 = require("fs").promises;
2038
- const path12 = require("path");
2144
+ const path13 = require("path");
2039
2145
  if (typeof schema === "object" && schema !== null) {
2040
2146
  log("\u{1F4CB} Using inline schema object from configuration");
2041
2147
  return JSON.stringify(schema);
@@ -2048,12 +2154,12 @@ ${"=".repeat(60)}
2048
2154
  }
2049
2155
  } catch {
2050
2156
  }
2051
- if ((schema.startsWith("./") || schema.includes(".json")) && !path12.isAbsolute(schema)) {
2157
+ if ((schema.startsWith("./") || schema.includes(".json")) && !path13.isAbsolute(schema)) {
2052
2158
  if (schema.includes("..") || schema.includes("\0")) {
2053
2159
  throw new Error("Invalid schema path: path traversal not allowed");
2054
2160
  }
2055
2161
  try {
2056
- const schemaPath2 = path12.resolve(process.cwd(), schema);
2162
+ const schemaPath2 = path13.resolve(process.cwd(), schema);
2057
2163
  log(`\u{1F4CB} Loading custom schema from file: ${schemaPath2}`);
2058
2164
  const schemaContent = await fs12.readFile(schemaPath2, "utf-8");
2059
2165
  return schemaContent.trim();
@@ -2067,7 +2173,7 @@ ${"=".repeat(60)}
2067
2173
  if (!sanitizedSchemaName || sanitizedSchemaName !== schema) {
2068
2174
  throw new Error("Invalid schema name");
2069
2175
  }
2070
- const schemaPath = path12.join(process.cwd(), "output", sanitizedSchemaName, "schema.json");
2176
+ const schemaPath = path13.join(process.cwd(), "output", sanitizedSchemaName, "schema.json");
2071
2177
  try {
2072
2178
  const schemaContent = await fs12.readFile(schemaPath, "utf-8");
2073
2179
  return schemaContent.trim();
@@ -2424,14 +2530,66 @@ var init_reviewer = __esm({
2424
2530
  "No configuration provided. Please create a .visor.yaml file with check definitions. Built-in prompts have been removed - all checks must be explicitly configured."
2425
2531
  );
2426
2532
  }
2533
+ /**
2534
+ * Helper to check if a schema is comment-generating
2535
+ * Comment-generating schemas include:
2536
+ * - Built-in schemas: code-review, overview, plain, text
2537
+ * - Custom schemas with a "text" field in properties
2538
+ */
2539
+ async isCommentGeneratingSchema(schema) {
2540
+ try {
2541
+ if (typeof schema === "string") {
2542
+ if (["code-review", "overview", "plain", "text"].includes(schema)) {
2543
+ return true;
2544
+ }
2545
+ const fs12 = require("fs").promises;
2546
+ const path13 = require("path");
2547
+ const sanitizedSchemaName = schema.replace(/[^a-zA-Z0-9-]/g, "");
2548
+ if (!sanitizedSchemaName || sanitizedSchemaName !== schema) {
2549
+ return false;
2550
+ }
2551
+ const schemaPath = path13.join(process.cwd(), "output", sanitizedSchemaName, "schema.json");
2552
+ try {
2553
+ const schemaContent = await fs12.readFile(schemaPath, "utf-8");
2554
+ const schemaObj = JSON.parse(schemaContent);
2555
+ const properties = schemaObj.properties;
2556
+ return !!(properties && "text" in properties);
2557
+ } catch {
2558
+ return false;
2559
+ }
2560
+ } else {
2561
+ const properties = schema.properties;
2562
+ return !!(properties && "text" in properties);
2563
+ }
2564
+ } catch {
2565
+ return false;
2566
+ }
2567
+ }
2568
+ /**
2569
+ * Filter check results to only include those that should post GitHub comments
2570
+ */
2571
+ async filterCommentGeneratingChecks(checkResults, config) {
2572
+ const filtered = [];
2573
+ for (const r of checkResults) {
2574
+ const cfg = config.checks?.[r.checkName];
2575
+ const type = cfg?.type || "ai";
2576
+ const schema = cfg?.schema;
2577
+ let shouldPostComment = false;
2578
+ const isAICheck = type === "ai" || type === "claude-code";
2579
+ if (!schema || schema === "") {
2580
+ shouldPostComment = isAICheck;
2581
+ } else {
2582
+ shouldPostComment = await this.isCommentGeneratingSchema(schema);
2583
+ }
2584
+ if (shouldPostComment) {
2585
+ filtered.push(r);
2586
+ }
2587
+ }
2588
+ return filtered;
2589
+ }
2427
2590
  async postReviewComment(owner, repo, prNumber, groupedResults, options = {}) {
2428
2591
  for (const [groupName, checkResults] of Object.entries(groupedResults)) {
2429
- const filteredResults = options.config ? checkResults.filter((r) => {
2430
- const cfg = options.config.checks?.[r.checkName];
2431
- const t = cfg?.type || "";
2432
- const isGitHubOps = t === "github" || r.group === "github" || t === "noop" || t === "command";
2433
- return !isGitHubOps;
2434
- }) : checkResults;
2592
+ const filteredResults = options.config ? await this.filterCommentGeneratingChecks(checkResults, options.config) : checkResults;
2435
2593
  if (!filteredResults || filteredResults.length === 0) {
2436
2594
  continue;
2437
2595
  }
@@ -2564,14 +2722,14 @@ var init_reviewer = __esm({
2564
2722
  saveDebugArtifact(debug) {
2565
2723
  try {
2566
2724
  const fs12 = require("fs");
2567
- const path12 = require("path");
2568
- const debugDir = path12.join(process.cwd(), "debug-artifacts");
2725
+ const path13 = require("path");
2726
+ const debugDir = path13.join(process.cwd(), "debug-artifacts");
2569
2727
  if (!fs12.existsSync(debugDir)) {
2570
2728
  fs12.mkdirSync(debugDir, { recursive: true });
2571
2729
  }
2572
2730
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
2573
2731
  const filename = `visor-debug-${timestamp}.md`;
2574
- const filepath = path12.join(debugDir, filename);
2732
+ const filepath = path13.join(debugDir, filename);
2575
2733
  const content = [
2576
2734
  `# Visor Debug Information`,
2577
2735
  ``,
@@ -2604,12 +2762,12 @@ var init_reviewer = __esm({
2604
2762
  });
2605
2763
 
2606
2764
  // src/git-repository-analyzer.ts
2607
- var import_simple_git, path2, fs2, MAX_PATCH_SIZE, GitRepositoryAnalyzer;
2765
+ var import_simple_git, path3, fs2, MAX_PATCH_SIZE, GitRepositoryAnalyzer;
2608
2766
  var init_git_repository_analyzer = __esm({
2609
2767
  "src/git-repository-analyzer.ts"() {
2610
2768
  "use strict";
2611
2769
  import_simple_git = require("simple-git");
2612
- path2 = __toESM(require("path"));
2770
+ path3 = __toESM(require("path"));
2613
2771
  fs2 = __toESM(require("fs"));
2614
2772
  MAX_PATCH_SIZE = 50 * 1024;
2615
2773
  GitRepositoryAnalyzer = class {
@@ -2824,7 +2982,7 @@ ${file.patch}`).join("\n\n");
2824
2982
  console.error(`\u23ED\uFE0F Skipping excluded file: ${file}`);
2825
2983
  continue;
2826
2984
  }
2827
- const filePath = path2.join(this.cwd, file);
2985
+ const filePath = path3.join(this.cwd, file);
2828
2986
  const fileChange = await this.analyzeFileChange(file, status2, filePath, includeContext);
2829
2987
  changes.push(fileChange);
2830
2988
  }
@@ -3304,12 +3462,12 @@ var init_env_resolver = __esm({
3304
3462
  });
3305
3463
 
3306
3464
  // src/issue-filter.ts
3307
- var fs3, path3, IssueFilter;
3465
+ var fs3, path4, IssueFilter;
3308
3466
  var init_issue_filter = __esm({
3309
3467
  "src/issue-filter.ts"() {
3310
3468
  "use strict";
3311
3469
  fs3 = __toESM(require("fs"));
3312
- path3 = __toESM(require("path"));
3470
+ path4 = __toESM(require("path"));
3313
3471
  IssueFilter = class {
3314
3472
  fileCache = /* @__PURE__ */ new Map();
3315
3473
  suppressionEnabled;
@@ -3377,7 +3535,7 @@ var init_issue_filter = __esm({
3377
3535
  return this.fileCache.get(filePath);
3378
3536
  }
3379
3537
  try {
3380
- const resolvedPath = path3.isAbsolute(filePath) ? filePath : path3.join(workingDir, filePath);
3538
+ const resolvedPath = path4.isAbsolute(filePath) ? filePath : path4.join(workingDir, filePath);
3381
3539
  if (!fs3.existsSync(resolvedPath)) {
3382
3540
  if (fs3.existsSync(filePath)) {
3383
3541
  const content2 = fs3.readFileSync(filePath, "utf8");
@@ -5127,7 +5285,8 @@ var init_log_check_provider = __esm({
5127
5285
  dependencyResults,
5128
5286
  includePrContext,
5129
5287
  includeDependencies,
5130
- includeMetadata
5288
+ includeMetadata,
5289
+ config.__outputHistory
5131
5290
  );
5132
5291
  const renderedMessage = await this.liquid.parseAndRender(message, templateContext);
5133
5292
  const logOutput = this.formatLogOutput(
@@ -5148,7 +5307,7 @@ var init_log_check_provider = __esm({
5148
5307
  logOutput
5149
5308
  };
5150
5309
  }
5151
- buildTemplateContext(prInfo, dependencyResults, _includePrContext = true, _includeDependencies = true, includeMetadata = true) {
5310
+ buildTemplateContext(prInfo, dependencyResults, _includePrContext = true, _includeDependencies = true, includeMetadata = true, outputHistory) {
5152
5311
  const context2 = {};
5153
5312
  context2.pr = {
5154
5313
  number: prInfo.number,
@@ -5172,6 +5331,7 @@ var init_log_check_provider = __esm({
5172
5331
  if (dependencyResults) {
5173
5332
  const dependencies = {};
5174
5333
  const outputs = {};
5334
+ const history = {};
5175
5335
  context2.dependencyCount = dependencyResults.size;
5176
5336
  for (const [checkName, result] of dependencyResults.entries()) {
5177
5337
  dependencies[checkName] = {
@@ -5182,6 +5342,12 @@ var init_log_check_provider = __esm({
5182
5342
  const summary = result;
5183
5343
  outputs[checkName] = summary.output !== void 0 ? summary.output : summary;
5184
5344
  }
5345
+ if (outputHistory) {
5346
+ for (const [checkName, historyArray] of outputHistory) {
5347
+ history[checkName] = historyArray;
5348
+ }
5349
+ }
5350
+ outputs.history = history;
5185
5351
  context2.dependencies = dependencies;
5186
5352
  context2.outputs = outputs;
5187
5353
  }
@@ -6084,7 +6250,10 @@ var init_command_check_provider = __esm({
6084
6250
  },
6085
6251
  files: prInfo.files,
6086
6252
  fileCount: prInfo.files.length,
6087
- outputs: this.buildOutputContext(dependencyResults),
6253
+ outputs: this.buildOutputContext(
6254
+ dependencyResults,
6255
+ config.__outputHistory
6256
+ ),
6088
6257
  env: this.getSafeEnvironmentVariables()
6089
6258
  };
6090
6259
  logger.debug(
@@ -6810,16 +6979,23 @@ ${stderrOutput}` : `Command execution failed: ${errorMessage}`;
6810
6979
  };
6811
6980
  }
6812
6981
  }
6813
- buildOutputContext(dependencyResults) {
6982
+ buildOutputContext(dependencyResults, outputHistory) {
6814
6983
  if (!dependencyResults) {
6815
6984
  return {};
6816
6985
  }
6817
6986
  const outputs = {};
6987
+ const history = {};
6818
6988
  for (const [checkName, result] of dependencyResults) {
6819
6989
  const summary = result;
6820
6990
  const value = summary.output !== void 0 ? summary.output : summary;
6821
6991
  outputs[checkName] = this.makeJsonSmart(value);
6822
6992
  }
6993
+ if (outputHistory) {
6994
+ for (const [checkName, historyArray] of outputHistory) {
6995
+ history[checkName] = historyArray.map((val) => this.makeJsonSmart(val));
6996
+ }
6997
+ }
6998
+ outputs.history = history;
6823
6999
  return outputs;
6824
7000
  }
6825
7001
  /**
@@ -7377,7 +7553,12 @@ var init_memory_check_provider = __esm({
7377
7553
  const key = config.key;
7378
7554
  const namespace = config.namespace;
7379
7555
  const memoryStore = MemoryStore.getInstance();
7380
- const templateContext = this.buildTemplateContext(prInfo, dependencyResults, memoryStore);
7556
+ const templateContext = this.buildTemplateContext(
7557
+ prInfo,
7558
+ dependencyResults,
7559
+ memoryStore,
7560
+ config.__outputHistory
7561
+ );
7381
7562
  let result;
7382
7563
  try {
7383
7564
  switch (operation) {
@@ -7660,7 +7841,7 @@ var init_memory_check_provider = __esm({
7660
7841
  /**
7661
7842
  * Build template context for Liquid and JS evaluation
7662
7843
  */
7663
- buildTemplateContext(prInfo, dependencyResults, memoryStore) {
7844
+ buildTemplateContext(prInfo, dependencyResults, memoryStore, outputHistory) {
7664
7845
  const context2 = {};
7665
7846
  context2.pr = {
7666
7847
  number: prInfo.number,
@@ -7679,14 +7860,21 @@ var init_memory_check_provider = __esm({
7679
7860
  changes: f.changes
7680
7861
  }))
7681
7862
  };
7863
+ const outputs = {};
7864
+ const history = {};
7682
7865
  if (dependencyResults) {
7683
- const outputs = {};
7684
7866
  for (const [checkName, result] of dependencyResults.entries()) {
7685
7867
  const summary = result;
7686
7868
  outputs[checkName] = summary.output !== void 0 ? summary.output : summary;
7687
7869
  }
7688
- context2.outputs = outputs;
7689
7870
  }
7871
+ if (outputHistory) {
7872
+ for (const [checkName, historyArray] of outputHistory) {
7873
+ history[checkName] = historyArray;
7874
+ }
7875
+ }
7876
+ outputs.history = history;
7877
+ context2.outputs = outputs;
7690
7878
  if (memoryStore) {
7691
7879
  context2.memory = {
7692
7880
  get: (key, ns) => memoryStore.get(key, ns),
@@ -9224,7 +9412,7 @@ function resolveTargetPath(outDir) {
9224
9412
  }
9225
9413
  if (CURRENT_FILE) return CURRENT_FILE;
9226
9414
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
9227
- CURRENT_FILE = path8.join(outDir, `${ts}.ndjson`);
9415
+ CURRENT_FILE = path9.join(outDir, `${ts}.ndjson`);
9228
9416
  return CURRENT_FILE;
9229
9417
  }
9230
9418
  function isEnabled() {
@@ -9254,7 +9442,7 @@ async function flushNdjson() {
9254
9442
  function emitNdjsonFallback(name, attrs) {
9255
9443
  try {
9256
9444
  if (!isEnabled()) return;
9257
- const outDir = process.env.VISOR_TRACE_DIR || path8.join(process.cwd(), "output", "traces");
9445
+ const outDir = process.env.VISOR_TRACE_DIR || path9.join(process.cwd(), "output", "traces");
9258
9446
  const line = JSON.stringify({ name, attributes: attrs }) + "\n";
9259
9447
  appendAsync(outDir, line);
9260
9448
  } catch {
@@ -9263,18 +9451,18 @@ function emitNdjsonFallback(name, attrs) {
9263
9451
  function emitNdjsonSpanWithEvents(name, attrs, events) {
9264
9452
  try {
9265
9453
  if (!isEnabled()) return;
9266
- const outDir = process.env.VISOR_TRACE_DIR || path8.join(process.cwd(), "output", "traces");
9454
+ const outDir = process.env.VISOR_TRACE_DIR || path9.join(process.cwd(), "output", "traces");
9267
9455
  const line = JSON.stringify({ name, attributes: attrs, events }) + "\n";
9268
9456
  appendAsync(outDir, line);
9269
9457
  } catch {
9270
9458
  }
9271
9459
  }
9272
- var fs8, path8, CURRENT_FILE, dirReady, writeChain;
9460
+ var fs8, path9, CURRENT_FILE, dirReady, writeChain;
9273
9461
  var init_fallback_ndjson = __esm({
9274
9462
  "src/telemetry/fallback-ndjson.ts"() {
9275
9463
  "use strict";
9276
9464
  fs8 = __toESM(require("fs"));
9277
- path8 = __toESM(require("path"));
9465
+ path9 = __toESM(require("path"));
9278
9466
  CURRENT_FILE = null;
9279
9467
  dirReady = false;
9280
9468
  writeChain = Promise.resolve();
@@ -9750,10 +9938,6 @@ var init_failure_condition_evaluator = __esm({
9750
9938
  if (!Array.isArray(issues2)) return false;
9751
9939
  return issues2.some((issue) => issue.file?.includes(pattern));
9752
9940
  };
9753
- const hasSuggestion = (suggestions2, text) => {
9754
- if (!Array.isArray(suggestions2)) return false;
9755
- return suggestions2.some((s) => s.toLowerCase().includes(text.toLowerCase()));
9756
- };
9757
9941
  const hasIssueWith = hasIssue;
9758
9942
  const hasFileWith = hasFileMatching;
9759
9943
  const permissionHelpers = createPermissionHelpers(
@@ -9768,7 +9952,6 @@ var init_failure_condition_evaluator = __esm({
9768
9952
  const isFirstTimer2 = permissionHelpers.isFirstTimer;
9769
9953
  const output = context2.output || {};
9770
9954
  const issues = output.issues || [];
9771
- const suggestions = [];
9772
9955
  const metadata = context2.metadata || {
9773
9956
  checkName: context2.checkName || "",
9774
9957
  schema: context2.schema || "",
@@ -9812,7 +9995,6 @@ var init_failure_condition_evaluator = __esm({
9812
9995
  memory: memoryAccessor,
9813
9996
  // Legacy compatibility variables
9814
9997
  issues,
9815
- suggestions,
9816
9998
  metadata,
9817
9999
  criticalIssues,
9818
10000
  errorIssues,
@@ -9841,7 +10023,6 @@ var init_failure_condition_evaluator = __esm({
9841
10023
  hasIssue,
9842
10024
  countIssues,
9843
10025
  hasFileMatching,
9844
- hasSuggestion,
9845
10026
  hasIssueWith,
9846
10027
  hasFileWith,
9847
10028
  // Permission helpers
@@ -10111,6 +10292,7 @@ var GitHubCheckService;
10111
10292
  var init_github_check_service = __esm({
10112
10293
  "src/github-check-service.ts"() {
10113
10294
  "use strict";
10295
+ init_footer();
10114
10296
  GitHubCheckService = class {
10115
10297
  octokit;
10116
10298
  maxAnnotations = 50;
@@ -10351,11 +10533,7 @@ Please check your configuration and try again.`
10351
10533
  });
10352
10534
  }
10353
10535
  sections.push("");
10354
- sections.push("---");
10355
- sections.push("");
10356
- sections.push(
10357
- "*Powered by [Visor](https://probelabs.com/visor) from [Probelabs](https://probelabs.com)*"
10358
- );
10536
+ sections.push(generateFooter());
10359
10537
  return sections.join("\n");
10360
10538
  }
10361
10539
  /**
@@ -10573,12 +10751,12 @@ function emitMermaidFromMarkdown(checkName, markdown, origin) {
10573
10751
  addEvent("diagram.block", { check: checkName, origin, code });
10574
10752
  addDiagramBlock(origin);
10575
10753
  if (process.env.VISOR_TRACE_REPORT === "true") {
10576
- const outDir = process.env.VISOR_TRACE_DIR || path9.join(process.cwd(), "output", "traces");
10754
+ const outDir = process.env.VISOR_TRACE_DIR || path10.join(process.cwd(), "output", "traces");
10577
10755
  try {
10578
10756
  if (!fs9.existsSync(outDir)) fs9.mkdirSync(outDir, { recursive: true });
10579
10757
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
10580
- const jsonPath = path9.join(outDir, `${ts}.trace.json`);
10581
- const htmlPath = path9.join(outDir, `${ts}.report.html`);
10758
+ const jsonPath = path10.join(outDir, `${ts}.trace.json`);
10759
+ const htmlPath = path10.join(outDir, `${ts}.report.html`);
10582
10760
  let data = { spans: [] };
10583
10761
  if (fs9.existsSync(jsonPath)) {
10584
10762
  try {
@@ -10608,14 +10786,14 @@ function emitMermaidFromMarkdown(checkName, markdown, origin) {
10608
10786
  }
10609
10787
  return count;
10610
10788
  }
10611
- var fs9, path9, MERMAID_RE;
10789
+ var fs9, path10, MERMAID_RE;
10612
10790
  var init_mermaid_telemetry = __esm({
10613
10791
  "src/utils/mermaid-telemetry.ts"() {
10614
10792
  "use strict";
10615
10793
  init_trace_helpers();
10616
10794
  init_metrics2();
10617
10795
  fs9 = __toESM(require("fs"));
10618
- path9 = __toESM(require("path"));
10796
+ path10 = __toESM(require("path"));
10619
10797
  MERMAID_RE = /```mermaid\s*\n([\s\S]*?)\n```/gi;
10620
10798
  }
10621
10799
  });
@@ -10681,6 +10859,8 @@ var init_check_execution_engine = __esm({
10681
10859
  webhookContext;
10682
10860
  routingSandbox;
10683
10861
  executionStats = /* @__PURE__ */ new Map();
10862
+ // Track history of all outputs for each check (useful for loops and goto)
10863
+ outputHistory = /* @__PURE__ */ new Map();
10684
10864
  // Event override to simulate alternate event (used during routing goto)
10685
10865
  routingEventOverride;
10686
10866
  // Cached GitHub context for context elevation when running in Actions
@@ -10924,6 +11104,8 @@ ${expr}
10924
11104
  transform_js: targetCfg.transform_js,
10925
11105
  env: targetCfg.env,
10926
11106
  forEach: targetCfg.forEach,
11107
+ // Pass output history for loop/goto scenarios
11108
+ __outputHistory: this.outputHistory,
10927
11109
  // Include provider-specific keys (e.g., op/values for github)
10928
11110
  ...targetCfg,
10929
11111
  ai: {
@@ -10991,6 +11173,10 @@ ${expr}
10991
11173
  timestamp: Date.now()
10992
11174
  }));
10993
11175
  const enriched = { ...r, issues: enrichedIssues };
11176
+ const enrichedWithOutput = enriched;
11177
+ if (enrichedWithOutput.output !== void 0) {
11178
+ this.trackOutputHistory(target, enrichedWithOutput.output);
11179
+ }
10994
11180
  resultsMap?.set(target, enriched);
10995
11181
  if (debug) log2(`\u{1F527} Debug: inline executed '${target}', issues: ${enrichedIssues.length}`);
10996
11182
  return enriched;
@@ -11759,6 +11945,8 @@ ${expr}
11759
11945
  ai_model: checkConfig.ai_model || config.ai_model,
11760
11946
  // Pass claude_code config if present
11761
11947
  claude_code: checkConfig.claude_code,
11948
+ // Pass output history for loop/goto scenarios
11949
+ __outputHistory: this.outputHistory,
11762
11950
  // Pass any provider-specific config
11763
11951
  ...checkConfig
11764
11952
  };
@@ -11795,10 +11983,14 @@ ${expr}
11795
11983
  }
11796
11984
  }
11797
11985
  const content = await this.renderCheckContent(checkName, result, checkConfig, prInfo);
11986
+ let group = checkConfig.group || "default";
11987
+ if (config?.output?.pr_comment?.group_by === "check" && !checkConfig.group) {
11988
+ group = checkName;
11989
+ }
11798
11990
  return {
11799
11991
  checkName,
11800
11992
  content,
11801
- group: checkConfig.group || "default",
11993
+ group,
11802
11994
  output: result.output,
11803
11995
  debug: result.debug,
11804
11996
  issues: result.issues
@@ -11937,16 +12129,19 @@ ${expr}
11937
12129
  }
11938
12130
  ];
11939
12131
  }
12132
+ let group = checkConfig.group || "default";
12133
+ if (config?.output?.pr_comment?.group_by === "check" && !checkConfig.group) {
12134
+ group = checkName;
12135
+ }
11940
12136
  const checkResult = {
11941
12137
  checkName,
11942
12138
  content,
11943
- group: checkConfig.group || "default",
12139
+ group,
11944
12140
  output: checkSummary.output,
11945
12141
  debug: reviewSummary.debug,
11946
12142
  issues: issuesForCheck
11947
12143
  // Include structured issues + rendering error if any
11948
12144
  };
11949
- const group = checkResult.group;
11950
12145
  if (!groupedResults[group]) {
11951
12146
  groupedResults[group] = [];
11952
12147
  }
@@ -11964,7 +12159,7 @@ ${expr}
11964
12159
  * - Enforcing .liquid file extension
11965
12160
  */
11966
12161
  async validateTemplatePath(templatePath) {
11967
- const path12 = await import("path");
12162
+ const path13 = await import("path");
11968
12163
  if (!templatePath || typeof templatePath !== "string" || templatePath.trim() === "") {
11969
12164
  throw new Error("Template path must be a non-empty string");
11970
12165
  }
@@ -11974,7 +12169,7 @@ ${expr}
11974
12169
  if (!templatePath.endsWith(".liquid")) {
11975
12170
  throw new Error("Template file must have .liquid extension");
11976
12171
  }
11977
- if (path12.isAbsolute(templatePath)) {
12172
+ if (path13.isAbsolute(templatePath)) {
11978
12173
  throw new Error("Template path must be relative to project directory");
11979
12174
  }
11980
12175
  if (templatePath.includes("..")) {
@@ -11988,14 +12183,14 @@ ${expr}
11988
12183
  if (!projectRoot || typeof projectRoot !== "string") {
11989
12184
  throw new Error("Unable to determine project root directory");
11990
12185
  }
11991
- const resolvedPath = path12.resolve(projectRoot, templatePath);
11992
- const resolvedProjectRoot = path12.resolve(projectRoot);
12186
+ const resolvedPath = path13.resolve(projectRoot, templatePath);
12187
+ const resolvedProjectRoot = path13.resolve(projectRoot);
11993
12188
  if (!resolvedPath || !resolvedProjectRoot || resolvedPath === "" || resolvedProjectRoot === "") {
11994
12189
  throw new Error(
11995
12190
  `Unable to resolve template path: projectRoot="${projectRoot}", templatePath="${templatePath}", resolvedPath="${resolvedPath}", resolvedProjectRoot="${resolvedProjectRoot}"`
11996
12191
  );
11997
12192
  }
11998
- if (!resolvedPath.startsWith(resolvedProjectRoot + path12.sep) && resolvedPath !== resolvedProjectRoot) {
12193
+ if (!resolvedPath.startsWith(resolvedProjectRoot + path13.sep) && resolvedPath !== resolvedProjectRoot) {
11999
12194
  throw new Error("Template path escapes project directory");
12000
12195
  }
12001
12196
  return resolvedPath;
@@ -12040,7 +12235,7 @@ ${expr}
12040
12235
  }
12041
12236
  const { createExtendedLiquid: createExtendedLiquid2 } = await Promise.resolve().then(() => (init_liquid_extensions(), liquid_extensions_exports));
12042
12237
  const fs12 = await import("fs/promises");
12043
- const path12 = await import("path");
12238
+ const path13 = await import("path");
12044
12239
  const liquid = createExtendedLiquid2({
12045
12240
  trimTagLeft: false,
12046
12241
  trimTagRight: false,
@@ -12074,7 +12269,7 @@ ${expr}
12074
12269
  if (!sanitizedSchema) {
12075
12270
  throw new Error("Invalid schema name");
12076
12271
  }
12077
- const templatePath = path12.join(__dirname, `../output/${sanitizedSchema}/template.liquid`);
12272
+ const templatePath = path13.join(__dirname, `../output/${sanitizedSchema}/template.liquid`);
12078
12273
  templateContent = await fs12.readFile(templatePath, "utf-8");
12079
12274
  if (sanitizedSchema === "issue-assistant") {
12080
12275
  enrichAssistantContext = true;
@@ -12847,6 +13042,10 @@ ${expr}
12847
13042
  itemResult.issues || [],
12848
13043
  itemResult.output
12849
13044
  );
13045
+ const itemOutput = itemResult.output;
13046
+ if (itemOutput !== void 0) {
13047
+ this.trackOutputHistory(checkName, itemOutput);
13048
+ }
12850
13049
  const descendantSet = (() => {
12851
13050
  const visited = /* @__PURE__ */ new Set();
12852
13051
  const stack = [checkName];
@@ -13477,6 +13676,10 @@ ${error.stack || ""}` : String(error);
13477
13676
  ]);
13478
13677
  } catch {
13479
13678
  }
13679
+ const reviewResultWithOutput = reviewResult;
13680
+ if (reviewResultWithOutput.output !== void 0) {
13681
+ this.trackOutputHistory(checkName, reviewResultWithOutput.output);
13682
+ }
13480
13683
  results.set(checkName, reviewResult);
13481
13684
  } else {
13482
13685
  const errorSummary = {
@@ -14720,6 +14923,16 @@ ${result.value.result.debug.rawResponse}`;
14720
14923
  stats.outputsProduced = (stats.outputsProduced || 0) + 1;
14721
14924
  }
14722
14925
  }
14926
+ /**
14927
+ * Track output in history for loop/goto scenarios
14928
+ */
14929
+ trackOutputHistory(checkName, output) {
14930
+ if (output === void 0) return;
14931
+ if (!this.outputHistory.has(checkName)) {
14932
+ this.outputHistory.set(checkName, []);
14933
+ }
14934
+ this.outputHistory.get(checkName).push(output);
14935
+ }
14723
14936
  /**
14724
14937
  * Record that a check was skipped
14725
14938
  */
@@ -16225,13 +16438,13 @@ init_check_execution_engine();
16225
16438
  // src/config.ts
16226
16439
  var yaml2 = __toESM(require("js-yaml"));
16227
16440
  var fs11 = __toESM(require("fs"));
16228
- var path11 = __toESM(require("path"));
16441
+ var path12 = __toESM(require("path"));
16229
16442
  init_logger();
16230
16443
  var import_simple_git2 = __toESM(require("simple-git"));
16231
16444
 
16232
16445
  // src/utils/config-loader.ts
16233
16446
  var fs10 = __toESM(require("fs"));
16234
- var path10 = __toESM(require("path"));
16447
+ var path11 = __toESM(require("path"));
16235
16448
  var yaml = __toESM(require("js-yaml"));
16236
16449
  var ConfigLoader = class {
16237
16450
  constructor(options = {}) {
@@ -16312,7 +16525,7 @@ var ConfigLoader = class {
16312
16525
  return source.toLowerCase();
16313
16526
  case "local" /* LOCAL */:
16314
16527
  const basePath = this.options.baseDir || process.cwd();
16315
- return path10.resolve(basePath, source);
16528
+ return path11.resolve(basePath, source);
16316
16529
  default:
16317
16530
  return source;
16318
16531
  }
@@ -16322,7 +16535,7 @@ var ConfigLoader = class {
16322
16535
  */
16323
16536
  async fetchLocalConfig(filePath) {
16324
16537
  const basePath = this.options.baseDir || process.cwd();
16325
- const resolvedPath = path10.resolve(basePath, filePath);
16538
+ const resolvedPath = path11.resolve(basePath, filePath);
16326
16539
  this.validateLocalPath(resolvedPath);
16327
16540
  if (!fs10.existsSync(resolvedPath)) {
16328
16541
  throw new Error(`Configuration file not found: ${resolvedPath}`);
@@ -16334,7 +16547,7 @@ var ConfigLoader = class {
16334
16547
  throw new Error(`Invalid YAML in configuration file: ${resolvedPath}`);
16335
16548
  }
16336
16549
  const previousBaseDir = this.options.baseDir;
16337
- this.options.baseDir = path10.dirname(resolvedPath);
16550
+ this.options.baseDir = path11.dirname(resolvedPath);
16338
16551
  try {
16339
16552
  if (config.extends) {
16340
16553
  const processedConfig = await this.processExtends(config);
@@ -16414,14 +16627,14 @@ var ConfigLoader = class {
16414
16627
  async fetchDefaultConfig() {
16415
16628
  const possiblePaths = [
16416
16629
  // When running as GitHub Action (bundled in dist/)
16417
- path10.join(__dirname, "defaults", ".visor.yaml"),
16630
+ path11.join(__dirname, "defaults", ".visor.yaml"),
16418
16631
  // When running from source
16419
- path10.join(__dirname, "..", "..", "defaults", ".visor.yaml"),
16632
+ path11.join(__dirname, "..", "..", "defaults", ".visor.yaml"),
16420
16633
  // Try via package root
16421
- this.findPackageRoot() ? path10.join(this.findPackageRoot(), "defaults", ".visor.yaml") : "",
16634
+ this.findPackageRoot() ? path11.join(this.findPackageRoot(), "defaults", ".visor.yaml") : "",
16422
16635
  // GitHub Action environment variable
16423
- process.env.GITHUB_ACTION_PATH ? path10.join(process.env.GITHUB_ACTION_PATH, "defaults", ".visor.yaml") : "",
16424
- process.env.GITHUB_ACTION_PATH ? path10.join(process.env.GITHUB_ACTION_PATH, "dist", "defaults", ".visor.yaml") : ""
16636
+ process.env.GITHUB_ACTION_PATH ? path11.join(process.env.GITHUB_ACTION_PATH, "defaults", ".visor.yaml") : "",
16637
+ process.env.GITHUB_ACTION_PATH ? path11.join(process.env.GITHUB_ACTION_PATH, "dist", "defaults", ".visor.yaml") : ""
16425
16638
  ].filter((p) => p);
16426
16639
  let defaultConfigPath;
16427
16640
  for (const possiblePath of possiblePaths) {
@@ -16511,8 +16724,8 @@ var ConfigLoader = class {
16511
16724
  */
16512
16725
  validateLocalPath(resolvedPath) {
16513
16726
  const projectRoot = this.options.projectRoot || process.cwd();
16514
- const normalizedPath = path10.normalize(resolvedPath);
16515
- const normalizedRoot = path10.normalize(projectRoot);
16727
+ const normalizedPath = path11.normalize(resolvedPath);
16728
+ const normalizedRoot = path11.normalize(projectRoot);
16516
16729
  if (!normalizedPath.startsWith(normalizedRoot)) {
16517
16730
  throw new Error(
16518
16731
  `Security error: Path traversal detected. Cannot access files outside project root: ${projectRoot}`
@@ -16538,9 +16751,9 @@ var ConfigLoader = class {
16538
16751
  */
16539
16752
  findPackageRoot() {
16540
16753
  let currentDir = __dirname;
16541
- const root = path10.parse(currentDir).root;
16754
+ const root = path11.parse(currentDir).root;
16542
16755
  while (currentDir !== root) {
16543
- const packageJsonPath = path10.join(currentDir, "package.json");
16756
+ const packageJsonPath = path11.join(currentDir, "package.json");
16544
16757
  if (fs10.existsSync(packageJsonPath)) {
16545
16758
  try {
16546
16759
  const packageJson = JSON.parse(fs10.readFileSync(packageJsonPath, "utf8"));
@@ -16550,7 +16763,7 @@ var ConfigLoader = class {
16550
16763
  } catch {
16551
16764
  }
16552
16765
  }
16553
- currentDir = path10.dirname(currentDir);
16766
+ currentDir = path11.dirname(currentDir);
16554
16767
  }
16555
16768
  return null;
16556
16769
  }
@@ -16573,6 +16786,16 @@ var ConfigLoader = class {
16573
16786
  init_config_merger();
16574
16787
  var import_ajv = __toESM(require("ajv"));
16575
16788
  var import_ajv_formats = __toESM(require("ajv-formats"));
16789
+ var VALID_EVENT_TRIGGERS = [
16790
+ "pr_opened",
16791
+ "pr_updated",
16792
+ "pr_closed",
16793
+ "issue_opened",
16794
+ "issue_comment",
16795
+ "manual",
16796
+ "schedule",
16797
+ "webhook_received"
16798
+ ];
16576
16799
  var ConfigManager = class {
16577
16800
  validCheckTypes = [
16578
16801
  "ai",
@@ -16581,20 +16804,13 @@ var ConfigManager = class {
16581
16804
  "http",
16582
16805
  "http_input",
16583
16806
  "http_client",
16807
+ "memory",
16584
16808
  "noop",
16585
16809
  "log",
16810
+ "memory",
16586
16811
  "github"
16587
16812
  ];
16588
- validEventTriggers = [
16589
- "pr_opened",
16590
- "pr_updated",
16591
- "pr_closed",
16592
- "issue_opened",
16593
- "issue_comment",
16594
- "manual",
16595
- "schedule",
16596
- "webhook_received"
16597
- ];
16813
+ validEventTriggers = [...VALID_EVENT_TRIGGERS];
16598
16814
  validOutputFormats = ["table", "json", "markdown", "sarif"];
16599
16815
  validGroupByOptions = ["check", "file", "severity", "group"];
16600
16816
  /**
@@ -16602,24 +16818,25 @@ var ConfigManager = class {
16602
16818
  */
16603
16819
  async loadConfig(configPath, options = {}) {
16604
16820
  const { validate = true, mergeDefaults = true, allowedRemotePatterns } = options;
16821
+ const resolvedPath = path12.isAbsolute(configPath) ? configPath : path12.resolve(process.cwd(), configPath);
16605
16822
  try {
16606
- if (!fs11.existsSync(configPath)) {
16607
- throw new Error(`Configuration file not found: ${configPath}`);
16823
+ if (!fs11.existsSync(resolvedPath)) {
16824
+ throw new Error(`Configuration file not found: ${resolvedPath}`);
16608
16825
  }
16609
- const configContent = fs11.readFileSync(configPath, "utf8");
16826
+ const configContent = fs11.readFileSync(resolvedPath, "utf8");
16610
16827
  let parsedConfig;
16611
16828
  try {
16612
16829
  parsedConfig = yaml2.load(configContent);
16613
16830
  } catch (yamlError) {
16614
16831
  const errorMessage = yamlError instanceof Error ? yamlError.message : String(yamlError);
16615
- throw new Error(`Invalid YAML syntax in ${configPath}: ${errorMessage}`);
16832
+ throw new Error(`Invalid YAML syntax in ${resolvedPath}: ${errorMessage}`);
16616
16833
  }
16617
16834
  if (!parsedConfig || typeof parsedConfig !== "object") {
16618
16835
  throw new Error("Configuration file must contain a valid YAML object");
16619
16836
  }
16620
16837
  if (parsedConfig.extends) {
16621
16838
  const loaderOptions = {
16622
- baseDir: path11.dirname(configPath),
16839
+ baseDir: path12.dirname(resolvedPath),
16623
16840
  allowRemote: this.isRemoteExtendsAllowed(),
16624
16841
  maxDepth: 10,
16625
16842
  allowedRemotePatterns
@@ -16651,12 +16868,12 @@ var ConfigManager = class {
16651
16868
  throw error;
16652
16869
  }
16653
16870
  if (error.message.includes("ENOENT")) {
16654
- throw new Error(`Configuration file not found: ${configPath}`);
16871
+ throw new Error(`Configuration file not found: ${resolvedPath}`);
16655
16872
  }
16656
16873
  if (error.message.includes("EPERM")) {
16657
- throw new Error(`Permission denied reading configuration file: ${configPath}`);
16874
+ throw new Error(`Permission denied reading configuration file: ${resolvedPath}`);
16658
16875
  }
16659
- throw new Error(`Failed to read configuration file ${configPath}: ${error.message}`);
16876
+ throw new Error(`Failed to read configuration file ${resolvedPath}: ${error.message}`);
16660
16877
  }
16661
16878
  throw error;
16662
16879
  }
@@ -16668,7 +16885,7 @@ var ConfigManager = class {
16668
16885
  const gitRoot = await this.findGitRepositoryRoot();
16669
16886
  const searchDirs = [gitRoot, process.cwd()].filter(Boolean);
16670
16887
  for (const baseDir of searchDirs) {
16671
- const possiblePaths = [path11.join(baseDir, ".visor.yaml"), path11.join(baseDir, ".visor.yml")];
16888
+ const possiblePaths = [path12.join(baseDir, ".visor.yaml"), path12.join(baseDir, ".visor.yml")];
16672
16889
  for (const configPath of possiblePaths) {
16673
16890
  if (fs11.existsSync(configPath)) {
16674
16891
  return this.loadConfig(configPath, options);
@@ -16722,18 +16939,18 @@ var ConfigManager = class {
16722
16939
  const possiblePaths = [];
16723
16940
  if (typeof __dirname !== "undefined") {
16724
16941
  possiblePaths.push(
16725
- path11.join(__dirname, "defaults", ".visor.yaml"),
16726
- path11.join(__dirname, "..", "defaults", ".visor.yaml")
16942
+ path12.join(__dirname, "defaults", ".visor.yaml"),
16943
+ path12.join(__dirname, "..", "defaults", ".visor.yaml")
16727
16944
  );
16728
16945
  }
16729
16946
  const pkgRoot = this.findPackageRoot();
16730
16947
  if (pkgRoot) {
16731
- possiblePaths.push(path11.join(pkgRoot, "defaults", ".visor.yaml"));
16948
+ possiblePaths.push(path12.join(pkgRoot, "defaults", ".visor.yaml"));
16732
16949
  }
16733
16950
  if (process.env.GITHUB_ACTION_PATH) {
16734
16951
  possiblePaths.push(
16735
- path11.join(process.env.GITHUB_ACTION_PATH, "defaults", ".visor.yaml"),
16736
- path11.join(process.env.GITHUB_ACTION_PATH, "dist", "defaults", ".visor.yaml")
16952
+ path12.join(process.env.GITHUB_ACTION_PATH, "defaults", ".visor.yaml"),
16953
+ path12.join(process.env.GITHUB_ACTION_PATH, "dist", "defaults", ".visor.yaml")
16737
16954
  );
16738
16955
  }
16739
16956
  let bundledConfigPath;
@@ -16766,8 +16983,8 @@ var ConfigManager = class {
16766
16983
  */
16767
16984
  findPackageRoot() {
16768
16985
  let currentDir = __dirname;
16769
- while (currentDir !== path11.dirname(currentDir)) {
16770
- const packageJsonPath = path11.join(currentDir, "package.json");
16986
+ while (currentDir !== path12.dirname(currentDir)) {
16987
+ const packageJsonPath = path12.join(currentDir, "package.json");
16771
16988
  if (fs11.existsSync(packageJsonPath)) {
16772
16989
  try {
16773
16990
  const packageJson = JSON.parse(fs11.readFileSync(packageJsonPath, "utf8"));
@@ -16777,7 +16994,7 @@ var ConfigManager = class {
16777
16994
  } catch {
16778
16995
  }
16779
16996
  }
16780
- currentDir = path11.dirname(currentDir);
16997
+ currentDir = path12.dirname(currentDir);
16781
16998
  }
16782
16999
  return null;
16783
17000
  }
@@ -16823,8 +17040,10 @@ var ConfigManager = class {
16823
17040
  }
16824
17041
  /**
16825
17042
  * Validate configuration against schema
17043
+ * @param config The config to validate
17044
+ * @param strict If true, treat warnings as errors (default: false)
16826
17045
  */
16827
- validateConfig(config) {
17046
+ validateConfig(config, strict = false) {
16828
17047
  const errors = [];
16829
17048
  const warnings = [];
16830
17049
  this.validateWithAjvSchema(config, errors, warnings);
@@ -16932,10 +17151,13 @@ var ConfigManager = class {
16932
17151
  if (config.tag_filter) {
16933
17152
  this.validateTagFilter(config.tag_filter, errors);
16934
17153
  }
17154
+ if (strict && warnings.length > 0) {
17155
+ errors.push(...warnings);
17156
+ }
16935
17157
  if (errors.length > 0) {
16936
17158
  throw new Error(errors[0].message);
16937
17159
  }
16938
- if (warnings.length > 0) {
17160
+ if (!strict && warnings.length > 0) {
16939
17161
  for (const w of warnings) {
16940
17162
  logger.warn(`\u26A0\uFE0F Config warning [${w.field}]: ${w.message}`);
16941
17163
  }
@@ -17157,7 +17379,7 @@ var ConfigManager = class {
17157
17379
  try {
17158
17380
  if (!__ajvValidate) {
17159
17381
  try {
17160
- const jsonPath = path11.resolve(__dirname, "generated", "config-schema.json");
17382
+ const jsonPath = path12.resolve(__dirname, "generated", "config-schema.json");
17161
17383
  const jsonSchema = require(jsonPath);
17162
17384
  if (jsonSchema) {
17163
17385
  const ajv = new import_ajv.default({ allErrors: true, allowUnionTypes: true, strict: false });
@@ -17391,9 +17613,25 @@ var __ajvValidate = null;
17391
17613
  var __ajvErrors = null;
17392
17614
 
17393
17615
  // src/sdk.ts
17394
- async function loadConfig(configPath) {
17616
+ async function loadConfig(configOrPath, options) {
17395
17617
  const cm = new ConfigManager();
17396
- if (configPath) return cm.loadConfig(configPath);
17618
+ if (typeof configOrPath === "object" && configOrPath !== null) {
17619
+ cm.validateConfig(configOrPath, options?.strict ?? false);
17620
+ const defaultConfig = {
17621
+ version: "1.0",
17622
+ checks: {},
17623
+ max_parallelism: 3,
17624
+ fail_fast: false
17625
+ };
17626
+ return {
17627
+ ...defaultConfig,
17628
+ ...configOrPath,
17629
+ checks: configOrPath.checks || {}
17630
+ };
17631
+ }
17632
+ if (typeof configOrPath === "string") {
17633
+ return cm.loadConfig(configOrPath);
17634
+ }
17397
17635
  return cm.findAndLoadConfig();
17398
17636
  }
17399
17637
  function resolveChecks(checkIds, config) {
@@ -17419,7 +17657,15 @@ function resolveChecks(checkIds, config) {
17419
17657
  }
17420
17658
  async function runChecks(opts = {}) {
17421
17659
  const cm = new ConfigManager();
17422
- const config = opts.config ? opts.config : opts.configPath ? await cm.loadConfig(opts.configPath) : await cm.findAndLoadConfig();
17660
+ let config;
17661
+ if (opts.config) {
17662
+ cm.validateConfig(opts.config, opts.strictValidation ?? false);
17663
+ config = opts.config;
17664
+ } else if (opts.configPath) {
17665
+ config = await cm.loadConfig(opts.configPath);
17666
+ } else {
17667
+ config = await cm.findAndLoadConfig();
17668
+ }
17423
17669
  const checks = opts.checks && opts.checks.length > 0 ? resolveChecks(opts.checks, config) : Object.keys(config.checks || {});
17424
17670
  const engine = new CheckExecutionEngine(opts.cwd);
17425
17671
  const result = await engine.executeChecks({