@probelabs/visor 0.1.88 → 0.1.90

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.
@@ -177,6 +177,112 @@ var init_session_registry = __esm({
177
177
  }
178
178
  });
179
179
 
180
+ // src/logger.ts
181
+ var logger_exports = {};
182
+ __export(logger_exports, {
183
+ configureLoggerFromCli: () => configureLoggerFromCli,
184
+ logger: () => logger
185
+ });
186
+ function levelToNumber(level) {
187
+ switch (level) {
188
+ case "silent":
189
+ return 0;
190
+ case "error":
191
+ return 10;
192
+ case "warn":
193
+ return 20;
194
+ case "info":
195
+ return 30;
196
+ case "verbose":
197
+ return 40;
198
+ case "debug":
199
+ return 50;
200
+ }
201
+ }
202
+ function configureLoggerFromCli(options) {
203
+ logger.configure({
204
+ outputFormat: options.output,
205
+ debug: options.debug,
206
+ verbose: options.verbose,
207
+ quiet: options.quiet
208
+ });
209
+ try {
210
+ if (options.output) process.env.VISOR_OUTPUT_FORMAT = String(options.output);
211
+ if (typeof options.debug === "boolean") {
212
+ process.env.VISOR_DEBUG = options.debug ? "true" : "false";
213
+ }
214
+ } catch {
215
+ }
216
+ }
217
+ var Logger, logger;
218
+ var init_logger = __esm({
219
+ "src/logger.ts"() {
220
+ "use strict";
221
+ Logger = class {
222
+ level = "info";
223
+ isJsonLike = false;
224
+ isTTY = typeof process !== "undefined" ? !!process.stderr.isTTY : false;
225
+ configure(opts = {}) {
226
+ let lvl = "info";
227
+ if (opts.debug || process.env.VISOR_DEBUG === "true") {
228
+ lvl = "debug";
229
+ } else if (opts.verbose || process.env.VISOR_LOG_LEVEL === "verbose") {
230
+ lvl = "verbose";
231
+ } else if (opts.quiet || process.env.VISOR_LOG_LEVEL === "quiet") {
232
+ lvl = "warn";
233
+ } else if (opts.level) {
234
+ lvl = opts.level;
235
+ } else if (process.env.VISOR_LOG_LEVEL) {
236
+ const envLvl = process.env.VISOR_LOG_LEVEL;
237
+ if (["silent", "error", "warn", "info", "verbose", "debug"].includes(envLvl)) {
238
+ lvl = envLvl;
239
+ }
240
+ }
241
+ this.level = lvl;
242
+ const output = opts.outputFormat || process.env.VISOR_OUTPUT_FORMAT || "table";
243
+ this.isJsonLike = output === "json" || output === "sarif";
244
+ }
245
+ shouldLog(level) {
246
+ const desired = levelToNumber(level);
247
+ const current = levelToNumber(this.level);
248
+ if (desired > current) return false;
249
+ if (this.isJsonLike && desired < levelToNumber("error") && this.level !== "debug" && this.level !== "verbose") {
250
+ return false;
251
+ }
252
+ return true;
253
+ }
254
+ write(msg) {
255
+ try {
256
+ process.stderr.write(msg + "\n");
257
+ } catch {
258
+ }
259
+ }
260
+ info(msg) {
261
+ if (this.shouldLog("info")) this.write(msg);
262
+ }
263
+ warn(msg) {
264
+ if (this.shouldLog("warn")) this.write(msg);
265
+ }
266
+ error(msg) {
267
+ if (this.shouldLog("error")) this.write(msg);
268
+ }
269
+ verbose(msg) {
270
+ if (this.shouldLog("verbose")) this.write(msg);
271
+ }
272
+ debug(msg) {
273
+ if (this.shouldLog("debug")) this.write(msg);
274
+ }
275
+ step(msg) {
276
+ if (this.shouldLog("info")) this.write(`\u25B6 ${msg}`);
277
+ }
278
+ success(msg) {
279
+ if (this.shouldLog("info")) this.write(`\u2714 ${msg}`);
280
+ }
281
+ };
282
+ logger = new Logger();
283
+ }
284
+ });
285
+
180
286
  // src/github-comments.ts
181
287
  import { v4 as uuidv4 } from "uuid";
182
288
  var CommentManager = class {
@@ -457,89 +563,8 @@ ${content}
457
563
 
458
564
  // src/ai-review-service.ts
459
565
  init_session_registry();
566
+ init_logger();
460
567
  import { ProbeAgent } from "@probelabs/probe";
461
-
462
- // src/logger.ts
463
- function levelToNumber(level) {
464
- switch (level) {
465
- case "silent":
466
- return 0;
467
- case "error":
468
- return 10;
469
- case "warn":
470
- return 20;
471
- case "info":
472
- return 30;
473
- case "verbose":
474
- return 40;
475
- case "debug":
476
- return 50;
477
- }
478
- }
479
- var Logger = class {
480
- level = "info";
481
- isJsonLike = false;
482
- isTTY = typeof process !== "undefined" ? !!process.stderr.isTTY : false;
483
- configure(opts = {}) {
484
- let lvl = "info";
485
- if (opts.debug || process.env.VISOR_DEBUG === "true") {
486
- lvl = "debug";
487
- } else if (opts.verbose || process.env.VISOR_LOG_LEVEL === "verbose") {
488
- lvl = "verbose";
489
- } else if (opts.quiet || process.env.VISOR_LOG_LEVEL === "quiet") {
490
- lvl = "warn";
491
- } else if (opts.level) {
492
- lvl = opts.level;
493
- } else if (process.env.VISOR_LOG_LEVEL) {
494
- const envLvl = process.env.VISOR_LOG_LEVEL;
495
- if (["silent", "error", "warn", "info", "verbose", "debug"].includes(envLvl)) {
496
- lvl = envLvl;
497
- }
498
- }
499
- this.level = lvl;
500
- const output = opts.outputFormat || process.env.VISOR_OUTPUT_FORMAT || "table";
501
- this.isJsonLike = output === "json" || output === "sarif";
502
- }
503
- shouldLog(level) {
504
- const desired = levelToNumber(level);
505
- const current = levelToNumber(this.level);
506
- if (desired > current) return false;
507
- if (this.isJsonLike && desired < levelToNumber("error") && this.level !== "debug" && this.level !== "verbose") {
508
- return false;
509
- }
510
- return true;
511
- }
512
- write(msg) {
513
- try {
514
- process.stderr.write(msg + "\n");
515
- } catch {
516
- }
517
- }
518
- info(msg) {
519
- if (this.shouldLog("info")) this.write(msg);
520
- }
521
- warn(msg) {
522
- if (this.shouldLog("warn")) this.write(msg);
523
- }
524
- error(msg) {
525
- if (this.shouldLog("error")) this.write(msg);
526
- }
527
- verbose(msg) {
528
- if (this.shouldLog("verbose")) this.write(msg);
529
- }
530
- debug(msg) {
531
- if (this.shouldLog("debug")) this.write(msg);
532
- }
533
- step(msg) {
534
- if (this.shouldLog("info")) this.write(`\u25B6 ${msg}`);
535
- }
536
- success(msg) {
537
- if (this.shouldLog("info")) this.write(`\u2714 ${msg}`);
538
- }
539
- };
540
- var logger = new Logger();
541
-
542
- // src/ai-review-service.ts
543
568
  function log(...args) {
544
569
  logger.debug(args.join(" "));
545
570
  }
@@ -1612,7 +1637,7 @@ var PRReviewer = class {
1612
1637
  async reviewPR(owner, repo, prNumber, prInfo, options = {}) {
1613
1638
  const { debug = false, config, checks } = options;
1614
1639
  if (config && checks && checks.length > 0) {
1615
- const { CheckExecutionEngine: CheckExecutionEngine2 } = await import("./check-execution-engine-D6FPIIKR.mjs");
1640
+ const { CheckExecutionEngine: CheckExecutionEngine2 } = await import("./check-execution-engine-Z2USLMN5.mjs");
1616
1641
  const engine = new CheckExecutionEngine2();
1617
1642
  const { results } = await engine.executeGroupedChecks(
1618
1643
  prInfo,
@@ -2894,6 +2919,7 @@ var HttpCheckProvider = class extends CheckProvider {
2894
2919
  };
2895
2920
 
2896
2921
  // src/providers/http-input-provider.ts
2922
+ init_logger();
2897
2923
  var HttpInputProvider = class extends CheckProvider {
2898
2924
  liquid;
2899
2925
  webhookContext;
@@ -3234,6 +3260,7 @@ var NoopCheckProvider = class extends CheckProvider {
3234
3260
  };
3235
3261
 
3236
3262
  // src/providers/log-check-provider.ts
3263
+ init_logger();
3237
3264
  var LogCheckProvider = class extends CheckProvider {
3238
3265
  liquid;
3239
3266
  constructor() {
@@ -3879,6 +3906,7 @@ var ClaudeCodeCheckProvider = class extends CheckProvider {
3879
3906
 
3880
3907
  // src/providers/command-check-provider.ts
3881
3908
  import Sandbox from "@nyariv/sandboxjs";
3909
+ init_logger();
3882
3910
  var CommandCheckProvider = class extends CheckProvider {
3883
3911
  liquid;
3884
3912
  sandbox;
@@ -3916,6 +3944,14 @@ var CommandCheckProvider = class extends CheckProvider {
3916
3944
  return true;
3917
3945
  }
3918
3946
  async execute(prInfo, config, dependencyResults) {
3947
+ try {
3948
+ logger.info(
3949
+ ` command provider: executing check=${String(config.checkName || config.type)} hasTransformJs=${Boolean(
3950
+ config.transform_js
3951
+ )}`
3952
+ );
3953
+ } catch {
3954
+ }
3919
3955
  const command = config.exec;
3920
3956
  const transform = config.transform;
3921
3957
  const transformJs = config.transform_js;
@@ -3975,33 +4011,29 @@ var CommandCheckProvider = class extends CheckProvider {
3975
4011
  output = parsed;
3976
4012
  logger.debug(`\u{1F527} Debug: Parsed entire output as JSON successfully`);
3977
4013
  } catch {
3978
- const extracted2 = this.extractJsonFromEnd(rawOutput);
3979
- if (extracted2) {
4014
+ const extractedTail = this.extractJsonFromEnd(rawOutput);
4015
+ if (extractedTail) {
3980
4016
  try {
3981
- output = JSON.parse(extracted2);
3982
- logger.debug(
3983
- `\u{1F527} Debug: Extracted and parsed JSON from end of output (${extracted2.length} chars from ${rawOutput.length} total)`
3984
- );
3985
- logger.debug(`\u{1F527} Debug: Extracted JSON content: ${extracted2.slice(0, 200)}`);
3986
- } catch (parseError) {
3987
- logger.debug(
3988
- `\u{1F527} Debug: Extracted text is not valid JSON: ${parseError instanceof Error ? parseError.message : "Unknown error"}`
3989
- );
4017
+ output = JSON.parse(extractedTail);
4018
+ } catch {
3990
4019
  output = rawOutput;
3991
4020
  }
3992
4021
  } else {
3993
- logger.debug(`\u{1F527} Debug: No JSON found in output, keeping as string`);
3994
- output = rawOutput;
3995
- }
3996
- }
3997
- if (output !== rawOutput) {
3998
- try {
3999
- const outputType = Array.isArray(output) ? `array[${output.length}]` : typeof output;
4000
- logger.debug(`\u{1F527} Debug: Parsed output type: ${outputType}`);
4001
- if (typeof output === "object" && output !== null) {
4002
- logger.debug(`\u{1F527} Debug: Parsed output keys: ${Object.keys(output).join(", ")}`);
4022
+ const extractedAny = this.extractJsonAnywhere(rawOutput);
4023
+ if (extractedAny) {
4024
+ try {
4025
+ output = JSON.parse(extractedAny);
4026
+ } catch {
4027
+ output = rawOutput;
4028
+ }
4029
+ } else {
4030
+ const m = /\berror\b\s*[:=]\s*(true|false)/i.exec(rawOutput);
4031
+ if (m) {
4032
+ output = { error: m[1].toLowerCase() === "true" };
4033
+ } else {
4034
+ output = rawOutput;
4035
+ }
4003
4036
  }
4004
- } catch {
4005
4037
  }
4006
4038
  }
4007
4039
  let finalOutput = output;
@@ -4048,65 +4080,130 @@ var CommandCheckProvider = class extends CheckProvider {
4048
4080
  env: templateContext.env
4049
4081
  };
4050
4082
  const trimmedTransform = transformJs.trim();
4051
- let transformExpression;
4052
- if (/return\s+/.test(trimmedTransform)) {
4053
- transformExpression = `(() => {
4054
- ${trimmedTransform}
4055
- })()`;
4056
- } else {
4057
- const lines = trimmedTransform.split("\n");
4058
- if (lines.length > 1) {
4059
- const lastLine = lines[lines.length - 1].trim();
4060
- const remaining = lines.slice(0, -1).join("\n");
4061
- if (lastLine && !lastLine.includes("}") && !lastLine.includes("{")) {
4062
- const returnTarget = lastLine.replace(/;$/, "");
4063
- transformExpression = `(() => {
4064
- ${remaining}
4065
- return ${returnTarget};
4066
- })()`;
4067
- } else {
4068
- transformExpression = `(${trimmedTransform})`;
4069
- }
4070
- } else {
4071
- transformExpression = `(${trimmedTransform})`;
4083
+ const buildBodyWithReturn = (raw) => {
4084
+ const t = raw.trim();
4085
+ const lines = t.split(/\n/);
4086
+ let i = lines.length - 1;
4087
+ while (i >= 0 && lines[i].trim().length === 0) i--;
4088
+ if (i < 0) return "return undefined;";
4089
+ const lastLine = lines[i].trim();
4090
+ if (/^return\b/i.test(lastLine)) {
4091
+ return t;
4072
4092
  }
4073
- }
4093
+ const idx = t.lastIndexOf(lastLine);
4094
+ const head = idx >= 0 ? t.slice(0, idx) : "";
4095
+ const lastExpr = lastLine.replace(/;\s*$/, "");
4096
+ return `${head}
4097
+ return (${lastExpr});`;
4098
+ };
4099
+ const bodyWithReturn = buildBodyWithReturn(trimmedTransform);
4074
4100
  const code = `
4075
4101
  const output = scope.output;
4076
4102
  const pr = scope.pr;
4077
4103
  const files = scope.files;
4078
4104
  const outputs = scope.outputs;
4079
4105
  const env = scope.env;
4080
- const log = (...args) => {
4081
- console.log('\u{1F50D} Debug:', ...args);
4082
- };
4083
- return ${transformExpression};
4106
+ const log = (...args) => { console.log('\u{1F50D} Debug:', ...args); };
4107
+ const __result = (function(){
4108
+ ${bodyWithReturn}
4109
+ })();
4110
+ return __result;
4084
4111
  `;
4112
+ if (!this.sandbox) {
4113
+ this.sandbox = this.createSecureSandbox();
4114
+ }
4115
+ let parsedFromSandboxJson = void 0;
4085
4116
  try {
4086
- logger.debug(`\u{1F527} Debug: JavaScript transform code: ${code}`);
4087
- logger.debug(
4088
- `\u{1F527} Debug: JavaScript context: ${JSON.stringify(jsContext).slice(0, 200)}`
4089
- );
4117
+ const stringifyCode = `
4118
+ const output = scope.output;
4119
+ const pr = scope.pr;
4120
+ const files = scope.files;
4121
+ const outputs = scope.outputs;
4122
+ const env = scope.env;
4123
+ const log = (...args) => { console.log('\u{1F50D} Debug:', ...args); };
4124
+ const __ret = (function(){
4125
+ ${bodyWithReturn}
4126
+ })();
4127
+ return typeof __ret === 'object' && __ret !== null ? JSON.stringify(__ret) : null;
4128
+ `;
4129
+ const stringifyExec = this.sandbox.compile(stringifyCode);
4130
+ const jsonStr = stringifyExec({ scope: jsContext }).run();
4131
+ if (typeof jsonStr === "string" && jsonStr.trim().startsWith("{")) {
4132
+ parsedFromSandboxJson = JSON.parse(jsonStr);
4133
+ }
4090
4134
  } catch {
4091
4135
  }
4092
- if (!this.sandbox) {
4093
- this.sandbox = this.createSecureSandbox();
4136
+ if (parsedFromSandboxJson !== void 0) {
4137
+ finalOutput = parsedFromSandboxJson;
4138
+ } else {
4139
+ const exec2 = this.sandbox.compile(code);
4140
+ finalOutput = exec2({ scope: jsContext }).run();
4094
4141
  }
4095
- const exec2 = this.sandbox.compile(code);
4096
- finalOutput = exec2({ scope: jsContext }).run();
4097
- logger.verbose(`\u2713 Applied JavaScript transform successfully`);
4098
4142
  try {
4099
- const preview = JSON.stringify(finalOutput);
4143
+ if (finalOutput && typeof finalOutput === "object" && !Array.isArray(finalOutput) && (finalOutput.error === void 0 || finalOutput.issues === void 0)) {
4144
+ const vm = await import("vm");
4145
+ const vmContext = vm.createContext({ scope: jsContext });
4146
+ const vmCode = `
4147
+ (function(){
4148
+ const output = scope.output; const pr = scope.pr; const files = scope.files; const outputs = scope.outputs; const env = scope.env; const log = ()=>{};
4149
+ ${bodyWithReturn}
4150
+ })()
4151
+ `;
4152
+ const vmResult = vm.runInContext(vmCode, vmContext, { timeout: 1e3 });
4153
+ if (vmResult && typeof vmResult === "object") {
4154
+ finalOutput = vmResult;
4155
+ }
4156
+ }
4157
+ } catch {
4158
+ }
4159
+ let finalSnapshot = null;
4160
+ try {
4161
+ if (finalOutput && typeof finalOutput === "object" && !Array.isArray(finalOutput)) {
4162
+ try {
4163
+ const stringifyExec = this.sandbox.compile("return JSON.stringify(scope.obj);");
4164
+ const jsonStr = stringifyExec({ obj: finalOutput }).run();
4165
+ if (typeof jsonStr === "string" && jsonStr.trim().startsWith("{")) {
4166
+ finalSnapshot = JSON.parse(jsonStr);
4167
+ }
4168
+ } catch {
4169
+ }
4170
+ if (!finalSnapshot) {
4171
+ try {
4172
+ finalSnapshot = JSON.parse(JSON.stringify(finalOutput));
4173
+ } catch {
4174
+ }
4175
+ }
4176
+ if (!finalSnapshot) {
4177
+ const tmp = {};
4178
+ for (const k of Object.keys(finalOutput)) {
4179
+ tmp[k] = finalOutput[k];
4180
+ }
4181
+ finalSnapshot = tmp;
4182
+ }
4183
+ }
4184
+ } catch {
4185
+ }
4186
+ this.__lastTransformSnapshot = finalSnapshot;
4187
+ try {
4188
+ const isObj = finalOutput && typeof finalOutput === "object" && !Array.isArray(finalOutput);
4189
+ const keys = isObj ? Object.keys(finalOutput).join(",") : typeof finalOutput;
4100
4190
  logger.debug(
4101
- `\u{1F527} Debug: transform_js result: ${typeof preview === "string" ? preview.slice(0, 200) : String(preview).slice(0, 200)}`
4191
+ ` transform_js: output typeof=${Array.isArray(finalOutput) ? "array" : typeof finalOutput} keys=${keys}`
4102
4192
  );
4103
- } catch {
4193
+ if (isObj && finalOutput.issues) {
4194
+ const mi = finalOutput.issues;
4195
+ logger.debug(
4196
+ ` transform_js: issues typeof=${Array.isArray(mi) ? "array" : typeof mi} len=${mi && mi.length || 0}`
4197
+ );
4198
+ }
4104
4199
  try {
4105
- const preview = String(finalOutput);
4106
- logger.debug(`\u{1F527} Debug: transform_js result: ${preview.slice(0, 200)}`);
4200
+ if (isObj)
4201
+ logger.debug(` transform_js: error value=${String(finalOutput.error)}`);
4107
4202
  } catch {
4108
4203
  }
4204
+ } catch {
4109
4205
  }
4206
+ logger.verbose(`\u2713 Applied JavaScript transform successfully`);
4110
4207
  } catch (error) {
4111
4208
  logger.error(
4112
4209
  `\u2717 Failed to apply JavaScript transform: ${error instanceof Error ? error.message : "Unknown error"}`
@@ -4127,13 +4224,175 @@ return ${returnTarget};
4127
4224
  }
4128
4225
  let issues = [];
4129
4226
  let outputForDependents = finalOutput;
4227
+ const snapshotForExtraction = this.__lastTransformSnapshot || null;
4228
+ try {
4229
+ if (snapshotForExtraction) {
4230
+ logger.debug(` provider: snapshot keys=${Object.keys(snapshotForExtraction).join(",")}`);
4231
+ } else {
4232
+ logger.debug(` provider: snapshot is null`);
4233
+ }
4234
+ } catch {
4235
+ }
4236
+ try {
4237
+ if (Array.isArray(outputForDependents) && outputForDependents.length === 1) {
4238
+ const first = outputForDependents[0];
4239
+ if (typeof first === "string") {
4240
+ try {
4241
+ outputForDependents = JSON.parse(first);
4242
+ } catch {
4243
+ }
4244
+ } else if (first && typeof first === "object") {
4245
+ outputForDependents = first;
4246
+ }
4247
+ }
4248
+ } catch {
4249
+ }
4130
4250
  let content;
4131
4251
  let extracted = null;
4132
4252
  const trimmedRawOutput = typeof rawOutput === "string" ? rawOutput.trim() : void 0;
4133
4253
  const commandConfig = config;
4134
4254
  const isForEachParent = commandConfig.forEach === true;
4135
4255
  if (!isForEachParent) {
4136
- extracted = this.extractIssuesFromOutput(finalOutput);
4256
+ try {
4257
+ const baseObj = snapshotForExtraction || finalOutput;
4258
+ if (baseObj && typeof baseObj === "object" && Object.prototype.hasOwnProperty.call(baseObj, "issues")) {
4259
+ const remaining = { ...baseObj };
4260
+ delete remaining.issues;
4261
+ outputForDependents = Object.keys(remaining).length > 0 ? remaining : void 0;
4262
+ try {
4263
+ const k = outputForDependents && typeof outputForDependents === "object" ? Object.keys(outputForDependents).join(",") : String(outputForDependents);
4264
+ logger.debug(` provider: generic-remaining keys=${k}`);
4265
+ } catch {
4266
+ }
4267
+ }
4268
+ } catch {
4269
+ }
4270
+ const objForExtraction = snapshotForExtraction || finalOutput;
4271
+ if (objForExtraction && typeof objForExtraction === "object") {
4272
+ try {
4273
+ const rec = objForExtraction;
4274
+ const maybeIssues = rec.issues;
4275
+ const toPlainArray = (v) => {
4276
+ if (Array.isArray(v)) return v;
4277
+ try {
4278
+ if (v && typeof v === "object" && typeof v[Symbol.iterator] === "function") {
4279
+ return Array.from(v);
4280
+ }
4281
+ } catch {
4282
+ }
4283
+ const len = Number((v || {}).length);
4284
+ if (Number.isFinite(len) && len >= 0) {
4285
+ const arr2 = [];
4286
+ for (let i = 0; i < len; i++) arr2.push(v[i]);
4287
+ return arr2;
4288
+ }
4289
+ try {
4290
+ const cloned = JSON.parse(JSON.stringify(v));
4291
+ return Array.isArray(cloned) ? cloned : null;
4292
+ } catch {
4293
+ return null;
4294
+ }
4295
+ };
4296
+ try {
4297
+ const ctor = maybeIssues && maybeIssues.constructor ? maybeIssues.constructor.name : "unknown";
4298
+ logger.debug(
4299
+ ` provider: issues inspect typeof=${typeof maybeIssues} Array.isArray=${Array.isArray(
4300
+ maybeIssues
4301
+ )} ctor=${ctor} keys=${Object.keys(maybeIssues || {}).join(",")}`
4302
+ );
4303
+ } catch {
4304
+ }
4305
+ const arr = toPlainArray(maybeIssues);
4306
+ if (arr) {
4307
+ const norm = this.normalizeIssueArray(arr);
4308
+ if (norm) {
4309
+ issues = norm;
4310
+ const remaining = { ...rec };
4311
+ delete remaining.issues;
4312
+ outputForDependents = Object.keys(remaining).length > 0 ? remaining : void 0;
4313
+ try {
4314
+ const keys = outputForDependents && typeof outputForDependents === "object" ? Object.keys(outputForDependents).join(",") : String(outputForDependents);
4315
+ logger.info(
4316
+ ` provider: fast-path issues=${issues.length} remaining keys=${keys}`
4317
+ );
4318
+ } catch {
4319
+ }
4320
+ } else {
4321
+ try {
4322
+ logger.info(" provider: fast-path norm failed");
4323
+ } catch {
4324
+ }
4325
+ }
4326
+ } else {
4327
+ try {
4328
+ logger.info(" provider: fast-path arr unavailable");
4329
+ } catch {
4330
+ }
4331
+ }
4332
+ } catch {
4333
+ }
4334
+ }
4335
+ let extractionTarget = snapshotForExtraction || finalOutput;
4336
+ try {
4337
+ if (Array.isArray(extractionTarget) && extractionTarget.length === 1) {
4338
+ const first = extractionTarget[0];
4339
+ if (typeof first === "string") {
4340
+ try {
4341
+ extractionTarget = JSON.parse(first);
4342
+ } catch {
4343
+ extractionTarget = first;
4344
+ }
4345
+ } else if (first && typeof first === "object") {
4346
+ extractionTarget = first;
4347
+ }
4348
+ }
4349
+ } catch {
4350
+ }
4351
+ extracted = this.extractIssuesFromOutput(extractionTarget);
4352
+ try {
4353
+ if (extractionTarget !== (snapshotForExtraction || finalOutput)) {
4354
+ finalOutput = extractionTarget;
4355
+ }
4356
+ } catch {
4357
+ }
4358
+ if (!extracted && finalOutput && typeof finalOutput === "object") {
4359
+ try {
4360
+ const rec = finalOutput;
4361
+ const maybeIssues = rec.issues;
4362
+ if (maybeIssues && typeof maybeIssues === "object") {
4363
+ let arr = null;
4364
+ try {
4365
+ if (typeof maybeIssues[Symbol.iterator] === "function") {
4366
+ arr = Array.from(maybeIssues);
4367
+ }
4368
+ } catch {
4369
+ }
4370
+ if (!arr) {
4371
+ const len = Number(maybeIssues.length);
4372
+ if (Number.isFinite(len) && len >= 0) {
4373
+ arr = [];
4374
+ for (let i = 0; i < len; i++) arr.push(maybeIssues[i]);
4375
+ }
4376
+ }
4377
+ if (!arr) {
4378
+ try {
4379
+ arr = JSON.parse(JSON.stringify(maybeIssues));
4380
+ } catch {
4381
+ }
4382
+ }
4383
+ if (arr && Array.isArray(arr)) {
4384
+ const norm = this.normalizeIssueArray(arr);
4385
+ if (norm) {
4386
+ issues = norm;
4387
+ const remaining = { ...rec };
4388
+ delete remaining.issues;
4389
+ outputForDependents = Object.keys(remaining).length > 0 ? remaining : void 0;
4390
+ }
4391
+ }
4392
+ }
4393
+ } catch {
4394
+ }
4395
+ }
4137
4396
  if (!extracted && typeof finalOutput === "string") {
4138
4397
  try {
4139
4398
  const parsed = JSON.parse(finalOutput);
@@ -4141,39 +4400,213 @@ return ${returnTarget};
4141
4400
  if (extracted) {
4142
4401
  issues = extracted.issues;
4143
4402
  outputForDependents = extracted.remainingOutput;
4403
+ if (typeof extracted.remainingOutput === "object" && extracted.remainingOutput !== null && typeof extracted.remainingOutput.content === "string") {
4404
+ const c = String(extracted.remainingOutput.content).trim();
4405
+ if (c) content = c;
4406
+ }
4407
+ }
4408
+ } catch {
4409
+ try {
4410
+ const any = this.extractJsonAnywhere(finalOutput);
4411
+ if (any) {
4412
+ const parsed = JSON.parse(any);
4413
+ extracted = this.extractIssuesFromOutput(parsed);
4414
+ if (extracted) {
4415
+ issues = extracted.issues;
4416
+ outputForDependents = extracted.remainingOutput;
4417
+ if (typeof extracted.remainingOutput === "object" && extracted.remainingOutput !== null && typeof extracted.remainingOutput.content === "string") {
4418
+ const c = String(extracted.remainingOutput.content).trim();
4419
+ if (c) content = c;
4420
+ }
4421
+ }
4422
+ }
4423
+ } catch {
4424
+ }
4425
+ }
4426
+ } else if (extracted) {
4427
+ issues = extracted.issues;
4428
+ outputForDependents = extracted.remainingOutput;
4429
+ if (typeof extracted.remainingOutput === "object" && extracted.remainingOutput !== null && typeof extracted.remainingOutput.content === "string") {
4430
+ const c = String(extracted.remainingOutput.content).trim();
4431
+ if (c) content = c;
4432
+ }
4433
+ }
4434
+ if (!issues.length && this.shouldTreatAsTextOutput(trimmedRawOutput)) {
4435
+ content = trimmedRawOutput;
4436
+ } else if (issues.length && typeof extracted?.remainingOutput === "string") {
4437
+ const trimmed = extracted.remainingOutput.trim();
4438
+ if (trimmed) {
4439
+ content = trimmed;
4440
+ }
4441
+ }
4442
+ if (!issues.length && typeof trimmedRawOutput === "string") {
4443
+ try {
4444
+ const tryParsed = JSON.parse(trimmedRawOutput);
4445
+ const reextract = this.extractIssuesFromOutput(tryParsed);
4446
+ if (reextract && reextract.issues && reextract.issues.length) {
4447
+ issues = reextract.issues;
4448
+ if (!outputForDependents && reextract.remainingOutput) {
4449
+ outputForDependents = reextract.remainingOutput;
4450
+ }
4451
+ } else if (Array.isArray(tryParsed)) {
4452
+ const first = tryParsed[0];
4453
+ if (first && typeof first === "object" && Array.isArray(first.issues)) {
4454
+ const merged = [];
4455
+ for (const el of tryParsed) {
4456
+ if (el && typeof el === "object" && Array.isArray(el.issues)) {
4457
+ merged.push(...el.issues);
4458
+ }
4459
+ }
4460
+ const flat = this.normalizeIssueArray(merged);
4461
+ if (flat) issues = flat;
4462
+ } else {
4463
+ const converted = [];
4464
+ for (const el of tryParsed) {
4465
+ if (typeof el === "string") {
4466
+ try {
4467
+ const obj = JSON.parse(el);
4468
+ converted.push(obj);
4469
+ } catch {
4470
+ }
4471
+ } else {
4472
+ converted.push(el);
4473
+ }
4474
+ }
4475
+ const flat = this.normalizeIssueArray(converted);
4476
+ if (flat) issues = flat;
4477
+ }
4478
+ }
4479
+ } catch {
4480
+ }
4481
+ if (!issues.length) {
4482
+ try {
4483
+ const any = this.extractJsonAnywhere(trimmedRawOutput);
4484
+ if (any) {
4485
+ const tryParsed = JSON.parse(any);
4486
+ const reextract = this.extractIssuesFromOutput(tryParsed);
4487
+ if (reextract && reextract.issues && reextract.issues.length) {
4488
+ issues = reextract.issues;
4489
+ if (!outputForDependents && reextract.remainingOutput) {
4490
+ outputForDependents = reextract.remainingOutput;
4491
+ }
4492
+ }
4493
+ }
4494
+ } catch {
4495
+ }
4496
+ }
4497
+ }
4498
+ try {
4499
+ const srcObj = snapshotForExtraction || finalOutput;
4500
+ if (outputForDependents && typeof outputForDependents === "object" && srcObj && typeof srcObj === "object") {
4501
+ for (const k of Object.keys(srcObj)) {
4502
+ const v = srcObj[k];
4503
+ if (typeof v === "boolean" || typeof v === "number" || typeof v === "string") {
4504
+ outputForDependents[k] = v;
4505
+ }
4506
+ }
4507
+ }
4508
+ } catch {
4509
+ }
4510
+ try {
4511
+ if (outputForDependents && typeof outputForDependents === "object" && !Array.isArray(outputForDependents)) {
4512
+ const plain = {};
4513
+ for (const k of Object.keys(outputForDependents)) {
4514
+ plain[k] = outputForDependents[k];
4515
+ }
4516
+ outputForDependents = plain;
4517
+ }
4518
+ } catch {
4519
+ }
4520
+ }
4521
+ if (!content && this.shouldTreatAsTextOutput(trimmedRawOutput) && !isForEachParent) {
4522
+ content = trimmedRawOutput;
4523
+ }
4524
+ try {
4525
+ if (outputForDependents && typeof outputForDependents === "object") {
4526
+ outputForDependents = JSON.parse(JSON.stringify(outputForDependents));
4527
+ }
4528
+ } catch {
4529
+ }
4530
+ const promoted = {};
4531
+ try {
4532
+ const srcObj = snapshotForExtraction || finalOutput;
4533
+ if (srcObj && typeof srcObj === "object") {
4534
+ for (const k of Object.keys(srcObj)) {
4535
+ const v = srcObj[k];
4536
+ if (typeof v === "boolean") {
4537
+ if (v === true && promoted[k] === void 0) promoted[k] = true;
4538
+ } else if ((typeof v === "number" || typeof v === "string") && promoted[k] === void 0) {
4539
+ promoted[k] = v;
4540
+ }
4541
+ }
4542
+ }
4543
+ } catch {
4544
+ }
4545
+ const result = {
4546
+ issues,
4547
+ output: outputForDependents,
4548
+ ...content ? { content } : {},
4549
+ ...promoted
4550
+ };
4551
+ try {
4552
+ if (transformJs) {
4553
+ const rawObj = snapshotForExtraction || finalOutput;
4554
+ if (rawObj && typeof rawObj === "object") {
4555
+ result.__raw = rawObj;
4556
+ }
4557
+ }
4558
+ } catch {
4559
+ }
4560
+ try {
4561
+ const srcObj = snapshotForExtraction || finalOutput;
4562
+ const srcErr = (() => {
4563
+ try {
4564
+ if (snapshotForExtraction && typeof snapshotForExtraction === "object" && snapshotForExtraction.error !== void 0) {
4565
+ return Boolean(snapshotForExtraction.error);
4566
+ }
4567
+ if (finalOutput && typeof finalOutput === "object" && finalOutput.error !== void 0) {
4568
+ return Boolean(finalOutput.error);
4569
+ }
4570
+ } catch {
4571
+ }
4572
+ return void 0;
4573
+ })();
4574
+ const dst = result.output;
4575
+ if (srcObj && typeof srcObj === "object" && dst && typeof dst === "object") {
4576
+ try {
4577
+ logger.debug(
4578
+ ` provider: safeguard src.error typeof=${typeof srcObj.error} val=${String(srcObj.error)} dst.hasErrorBefore=${String(dst.error !== void 0)}`
4579
+ );
4580
+ } catch {
4581
+ }
4582
+ for (const k of Object.keys(srcObj)) {
4583
+ const v = srcObj[k];
4584
+ if (typeof v === "boolean" || typeof v === "number" || typeof v === "string") {
4585
+ dst[k] = v;
4586
+ }
4587
+ }
4588
+ if (srcErr !== void 0 && dst.error === void 0) {
4589
+ dst.error = srcErr;
4590
+ try {
4591
+ const k = Object.keys(dst).join(",");
4592
+ logger.debug(
4593
+ ` provider: safeguard merged error -> output keys=${k} val=${String(dst.error)}`
4594
+ );
4595
+ } catch {
4144
4596
  }
4145
- } catch {
4146
- }
4147
- } else if (extracted) {
4148
- issues = extracted.issues;
4149
- outputForDependents = extracted.remainingOutput;
4150
- }
4151
- if (!issues.length && this.shouldTreatAsTextOutput(trimmedRawOutput)) {
4152
- content = trimmedRawOutput;
4153
- } else if (issues.length && typeof extracted?.remainingOutput === "string") {
4154
- const trimmed = extracted.remainingOutput.trim();
4155
- if (trimmed) {
4156
- content = trimmed;
4157
4597
  }
4158
4598
  }
4599
+ } catch {
4159
4600
  }
4160
- if (!content && this.shouldTreatAsTextOutput(trimmedRawOutput) && !isForEachParent) {
4161
- content = trimmedRawOutput;
4162
- }
4163
- const result = {
4164
- issues,
4165
- output: outputForDependents,
4166
- ...content ? { content } : {}
4167
- };
4168
- if (transformJs) {
4169
- try {
4170
- const outputValue = result.output;
4171
- const stringified = JSON.stringify(outputValue);
4172
- logger.debug(
4173
- `\u{1F527} Debug: Command provider returning output: ${stringified ? stringified.slice(0, 200) : "(empty)"}`
4174
- );
4175
- } catch {
4601
+ try {
4602
+ const out = result.output;
4603
+ if (out && typeof out === "object") {
4604
+ const k = Object.keys(out).join(",");
4605
+ logger.debug(` provider: return output keys=${k}`);
4606
+ } else {
4607
+ logger.debug(` provider: return output type=${typeof out}`);
4176
4608
  }
4609
+ } catch {
4177
4610
  }
4178
4611
  return result;
4179
4612
  } catch (error) {
@@ -4323,19 +4756,89 @@ ${stderrOutput}` : `Command execution failed: ${errorMessage}`;
4323
4756
  * Looks for the last occurrence of { or [ and tries to parse from there
4324
4757
  */
4325
4758
  extractJsonFromEnd(text) {
4326
- const lines = text.split("\n");
4327
- for (let i = lines.length - 1; i >= 0; i--) {
4328
- const trimmed = lines[i].trim();
4329
- if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
4330
- const candidate = lines.slice(i).join("\n");
4331
- const trimmedCandidate = candidate.trim();
4332
- if (trimmedCandidate.startsWith("{") && trimmedCandidate.endsWith("}") || trimmedCandidate.startsWith("[") && trimmedCandidate.endsWith("]")) {
4333
- return trimmedCandidate;
4759
+ const lastBrace = Math.max(text.lastIndexOf("}"), text.lastIndexOf("]"));
4760
+ if (lastBrace === -1) return null;
4761
+ let open = 0;
4762
+ for (let i = lastBrace; i >= 0; i--) {
4763
+ const ch = text[i];
4764
+ if (ch === "}" || ch === "]") open++;
4765
+ else if (ch === "{" || ch === "[") open--;
4766
+ if (open === 0 && (ch === "{" || ch === "[")) {
4767
+ const candidate = text.slice(i, lastBrace + 1).trim();
4768
+ try {
4769
+ JSON.parse(candidate);
4770
+ return candidate;
4771
+ } catch {
4772
+ return null;
4334
4773
  }
4335
4774
  }
4336
4775
  }
4337
4776
  return null;
4338
4777
  }
4778
+ // Extract any balanced JSON object/array substring from anywhere in the text
4779
+ extractJsonAnywhere(text) {
4780
+ const n = text.length;
4781
+ let best = null;
4782
+ for (let i = 0; i < n; i++) {
4783
+ const start = text[i];
4784
+ if (start !== "{" && start !== "[") continue;
4785
+ let open = 0;
4786
+ let inString = false;
4787
+ let escape = false;
4788
+ for (let j = i; j < n; j++) {
4789
+ const ch = text[j];
4790
+ if (escape) {
4791
+ escape = false;
4792
+ continue;
4793
+ }
4794
+ if (ch === "\\") {
4795
+ escape = true;
4796
+ continue;
4797
+ }
4798
+ if (ch === '"') {
4799
+ inString = !inString;
4800
+ continue;
4801
+ }
4802
+ if (inString) continue;
4803
+ if (ch === "{" || ch === "[") open++;
4804
+ else if (ch === "}" || ch === "]") open--;
4805
+ if (open === 0 && (ch === "}" || ch === "]")) {
4806
+ const candidate = text.slice(i, j + 1).trim();
4807
+ try {
4808
+ JSON.parse(candidate);
4809
+ best = candidate;
4810
+ } catch {
4811
+ const strict = this.looseJsonToStrict(candidate);
4812
+ if (strict) {
4813
+ try {
4814
+ JSON.parse(strict);
4815
+ best = strict;
4816
+ } catch {
4817
+ }
4818
+ }
4819
+ }
4820
+ break;
4821
+ }
4822
+ }
4823
+ }
4824
+ return best;
4825
+ }
4826
+ // Best-effort conversion of object-literal-like strings to strict JSON
4827
+ looseJsonToStrict(candidate) {
4828
+ try {
4829
+ let s = candidate.trim();
4830
+ s = s.replace(/'/g, '"');
4831
+ s = s.replace(/([\{,]\s*)([A-Za-z_][A-Za-z0-9_-]*)\s*:/g, '$1"$2":');
4832
+ s = s.replace(/:\s*([A-Za-z_][A-Za-z0-9_-]*)\s*(?=[,}])/g, (m, word) => {
4833
+ const lw = String(word).toLowerCase();
4834
+ if (lw === "true" || lw === "false" || lw === "null") return `:${lw}`;
4835
+ return `:"${word}"`;
4836
+ });
4837
+ return s;
4838
+ } catch {
4839
+ return null;
4840
+ }
4841
+ }
4339
4842
  /**
4340
4843
  * Recursively apply JSON-smart wrapper to outputs object values
4341
4844
  */
@@ -4383,6 +4886,20 @@ ${stderrOutput}` : `Command execution failed: ${errorMessage}`;
4383
4886
  ];
4384
4887
  }
4385
4888
  extractIssuesFromOutput(output) {
4889
+ try {
4890
+ logger.info(
4891
+ ` extractIssuesFromOutput: typeof=${Array.isArray(output) ? "array" : typeof output}`
4892
+ );
4893
+ if (typeof output === "object" && output) {
4894
+ const rec = output;
4895
+ logger.info(
4896
+ ` extractIssuesFromOutput: keys=${Object.keys(rec).join(",")} issuesIsArray=${Array.isArray(
4897
+ rec.issues
4898
+ )}`
4899
+ );
4900
+ }
4901
+ } catch {
4902
+ }
4386
4903
  if (output === null || output === void 0) {
4387
4904
  return null;
4388
4905
  }
@@ -4390,9 +4907,21 @@ ${stderrOutput}` : `Command execution failed: ${errorMessage}`;
4390
4907
  return null;
4391
4908
  }
4392
4909
  if (Array.isArray(output)) {
4393
- const issues = this.normalizeIssueArray(output);
4394
- if (issues) {
4395
- return { issues, remainingOutput: void 0 };
4910
+ const first = output[0];
4911
+ if (first && typeof first === "object" && !Array.isArray(first.message) && Array.isArray(first.issues)) {
4912
+ const merged = [];
4913
+ for (const el of output) {
4914
+ if (el && typeof el === "object" && Array.isArray(el.issues)) {
4915
+ merged.push(...el.issues);
4916
+ }
4917
+ }
4918
+ const flat = this.normalizeIssueArray(merged);
4919
+ if (flat) return { issues: flat, remainingOutput: void 0 };
4920
+ } else {
4921
+ const issues = this.normalizeIssueArray(output);
4922
+ if (issues) {
4923
+ return { issues, remainingOutput: void 0 };
4924
+ }
4396
4925
  }
4397
4926
  return null;
4398
4927
  }
@@ -4520,10 +5049,28 @@ ${stderrOutput}` : `Command execution failed: ${errorMessage}`;
4520
5049
  }
4521
5050
  async renderCommandTemplate(template, context) {
4522
5051
  try {
4523
- return await this.liquid.parseAndRender(template, context);
5052
+ let tpl = template;
5053
+ if (tpl.includes("{{")) {
5054
+ tpl = tpl.replace(/\{\{([\s\S]*?)\}\}/g, (_m, inner) => {
5055
+ const fixed = String(inner).replace(/\[\"/g, "['").replace(/\"\]/g, "']");
5056
+ return `{{ ${fixed} }}`;
5057
+ });
5058
+ }
5059
+ let rendered = await this.liquid.parseAndRender(tpl, context);
5060
+ if (/\{\{[\s\S]*?\}\}/.test(rendered)) {
5061
+ try {
5062
+ rendered = this.renderWithJsExpressions(rendered, context);
5063
+ } catch {
5064
+ }
5065
+ }
5066
+ return rendered;
4524
5067
  } catch (error) {
4525
- logger.debug(`\u{1F527} Debug: Liquid rendering failed, falling back to JS evaluation: ${error}`);
4526
- return this.renderWithJsExpressions(template, context);
5068
+ logger.debug(`\u{1F527} Debug: Liquid templating failed, trying JS-expression fallback: ${error}`);
5069
+ try {
5070
+ return this.renderWithJsExpressions(template, context);
5071
+ } catch {
5072
+ return template;
5073
+ }
4527
5074
  }
4528
5075
  }
4529
5076
  renderWithJsExpressions(template, context) {
@@ -4536,9 +5083,7 @@ ${stderrOutput}` : `Command execution failed: ${errorMessage}`;
4536
5083
  const expressionRegex = /\{\{\s*([^{}]+?)\s*\}\}/g;
4537
5084
  return template.replace(expressionRegex, (_match, expr) => {
4538
5085
  const expression = String(expr).trim();
4539
- if (!expression) {
4540
- return "";
4541
- }
5086
+ if (!expression) return "";
4542
5087
  try {
4543
5088
  const evalCode = `
4544
5089
  const pr = scope.pr;
@@ -4547,14 +5092,11 @@ ${stderrOutput}` : `Command execution failed: ${errorMessage}`;
4547
5092
  const env = scope.env;
4548
5093
  return (${expression});
4549
5094
  `;
4550
- if (!this.sandbox) {
4551
- this.sandbox = this.createSecureSandbox();
4552
- }
5095
+ if (!this.sandbox) this.sandbox = this.createSecureSandbox();
4553
5096
  const evaluator = this.sandbox.compile(evalCode);
4554
5097
  const result = evaluator({ scope }).run();
4555
5098
  return result === void 0 || result === null ? "" : String(result);
4556
- } catch (evaluationError) {
4557
- logger.debug(`\u{1F527} Debug: Failed to evaluate expression: ${expression} - ${evaluationError}`);
5099
+ } catch {
4558
5100
  return "";
4559
5101
  }
4560
5102
  });
@@ -4955,7 +5497,19 @@ var FailureConditionEvaluator = class _FailureConditionEvaluator {
4955
5497
  previousOutputs
4956
5498
  );
4957
5499
  try {
4958
- return this.evaluateExpression(expression, context);
5500
+ try {
5501
+ const isObj = context.output && typeof context.output === "object";
5502
+ const keys = isObj ? Object.keys(context.output).join(",") : typeof context.output;
5503
+ let errorVal = void 0;
5504
+ if (isObj && context.output.error !== void 0)
5505
+ errorVal = context.output.error;
5506
+ (init_logger(), __toCommonJS(logger_exports)).logger.debug(
5507
+ ` fail_if: evaluating '${expression}' with output keys=${keys} error=${String(errorVal)}`
5508
+ );
5509
+ } catch {
5510
+ }
5511
+ const res = this.evaluateExpression(expression, context);
5512
+ return res;
4959
5513
  } catch (error) {
4960
5514
  console.warn(`Failed to evaluate fail_if expression: ${error}`);
4961
5515
  return false;
@@ -5059,6 +5613,16 @@ var FailureConditionEvaluator = class _FailureConditionEvaluator {
5059
5613
  results.length = 0;
5060
5614
  results.push(...filteredResults, ...checkResults);
5061
5615
  }
5616
+ try {
5617
+ if (checkName === "B") {
5618
+ console.error(
5619
+ `\u{1F527} Debug: fail_if results for ${checkName}: ${JSON.stringify(results)} context.output=${JSON.stringify(
5620
+ context.output
5621
+ )}`
5622
+ );
5623
+ }
5624
+ } catch {
5625
+ }
5062
5626
  return results;
5063
5627
  }
5064
5628
  /**
@@ -5239,6 +5803,10 @@ var FailureConditionEvaluator = class _FailureConditionEvaluator {
5239
5803
  exec = this.sandbox.compile(`return (${normalizedExpr});`);
5240
5804
  }
5241
5805
  const result = exec(scope).run();
5806
+ try {
5807
+ (init_logger(), __toCommonJS(logger_exports)).logger.debug(` fail_if: result=${Boolean(result)}`);
5808
+ } catch {
5809
+ }
5242
5810
  return Boolean(result);
5243
5811
  } catch (error) {
5244
5812
  console.error("\u274C Failed to evaluate expression:", condition, error);
@@ -5308,6 +5876,66 @@ var FailureConditionEvaluator = class _FailureConditionEvaluator {
5308
5876
  } else if (extractedOutput && typeof extractedOutput === "object") {
5309
5877
  Object.assign(aggregatedOutput, extractedOutput);
5310
5878
  }
5879
+ try {
5880
+ const raw = reviewSummaryWithOutput.__raw;
5881
+ if (raw && typeof raw === "object") {
5882
+ Object.assign(aggregatedOutput, raw);
5883
+ }
5884
+ } catch {
5885
+ }
5886
+ try {
5887
+ if (typeof extractedOutput === "string") {
5888
+ const parsed = this.tryExtractJsonFromEnd(extractedOutput) ?? (() => {
5889
+ try {
5890
+ return JSON.parse(extractedOutput);
5891
+ } catch {
5892
+ return null;
5893
+ }
5894
+ })();
5895
+ if (parsed !== null) {
5896
+ if (Array.isArray(parsed)) {
5897
+ aggregatedOutput.items = parsed;
5898
+ } else if (typeof parsed === "object") {
5899
+ Object.assign(aggregatedOutput, parsed);
5900
+ }
5901
+ }
5902
+ const lower = extractedOutput.toLowerCase();
5903
+ const boolFrom = (key) => {
5904
+ const reTrue = new RegExp(
5905
+ `(?:^|[^a-z0-9_])${key}[^a-z0-9_]*[:=][^a-z0-9_]*true(?:[^a-z0-9_]|$)`
5906
+ );
5907
+ const reFalse = new RegExp(
5908
+ `(?:^|[^a-z0-9_])${key}[^a-z0-9_]*[:=][^a-z0-9_]*false(?:[^a-z0-9_]|$)`
5909
+ );
5910
+ if (reTrue.test(lower)) return true;
5911
+ if (reFalse.test(lower)) return false;
5912
+ return null;
5913
+ };
5914
+ const keys = ["error"];
5915
+ for (const k of keys) {
5916
+ const v = boolFrom(k);
5917
+ if (v !== null && aggregatedOutput[k] === void 0) {
5918
+ aggregatedOutput[k] = v;
5919
+ }
5920
+ }
5921
+ }
5922
+ } catch {
5923
+ }
5924
+ try {
5925
+ const rsAny = reviewSummaryWithOutput;
5926
+ const hasStructuredOutput = extractedOutput !== void 0 && extractedOutput !== null;
5927
+ if (!hasStructuredOutput && typeof rsAny?.content === "string") {
5928
+ const parsedFromContent = this.tryExtractJsonFromEnd(rsAny.content);
5929
+ if (parsedFromContent !== null && parsedFromContent !== void 0) {
5930
+ if (Array.isArray(parsedFromContent)) {
5931
+ aggregatedOutput.items = parsedFromContent;
5932
+ } else if (typeof parsedFromContent === "object") {
5933
+ Object.assign(aggregatedOutput, parsedFromContent);
5934
+ }
5935
+ }
5936
+ }
5937
+ } catch {
5938
+ }
5311
5939
  const context = {
5312
5940
  output: aggregatedOutput,
5313
5941
  outputs: (() => {
@@ -5334,6 +5962,23 @@ var FailureConditionEvaluator = class _FailureConditionEvaluator {
5334
5962
  }
5335
5963
  return context;
5336
5964
  }
5965
+ // Minimal JSON-from-end extractor for fail_if context fallback
5966
+ tryExtractJsonFromEnd(text) {
5967
+ try {
5968
+ const lines = text.split("\n");
5969
+ for (let i = lines.length - 1; i >= 0; i--) {
5970
+ const t = lines[i].trim();
5971
+ if (t.startsWith("{") || t.startsWith("[")) {
5972
+ const candidate = lines.slice(i).join("\n").trim();
5973
+ if (candidate.startsWith("{") && candidate.endsWith("}") || candidate.startsWith("[") && candidate.endsWith("]")) {
5974
+ return JSON.parse(candidate);
5975
+ }
5976
+ }
5977
+ }
5978
+ } catch {
5979
+ }
5980
+ return null;
5981
+ }
5337
5982
  /**
5338
5983
  * Check if any failure condition requires halting execution
5339
5984
  */
@@ -5834,6 +6479,7 @@ Please check your configuration and try again.`
5834
6479
  };
5835
6480
 
5836
6481
  // src/check-execution-engine.ts
6482
+ init_logger();
5837
6483
  import Sandbox3 from "@nyariv/sandboxjs";
5838
6484
  function getSafeEnvironmentVariables() {
5839
6485
  const safeEnvVars = [
@@ -6790,6 +7436,8 @@ ${expr}
6790
7436
  let normalizedOutput;
6791
7437
  if (Array.isArray(output)) {
6792
7438
  normalizedOutput = output;
7439
+ } else if (output && typeof output === "object" && Array.isArray(output.items)) {
7440
+ normalizedOutput = output.items;
6793
7441
  } else if (typeof output === "string") {
6794
7442
  try {
6795
7443
  const parsed = JSON.parse(output);
@@ -6986,8 +7634,11 @@ ${expr}
6986
7634
  const templatePath = path5.join(__dirname, `../output/${sanitizedSchema}/template.liquid`);
6987
7635
  templateContent = await fs5.readFile(templatePath, "utf-8");
6988
7636
  }
7637
+ const filteredIssues = (reviewSummary.issues || []).filter(
7638
+ (issue) => !(issue.file === "system" && issue.line === 0)
7639
+ );
6989
7640
  const templateData = {
6990
- issues: reviewSummary.issues || [],
7641
+ issues: filteredIssues,
6991
7642
  checkName
6992
7643
  };
6993
7644
  const rendered = await liquid.parseAndRender(templateContent, templateData);
@@ -7087,6 +7738,13 @@ ${expr}
7087
7738
  ]
7088
7739
  };
7089
7740
  }
7741
+ const childrenByParent = /* @__PURE__ */ new Map();
7742
+ for (const [child, depsArr] of Object.entries(dependencies)) {
7743
+ for (const p of depsArr || []) {
7744
+ if (!childrenByParent.has(p)) childrenByParent.set(p, []);
7745
+ childrenByParent.get(p).push(child);
7746
+ }
7747
+ }
7090
7748
  const stats = DependencyResolver.getExecutionStats(dependencyGraph);
7091
7749
  if (debug) {
7092
7750
  log2(
@@ -7122,7 +7780,12 @@ ${expr}
7122
7780
  `\u{1F527} Debug: Executing level ${executionGroup.level} with ${executionGroup.parallel.length} checks (parallelism: ${actualParallelism})`
7123
7781
  );
7124
7782
  }
7125
- const levelTaskFunctions = executionGroup.parallel.map((checkName) => async () => {
7783
+ const levelChecks = executionGroup.parallel.filter((name) => !results.has(name));
7784
+ const levelTaskFunctions = levelChecks.map((checkName) => async () => {
7785
+ if (results.has(checkName)) {
7786
+ if (debug) log2(`\u{1F527} Debug: Skipping ${checkName} (already satisfied earlier)`);
7787
+ return { checkName, error: null, result: results.get(checkName) };
7788
+ }
7126
7789
  const checkConfig = config.checks[checkName];
7127
7790
  if (!checkConfig) {
7128
7791
  return {
@@ -7195,13 +7858,21 @@ ${expr}
7195
7858
  const id = issue.ruleId || "";
7196
7859
  return id.endsWith("/__skipped");
7197
7860
  });
7198
- let hasFatalFailure = (depRes.issues || []).some((issue) => {
7199
- const id = issue.ruleId || "";
7200
- return id === "command/execution_error" || id.endsWith("/command/execution_error") || id === "command/timeout" || id.endsWith("/command/timeout") || id === "command/transform_js_error" || id.endsWith("/command/transform_js_error") || id === "command/transform_error" || id.endsWith("/command/transform_error") || id.endsWith("/forEach/iteration_error") || id === "forEach/undefined_output" || id.endsWith("/forEach/undefined_output") || id.endsWith("_fail_if") || id.endsWith("/global_fail_if");
7201
- });
7202
- if (!hasFatalFailure && config && (config.fail_if || config.checks[depId]?.fail_if)) {
7203
- const failIfResults = await this.evaluateFailureConditions(depId, depRes, config);
7204
- hasFatalFailure = failIfResults.some((r) => r.failed);
7861
+ const depExtended = depRes;
7862
+ const isDepForEachParent = !!depExtended.isForEach;
7863
+ let hasFatalFailure = false;
7864
+ if (!isDepForEachParent) {
7865
+ const issues = depRes.issues || [];
7866
+ hasFatalFailure = issues.some((issue) => {
7867
+ const id = issue.ruleId || "";
7868
+ return id === "command/execution_error" || id.endsWith("/command/execution_error") || id === "command/timeout" || id.endsWith("/command/timeout") || id === "command/transform_js_error" || id.endsWith("/command/transform_js_error") || id === "command/transform_error" || id.endsWith("/command/transform_error") || id === "forEach/undefined_output" || id.endsWith("/forEach/undefined_output") || id.endsWith("/forEach/iteration_error") || id.endsWith("_fail_if") || id.endsWith("/global_fail_if");
7869
+ });
7870
+ if (!hasFatalFailure && config && (config.fail_if || config.checks[depId]?.fail_if)) {
7871
+ try {
7872
+ hasFatalFailure = await this.failIfTriggered(depId, depRes, config);
7873
+ } catch {
7874
+ }
7875
+ }
7205
7876
  }
7206
7877
  if (debug) {
7207
7878
  log2(
@@ -7224,10 +7895,12 @@ ${expr}
7224
7895
  if (results.has(depId)) {
7225
7896
  const depResult = results.get(depId);
7226
7897
  const depForEachResult = depResult;
7227
- if (depForEachResult.isForEach && Array.isArray(depForEachResult.forEachItems)) {
7898
+ if (depForEachResult.isForEach || Array.isArray(depForEachResult.forEachItemResults) || Array.isArray(depForEachResult.forEachItems)) {
7228
7899
  if (!isForEachDependent) {
7229
7900
  isForEachDependent = true;
7230
- forEachItems = depForEachResult.forEachItems;
7901
+ forEachItems = Array.isArray(depForEachResult.forEachItems) ? depForEachResult.forEachItems : new Array(
7902
+ Array.isArray(depForEachResult.forEachItemResults) ? depForEachResult.forEachItemResults.length : 0
7903
+ ).fill(void 0);
7231
7904
  forEachParentName = depId;
7232
7905
  }
7233
7906
  forEachParents.push(depId);
@@ -7268,6 +7941,18 @@ ${expr}
7268
7941
  }
7269
7942
  let finalResult;
7270
7943
  if (isForEachDependent && forEachParentName) {
7944
+ if (!Array.isArray(forEachItems)) {
7945
+ forEachItems = [];
7946
+ }
7947
+ if (!Array.isArray(forEachItems)) {
7948
+ this.recordSkip(checkName, "dependency_failed");
7949
+ return {
7950
+ checkName,
7951
+ error: null,
7952
+ result: { issues: [] },
7953
+ skipped: true
7954
+ };
7955
+ }
7271
7956
  this.recordForEachPreview(checkName, forEachItems);
7272
7957
  if (forEachItems.length === 0) {
7273
7958
  if (debug) {
@@ -7276,6 +7961,7 @@ ${expr}
7276
7961
  );
7277
7962
  }
7278
7963
  logger.info(` forEach: no items from "${forEachParentName}", skipping check...`);
7964
+ this.recordSkip(checkName, "dependency_failed");
7279
7965
  finalResult = {
7280
7966
  issues: [],
7281
7967
  output: []
@@ -7284,22 +7970,187 @@ ${expr}
7284
7970
  finalResult.forEachItems = [];
7285
7971
  } else {
7286
7972
  if (debug) {
7287
- log2(
7973
+ console.log(
7288
7974
  `\u{1F504} Debug: Check "${checkName}" depends on forEach check "${forEachParentName}", executing ${forEachItems.length} times`
7289
7975
  );
7290
7976
  }
7977
+ const __itemCount = Array.isArray(forEachItems) ? forEachItems.length : 0;
7291
7978
  logger.info(
7292
- ` forEach: processing ${forEachItems.length} items from "${forEachParentName}"...`
7979
+ ` forEach: processing ${__itemCount} items from "${forEachParentName}"...`
7293
7980
  );
7294
7981
  const allIssues = [];
7295
- const allOutputs = [];
7982
+ const allOutputs = new Array(forEachItems.length);
7296
7983
  const aggregatedContents = [];
7984
+ const perItemResults = new Array(
7985
+ forEachItems.length
7986
+ );
7987
+ const inlineAgg = /* @__PURE__ */ new Map();
7988
+ const execInlineDescendants = async (parentName, itemIndex, baseDeps) => {
7989
+ const children = (childrenByParent.get(parentName) || []).filter((child) => {
7990
+ const deps = dependencies[child] || [];
7991
+ return deps.length === 1 && deps[0] === parentName;
7992
+ });
7993
+ for (const childName of children) {
7994
+ const childCfg = config.checks[childName];
7995
+ const childProviderType = childCfg.type || "ai";
7996
+ const childProv = this.providerRegistry.getProviderOrThrow(childProviderType);
7997
+ this.setProviderWebhookContext(childProv);
7998
+ const childProviderConfig = {
7999
+ type: childProviderType,
8000
+ prompt: childCfg.prompt,
8001
+ exec: childCfg.exec,
8002
+ focus: childCfg.focus || this.mapCheckNameToFocus(childName),
8003
+ schema: childCfg.schema,
8004
+ group: childCfg.group,
8005
+ checkName: childName,
8006
+ eventContext: prInfo.eventContext,
8007
+ transform: childCfg.transform,
8008
+ transform_js: childCfg.transform_js,
8009
+ env: childCfg.env,
8010
+ forEach: childCfg.forEach,
8011
+ ai: {
8012
+ timeout: timeout || 6e5,
8013
+ debug,
8014
+ ...childCfg.ai || {}
8015
+ }
8016
+ };
8017
+ const childDepResults = /* @__PURE__ */ new Map();
8018
+ const childAllDeps = DependencyResolver.getAllDependencies(
8019
+ childName,
8020
+ dependencyGraph.nodes
8021
+ );
8022
+ for (const dep of childAllDeps) {
8023
+ const baseRes = baseDeps.get(dep);
8024
+ if (baseRes) {
8025
+ childDepResults.set(dep, baseRes);
8026
+ continue;
8027
+ }
8028
+ const globalRes = results.get(dep);
8029
+ if (!globalRes) continue;
8030
+ if (globalRes && (globalRes.isForEach || Array.isArray(globalRes.forEachItemResults) || Array.isArray(globalRes.output))) {
8031
+ if (Array.isArray(globalRes.forEachItemResults) && globalRes.forEachItemResults[itemIndex]) {
8032
+ childDepResults.set(dep, globalRes.forEachItemResults[itemIndex]);
8033
+ } else if (Array.isArray(globalRes.output) && globalRes.output[itemIndex] !== void 0) {
8034
+ childDepResults.set(dep, {
8035
+ issues: [],
8036
+ output: globalRes.output[itemIndex]
8037
+ });
8038
+ } else {
8039
+ childDepResults.set(dep, globalRes);
8040
+ }
8041
+ } else {
8042
+ childDepResults.set(dep, globalRes);
8043
+ }
8044
+ }
8045
+ const parentItemRes = childDepResults.get(parentName);
8046
+ if (parentItemRes) {
8047
+ try {
8048
+ const pout = parentItemRes.output;
8049
+ if (pout && typeof pout === "object" && pout.error === true) {
8050
+ continue;
8051
+ }
8052
+ } catch {
8053
+ }
8054
+ const fatal = (parentItemRes.issues || []).some((issue) => {
8055
+ const id = issue.ruleId || "";
8056
+ const sev = issue.severity || "error";
8057
+ return sev === "error" || sev === "critical" || id === "command/execution_error" || id.endsWith("/command/execution_error") || id === "command/timeout" || id.endsWith("/command/timeout") || id === "command/transform_js_error" || id.endsWith("/command/transform_js_error") || id === "command/transform_error" || id.endsWith("/command/transform_error") || id.endsWith("/forEach/iteration_error") || id === "forEach/undefined_output" || id.endsWith("/forEach/undefined_output") || id.endsWith("_fail_if") || id.endsWith("/global_fail_if");
8058
+ });
8059
+ if (fatal) {
8060
+ continue;
8061
+ }
8062
+ }
8063
+ if (childCfg.if) {
8064
+ const condResults = new Map(results);
8065
+ for (const [k, v] of childDepResults) condResults.set(k, v);
8066
+ const shouldRunChild = await this.evaluateCheckCondition(
8067
+ childName,
8068
+ childCfg.if,
8069
+ prInfo,
8070
+ condResults,
8071
+ debug
8072
+ );
8073
+ if (!shouldRunChild) {
8074
+ continue;
8075
+ }
8076
+ }
8077
+ const childIterStart = this.recordIterationStart(childName);
8078
+ const childItemRes = await this.executeWithRouting(
8079
+ childName,
8080
+ childCfg,
8081
+ childProv,
8082
+ childProviderConfig,
8083
+ prInfo,
8084
+ childDepResults,
8085
+ sessionInfo,
8086
+ config,
8087
+ dependencyGraph,
8088
+ debug,
8089
+ results,
8090
+ { index: itemIndex, total: forEachItems.length, parent: parentName }
8091
+ );
8092
+ if (config && (config.fail_if || childCfg.fail_if)) {
8093
+ const fRes = await this.evaluateFailureConditions(
8094
+ childName,
8095
+ childItemRes,
8096
+ config
8097
+ );
8098
+ if (fRes.length > 0) {
8099
+ const fIssues = fRes.filter((f) => f.failed).map((f) => ({
8100
+ file: "system",
8101
+ line: 0,
8102
+ ruleId: f.conditionName,
8103
+ message: f.message || `Failure condition met: ${f.expression}`,
8104
+ severity: f.severity || "error",
8105
+ category: "logic"
8106
+ }));
8107
+ childItemRes.issues = [...childItemRes.issues || [], ...fIssues];
8108
+ }
8109
+ }
8110
+ if (!inlineAgg.has(childName)) {
8111
+ inlineAgg.set(childName, {
8112
+ issues: [],
8113
+ outputs: new Array(forEachItems.length),
8114
+ contents: [],
8115
+ perItemResults: new Array(forEachItems.length)
8116
+ });
8117
+ }
8118
+ const agg = inlineAgg.get(childName);
8119
+ if (childItemRes.issues) agg.issues.push(...childItemRes.issues);
8120
+ const out = childItemRes.output;
8121
+ agg.outputs[itemIndex] = out;
8122
+ agg.perItemResults[itemIndex] = childItemRes;
8123
+ const c = childItemRes.content;
8124
+ if (typeof c === "string" && c.trim()) agg.contents.push(c.trim());
8125
+ const childHadFatal = this.hasFatal(childItemRes.issues || []);
8126
+ this.recordIterationComplete(
8127
+ childName,
8128
+ childIterStart,
8129
+ !childHadFatal,
8130
+ childItemRes.issues || [],
8131
+ childItemRes.output
8132
+ );
8133
+ const nextBase = new Map(baseDeps);
8134
+ nextBase.set(childName, childItemRes);
8135
+ await execInlineDescendants(childName, itemIndex, nextBase);
8136
+ }
8137
+ };
7297
8138
  const itemTasks = forEachItems.map((item, itemIndex) => async () => {
7298
8139
  const forEachDependencyResults = /* @__PURE__ */ new Map();
7299
8140
  for (const [depName, depResult] of dependencyResults) {
7300
8141
  if (forEachParents.includes(depName)) {
7301
8142
  const depForEachResult = depResult;
7302
- if (Array.isArray(depForEachResult.output) && depForEachResult.output[itemIndex] !== void 0) {
8143
+ if (Array.isArray(depForEachResult.forEachItemResults) && depForEachResult.forEachItemResults[itemIndex]) {
8144
+ forEachDependencyResults.set(
8145
+ depName,
8146
+ depForEachResult.forEachItemResults[itemIndex]
8147
+ );
8148
+ const rawResult = {
8149
+ issues: [],
8150
+ output: depForEachResult.output
8151
+ };
8152
+ forEachDependencyResults.set(`${depName}-raw`, rawResult);
8153
+ } else if (Array.isArray(depForEachResult.output) && depForEachResult.output[itemIndex] !== void 0) {
7303
8154
  const modifiedResult = {
7304
8155
  issues: [],
7305
8156
  output: depForEachResult.output[itemIndex]
@@ -7317,6 +8168,46 @@ ${expr}
7317
8168
  forEachDependencyResults.set(depName, depResult);
7318
8169
  }
7319
8170
  }
8171
+ if ((checkConfig.depends_on || []).length > 0) {
8172
+ const directDeps2 = checkConfig.depends_on || [];
8173
+ for (const depId of directDeps2) {
8174
+ if (!forEachParents.includes(depId)) continue;
8175
+ const depItemRes = forEachDependencyResults.get(depId);
8176
+ if (!depItemRes) continue;
8177
+ const wasSkippedDep = (depItemRes.issues || []).some(
8178
+ (i) => (i.ruleId || "").endsWith("/__skipped")
8179
+ );
8180
+ let hasFatalDepFailure = (depItemRes.issues || []).some((issue) => {
8181
+ const id = issue.ruleId || "";
8182
+ return id === "command/execution_error" || id.endsWith("/command/execution_error") || id === "command/timeout" || id.endsWith("/command/timeout") || id === "command/transform_js_error" || id.endsWith("/command/transform_js_error") || id === "command/transform_error" || id.endsWith("/command/transform_error") || id.endsWith("/forEach/iteration_error") || id === "forEach/undefined_output" || id.endsWith("/forEach/undefined_output") || id.endsWith("_fail_if") || id.endsWith("/global_fail_if");
8183
+ });
8184
+ if (!hasFatalDepFailure && config && (config.fail_if || config.checks[depId]?.fail_if)) {
8185
+ try {
8186
+ const depFailures = await this.evaluateFailureConditions(
8187
+ depId,
8188
+ depItemRes,
8189
+ config
8190
+ );
8191
+ hasFatalDepFailure = depFailures.some((f) => f.failed);
8192
+ } catch {
8193
+ }
8194
+ }
8195
+ const depAgg = dependencyResults.get(depId);
8196
+ const maskFatal = !!depAgg?.forEachFatalMask && depAgg.forEachFatalMask[itemIndex] === true;
8197
+ if (wasSkippedDep || hasFatalDepFailure || maskFatal) {
8198
+ if (debug) {
8199
+ log2(
8200
+ `\u{1F504} Debug: Skipping item ${itemIndex + 1}/${forEachItems.length} for check "${checkName}" due to failed dependency '${depId}'`
8201
+ );
8202
+ }
8203
+ return {
8204
+ index: itemIndex,
8205
+ itemResult: { issues: [] },
8206
+ skipped: true
8207
+ };
8208
+ }
8209
+ }
8210
+ }
7320
8211
  if (checkConfig.if) {
7321
8212
  const conditionResults = new Map(results);
7322
8213
  for (const [depName, depResult] of forEachDependencyResults) {
@@ -7367,6 +8258,24 @@ ${expr}
7367
8258
  parent: forEachParentName
7368
8259
  }
7369
8260
  );
8261
+ if (config && (config.fail_if || checkConfig.fail_if)) {
8262
+ const itemFailures = await this.evaluateFailureConditions(
8263
+ checkName,
8264
+ itemResult,
8265
+ config
8266
+ );
8267
+ if (itemFailures.length > 0) {
8268
+ const failureIssues = itemFailures.filter((f) => f.failed).map((f) => ({
8269
+ file: "system",
8270
+ line: 0,
8271
+ ruleId: f.conditionName,
8272
+ message: f.message || `Failure condition met: ${f.expression}`,
8273
+ severity: f.severity || "error",
8274
+ category: "logic"
8275
+ }));
8276
+ itemResult.issues = [...itemResult.issues || [], ...failureIssues];
8277
+ }
8278
+ }
7370
8279
  const hadFatalError = (itemResult.issues || []).some((issue) => {
7371
8280
  const id = issue.ruleId || "";
7372
8281
  return id === "command/execution_error" || id.endsWith("/command/execution_error") || id === "command/transform_js_error" || id.endsWith("/command/transform_js_error") || id === "command/transform_error" || id.endsWith("/command/transform_error") || id === "forEach/undefined_output" || id.endsWith("/forEach/undefined_output");
@@ -7380,25 +8289,286 @@ ${expr}
7380
8289
  itemResult.issues || [],
7381
8290
  itemResult.output
7382
8291
  );
8292
+ const descendantSet = (() => {
8293
+ const visited = /* @__PURE__ */ new Set();
8294
+ const stack = [checkName];
8295
+ while (stack.length) {
8296
+ const p = stack.pop();
8297
+ const kids = childrenByParent.get(p) || [];
8298
+ for (const k of kids) {
8299
+ if (!visited.has(k)) {
8300
+ visited.add(k);
8301
+ stack.push(k);
8302
+ }
8303
+ }
8304
+ }
8305
+ return visited;
8306
+ })();
8307
+ const perItemDone = /* @__PURE__ */ new Set([...forEachParents, checkName]);
8308
+ const perItemDepMap = /* @__PURE__ */ new Map();
8309
+ for (const [k, v] of forEachDependencyResults) perItemDepMap.set(k, v);
8310
+ perItemDepMap.set(checkName, itemResult);
8311
+ const isFatal = (r) => {
8312
+ if (!r) return true;
8313
+ return this.hasFatal(r.issues || []);
8314
+ };
8315
+ while (true) {
8316
+ let progressed = false;
8317
+ for (const node of descendantSet) {
8318
+ if (perItemDone.has(node)) continue;
8319
+ const nodeCfg = config.checks[node];
8320
+ if (!nodeCfg) continue;
8321
+ const deps = dependencies[node] || [];
8322
+ let ready = true;
8323
+ const childDepsMap = /* @__PURE__ */ new Map();
8324
+ for (const d of deps) {
8325
+ const perItemRes = perItemDepMap.get(d);
8326
+ if (perItemRes) {
8327
+ if (isFatal(perItemRes)) {
8328
+ ready = false;
8329
+ break;
8330
+ }
8331
+ childDepsMap.set(d, perItemRes);
8332
+ continue;
8333
+ }
8334
+ const agg2 = results.get(d);
8335
+ if (agg2 && (agg2.isForEach || Array.isArray(agg2.forEachItemResults))) {
8336
+ const r = agg2.forEachItemResults && agg2.forEachItemResults[itemIndex] || void 0;
8337
+ const maskFatal = !!agg2.forEachFatalMask && agg2.forEachFatalMask[itemIndex] === true;
8338
+ if (!r || maskFatal || isFatal(r)) {
8339
+ ready = false;
8340
+ break;
8341
+ }
8342
+ childDepsMap.set(d, r);
8343
+ continue;
8344
+ }
8345
+ if (!agg2 || isFatal(agg2)) {
8346
+ ready = false;
8347
+ break;
8348
+ }
8349
+ childDepsMap.set(d, agg2);
8350
+ }
8351
+ if (!ready) continue;
8352
+ if (nodeCfg.if) {
8353
+ const condResults = new Map(results);
8354
+ for (const [k, v] of childDepsMap) condResults.set(k, v);
8355
+ const shouldRun = await this.evaluateCheckCondition(
8356
+ node,
8357
+ nodeCfg.if,
8358
+ prInfo,
8359
+ condResults,
8360
+ debug
8361
+ );
8362
+ if (!shouldRun) {
8363
+ perItemDone.add(node);
8364
+ progressed = true;
8365
+ continue;
8366
+ }
8367
+ }
8368
+ const nodeProvType = nodeCfg.type || "ai";
8369
+ const nodeProv = this.providerRegistry.getProviderOrThrow(nodeProvType);
8370
+ this.setProviderWebhookContext(nodeProv);
8371
+ const nodeProviderConfig = {
8372
+ type: nodeProvType,
8373
+ prompt: nodeCfg.prompt,
8374
+ exec: nodeCfg.exec,
8375
+ focus: nodeCfg.focus || this.mapCheckNameToFocus(node),
8376
+ schema: nodeCfg.schema,
8377
+ group: nodeCfg.group,
8378
+ checkName: node,
8379
+ eventContext: prInfo.eventContext,
8380
+ transform: nodeCfg.transform,
8381
+ transform_js: nodeCfg.transform_js,
8382
+ env: nodeCfg.env,
8383
+ forEach: nodeCfg.forEach,
8384
+ ai: { timeout: timeout || 6e5, debug, ...nodeCfg.ai || {} }
8385
+ };
8386
+ const iterStart = this.recordIterationStart(node);
8387
+ const execDepMap = new Map(childDepsMap);
8388
+ const nodeAllDeps = DependencyResolver.getAllDependencies(
8389
+ node,
8390
+ dependencyGraph.nodes
8391
+ );
8392
+ for (const dep of nodeAllDeps) {
8393
+ if (execDepMap.has(dep)) continue;
8394
+ const perItemRes = perItemDepMap.get(dep);
8395
+ if (perItemRes) {
8396
+ execDepMap.set(dep, perItemRes);
8397
+ continue;
8398
+ }
8399
+ const agg2 = results.get(dep);
8400
+ if (!agg2) continue;
8401
+ if (agg2 && (agg2.isForEach || Array.isArray(agg2.forEachItemResults) || Array.isArray(agg2.output))) {
8402
+ if (Array.isArray(agg2.forEachItemResults) && agg2.forEachItemResults[itemIndex]) {
8403
+ execDepMap.set(dep, agg2.forEachItemResults[itemIndex]);
8404
+ } else if (Array.isArray(agg2.output) && agg2.output[itemIndex] !== void 0) {
8405
+ execDepMap.set(dep, {
8406
+ issues: [],
8407
+ output: agg2.output[itemIndex]
8408
+ });
8409
+ } else {
8410
+ execDepMap.set(dep, agg2);
8411
+ }
8412
+ } else {
8413
+ execDepMap.set(dep, agg2);
8414
+ }
8415
+ }
8416
+ const nodeItemRes = await this.executeWithRouting(
8417
+ node,
8418
+ nodeCfg,
8419
+ nodeProv,
8420
+ nodeProviderConfig,
8421
+ prInfo,
8422
+ execDepMap,
8423
+ sessionInfo,
8424
+ config,
8425
+ dependencyGraph,
8426
+ debug,
8427
+ results,
8428
+ { index: itemIndex, total: forEachItems.length, parent: forEachParentName }
8429
+ );
8430
+ if (config && (config.fail_if || nodeCfg.fail_if)) {
8431
+ const fRes = await this.evaluateFailureConditions(node, nodeItemRes, config);
8432
+ if (fRes.length > 0) {
8433
+ const fIssues = fRes.filter((f) => f.failed).map((f) => ({
8434
+ file: "system",
8435
+ line: 0,
8436
+ ruleId: f.conditionName,
8437
+ message: f.message || `Failure condition met: ${f.expression}`,
8438
+ severity: f.severity || "error",
8439
+ category: "logic"
8440
+ }));
8441
+ nodeItemRes.issues = [...nodeItemRes.issues || [], ...fIssues];
8442
+ }
8443
+ }
8444
+ const hadFatal = isFatal(nodeItemRes);
8445
+ this.recordIterationComplete(
8446
+ node,
8447
+ iterStart,
8448
+ !hadFatal,
8449
+ nodeItemRes.issues || [],
8450
+ nodeItemRes.output
8451
+ );
8452
+ if (!inlineAgg.has(node))
8453
+ inlineAgg.set(node, {
8454
+ issues: [],
8455
+ outputs: [],
8456
+ contents: [],
8457
+ perItemResults: []
8458
+ });
8459
+ const agg = inlineAgg.get(node);
8460
+ if (nodeItemRes.issues) agg.issues.push(...nodeItemRes.issues);
8461
+ const nout = nodeItemRes.output;
8462
+ if (nout !== void 0) agg.outputs.push(nout);
8463
+ agg.perItemResults.push(nodeItemRes);
8464
+ const ncontent = nodeItemRes.content;
8465
+ if (typeof ncontent === "string" && ncontent.trim())
8466
+ agg.contents.push(ncontent.trim());
8467
+ perItemDepMap.set(node, nodeItemRes);
8468
+ perItemDone.add(node);
8469
+ progressed = true;
8470
+ }
8471
+ if (!progressed) break;
8472
+ }
7383
8473
  logger.info(
7384
8474
  ` \u2714 ${itemIndex + 1}/${forEachItems.length} (${iterationDuration.toFixed(1)}s)`
7385
8475
  );
8476
+ perItemResults[itemIndex] = itemResult;
7386
8477
  return { index: itemIndex, itemResult };
7387
8478
  });
8479
+ const directForEachParents = (checkConfig.depends_on || []).filter((dep) => {
8480
+ const r = results.get(dep);
8481
+ return !!r && (r.isForEach || Array.isArray(r.forEachItemResults) || Array.isArray(r.forEachItems));
8482
+ });
8483
+ if (directForEachParents.length > 0) {
8484
+ logger.debug(
8485
+ ` forEach: direct parents for "${checkName}": ${directForEachParents.join(", ")}`
8486
+ );
8487
+ }
8488
+ const isIndexFatalForParent = async (parent, idx) => {
8489
+ const agg = results.get(parent);
8490
+ if (!agg) return false;
8491
+ if (agg.forEachFatalMask && agg.forEachFatalMask[idx] === true) return true;
8492
+ const r = agg.forEachItemResults && agg.forEachItemResults[idx] || void 0;
8493
+ if (!r) return false;
8494
+ const hadFatalByIssues = this.hasFatal(r.issues || []);
8495
+ if (hadFatalByIssues) return true;
8496
+ try {
8497
+ if (config && (config.fail_if || config.checks[parent]?.fail_if)) {
8498
+ let rForEval = r;
8499
+ const rawOut = r?.output;
8500
+ if (typeof rawOut === "string") {
8501
+ const parseTail = (text) => {
8502
+ try {
8503
+ const lines = text.split("\n");
8504
+ for (let i = lines.length - 1; i >= 0; i--) {
8505
+ const t = lines[i].trim();
8506
+ if (t.startsWith("{") || t.startsWith("[")) {
8507
+ const candidate = lines.slice(i).join("\n").trim();
8508
+ if (candidate.startsWith("{") && candidate.endsWith("}") || candidate.startsWith("[") && candidate.endsWith("]")) {
8509
+ return JSON.parse(candidate);
8510
+ }
8511
+ }
8512
+ }
8513
+ } catch {
8514
+ }
8515
+ try {
8516
+ return JSON.parse(text);
8517
+ } catch {
8518
+ return null;
8519
+ }
8520
+ };
8521
+ const parsed = parseTail(rawOut);
8522
+ if (parsed && typeof parsed === "object") {
8523
+ rForEval = { ...r, output: parsed };
8524
+ }
8525
+ }
8526
+ const failures = await this.evaluateFailureConditions(parent, rForEval, config);
8527
+ if (failures.some((f) => f.failed)) {
8528
+ }
8529
+ if (failures.some((f) => f.failed)) return true;
8530
+ }
8531
+ } catch {
8532
+ }
8533
+ return false;
8534
+ };
8535
+ const runnableIndices = [];
8536
+ for (let idx = 0; idx < forEachItems.length; idx++) {
8537
+ let ok = true;
8538
+ for (const p of directForEachParents) {
8539
+ if (await isIndexFatalForParent(p, idx)) {
8540
+ ok = false;
8541
+ break;
8542
+ }
8543
+ }
8544
+ if (ok && typeof itemTasks[idx] === "function") runnableIndices.push(idx);
8545
+ }
8546
+ if (runnableIndices.length === 0) {
8547
+ this.recordSkip(checkName, "dependency_failed");
8548
+ logger.info(`\u23ED Skipped (dependency failed: no runnable items)`);
8549
+ return {
8550
+ checkName,
8551
+ error: null,
8552
+ result: { issues: [] },
8553
+ skipped: true
8554
+ };
8555
+ }
7388
8556
  const forEachConcurrency = Math.max(
7389
8557
  1,
7390
- Math.min(forEachItems.length, effectiveMaxParallelism)
8558
+ Math.min(runnableIndices.length, effectiveMaxParallelism)
7391
8559
  );
7392
8560
  if (debug && forEachConcurrency > 1) {
7393
8561
  log2(
7394
8562
  `\u{1F504} Debug: Limiting forEach concurrency for check "${checkName}" to ${forEachConcurrency}`
7395
8563
  );
7396
8564
  }
8565
+ const scheduledTasks = runnableIndices.map((i) => itemTasks[i]).filter((fn) => typeof fn === "function");
7397
8566
  const forEachResults = await this.executeWithLimitedParallelism(
7398
- itemTasks,
8567
+ scheduledTasks,
7399
8568
  forEachConcurrency,
7400
8569
  false
7401
8570
  );
8571
+ let processedCount = 0;
7402
8572
  for (const result of forEachResults) {
7403
8573
  if (result.status === "rejected") {
7404
8574
  const error = result.reason;
@@ -7421,52 +8591,117 @@ ${expr}
7421
8591
  if (result.value.skipped) {
7422
8592
  continue;
7423
8593
  }
7424
- const { itemResult } = result.value;
8594
+ const { index: finishedIndex, itemResult } = result.value;
8595
+ processedCount++;
7425
8596
  if (itemResult.issues) {
7426
8597
  allIssues.push(...itemResult.issues);
7427
8598
  }
7428
8599
  const resultWithOutput = itemResult;
7429
- if (resultWithOutput.output !== void 0) {
7430
- allOutputs.push(resultWithOutput.output);
7431
- }
8600
+ allOutputs[finishedIndex] = resultWithOutput.output;
7432
8601
  const itemContent = resultWithOutput.content;
7433
8602
  if (typeof itemContent === "string" && itemContent.trim()) {
7434
8603
  aggregatedContents.push(itemContent.trim());
8604
+ } else {
8605
+ const outStr = typeof resultWithOutput.output === "string" ? resultWithOutput.output.trim() : "";
8606
+ if (outStr) aggregatedContents.push(outStr);
7435
8607
  }
7436
8608
  }
8609
+ if (processedCount === 0) {
8610
+ this.recordSkip(checkName, "dependency_failed");
8611
+ logger.info(`\u23ED Skipped (dependency failed for all items)`);
8612
+ return {
8613
+ checkName,
8614
+ error: null,
8615
+ result: { issues: [] },
8616
+ skipped: true
8617
+ };
8618
+ }
7437
8619
  const finalOutput = allOutputs.length > 0 ? allOutputs : void 0;
7438
8620
  finalResult = {
7439
8621
  issues: allIssues,
7440
8622
  ...finalOutput !== void 0 ? { output: finalOutput } : {}
7441
8623
  };
7442
- if (config && (config.fail_if || checkConfig.fail_if)) {
7443
- const failureResults = await this.evaluateFailureConditions(
7444
- checkName,
7445
- finalResult,
7446
- config
8624
+ finalResult.isForEach = true;
8625
+ finalResult.forEachItems = allOutputs;
8626
+ finalResult.forEachItemResults = perItemResults;
8627
+ try {
8628
+ const mask = finalResult.forEachItemResults ? await Promise.all(
8629
+ Array.from({ length: forEachItems.length }, async (_, idx) => {
8630
+ const r = finalResult.forEachItemResults[idx];
8631
+ if (!r) return false;
8632
+ let hadFatal = this.hasFatal(r.issues || []);
8633
+ try {
8634
+ const ids = (r.issues || []).map((i) => i.ruleId).join(",");
8635
+ logger.debug(
8636
+ ` forEach: item ${idx + 1}/${forEachItems.length} issues=${(r.issues || []).length} ids=[${ids}]`
8637
+ );
8638
+ } catch {
8639
+ }
8640
+ if (!hadFatal && config && (config.fail_if || checkConfig.fail_if)) {
8641
+ try {
8642
+ const failures = await this.evaluateFailureConditions(
8643
+ checkName,
8644
+ r,
8645
+ config
8646
+ );
8647
+ hadFatal = failures.some((f) => f.failed);
8648
+ } catch {
8649
+ }
8650
+ }
8651
+ return hadFatal;
8652
+ })
8653
+ ) : [];
8654
+ finalResult.forEachFatalMask = mask;
8655
+ logger.debug(
8656
+ ` forEach: mask for "${checkName}" \u2192 fatals=${mask.filter(Boolean).length}/${mask.length}`
7447
8657
  );
7448
- if (failureResults.length > 0) {
7449
- const failureIssues = failureResults.filter((f) => f.failed).map((f) => ({
7450
- file: "system",
7451
- line: 0,
7452
- ruleId: f.conditionName,
7453
- message: f.message || `Failure condition met: ${f.expression}`,
7454
- severity: f.severity || "error",
7455
- category: "logic"
7456
- }));
7457
- finalResult.issues = [...finalResult.issues || [], ...failureIssues];
7458
- }
7459
- }
7460
- if (allOutputs.length > 0) {
7461
- finalResult.isForEach = true;
7462
- finalResult.forEachItems = allOutputs;
8658
+ } catch {
7463
8659
  }
7464
8660
  if (aggregatedContents.length > 0) {
7465
8661
  finalResult.content = aggregatedContents.join("\n");
7466
8662
  }
7467
- log2(
7468
- `\u{1F504} Debug: Completed forEach execution for check "${checkName}", total issues: ${allIssues.length}`
7469
- );
8663
+ for (const [childName, agg] of inlineAgg.entries()) {
8664
+ const childCfg = config.checks[childName];
8665
+ const childEnrichedIssues = (agg.issues || []).map((issue) => ({
8666
+ ...issue,
8667
+ checkName: childName,
8668
+ ruleId: `${childName}/${issue.ruleId}`,
8669
+ group: childCfg.group,
8670
+ schema: typeof childCfg.schema === "object" ? "custom" : childCfg.schema,
8671
+ template: childCfg.template,
8672
+ timestamp: Date.now()
8673
+ }));
8674
+ const childFinal = {
8675
+ issues: childEnrichedIssues,
8676
+ ...agg.outputs.length > 0 ? { output: agg.outputs } : {},
8677
+ isForEach: true,
8678
+ forEachItems: agg.outputs,
8679
+ forEachItemResults: agg.perItemResults,
8680
+ ...agg.contents.length > 0 ? { content: agg.contents.join("\n") } : {}
8681
+ };
8682
+ try {
8683
+ const mask = Array.from(
8684
+ { length: agg.perItemResults.length },
8685
+ (_, idx) => {
8686
+ const r = agg.perItemResults[idx];
8687
+ if (!r) return false;
8688
+ const hadFatal = (r.issues || []).some((issue) => {
8689
+ const id = issue.ruleId || "";
8690
+ return issue.severity === "error" || issue.severity === "critical" || id === "command/execution_error" || id.endsWith("/command/execution_error") || id === "command/timeout" || id.endsWith("/command/timeout") || id === "command/transform_js_error" || id.endsWith("/command/transform_js_error") || id === "command/transform_error" || id.endsWith("/command/transform_error") || id.endsWith("/forEach/iteration_error") || id === "forEach/undefined_output" || id.endsWith("/forEach/undefined_output") || id.endsWith("_fail_if") || id.endsWith("/global_fail_if");
8691
+ });
8692
+ return hadFatal;
8693
+ }
8694
+ );
8695
+ childFinal.forEachFatalMask = mask;
8696
+ } catch {
8697
+ }
8698
+ results.set(childName, childFinal);
8699
+ }
8700
+ if (debug && process.env.VISOR_OUTPUT_FORMAT !== "json" && process.env.VISOR_OUTPUT_FORMAT !== "sarif") {
8701
+ console.log(
8702
+ `\u{1F504} Debug: Completed forEach execution for check "${checkName}", total issues: ${allIssues.length}`
8703
+ );
8704
+ }
7470
8705
  }
7471
8706
  } else {
7472
8707
  if (checkConfig.if) {
@@ -7596,7 +8831,8 @@ ${expr}
7596
8831
  result: enrichedResult
7597
8832
  };
7598
8833
  } catch (error) {
7599
- const errorMessage = error instanceof Error ? error.message : String(error);
8834
+ const errorMessage = error instanceof Error ? `${error.message}
8835
+ ${error.stack || ""}` : String(error);
7600
8836
  const checkDuration = ((Date.now() - checkStartTime) / 1e3).toFixed(1);
7601
8837
  this.recordError(checkName, error instanceof Error ? error : new Error(String(error)));
7602
8838
  this.recordIterationComplete(checkName, checkStartTime, false, [], void 0);
@@ -7616,8 +8852,9 @@ ${expr}
7616
8852
  actualParallelism,
7617
8853
  effectiveFailFast
7618
8854
  );
8855
+ const levelChecksList = executionGroup.parallel.filter((name) => !results.has(name));
7619
8856
  for (let i = 0; i < levelResults.length; i++) {
7620
- const checkName = executionGroup.parallel[i];
8857
+ const checkName = levelChecksList[i];
7621
8858
  const result = levelResults[i];
7622
8859
  const checkConfig = config.checks[checkName];
7623
8860
  if (result.status === "fulfilled" && result.value.result && !result.value.error) {
@@ -8751,9 +9988,22 @@ ${result.value.result.debug.rawResponse}`;
8751
9988
  */
8752
9989
  recordForEachPreview(checkName, items) {
8753
9990
  const stats = this.executionStats.get(checkName);
8754
- if (!stats || !items.length) return;
9991
+ if (!stats) return;
9992
+ if (!Array.isArray(items) || items.length === 0) return;
8755
9993
  const preview = items.slice(0, 3).map((item) => {
8756
- const str = typeof item === "string" ? item : JSON.stringify(item);
9994
+ let str;
9995
+ if (typeof item === "string") {
9996
+ str = item;
9997
+ } else if (item === void 0 || item === null) {
9998
+ str = "(empty)";
9999
+ } else {
10000
+ try {
10001
+ const j = JSON.stringify(item);
10002
+ str = typeof j === "string" ? j : String(item);
10003
+ } catch {
10004
+ str = String(item);
10005
+ }
10006
+ }
8757
10007
  return str.length > 50 ? str.substring(0, 47) + "..." : str;
8758
10008
  });
8759
10009
  if (items.length > 3) {
@@ -8789,6 +10039,20 @@ ${result.value.result.debug.rawResponse}`;
8789
10039
  checks
8790
10040
  };
8791
10041
  }
10042
+ // Generic fatality helpers to avoid duplication
10043
+ isFatalRule(id, severity) {
10044
+ const sev = (severity || "").toLowerCase();
10045
+ return sev === "error" || sev === "critical" || id === "command/execution_error" || id.endsWith("/command/execution_error") || id === "command/timeout" || id.endsWith("/command/timeout") || id === "command/transform_js_error" || id.endsWith("/command/transform_js_error") || id === "command/transform_error" || id.endsWith("/command/transform_error") || id.endsWith("/forEach/iteration_error") || id === "forEach/undefined_output" || id.endsWith("/forEach/undefined_output") || id.endsWith("_fail_if") || id.endsWith("/global_fail_if");
10046
+ }
10047
+ hasFatal(issues) {
10048
+ if (!issues || issues.length === 0) return false;
10049
+ return issues.some((i) => this.isFatalRule(i.ruleId || "", i.severity));
10050
+ }
10051
+ async failIfTriggered(checkName, result, config) {
10052
+ if (!config) return false;
10053
+ const failures = await this.evaluateFailureConditions(checkName, result, config);
10054
+ return failures.some((f) => f.failed);
10055
+ }
8792
10056
  /**
8793
10057
  * Truncate a string to max length with ellipsis
8794
10058
  */
@@ -8898,6 +10162,7 @@ ${result.value.result.debug.rawResponse}`;
8898
10162
 
8899
10163
  export {
8900
10164
  logger,
10165
+ init_logger,
8901
10166
  CheckExecutionEngine
8902
10167
  };
8903
- //# sourceMappingURL=chunk-N34GS4A5.mjs.map
10168
+ //# sourceMappingURL=chunk-N2PPFOSF.mjs.map