@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.
package/dist/sdk/sdk.js CHANGED
@@ -484,6 +484,11 @@ var init_session_registry = __esm({
484
484
  });
485
485
 
486
486
  // src/logger.ts
487
+ var logger_exports = {};
488
+ __export(logger_exports, {
489
+ configureLoggerFromCli: () => configureLoggerFromCli,
490
+ logger: () => logger
491
+ });
487
492
  function levelToNumber(level) {
488
493
  switch (level) {
489
494
  case "silent":
@@ -500,6 +505,21 @@ function levelToNumber(level) {
500
505
  return 50;
501
506
  }
502
507
  }
508
+ function configureLoggerFromCli(options) {
509
+ logger.configure({
510
+ outputFormat: options.output,
511
+ debug: options.debug,
512
+ verbose: options.verbose,
513
+ quiet: options.quiet
514
+ });
515
+ try {
516
+ if (options.output) process.env.VISOR_OUTPUT_FORMAT = String(options.output);
517
+ if (typeof options.debug === "boolean") {
518
+ process.env.VISOR_DEBUG = options.debug ? "true" : "false";
519
+ }
520
+ } catch {
521
+ }
522
+ }
503
523
  var Logger, logger;
504
524
  var init_logger = __esm({
505
525
  "src/logger.ts"() {
@@ -4135,6 +4155,14 @@ var init_command_check_provider = __esm({
4135
4155
  return true;
4136
4156
  }
4137
4157
  async execute(prInfo, config, dependencyResults) {
4158
+ try {
4159
+ logger.info(
4160
+ ` command provider: executing check=${String(config.checkName || config.type)} hasTransformJs=${Boolean(
4161
+ config.transform_js
4162
+ )}`
4163
+ );
4164
+ } catch {
4165
+ }
4138
4166
  const command = config.exec;
4139
4167
  const transform = config.transform;
4140
4168
  const transformJs = config.transform_js;
@@ -4194,33 +4222,29 @@ var init_command_check_provider = __esm({
4194
4222
  output = parsed;
4195
4223
  logger.debug(`\u{1F527} Debug: Parsed entire output as JSON successfully`);
4196
4224
  } catch {
4197
- const extracted2 = this.extractJsonFromEnd(rawOutput);
4198
- if (extracted2) {
4225
+ const extractedTail = this.extractJsonFromEnd(rawOutput);
4226
+ if (extractedTail) {
4199
4227
  try {
4200
- output = JSON.parse(extracted2);
4201
- logger.debug(
4202
- `\u{1F527} Debug: Extracted and parsed JSON from end of output (${extracted2.length} chars from ${rawOutput.length} total)`
4203
- );
4204
- logger.debug(`\u{1F527} Debug: Extracted JSON content: ${extracted2.slice(0, 200)}`);
4205
- } catch (parseError) {
4206
- logger.debug(
4207
- `\u{1F527} Debug: Extracted text is not valid JSON: ${parseError instanceof Error ? parseError.message : "Unknown error"}`
4208
- );
4228
+ output = JSON.parse(extractedTail);
4229
+ } catch {
4209
4230
  output = rawOutput;
4210
4231
  }
4211
4232
  } else {
4212
- logger.debug(`\u{1F527} Debug: No JSON found in output, keeping as string`);
4213
- output = rawOutput;
4214
- }
4215
- }
4216
- if (output !== rawOutput) {
4217
- try {
4218
- const outputType = Array.isArray(output) ? `array[${output.length}]` : typeof output;
4219
- logger.debug(`\u{1F527} Debug: Parsed output type: ${outputType}`);
4220
- if (typeof output === "object" && output !== null) {
4221
- logger.debug(`\u{1F527} Debug: Parsed output keys: ${Object.keys(output).join(", ")}`);
4233
+ const extractedAny = this.extractJsonAnywhere(rawOutput);
4234
+ if (extractedAny) {
4235
+ try {
4236
+ output = JSON.parse(extractedAny);
4237
+ } catch {
4238
+ output = rawOutput;
4239
+ }
4240
+ } else {
4241
+ const m = /\berror\b\s*[:=]\s*(true|false)/i.exec(rawOutput);
4242
+ if (m) {
4243
+ output = { error: m[1].toLowerCase() === "true" };
4244
+ } else {
4245
+ output = rawOutput;
4246
+ }
4222
4247
  }
4223
- } catch {
4224
4248
  }
4225
4249
  }
4226
4250
  let finalOutput = output;
@@ -4267,65 +4291,130 @@ var init_command_check_provider = __esm({
4267
4291
  env: templateContext.env
4268
4292
  };
4269
4293
  const trimmedTransform = transformJs.trim();
4270
- let transformExpression;
4271
- if (/return\s+/.test(trimmedTransform)) {
4272
- transformExpression = `(() => {
4273
- ${trimmedTransform}
4274
- })()`;
4275
- } else {
4276
- const lines = trimmedTransform.split("\n");
4277
- if (lines.length > 1) {
4278
- const lastLine = lines[lines.length - 1].trim();
4279
- const remaining = lines.slice(0, -1).join("\n");
4280
- if (lastLine && !lastLine.includes("}") && !lastLine.includes("{")) {
4281
- const returnTarget = lastLine.replace(/;$/, "");
4282
- transformExpression = `(() => {
4283
- ${remaining}
4284
- return ${returnTarget};
4285
- })()`;
4286
- } else {
4287
- transformExpression = `(${trimmedTransform})`;
4288
- }
4289
- } else {
4290
- transformExpression = `(${trimmedTransform})`;
4294
+ const buildBodyWithReturn = (raw) => {
4295
+ const t = raw.trim();
4296
+ const lines = t.split(/\n/);
4297
+ let i = lines.length - 1;
4298
+ while (i >= 0 && lines[i].trim().length === 0) i--;
4299
+ if (i < 0) return "return undefined;";
4300
+ const lastLine = lines[i].trim();
4301
+ if (/^return\b/i.test(lastLine)) {
4302
+ return t;
4291
4303
  }
4292
- }
4304
+ const idx = t.lastIndexOf(lastLine);
4305
+ const head = idx >= 0 ? t.slice(0, idx) : "";
4306
+ const lastExpr = lastLine.replace(/;\s*$/, "");
4307
+ return `${head}
4308
+ return (${lastExpr});`;
4309
+ };
4310
+ const bodyWithReturn = buildBodyWithReturn(trimmedTransform);
4293
4311
  const code = `
4294
4312
  const output = scope.output;
4295
4313
  const pr = scope.pr;
4296
4314
  const files = scope.files;
4297
4315
  const outputs = scope.outputs;
4298
4316
  const env = scope.env;
4299
- const log = (...args) => {
4300
- console.log('\u{1F50D} Debug:', ...args);
4301
- };
4302
- return ${transformExpression};
4317
+ const log = (...args) => { console.log('\u{1F50D} Debug:', ...args); };
4318
+ const __result = (function(){
4319
+ ${bodyWithReturn}
4320
+ })();
4321
+ return __result;
4303
4322
  `;
4323
+ if (!this.sandbox) {
4324
+ this.sandbox = this.createSecureSandbox();
4325
+ }
4326
+ let parsedFromSandboxJson = void 0;
4304
4327
  try {
4305
- logger.debug(`\u{1F527} Debug: JavaScript transform code: ${code}`);
4306
- logger.debug(
4307
- `\u{1F527} Debug: JavaScript context: ${JSON.stringify(jsContext).slice(0, 200)}`
4308
- );
4328
+ const stringifyCode = `
4329
+ const output = scope.output;
4330
+ const pr = scope.pr;
4331
+ const files = scope.files;
4332
+ const outputs = scope.outputs;
4333
+ const env = scope.env;
4334
+ const log = (...args) => { console.log('\u{1F50D} Debug:', ...args); };
4335
+ const __ret = (function(){
4336
+ ${bodyWithReturn}
4337
+ })();
4338
+ return typeof __ret === 'object' && __ret !== null ? JSON.stringify(__ret) : null;
4339
+ `;
4340
+ const stringifyExec = this.sandbox.compile(stringifyCode);
4341
+ const jsonStr = stringifyExec({ scope: jsContext }).run();
4342
+ if (typeof jsonStr === "string" && jsonStr.trim().startsWith("{")) {
4343
+ parsedFromSandboxJson = JSON.parse(jsonStr);
4344
+ }
4309
4345
  } catch {
4310
4346
  }
4311
- if (!this.sandbox) {
4312
- this.sandbox = this.createSecureSandbox();
4347
+ if (parsedFromSandboxJson !== void 0) {
4348
+ finalOutput = parsedFromSandboxJson;
4349
+ } else {
4350
+ const exec2 = this.sandbox.compile(code);
4351
+ finalOutput = exec2({ scope: jsContext }).run();
4313
4352
  }
4314
- const exec2 = this.sandbox.compile(code);
4315
- finalOutput = exec2({ scope: jsContext }).run();
4316
- logger.verbose(`\u2713 Applied JavaScript transform successfully`);
4317
4353
  try {
4318
- const preview = JSON.stringify(finalOutput);
4354
+ if (finalOutput && typeof finalOutput === "object" && !Array.isArray(finalOutput) && (finalOutput.error === void 0 || finalOutput.issues === void 0)) {
4355
+ const vm = await import("vm");
4356
+ const vmContext = vm.createContext({ scope: jsContext });
4357
+ const vmCode = `
4358
+ (function(){
4359
+ const output = scope.output; const pr = scope.pr; const files = scope.files; const outputs = scope.outputs; const env = scope.env; const log = ()=>{};
4360
+ ${bodyWithReturn}
4361
+ })()
4362
+ `;
4363
+ const vmResult = vm.runInContext(vmCode, vmContext, { timeout: 1e3 });
4364
+ if (vmResult && typeof vmResult === "object") {
4365
+ finalOutput = vmResult;
4366
+ }
4367
+ }
4368
+ } catch {
4369
+ }
4370
+ let finalSnapshot = null;
4371
+ try {
4372
+ if (finalOutput && typeof finalOutput === "object" && !Array.isArray(finalOutput)) {
4373
+ try {
4374
+ const stringifyExec = this.sandbox.compile("return JSON.stringify(scope.obj);");
4375
+ const jsonStr = stringifyExec({ obj: finalOutput }).run();
4376
+ if (typeof jsonStr === "string" && jsonStr.trim().startsWith("{")) {
4377
+ finalSnapshot = JSON.parse(jsonStr);
4378
+ }
4379
+ } catch {
4380
+ }
4381
+ if (!finalSnapshot) {
4382
+ try {
4383
+ finalSnapshot = JSON.parse(JSON.stringify(finalOutput));
4384
+ } catch {
4385
+ }
4386
+ }
4387
+ if (!finalSnapshot) {
4388
+ const tmp = {};
4389
+ for (const k of Object.keys(finalOutput)) {
4390
+ tmp[k] = finalOutput[k];
4391
+ }
4392
+ finalSnapshot = tmp;
4393
+ }
4394
+ }
4395
+ } catch {
4396
+ }
4397
+ this.__lastTransformSnapshot = finalSnapshot;
4398
+ try {
4399
+ const isObj = finalOutput && typeof finalOutput === "object" && !Array.isArray(finalOutput);
4400
+ const keys = isObj ? Object.keys(finalOutput).join(",") : typeof finalOutput;
4319
4401
  logger.debug(
4320
- `\u{1F527} Debug: transform_js result: ${typeof preview === "string" ? preview.slice(0, 200) : String(preview).slice(0, 200)}`
4402
+ ` transform_js: output typeof=${Array.isArray(finalOutput) ? "array" : typeof finalOutput} keys=${keys}`
4321
4403
  );
4322
- } catch {
4404
+ if (isObj && finalOutput.issues) {
4405
+ const mi = finalOutput.issues;
4406
+ logger.debug(
4407
+ ` transform_js: issues typeof=${Array.isArray(mi) ? "array" : typeof mi} len=${mi && mi.length || 0}`
4408
+ );
4409
+ }
4323
4410
  try {
4324
- const preview = String(finalOutput);
4325
- logger.debug(`\u{1F527} Debug: transform_js result: ${preview.slice(0, 200)}`);
4411
+ if (isObj)
4412
+ logger.debug(` transform_js: error value=${String(finalOutput.error)}`);
4326
4413
  } catch {
4327
4414
  }
4415
+ } catch {
4328
4416
  }
4417
+ logger.verbose(`\u2713 Applied JavaScript transform successfully`);
4329
4418
  } catch (error) {
4330
4419
  logger.error(
4331
4420
  `\u2717 Failed to apply JavaScript transform: ${error instanceof Error ? error.message : "Unknown error"}`
@@ -4346,13 +4435,175 @@ return ${returnTarget};
4346
4435
  }
4347
4436
  let issues = [];
4348
4437
  let outputForDependents = finalOutput;
4438
+ const snapshotForExtraction = this.__lastTransformSnapshot || null;
4439
+ try {
4440
+ if (snapshotForExtraction) {
4441
+ logger.debug(` provider: snapshot keys=${Object.keys(snapshotForExtraction).join(",")}`);
4442
+ } else {
4443
+ logger.debug(` provider: snapshot is null`);
4444
+ }
4445
+ } catch {
4446
+ }
4447
+ try {
4448
+ if (Array.isArray(outputForDependents) && outputForDependents.length === 1) {
4449
+ const first = outputForDependents[0];
4450
+ if (typeof first === "string") {
4451
+ try {
4452
+ outputForDependents = JSON.parse(first);
4453
+ } catch {
4454
+ }
4455
+ } else if (first && typeof first === "object") {
4456
+ outputForDependents = first;
4457
+ }
4458
+ }
4459
+ } catch {
4460
+ }
4349
4461
  let content;
4350
4462
  let extracted = null;
4351
4463
  const trimmedRawOutput = typeof rawOutput === "string" ? rawOutput.trim() : void 0;
4352
4464
  const commandConfig = config;
4353
4465
  const isForEachParent = commandConfig.forEach === true;
4354
4466
  if (!isForEachParent) {
4355
- extracted = this.extractIssuesFromOutput(finalOutput);
4467
+ try {
4468
+ const baseObj = snapshotForExtraction || finalOutput;
4469
+ if (baseObj && typeof baseObj === "object" && Object.prototype.hasOwnProperty.call(baseObj, "issues")) {
4470
+ const remaining = { ...baseObj };
4471
+ delete remaining.issues;
4472
+ outputForDependents = Object.keys(remaining).length > 0 ? remaining : void 0;
4473
+ try {
4474
+ const k = outputForDependents && typeof outputForDependents === "object" ? Object.keys(outputForDependents).join(",") : String(outputForDependents);
4475
+ logger.debug(` provider: generic-remaining keys=${k}`);
4476
+ } catch {
4477
+ }
4478
+ }
4479
+ } catch {
4480
+ }
4481
+ const objForExtraction = snapshotForExtraction || finalOutput;
4482
+ if (objForExtraction && typeof objForExtraction === "object") {
4483
+ try {
4484
+ const rec = objForExtraction;
4485
+ const maybeIssues = rec.issues;
4486
+ const toPlainArray = (v) => {
4487
+ if (Array.isArray(v)) return v;
4488
+ try {
4489
+ if (v && typeof v === "object" && typeof v[Symbol.iterator] === "function") {
4490
+ return Array.from(v);
4491
+ }
4492
+ } catch {
4493
+ }
4494
+ const len = Number((v || {}).length);
4495
+ if (Number.isFinite(len) && len >= 0) {
4496
+ const arr2 = [];
4497
+ for (let i = 0; i < len; i++) arr2.push(v[i]);
4498
+ return arr2;
4499
+ }
4500
+ try {
4501
+ const cloned = JSON.parse(JSON.stringify(v));
4502
+ return Array.isArray(cloned) ? cloned : null;
4503
+ } catch {
4504
+ return null;
4505
+ }
4506
+ };
4507
+ try {
4508
+ const ctor = maybeIssues && maybeIssues.constructor ? maybeIssues.constructor.name : "unknown";
4509
+ logger.debug(
4510
+ ` provider: issues inspect typeof=${typeof maybeIssues} Array.isArray=${Array.isArray(
4511
+ maybeIssues
4512
+ )} ctor=${ctor} keys=${Object.keys(maybeIssues || {}).join(",")}`
4513
+ );
4514
+ } catch {
4515
+ }
4516
+ const arr = toPlainArray(maybeIssues);
4517
+ if (arr) {
4518
+ const norm = this.normalizeIssueArray(arr);
4519
+ if (norm) {
4520
+ issues = norm;
4521
+ const remaining = { ...rec };
4522
+ delete remaining.issues;
4523
+ outputForDependents = Object.keys(remaining).length > 0 ? remaining : void 0;
4524
+ try {
4525
+ const keys = outputForDependents && typeof outputForDependents === "object" ? Object.keys(outputForDependents).join(",") : String(outputForDependents);
4526
+ logger.info(
4527
+ ` provider: fast-path issues=${issues.length} remaining keys=${keys}`
4528
+ );
4529
+ } catch {
4530
+ }
4531
+ } else {
4532
+ try {
4533
+ logger.info(" provider: fast-path norm failed");
4534
+ } catch {
4535
+ }
4536
+ }
4537
+ } else {
4538
+ try {
4539
+ logger.info(" provider: fast-path arr unavailable");
4540
+ } catch {
4541
+ }
4542
+ }
4543
+ } catch {
4544
+ }
4545
+ }
4546
+ let extractionTarget = snapshotForExtraction || finalOutput;
4547
+ try {
4548
+ if (Array.isArray(extractionTarget) && extractionTarget.length === 1) {
4549
+ const first = extractionTarget[0];
4550
+ if (typeof first === "string") {
4551
+ try {
4552
+ extractionTarget = JSON.parse(first);
4553
+ } catch {
4554
+ extractionTarget = first;
4555
+ }
4556
+ } else if (first && typeof first === "object") {
4557
+ extractionTarget = first;
4558
+ }
4559
+ }
4560
+ } catch {
4561
+ }
4562
+ extracted = this.extractIssuesFromOutput(extractionTarget);
4563
+ try {
4564
+ if (extractionTarget !== (snapshotForExtraction || finalOutput)) {
4565
+ finalOutput = extractionTarget;
4566
+ }
4567
+ } catch {
4568
+ }
4569
+ if (!extracted && finalOutput && typeof finalOutput === "object") {
4570
+ try {
4571
+ const rec = finalOutput;
4572
+ const maybeIssues = rec.issues;
4573
+ if (maybeIssues && typeof maybeIssues === "object") {
4574
+ let arr = null;
4575
+ try {
4576
+ if (typeof maybeIssues[Symbol.iterator] === "function") {
4577
+ arr = Array.from(maybeIssues);
4578
+ }
4579
+ } catch {
4580
+ }
4581
+ if (!arr) {
4582
+ const len = Number(maybeIssues.length);
4583
+ if (Number.isFinite(len) && len >= 0) {
4584
+ arr = [];
4585
+ for (let i = 0; i < len; i++) arr.push(maybeIssues[i]);
4586
+ }
4587
+ }
4588
+ if (!arr) {
4589
+ try {
4590
+ arr = JSON.parse(JSON.stringify(maybeIssues));
4591
+ } catch {
4592
+ }
4593
+ }
4594
+ if (arr && Array.isArray(arr)) {
4595
+ const norm = this.normalizeIssueArray(arr);
4596
+ if (norm) {
4597
+ issues = norm;
4598
+ const remaining = { ...rec };
4599
+ delete remaining.issues;
4600
+ outputForDependents = Object.keys(remaining).length > 0 ? remaining : void 0;
4601
+ }
4602
+ }
4603
+ }
4604
+ } catch {
4605
+ }
4606
+ }
4356
4607
  if (!extracted && typeof finalOutput === "string") {
4357
4608
  try {
4358
4609
  const parsed = JSON.parse(finalOutput);
@@ -4360,12 +4611,36 @@ return ${returnTarget};
4360
4611
  if (extracted) {
4361
4612
  issues = extracted.issues;
4362
4613
  outputForDependents = extracted.remainingOutput;
4614
+ if (typeof extracted.remainingOutput === "object" && extracted.remainingOutput !== null && typeof extracted.remainingOutput.content === "string") {
4615
+ const c = String(extracted.remainingOutput.content).trim();
4616
+ if (c) content = c;
4617
+ }
4363
4618
  }
4364
4619
  } catch {
4620
+ try {
4621
+ const any = this.extractJsonAnywhere(finalOutput);
4622
+ if (any) {
4623
+ const parsed = JSON.parse(any);
4624
+ extracted = this.extractIssuesFromOutput(parsed);
4625
+ if (extracted) {
4626
+ issues = extracted.issues;
4627
+ outputForDependents = extracted.remainingOutput;
4628
+ if (typeof extracted.remainingOutput === "object" && extracted.remainingOutput !== null && typeof extracted.remainingOutput.content === "string") {
4629
+ const c = String(extracted.remainingOutput.content).trim();
4630
+ if (c) content = c;
4631
+ }
4632
+ }
4633
+ }
4634
+ } catch {
4635
+ }
4365
4636
  }
4366
4637
  } else if (extracted) {
4367
4638
  issues = extracted.issues;
4368
4639
  outputForDependents = extracted.remainingOutput;
4640
+ if (typeof extracted.remainingOutput === "object" && extracted.remainingOutput !== null && typeof extracted.remainingOutput.content === "string") {
4641
+ const c = String(extracted.remainingOutput.content).trim();
4642
+ if (c) content = c;
4643
+ }
4369
4644
  }
4370
4645
  if (!issues.length && this.shouldTreatAsTextOutput(trimmedRawOutput)) {
4371
4646
  content = trimmedRawOutput;
@@ -4375,24 +4650,174 @@ return ${returnTarget};
4375
4650
  content = trimmed;
4376
4651
  }
4377
4652
  }
4653
+ if (!issues.length && typeof trimmedRawOutput === "string") {
4654
+ try {
4655
+ const tryParsed = JSON.parse(trimmedRawOutput);
4656
+ const reextract = this.extractIssuesFromOutput(tryParsed);
4657
+ if (reextract && reextract.issues && reextract.issues.length) {
4658
+ issues = reextract.issues;
4659
+ if (!outputForDependents && reextract.remainingOutput) {
4660
+ outputForDependents = reextract.remainingOutput;
4661
+ }
4662
+ } else if (Array.isArray(tryParsed)) {
4663
+ const first = tryParsed[0];
4664
+ if (first && typeof first === "object" && Array.isArray(first.issues)) {
4665
+ const merged = [];
4666
+ for (const el of tryParsed) {
4667
+ if (el && typeof el === "object" && Array.isArray(el.issues)) {
4668
+ merged.push(...el.issues);
4669
+ }
4670
+ }
4671
+ const flat = this.normalizeIssueArray(merged);
4672
+ if (flat) issues = flat;
4673
+ } else {
4674
+ const converted = [];
4675
+ for (const el of tryParsed) {
4676
+ if (typeof el === "string") {
4677
+ try {
4678
+ const obj = JSON.parse(el);
4679
+ converted.push(obj);
4680
+ } catch {
4681
+ }
4682
+ } else {
4683
+ converted.push(el);
4684
+ }
4685
+ }
4686
+ const flat = this.normalizeIssueArray(converted);
4687
+ if (flat) issues = flat;
4688
+ }
4689
+ }
4690
+ } catch {
4691
+ }
4692
+ if (!issues.length) {
4693
+ try {
4694
+ const any = this.extractJsonAnywhere(trimmedRawOutput);
4695
+ if (any) {
4696
+ const tryParsed = JSON.parse(any);
4697
+ const reextract = this.extractIssuesFromOutput(tryParsed);
4698
+ if (reextract && reextract.issues && reextract.issues.length) {
4699
+ issues = reextract.issues;
4700
+ if (!outputForDependents && reextract.remainingOutput) {
4701
+ outputForDependents = reextract.remainingOutput;
4702
+ }
4703
+ }
4704
+ }
4705
+ } catch {
4706
+ }
4707
+ }
4708
+ }
4709
+ try {
4710
+ const srcObj = snapshotForExtraction || finalOutput;
4711
+ if (outputForDependents && typeof outputForDependents === "object" && srcObj && typeof srcObj === "object") {
4712
+ for (const k of Object.keys(srcObj)) {
4713
+ const v = srcObj[k];
4714
+ if (typeof v === "boolean" || typeof v === "number" || typeof v === "string") {
4715
+ outputForDependents[k] = v;
4716
+ }
4717
+ }
4718
+ }
4719
+ } catch {
4720
+ }
4721
+ try {
4722
+ if (outputForDependents && typeof outputForDependents === "object" && !Array.isArray(outputForDependents)) {
4723
+ const plain = {};
4724
+ for (const k of Object.keys(outputForDependents)) {
4725
+ plain[k] = outputForDependents[k];
4726
+ }
4727
+ outputForDependents = plain;
4728
+ }
4729
+ } catch {
4730
+ }
4378
4731
  }
4379
4732
  if (!content && this.shouldTreatAsTextOutput(trimmedRawOutput) && !isForEachParent) {
4380
4733
  content = trimmedRawOutput;
4381
4734
  }
4735
+ try {
4736
+ if (outputForDependents && typeof outputForDependents === "object") {
4737
+ outputForDependents = JSON.parse(JSON.stringify(outputForDependents));
4738
+ }
4739
+ } catch {
4740
+ }
4741
+ const promoted = {};
4742
+ try {
4743
+ const srcObj = snapshotForExtraction || finalOutput;
4744
+ if (srcObj && typeof srcObj === "object") {
4745
+ for (const k of Object.keys(srcObj)) {
4746
+ const v = srcObj[k];
4747
+ if (typeof v === "boolean") {
4748
+ if (v === true && promoted[k] === void 0) promoted[k] = true;
4749
+ } else if ((typeof v === "number" || typeof v === "string") && promoted[k] === void 0) {
4750
+ promoted[k] = v;
4751
+ }
4752
+ }
4753
+ }
4754
+ } catch {
4755
+ }
4382
4756
  const result = {
4383
4757
  issues,
4384
4758
  output: outputForDependents,
4385
- ...content ? { content } : {}
4759
+ ...content ? { content } : {},
4760
+ ...promoted
4386
4761
  };
4387
- if (transformJs) {
4388
- try {
4389
- const outputValue = result.output;
4390
- const stringified = JSON.stringify(outputValue);
4391
- logger.debug(
4392
- `\u{1F527} Debug: Command provider returning output: ${stringified ? stringified.slice(0, 200) : "(empty)"}`
4393
- );
4394
- } catch {
4762
+ try {
4763
+ if (transformJs) {
4764
+ const rawObj = snapshotForExtraction || finalOutput;
4765
+ if (rawObj && typeof rawObj === "object") {
4766
+ result.__raw = rawObj;
4767
+ }
4768
+ }
4769
+ } catch {
4770
+ }
4771
+ try {
4772
+ const srcObj = snapshotForExtraction || finalOutput;
4773
+ const srcErr = (() => {
4774
+ try {
4775
+ if (snapshotForExtraction && typeof snapshotForExtraction === "object" && snapshotForExtraction.error !== void 0) {
4776
+ return Boolean(snapshotForExtraction.error);
4777
+ }
4778
+ if (finalOutput && typeof finalOutput === "object" && finalOutput.error !== void 0) {
4779
+ return Boolean(finalOutput.error);
4780
+ }
4781
+ } catch {
4782
+ }
4783
+ return void 0;
4784
+ })();
4785
+ const dst = result.output;
4786
+ if (srcObj && typeof srcObj === "object" && dst && typeof dst === "object") {
4787
+ try {
4788
+ logger.debug(
4789
+ ` provider: safeguard src.error typeof=${typeof srcObj.error} val=${String(srcObj.error)} dst.hasErrorBefore=${String(dst.error !== void 0)}`
4790
+ );
4791
+ } catch {
4792
+ }
4793
+ for (const k of Object.keys(srcObj)) {
4794
+ const v = srcObj[k];
4795
+ if (typeof v === "boolean" || typeof v === "number" || typeof v === "string") {
4796
+ dst[k] = v;
4797
+ }
4798
+ }
4799
+ if (srcErr !== void 0 && dst.error === void 0) {
4800
+ dst.error = srcErr;
4801
+ try {
4802
+ const k = Object.keys(dst).join(",");
4803
+ logger.debug(
4804
+ ` provider: safeguard merged error -> output keys=${k} val=${String(dst.error)}`
4805
+ );
4806
+ } catch {
4807
+ }
4808
+ }
4809
+ }
4810
+ } catch {
4811
+ }
4812
+ try {
4813
+ const out = result.output;
4814
+ if (out && typeof out === "object") {
4815
+ const k = Object.keys(out).join(",");
4816
+ logger.debug(` provider: return output keys=${k}`);
4817
+ } else {
4818
+ logger.debug(` provider: return output type=${typeof out}`);
4395
4819
  }
4820
+ } catch {
4396
4821
  }
4397
4822
  return result;
4398
4823
  } catch (error) {
@@ -4542,19 +4967,89 @@ ${stderrOutput}` : `Command execution failed: ${errorMessage}`;
4542
4967
  * Looks for the last occurrence of { or [ and tries to parse from there
4543
4968
  */
4544
4969
  extractJsonFromEnd(text) {
4545
- const lines = text.split("\n");
4546
- for (let i = lines.length - 1; i >= 0; i--) {
4547
- const trimmed = lines[i].trim();
4548
- if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
4549
- const candidate = lines.slice(i).join("\n");
4550
- const trimmedCandidate = candidate.trim();
4551
- if (trimmedCandidate.startsWith("{") && trimmedCandidate.endsWith("}") || trimmedCandidate.startsWith("[") && trimmedCandidate.endsWith("]")) {
4552
- return trimmedCandidate;
4970
+ const lastBrace = Math.max(text.lastIndexOf("}"), text.lastIndexOf("]"));
4971
+ if (lastBrace === -1) return null;
4972
+ let open = 0;
4973
+ for (let i = lastBrace; i >= 0; i--) {
4974
+ const ch = text[i];
4975
+ if (ch === "}" || ch === "]") open++;
4976
+ else if (ch === "{" || ch === "[") open--;
4977
+ if (open === 0 && (ch === "{" || ch === "[")) {
4978
+ const candidate = text.slice(i, lastBrace + 1).trim();
4979
+ try {
4980
+ JSON.parse(candidate);
4981
+ return candidate;
4982
+ } catch {
4983
+ return null;
4553
4984
  }
4554
4985
  }
4555
4986
  }
4556
4987
  return null;
4557
4988
  }
4989
+ // Extract any balanced JSON object/array substring from anywhere in the text
4990
+ extractJsonAnywhere(text) {
4991
+ const n = text.length;
4992
+ let best = null;
4993
+ for (let i = 0; i < n; i++) {
4994
+ const start = text[i];
4995
+ if (start !== "{" && start !== "[") continue;
4996
+ let open = 0;
4997
+ let inString = false;
4998
+ let escape = false;
4999
+ for (let j = i; j < n; j++) {
5000
+ const ch = text[j];
5001
+ if (escape) {
5002
+ escape = false;
5003
+ continue;
5004
+ }
5005
+ if (ch === "\\") {
5006
+ escape = true;
5007
+ continue;
5008
+ }
5009
+ if (ch === '"') {
5010
+ inString = !inString;
5011
+ continue;
5012
+ }
5013
+ if (inString) continue;
5014
+ if (ch === "{" || ch === "[") open++;
5015
+ else if (ch === "}" || ch === "]") open--;
5016
+ if (open === 0 && (ch === "}" || ch === "]")) {
5017
+ const candidate = text.slice(i, j + 1).trim();
5018
+ try {
5019
+ JSON.parse(candidate);
5020
+ best = candidate;
5021
+ } catch {
5022
+ const strict = this.looseJsonToStrict(candidate);
5023
+ if (strict) {
5024
+ try {
5025
+ JSON.parse(strict);
5026
+ best = strict;
5027
+ } catch {
5028
+ }
5029
+ }
5030
+ }
5031
+ break;
5032
+ }
5033
+ }
5034
+ }
5035
+ return best;
5036
+ }
5037
+ // Best-effort conversion of object-literal-like strings to strict JSON
5038
+ looseJsonToStrict(candidate) {
5039
+ try {
5040
+ let s = candidate.trim();
5041
+ s = s.replace(/'/g, '"');
5042
+ s = s.replace(/([\{,]\s*)([A-Za-z_][A-Za-z0-9_-]*)\s*:/g, '$1"$2":');
5043
+ s = s.replace(/:\s*([A-Za-z_][A-Za-z0-9_-]*)\s*(?=[,}])/g, (m, word) => {
5044
+ const lw = String(word).toLowerCase();
5045
+ if (lw === "true" || lw === "false" || lw === "null") return `:${lw}`;
5046
+ return `:"${word}"`;
5047
+ });
5048
+ return s;
5049
+ } catch {
5050
+ return null;
5051
+ }
5052
+ }
4558
5053
  /**
4559
5054
  * Recursively apply JSON-smart wrapper to outputs object values
4560
5055
  */
@@ -4602,6 +5097,20 @@ ${stderrOutput}` : `Command execution failed: ${errorMessage}`;
4602
5097
  ];
4603
5098
  }
4604
5099
  extractIssuesFromOutput(output) {
5100
+ try {
5101
+ logger.info(
5102
+ ` extractIssuesFromOutput: typeof=${Array.isArray(output) ? "array" : typeof output}`
5103
+ );
5104
+ if (typeof output === "object" && output) {
5105
+ const rec = output;
5106
+ logger.info(
5107
+ ` extractIssuesFromOutput: keys=${Object.keys(rec).join(",")} issuesIsArray=${Array.isArray(
5108
+ rec.issues
5109
+ )}`
5110
+ );
5111
+ }
5112
+ } catch {
5113
+ }
4605
5114
  if (output === null || output === void 0) {
4606
5115
  return null;
4607
5116
  }
@@ -4609,9 +5118,21 @@ ${stderrOutput}` : `Command execution failed: ${errorMessage}`;
4609
5118
  return null;
4610
5119
  }
4611
5120
  if (Array.isArray(output)) {
4612
- const issues = this.normalizeIssueArray(output);
4613
- if (issues) {
4614
- return { issues, remainingOutput: void 0 };
5121
+ const first = output[0];
5122
+ if (first && typeof first === "object" && !Array.isArray(first.message) && Array.isArray(first.issues)) {
5123
+ const merged = [];
5124
+ for (const el of output) {
5125
+ if (el && typeof el === "object" && Array.isArray(el.issues)) {
5126
+ merged.push(...el.issues);
5127
+ }
5128
+ }
5129
+ const flat = this.normalizeIssueArray(merged);
5130
+ if (flat) return { issues: flat, remainingOutput: void 0 };
5131
+ } else {
5132
+ const issues = this.normalizeIssueArray(output);
5133
+ if (issues) {
5134
+ return { issues, remainingOutput: void 0 };
5135
+ }
4615
5136
  }
4616
5137
  return null;
4617
5138
  }
@@ -4739,10 +5260,28 @@ ${stderrOutput}` : `Command execution failed: ${errorMessage}`;
4739
5260
  }
4740
5261
  async renderCommandTemplate(template, context) {
4741
5262
  try {
4742
- return await this.liquid.parseAndRender(template, context);
5263
+ let tpl = template;
5264
+ if (tpl.includes("{{")) {
5265
+ tpl = tpl.replace(/\{\{([\s\S]*?)\}\}/g, (_m, inner) => {
5266
+ const fixed = String(inner).replace(/\[\"/g, "['").replace(/\"\]/g, "']");
5267
+ return `{{ ${fixed} }}`;
5268
+ });
5269
+ }
5270
+ let rendered = await this.liquid.parseAndRender(tpl, context);
5271
+ if (/\{\{[\s\S]*?\}\}/.test(rendered)) {
5272
+ try {
5273
+ rendered = this.renderWithJsExpressions(rendered, context);
5274
+ } catch {
5275
+ }
5276
+ }
5277
+ return rendered;
4743
5278
  } catch (error) {
4744
- logger.debug(`\u{1F527} Debug: Liquid rendering failed, falling back to JS evaluation: ${error}`);
4745
- return this.renderWithJsExpressions(template, context);
5279
+ logger.debug(`\u{1F527} Debug: Liquid templating failed, trying JS-expression fallback: ${error}`);
5280
+ try {
5281
+ return this.renderWithJsExpressions(template, context);
5282
+ } catch {
5283
+ return template;
5284
+ }
4746
5285
  }
4747
5286
  }
4748
5287
  renderWithJsExpressions(template, context) {
@@ -4755,9 +5294,7 @@ ${stderrOutput}` : `Command execution failed: ${errorMessage}`;
4755
5294
  const expressionRegex = /\{\{\s*([^{}]+?)\s*\}\}/g;
4756
5295
  return template.replace(expressionRegex, (_match, expr) => {
4757
5296
  const expression = String(expr).trim();
4758
- if (!expression) {
4759
- return "";
4760
- }
5297
+ if (!expression) return "";
4761
5298
  try {
4762
5299
  const evalCode = `
4763
5300
  const pr = scope.pr;
@@ -4766,14 +5303,11 @@ ${stderrOutput}` : `Command execution failed: ${errorMessage}`;
4766
5303
  const env = scope.env;
4767
5304
  return (${expression});
4768
5305
  `;
4769
- if (!this.sandbox) {
4770
- this.sandbox = this.createSecureSandbox();
4771
- }
5306
+ if (!this.sandbox) this.sandbox = this.createSecureSandbox();
4772
5307
  const evaluator = this.sandbox.compile(evalCode);
4773
5308
  const result = evaluator({ scope }).run();
4774
5309
  return result === void 0 || result === null ? "" : String(result);
4775
- } catch (evaluationError) {
4776
- logger.debug(`\u{1F527} Debug: Failed to evaluate expression: ${expression} - ${evaluationError}`);
5310
+ } catch {
4777
5311
  return "";
4778
5312
  }
4779
5313
  });
@@ -5200,7 +5734,19 @@ var init_failure_condition_evaluator = __esm({
5200
5734
  previousOutputs
5201
5735
  );
5202
5736
  try {
5203
- return this.evaluateExpression(expression, context);
5737
+ try {
5738
+ const isObj = context.output && typeof context.output === "object";
5739
+ const keys = isObj ? Object.keys(context.output).join(",") : typeof context.output;
5740
+ let errorVal = void 0;
5741
+ if (isObj && context.output.error !== void 0)
5742
+ errorVal = context.output.error;
5743
+ (init_logger(), __toCommonJS(logger_exports)).logger.debug(
5744
+ ` fail_if: evaluating '${expression}' with output keys=${keys} error=${String(errorVal)}`
5745
+ );
5746
+ } catch {
5747
+ }
5748
+ const res = this.evaluateExpression(expression, context);
5749
+ return res;
5204
5750
  } catch (error) {
5205
5751
  console.warn(`Failed to evaluate fail_if expression: ${error}`);
5206
5752
  return false;
@@ -5304,6 +5850,16 @@ var init_failure_condition_evaluator = __esm({
5304
5850
  results.length = 0;
5305
5851
  results.push(...filteredResults, ...checkResults);
5306
5852
  }
5853
+ try {
5854
+ if (checkName === "B") {
5855
+ console.error(
5856
+ `\u{1F527} Debug: fail_if results for ${checkName}: ${JSON.stringify(results)} context.output=${JSON.stringify(
5857
+ context.output
5858
+ )}`
5859
+ );
5860
+ }
5861
+ } catch {
5862
+ }
5307
5863
  return results;
5308
5864
  }
5309
5865
  /**
@@ -5484,6 +6040,10 @@ var init_failure_condition_evaluator = __esm({
5484
6040
  exec = this.sandbox.compile(`return (${normalizedExpr});`);
5485
6041
  }
5486
6042
  const result = exec(scope).run();
6043
+ try {
6044
+ (init_logger(), __toCommonJS(logger_exports)).logger.debug(` fail_if: result=${Boolean(result)}`);
6045
+ } catch {
6046
+ }
5487
6047
  return Boolean(result);
5488
6048
  } catch (error) {
5489
6049
  console.error("\u274C Failed to evaluate expression:", condition, error);
@@ -5553,6 +6113,66 @@ var init_failure_condition_evaluator = __esm({
5553
6113
  } else if (extractedOutput && typeof extractedOutput === "object") {
5554
6114
  Object.assign(aggregatedOutput, extractedOutput);
5555
6115
  }
6116
+ try {
6117
+ const raw = reviewSummaryWithOutput.__raw;
6118
+ if (raw && typeof raw === "object") {
6119
+ Object.assign(aggregatedOutput, raw);
6120
+ }
6121
+ } catch {
6122
+ }
6123
+ try {
6124
+ if (typeof extractedOutput === "string") {
6125
+ const parsed = this.tryExtractJsonFromEnd(extractedOutput) ?? (() => {
6126
+ try {
6127
+ return JSON.parse(extractedOutput);
6128
+ } catch {
6129
+ return null;
6130
+ }
6131
+ })();
6132
+ if (parsed !== null) {
6133
+ if (Array.isArray(parsed)) {
6134
+ aggregatedOutput.items = parsed;
6135
+ } else if (typeof parsed === "object") {
6136
+ Object.assign(aggregatedOutput, parsed);
6137
+ }
6138
+ }
6139
+ const lower = extractedOutput.toLowerCase();
6140
+ const boolFrom = (key) => {
6141
+ const reTrue = new RegExp(
6142
+ `(?:^|[^a-z0-9_])${key}[^a-z0-9_]*[:=][^a-z0-9_]*true(?:[^a-z0-9_]|$)`
6143
+ );
6144
+ const reFalse = new RegExp(
6145
+ `(?:^|[^a-z0-9_])${key}[^a-z0-9_]*[:=][^a-z0-9_]*false(?:[^a-z0-9_]|$)`
6146
+ );
6147
+ if (reTrue.test(lower)) return true;
6148
+ if (reFalse.test(lower)) return false;
6149
+ return null;
6150
+ };
6151
+ const keys = ["error"];
6152
+ for (const k of keys) {
6153
+ const v = boolFrom(k);
6154
+ if (v !== null && aggregatedOutput[k] === void 0) {
6155
+ aggregatedOutput[k] = v;
6156
+ }
6157
+ }
6158
+ }
6159
+ } catch {
6160
+ }
6161
+ try {
6162
+ const rsAny = reviewSummaryWithOutput;
6163
+ const hasStructuredOutput = extractedOutput !== void 0 && extractedOutput !== null;
6164
+ if (!hasStructuredOutput && typeof rsAny?.content === "string") {
6165
+ const parsedFromContent = this.tryExtractJsonFromEnd(rsAny.content);
6166
+ if (parsedFromContent !== null && parsedFromContent !== void 0) {
6167
+ if (Array.isArray(parsedFromContent)) {
6168
+ aggregatedOutput.items = parsedFromContent;
6169
+ } else if (typeof parsedFromContent === "object") {
6170
+ Object.assign(aggregatedOutput, parsedFromContent);
6171
+ }
6172
+ }
6173
+ }
6174
+ } catch {
6175
+ }
5556
6176
  const context = {
5557
6177
  output: aggregatedOutput,
5558
6178
  outputs: (() => {
@@ -5579,6 +6199,23 @@ var init_failure_condition_evaluator = __esm({
5579
6199
  }
5580
6200
  return context;
5581
6201
  }
6202
+ // Minimal JSON-from-end extractor for fail_if context fallback
6203
+ tryExtractJsonFromEnd(text) {
6204
+ try {
6205
+ const lines = text.split("\n");
6206
+ for (let i = lines.length - 1; i >= 0; i--) {
6207
+ const t = lines[i].trim();
6208
+ if (t.startsWith("{") || t.startsWith("[")) {
6209
+ const candidate = lines.slice(i).join("\n").trim();
6210
+ if (candidate.startsWith("{") && candidate.endsWith("}") || candidate.startsWith("[") && candidate.endsWith("]")) {
6211
+ return JSON.parse(candidate);
6212
+ }
6213
+ }
6214
+ }
6215
+ } catch {
6216
+ }
6217
+ return null;
6218
+ }
5582
6219
  /**
5583
6220
  * Check if any failure condition requires halting execution
5584
6221
  */
@@ -7059,6 +7696,8 @@ ${expr}
7059
7696
  let normalizedOutput;
7060
7697
  if (Array.isArray(output)) {
7061
7698
  normalizedOutput = output;
7699
+ } else if (output && typeof output === "object" && Array.isArray(output.items)) {
7700
+ normalizedOutput = output.items;
7062
7701
  } else if (typeof output === "string") {
7063
7702
  try {
7064
7703
  const parsed = JSON.parse(output);
@@ -7255,8 +7894,11 @@ ${expr}
7255
7894
  const templatePath = path8.join(__dirname, `../output/${sanitizedSchema}/template.liquid`);
7256
7895
  templateContent = await fs8.readFile(templatePath, "utf-8");
7257
7896
  }
7897
+ const filteredIssues = (reviewSummary.issues || []).filter(
7898
+ (issue) => !(issue.file === "system" && issue.line === 0)
7899
+ );
7258
7900
  const templateData = {
7259
- issues: reviewSummary.issues || [],
7901
+ issues: filteredIssues,
7260
7902
  checkName
7261
7903
  };
7262
7904
  const rendered = await liquid.parseAndRender(templateContent, templateData);
@@ -7356,6 +7998,13 @@ ${expr}
7356
7998
  ]
7357
7999
  };
7358
8000
  }
8001
+ const childrenByParent = /* @__PURE__ */ new Map();
8002
+ for (const [child, depsArr] of Object.entries(dependencies)) {
8003
+ for (const p of depsArr || []) {
8004
+ if (!childrenByParent.has(p)) childrenByParent.set(p, []);
8005
+ childrenByParent.get(p).push(child);
8006
+ }
8007
+ }
7359
8008
  const stats = DependencyResolver.getExecutionStats(dependencyGraph);
7360
8009
  if (debug) {
7361
8010
  log2(
@@ -7391,7 +8040,12 @@ ${expr}
7391
8040
  `\u{1F527} Debug: Executing level ${executionGroup.level} with ${executionGroup.parallel.length} checks (parallelism: ${actualParallelism})`
7392
8041
  );
7393
8042
  }
7394
- const levelTaskFunctions = executionGroup.parallel.map((checkName) => async () => {
8043
+ const levelChecks = executionGroup.parallel.filter((name) => !results.has(name));
8044
+ const levelTaskFunctions = levelChecks.map((checkName) => async () => {
8045
+ if (results.has(checkName)) {
8046
+ if (debug) log2(`\u{1F527} Debug: Skipping ${checkName} (already satisfied earlier)`);
8047
+ return { checkName, error: null, result: results.get(checkName) };
8048
+ }
7395
8049
  const checkConfig = config.checks[checkName];
7396
8050
  if (!checkConfig) {
7397
8051
  return {
@@ -7464,13 +8118,21 @@ ${expr}
7464
8118
  const id = issue.ruleId || "";
7465
8119
  return id.endsWith("/__skipped");
7466
8120
  });
7467
- let hasFatalFailure = (depRes.issues || []).some((issue) => {
7468
- const id = issue.ruleId || "";
7469
- 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");
7470
- });
7471
- if (!hasFatalFailure && config && (config.fail_if || config.checks[depId]?.fail_if)) {
7472
- const failIfResults = await this.evaluateFailureConditions(depId, depRes, config);
7473
- hasFatalFailure = failIfResults.some((r) => r.failed);
8121
+ const depExtended = depRes;
8122
+ const isDepForEachParent = !!depExtended.isForEach;
8123
+ let hasFatalFailure = false;
8124
+ if (!isDepForEachParent) {
8125
+ const issues = depRes.issues || [];
8126
+ hasFatalFailure = issues.some((issue) => {
8127
+ const id = issue.ruleId || "";
8128
+ 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");
8129
+ });
8130
+ if (!hasFatalFailure && config && (config.fail_if || config.checks[depId]?.fail_if)) {
8131
+ try {
8132
+ hasFatalFailure = await this.failIfTriggered(depId, depRes, config);
8133
+ } catch {
8134
+ }
8135
+ }
7474
8136
  }
7475
8137
  if (debug) {
7476
8138
  log2(
@@ -7493,10 +8155,12 @@ ${expr}
7493
8155
  if (results.has(depId)) {
7494
8156
  const depResult = results.get(depId);
7495
8157
  const depForEachResult = depResult;
7496
- if (depForEachResult.isForEach && Array.isArray(depForEachResult.forEachItems)) {
8158
+ if (depForEachResult.isForEach || Array.isArray(depForEachResult.forEachItemResults) || Array.isArray(depForEachResult.forEachItems)) {
7497
8159
  if (!isForEachDependent) {
7498
8160
  isForEachDependent = true;
7499
- forEachItems = depForEachResult.forEachItems;
8161
+ forEachItems = Array.isArray(depForEachResult.forEachItems) ? depForEachResult.forEachItems : new Array(
8162
+ Array.isArray(depForEachResult.forEachItemResults) ? depForEachResult.forEachItemResults.length : 0
8163
+ ).fill(void 0);
7500
8164
  forEachParentName = depId;
7501
8165
  }
7502
8166
  forEachParents.push(depId);
@@ -7537,6 +8201,18 @@ ${expr}
7537
8201
  }
7538
8202
  let finalResult;
7539
8203
  if (isForEachDependent && forEachParentName) {
8204
+ if (!Array.isArray(forEachItems)) {
8205
+ forEachItems = [];
8206
+ }
8207
+ if (!Array.isArray(forEachItems)) {
8208
+ this.recordSkip(checkName, "dependency_failed");
8209
+ return {
8210
+ checkName,
8211
+ error: null,
8212
+ result: { issues: [] },
8213
+ skipped: true
8214
+ };
8215
+ }
7540
8216
  this.recordForEachPreview(checkName, forEachItems);
7541
8217
  if (forEachItems.length === 0) {
7542
8218
  if (debug) {
@@ -7545,6 +8221,7 @@ ${expr}
7545
8221
  );
7546
8222
  }
7547
8223
  logger.info(` forEach: no items from "${forEachParentName}", skipping check...`);
8224
+ this.recordSkip(checkName, "dependency_failed");
7548
8225
  finalResult = {
7549
8226
  issues: [],
7550
8227
  output: []
@@ -7553,22 +8230,187 @@ ${expr}
7553
8230
  finalResult.forEachItems = [];
7554
8231
  } else {
7555
8232
  if (debug) {
7556
- log2(
8233
+ console.log(
7557
8234
  `\u{1F504} Debug: Check "${checkName}" depends on forEach check "${forEachParentName}", executing ${forEachItems.length} times`
7558
8235
  );
7559
8236
  }
8237
+ const __itemCount = Array.isArray(forEachItems) ? forEachItems.length : 0;
7560
8238
  logger.info(
7561
- ` forEach: processing ${forEachItems.length} items from "${forEachParentName}"...`
8239
+ ` forEach: processing ${__itemCount} items from "${forEachParentName}"...`
7562
8240
  );
7563
8241
  const allIssues = [];
7564
- const allOutputs = [];
8242
+ const allOutputs = new Array(forEachItems.length);
7565
8243
  const aggregatedContents = [];
8244
+ const perItemResults = new Array(
8245
+ forEachItems.length
8246
+ );
8247
+ const inlineAgg = /* @__PURE__ */ new Map();
8248
+ const execInlineDescendants = async (parentName, itemIndex, baseDeps) => {
8249
+ const children = (childrenByParent.get(parentName) || []).filter((child) => {
8250
+ const deps = dependencies[child] || [];
8251
+ return deps.length === 1 && deps[0] === parentName;
8252
+ });
8253
+ for (const childName of children) {
8254
+ const childCfg = config.checks[childName];
8255
+ const childProviderType = childCfg.type || "ai";
8256
+ const childProv = this.providerRegistry.getProviderOrThrow(childProviderType);
8257
+ this.setProviderWebhookContext(childProv);
8258
+ const childProviderConfig = {
8259
+ type: childProviderType,
8260
+ prompt: childCfg.prompt,
8261
+ exec: childCfg.exec,
8262
+ focus: childCfg.focus || this.mapCheckNameToFocus(childName),
8263
+ schema: childCfg.schema,
8264
+ group: childCfg.group,
8265
+ checkName: childName,
8266
+ eventContext: prInfo.eventContext,
8267
+ transform: childCfg.transform,
8268
+ transform_js: childCfg.transform_js,
8269
+ env: childCfg.env,
8270
+ forEach: childCfg.forEach,
8271
+ ai: {
8272
+ timeout: timeout || 6e5,
8273
+ debug,
8274
+ ...childCfg.ai || {}
8275
+ }
8276
+ };
8277
+ const childDepResults = /* @__PURE__ */ new Map();
8278
+ const childAllDeps = DependencyResolver.getAllDependencies(
8279
+ childName,
8280
+ dependencyGraph.nodes
8281
+ );
8282
+ for (const dep of childAllDeps) {
8283
+ const baseRes = baseDeps.get(dep);
8284
+ if (baseRes) {
8285
+ childDepResults.set(dep, baseRes);
8286
+ continue;
8287
+ }
8288
+ const globalRes = results.get(dep);
8289
+ if (!globalRes) continue;
8290
+ if (globalRes && (globalRes.isForEach || Array.isArray(globalRes.forEachItemResults) || Array.isArray(globalRes.output))) {
8291
+ if (Array.isArray(globalRes.forEachItemResults) && globalRes.forEachItemResults[itemIndex]) {
8292
+ childDepResults.set(dep, globalRes.forEachItemResults[itemIndex]);
8293
+ } else if (Array.isArray(globalRes.output) && globalRes.output[itemIndex] !== void 0) {
8294
+ childDepResults.set(dep, {
8295
+ issues: [],
8296
+ output: globalRes.output[itemIndex]
8297
+ });
8298
+ } else {
8299
+ childDepResults.set(dep, globalRes);
8300
+ }
8301
+ } else {
8302
+ childDepResults.set(dep, globalRes);
8303
+ }
8304
+ }
8305
+ const parentItemRes = childDepResults.get(parentName);
8306
+ if (parentItemRes) {
8307
+ try {
8308
+ const pout = parentItemRes.output;
8309
+ if (pout && typeof pout === "object" && pout.error === true) {
8310
+ continue;
8311
+ }
8312
+ } catch {
8313
+ }
8314
+ const fatal = (parentItemRes.issues || []).some((issue) => {
8315
+ const id = issue.ruleId || "";
8316
+ const sev = issue.severity || "error";
8317
+ 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");
8318
+ });
8319
+ if (fatal) {
8320
+ continue;
8321
+ }
8322
+ }
8323
+ if (childCfg.if) {
8324
+ const condResults = new Map(results);
8325
+ for (const [k, v] of childDepResults) condResults.set(k, v);
8326
+ const shouldRunChild = await this.evaluateCheckCondition(
8327
+ childName,
8328
+ childCfg.if,
8329
+ prInfo,
8330
+ condResults,
8331
+ debug
8332
+ );
8333
+ if (!shouldRunChild) {
8334
+ continue;
8335
+ }
8336
+ }
8337
+ const childIterStart = this.recordIterationStart(childName);
8338
+ const childItemRes = await this.executeWithRouting(
8339
+ childName,
8340
+ childCfg,
8341
+ childProv,
8342
+ childProviderConfig,
8343
+ prInfo,
8344
+ childDepResults,
8345
+ sessionInfo,
8346
+ config,
8347
+ dependencyGraph,
8348
+ debug,
8349
+ results,
8350
+ { index: itemIndex, total: forEachItems.length, parent: parentName }
8351
+ );
8352
+ if (config && (config.fail_if || childCfg.fail_if)) {
8353
+ const fRes = await this.evaluateFailureConditions(
8354
+ childName,
8355
+ childItemRes,
8356
+ config
8357
+ );
8358
+ if (fRes.length > 0) {
8359
+ const fIssues = fRes.filter((f) => f.failed).map((f) => ({
8360
+ file: "system",
8361
+ line: 0,
8362
+ ruleId: f.conditionName,
8363
+ message: f.message || `Failure condition met: ${f.expression}`,
8364
+ severity: f.severity || "error",
8365
+ category: "logic"
8366
+ }));
8367
+ childItemRes.issues = [...childItemRes.issues || [], ...fIssues];
8368
+ }
8369
+ }
8370
+ if (!inlineAgg.has(childName)) {
8371
+ inlineAgg.set(childName, {
8372
+ issues: [],
8373
+ outputs: new Array(forEachItems.length),
8374
+ contents: [],
8375
+ perItemResults: new Array(forEachItems.length)
8376
+ });
8377
+ }
8378
+ const agg = inlineAgg.get(childName);
8379
+ if (childItemRes.issues) agg.issues.push(...childItemRes.issues);
8380
+ const out = childItemRes.output;
8381
+ agg.outputs[itemIndex] = out;
8382
+ agg.perItemResults[itemIndex] = childItemRes;
8383
+ const c = childItemRes.content;
8384
+ if (typeof c === "string" && c.trim()) agg.contents.push(c.trim());
8385
+ const childHadFatal = this.hasFatal(childItemRes.issues || []);
8386
+ this.recordIterationComplete(
8387
+ childName,
8388
+ childIterStart,
8389
+ !childHadFatal,
8390
+ childItemRes.issues || [],
8391
+ childItemRes.output
8392
+ );
8393
+ const nextBase = new Map(baseDeps);
8394
+ nextBase.set(childName, childItemRes);
8395
+ await execInlineDescendants(childName, itemIndex, nextBase);
8396
+ }
8397
+ };
7566
8398
  const itemTasks = forEachItems.map((item, itemIndex) => async () => {
7567
8399
  const forEachDependencyResults = /* @__PURE__ */ new Map();
7568
8400
  for (const [depName, depResult] of dependencyResults) {
7569
8401
  if (forEachParents.includes(depName)) {
7570
8402
  const depForEachResult = depResult;
7571
- if (Array.isArray(depForEachResult.output) && depForEachResult.output[itemIndex] !== void 0) {
8403
+ if (Array.isArray(depForEachResult.forEachItemResults) && depForEachResult.forEachItemResults[itemIndex]) {
8404
+ forEachDependencyResults.set(
8405
+ depName,
8406
+ depForEachResult.forEachItemResults[itemIndex]
8407
+ );
8408
+ const rawResult = {
8409
+ issues: [],
8410
+ output: depForEachResult.output
8411
+ };
8412
+ forEachDependencyResults.set(`${depName}-raw`, rawResult);
8413
+ } else if (Array.isArray(depForEachResult.output) && depForEachResult.output[itemIndex] !== void 0) {
7572
8414
  const modifiedResult = {
7573
8415
  issues: [],
7574
8416
  output: depForEachResult.output[itemIndex]
@@ -7586,6 +8428,46 @@ ${expr}
7586
8428
  forEachDependencyResults.set(depName, depResult);
7587
8429
  }
7588
8430
  }
8431
+ if ((checkConfig.depends_on || []).length > 0) {
8432
+ const directDeps2 = checkConfig.depends_on || [];
8433
+ for (const depId of directDeps2) {
8434
+ if (!forEachParents.includes(depId)) continue;
8435
+ const depItemRes = forEachDependencyResults.get(depId);
8436
+ if (!depItemRes) continue;
8437
+ const wasSkippedDep = (depItemRes.issues || []).some(
8438
+ (i) => (i.ruleId || "").endsWith("/__skipped")
8439
+ );
8440
+ let hasFatalDepFailure = (depItemRes.issues || []).some((issue) => {
8441
+ const id = issue.ruleId || "";
8442
+ 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");
8443
+ });
8444
+ if (!hasFatalDepFailure && config && (config.fail_if || config.checks[depId]?.fail_if)) {
8445
+ try {
8446
+ const depFailures = await this.evaluateFailureConditions(
8447
+ depId,
8448
+ depItemRes,
8449
+ config
8450
+ );
8451
+ hasFatalDepFailure = depFailures.some((f) => f.failed);
8452
+ } catch {
8453
+ }
8454
+ }
8455
+ const depAgg = dependencyResults.get(depId);
8456
+ const maskFatal = !!depAgg?.forEachFatalMask && depAgg.forEachFatalMask[itemIndex] === true;
8457
+ if (wasSkippedDep || hasFatalDepFailure || maskFatal) {
8458
+ if (debug) {
8459
+ log2(
8460
+ `\u{1F504} Debug: Skipping item ${itemIndex + 1}/${forEachItems.length} for check "${checkName}" due to failed dependency '${depId}'`
8461
+ );
8462
+ }
8463
+ return {
8464
+ index: itemIndex,
8465
+ itemResult: { issues: [] },
8466
+ skipped: true
8467
+ };
8468
+ }
8469
+ }
8470
+ }
7589
8471
  if (checkConfig.if) {
7590
8472
  const conditionResults = new Map(results);
7591
8473
  for (const [depName, depResult] of forEachDependencyResults) {
@@ -7636,6 +8518,24 @@ ${expr}
7636
8518
  parent: forEachParentName
7637
8519
  }
7638
8520
  );
8521
+ if (config && (config.fail_if || checkConfig.fail_if)) {
8522
+ const itemFailures = await this.evaluateFailureConditions(
8523
+ checkName,
8524
+ itemResult,
8525
+ config
8526
+ );
8527
+ if (itemFailures.length > 0) {
8528
+ const failureIssues = itemFailures.filter((f) => f.failed).map((f) => ({
8529
+ file: "system",
8530
+ line: 0,
8531
+ ruleId: f.conditionName,
8532
+ message: f.message || `Failure condition met: ${f.expression}`,
8533
+ severity: f.severity || "error",
8534
+ category: "logic"
8535
+ }));
8536
+ itemResult.issues = [...itemResult.issues || [], ...failureIssues];
8537
+ }
8538
+ }
7639
8539
  const hadFatalError = (itemResult.issues || []).some((issue) => {
7640
8540
  const id = issue.ruleId || "";
7641
8541
  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");
@@ -7649,25 +8549,286 @@ ${expr}
7649
8549
  itemResult.issues || [],
7650
8550
  itemResult.output
7651
8551
  );
8552
+ const descendantSet = (() => {
8553
+ const visited = /* @__PURE__ */ new Set();
8554
+ const stack = [checkName];
8555
+ while (stack.length) {
8556
+ const p = stack.pop();
8557
+ const kids = childrenByParent.get(p) || [];
8558
+ for (const k of kids) {
8559
+ if (!visited.has(k)) {
8560
+ visited.add(k);
8561
+ stack.push(k);
8562
+ }
8563
+ }
8564
+ }
8565
+ return visited;
8566
+ })();
8567
+ const perItemDone = /* @__PURE__ */ new Set([...forEachParents, checkName]);
8568
+ const perItemDepMap = /* @__PURE__ */ new Map();
8569
+ for (const [k, v] of forEachDependencyResults) perItemDepMap.set(k, v);
8570
+ perItemDepMap.set(checkName, itemResult);
8571
+ const isFatal = (r) => {
8572
+ if (!r) return true;
8573
+ return this.hasFatal(r.issues || []);
8574
+ };
8575
+ while (true) {
8576
+ let progressed = false;
8577
+ for (const node of descendantSet) {
8578
+ if (perItemDone.has(node)) continue;
8579
+ const nodeCfg = config.checks[node];
8580
+ if (!nodeCfg) continue;
8581
+ const deps = dependencies[node] || [];
8582
+ let ready = true;
8583
+ const childDepsMap = /* @__PURE__ */ new Map();
8584
+ for (const d of deps) {
8585
+ const perItemRes = perItemDepMap.get(d);
8586
+ if (perItemRes) {
8587
+ if (isFatal(perItemRes)) {
8588
+ ready = false;
8589
+ break;
8590
+ }
8591
+ childDepsMap.set(d, perItemRes);
8592
+ continue;
8593
+ }
8594
+ const agg2 = results.get(d);
8595
+ if (agg2 && (agg2.isForEach || Array.isArray(agg2.forEachItemResults))) {
8596
+ const r = agg2.forEachItemResults && agg2.forEachItemResults[itemIndex] || void 0;
8597
+ const maskFatal = !!agg2.forEachFatalMask && agg2.forEachFatalMask[itemIndex] === true;
8598
+ if (!r || maskFatal || isFatal(r)) {
8599
+ ready = false;
8600
+ break;
8601
+ }
8602
+ childDepsMap.set(d, r);
8603
+ continue;
8604
+ }
8605
+ if (!agg2 || isFatal(agg2)) {
8606
+ ready = false;
8607
+ break;
8608
+ }
8609
+ childDepsMap.set(d, agg2);
8610
+ }
8611
+ if (!ready) continue;
8612
+ if (nodeCfg.if) {
8613
+ const condResults = new Map(results);
8614
+ for (const [k, v] of childDepsMap) condResults.set(k, v);
8615
+ const shouldRun = await this.evaluateCheckCondition(
8616
+ node,
8617
+ nodeCfg.if,
8618
+ prInfo,
8619
+ condResults,
8620
+ debug
8621
+ );
8622
+ if (!shouldRun) {
8623
+ perItemDone.add(node);
8624
+ progressed = true;
8625
+ continue;
8626
+ }
8627
+ }
8628
+ const nodeProvType = nodeCfg.type || "ai";
8629
+ const nodeProv = this.providerRegistry.getProviderOrThrow(nodeProvType);
8630
+ this.setProviderWebhookContext(nodeProv);
8631
+ const nodeProviderConfig = {
8632
+ type: nodeProvType,
8633
+ prompt: nodeCfg.prompt,
8634
+ exec: nodeCfg.exec,
8635
+ focus: nodeCfg.focus || this.mapCheckNameToFocus(node),
8636
+ schema: nodeCfg.schema,
8637
+ group: nodeCfg.group,
8638
+ checkName: node,
8639
+ eventContext: prInfo.eventContext,
8640
+ transform: nodeCfg.transform,
8641
+ transform_js: nodeCfg.transform_js,
8642
+ env: nodeCfg.env,
8643
+ forEach: nodeCfg.forEach,
8644
+ ai: { timeout: timeout || 6e5, debug, ...nodeCfg.ai || {} }
8645
+ };
8646
+ const iterStart = this.recordIterationStart(node);
8647
+ const execDepMap = new Map(childDepsMap);
8648
+ const nodeAllDeps = DependencyResolver.getAllDependencies(
8649
+ node,
8650
+ dependencyGraph.nodes
8651
+ );
8652
+ for (const dep of nodeAllDeps) {
8653
+ if (execDepMap.has(dep)) continue;
8654
+ const perItemRes = perItemDepMap.get(dep);
8655
+ if (perItemRes) {
8656
+ execDepMap.set(dep, perItemRes);
8657
+ continue;
8658
+ }
8659
+ const agg2 = results.get(dep);
8660
+ if (!agg2) continue;
8661
+ if (agg2 && (agg2.isForEach || Array.isArray(agg2.forEachItemResults) || Array.isArray(agg2.output))) {
8662
+ if (Array.isArray(agg2.forEachItemResults) && agg2.forEachItemResults[itemIndex]) {
8663
+ execDepMap.set(dep, agg2.forEachItemResults[itemIndex]);
8664
+ } else if (Array.isArray(agg2.output) && agg2.output[itemIndex] !== void 0) {
8665
+ execDepMap.set(dep, {
8666
+ issues: [],
8667
+ output: agg2.output[itemIndex]
8668
+ });
8669
+ } else {
8670
+ execDepMap.set(dep, agg2);
8671
+ }
8672
+ } else {
8673
+ execDepMap.set(dep, agg2);
8674
+ }
8675
+ }
8676
+ const nodeItemRes = await this.executeWithRouting(
8677
+ node,
8678
+ nodeCfg,
8679
+ nodeProv,
8680
+ nodeProviderConfig,
8681
+ prInfo,
8682
+ execDepMap,
8683
+ sessionInfo,
8684
+ config,
8685
+ dependencyGraph,
8686
+ debug,
8687
+ results,
8688
+ { index: itemIndex, total: forEachItems.length, parent: forEachParentName }
8689
+ );
8690
+ if (config && (config.fail_if || nodeCfg.fail_if)) {
8691
+ const fRes = await this.evaluateFailureConditions(node, nodeItemRes, config);
8692
+ if (fRes.length > 0) {
8693
+ const fIssues = fRes.filter((f) => f.failed).map((f) => ({
8694
+ file: "system",
8695
+ line: 0,
8696
+ ruleId: f.conditionName,
8697
+ message: f.message || `Failure condition met: ${f.expression}`,
8698
+ severity: f.severity || "error",
8699
+ category: "logic"
8700
+ }));
8701
+ nodeItemRes.issues = [...nodeItemRes.issues || [], ...fIssues];
8702
+ }
8703
+ }
8704
+ const hadFatal = isFatal(nodeItemRes);
8705
+ this.recordIterationComplete(
8706
+ node,
8707
+ iterStart,
8708
+ !hadFatal,
8709
+ nodeItemRes.issues || [],
8710
+ nodeItemRes.output
8711
+ );
8712
+ if (!inlineAgg.has(node))
8713
+ inlineAgg.set(node, {
8714
+ issues: [],
8715
+ outputs: [],
8716
+ contents: [],
8717
+ perItemResults: []
8718
+ });
8719
+ const agg = inlineAgg.get(node);
8720
+ if (nodeItemRes.issues) agg.issues.push(...nodeItemRes.issues);
8721
+ const nout = nodeItemRes.output;
8722
+ if (nout !== void 0) agg.outputs.push(nout);
8723
+ agg.perItemResults.push(nodeItemRes);
8724
+ const ncontent = nodeItemRes.content;
8725
+ if (typeof ncontent === "string" && ncontent.trim())
8726
+ agg.contents.push(ncontent.trim());
8727
+ perItemDepMap.set(node, nodeItemRes);
8728
+ perItemDone.add(node);
8729
+ progressed = true;
8730
+ }
8731
+ if (!progressed) break;
8732
+ }
7652
8733
  logger.info(
7653
8734
  ` \u2714 ${itemIndex + 1}/${forEachItems.length} (${iterationDuration.toFixed(1)}s)`
7654
8735
  );
8736
+ perItemResults[itemIndex] = itemResult;
7655
8737
  return { index: itemIndex, itemResult };
7656
8738
  });
8739
+ const directForEachParents = (checkConfig.depends_on || []).filter((dep) => {
8740
+ const r = results.get(dep);
8741
+ return !!r && (r.isForEach || Array.isArray(r.forEachItemResults) || Array.isArray(r.forEachItems));
8742
+ });
8743
+ if (directForEachParents.length > 0) {
8744
+ logger.debug(
8745
+ ` forEach: direct parents for "${checkName}": ${directForEachParents.join(", ")}`
8746
+ );
8747
+ }
8748
+ const isIndexFatalForParent = async (parent, idx) => {
8749
+ const agg = results.get(parent);
8750
+ if (!agg) return false;
8751
+ if (agg.forEachFatalMask && agg.forEachFatalMask[idx] === true) return true;
8752
+ const r = agg.forEachItemResults && agg.forEachItemResults[idx] || void 0;
8753
+ if (!r) return false;
8754
+ const hadFatalByIssues = this.hasFatal(r.issues || []);
8755
+ if (hadFatalByIssues) return true;
8756
+ try {
8757
+ if (config && (config.fail_if || config.checks[parent]?.fail_if)) {
8758
+ let rForEval = r;
8759
+ const rawOut = r?.output;
8760
+ if (typeof rawOut === "string") {
8761
+ const parseTail = (text) => {
8762
+ try {
8763
+ const lines = text.split("\n");
8764
+ for (let i = lines.length - 1; i >= 0; i--) {
8765
+ const t = lines[i].trim();
8766
+ if (t.startsWith("{") || t.startsWith("[")) {
8767
+ const candidate = lines.slice(i).join("\n").trim();
8768
+ if (candidate.startsWith("{") && candidate.endsWith("}") || candidate.startsWith("[") && candidate.endsWith("]")) {
8769
+ return JSON.parse(candidate);
8770
+ }
8771
+ }
8772
+ }
8773
+ } catch {
8774
+ }
8775
+ try {
8776
+ return JSON.parse(text);
8777
+ } catch {
8778
+ return null;
8779
+ }
8780
+ };
8781
+ const parsed = parseTail(rawOut);
8782
+ if (parsed && typeof parsed === "object") {
8783
+ rForEval = { ...r, output: parsed };
8784
+ }
8785
+ }
8786
+ const failures = await this.evaluateFailureConditions(parent, rForEval, config);
8787
+ if (failures.some((f) => f.failed)) {
8788
+ }
8789
+ if (failures.some((f) => f.failed)) return true;
8790
+ }
8791
+ } catch {
8792
+ }
8793
+ return false;
8794
+ };
8795
+ const runnableIndices = [];
8796
+ for (let idx = 0; idx < forEachItems.length; idx++) {
8797
+ let ok = true;
8798
+ for (const p of directForEachParents) {
8799
+ if (await isIndexFatalForParent(p, idx)) {
8800
+ ok = false;
8801
+ break;
8802
+ }
8803
+ }
8804
+ if (ok && typeof itemTasks[idx] === "function") runnableIndices.push(idx);
8805
+ }
8806
+ if (runnableIndices.length === 0) {
8807
+ this.recordSkip(checkName, "dependency_failed");
8808
+ logger.info(`\u23ED Skipped (dependency failed: no runnable items)`);
8809
+ return {
8810
+ checkName,
8811
+ error: null,
8812
+ result: { issues: [] },
8813
+ skipped: true
8814
+ };
8815
+ }
7657
8816
  const forEachConcurrency = Math.max(
7658
8817
  1,
7659
- Math.min(forEachItems.length, effectiveMaxParallelism)
8818
+ Math.min(runnableIndices.length, effectiveMaxParallelism)
7660
8819
  );
7661
8820
  if (debug && forEachConcurrency > 1) {
7662
8821
  log2(
7663
8822
  `\u{1F504} Debug: Limiting forEach concurrency for check "${checkName}" to ${forEachConcurrency}`
7664
8823
  );
7665
8824
  }
8825
+ const scheduledTasks = runnableIndices.map((i) => itemTasks[i]).filter((fn) => typeof fn === "function");
7666
8826
  const forEachResults = await this.executeWithLimitedParallelism(
7667
- itemTasks,
8827
+ scheduledTasks,
7668
8828
  forEachConcurrency,
7669
8829
  false
7670
8830
  );
8831
+ let processedCount = 0;
7671
8832
  for (const result of forEachResults) {
7672
8833
  if (result.status === "rejected") {
7673
8834
  const error = result.reason;
@@ -7690,52 +8851,117 @@ ${expr}
7690
8851
  if (result.value.skipped) {
7691
8852
  continue;
7692
8853
  }
7693
- const { itemResult } = result.value;
8854
+ const { index: finishedIndex, itemResult } = result.value;
8855
+ processedCount++;
7694
8856
  if (itemResult.issues) {
7695
8857
  allIssues.push(...itemResult.issues);
7696
8858
  }
7697
8859
  const resultWithOutput = itemResult;
7698
- if (resultWithOutput.output !== void 0) {
7699
- allOutputs.push(resultWithOutput.output);
7700
- }
8860
+ allOutputs[finishedIndex] = resultWithOutput.output;
7701
8861
  const itemContent = resultWithOutput.content;
7702
8862
  if (typeof itemContent === "string" && itemContent.trim()) {
7703
8863
  aggregatedContents.push(itemContent.trim());
8864
+ } else {
8865
+ const outStr = typeof resultWithOutput.output === "string" ? resultWithOutput.output.trim() : "";
8866
+ if (outStr) aggregatedContents.push(outStr);
7704
8867
  }
7705
8868
  }
8869
+ if (processedCount === 0) {
8870
+ this.recordSkip(checkName, "dependency_failed");
8871
+ logger.info(`\u23ED Skipped (dependency failed for all items)`);
8872
+ return {
8873
+ checkName,
8874
+ error: null,
8875
+ result: { issues: [] },
8876
+ skipped: true
8877
+ };
8878
+ }
7706
8879
  const finalOutput = allOutputs.length > 0 ? allOutputs : void 0;
7707
8880
  finalResult = {
7708
8881
  issues: allIssues,
7709
8882
  ...finalOutput !== void 0 ? { output: finalOutput } : {}
7710
8883
  };
7711
- if (config && (config.fail_if || checkConfig.fail_if)) {
7712
- const failureResults = await this.evaluateFailureConditions(
7713
- checkName,
7714
- finalResult,
7715
- config
8884
+ finalResult.isForEach = true;
8885
+ finalResult.forEachItems = allOutputs;
8886
+ finalResult.forEachItemResults = perItemResults;
8887
+ try {
8888
+ const mask = finalResult.forEachItemResults ? await Promise.all(
8889
+ Array.from({ length: forEachItems.length }, async (_, idx) => {
8890
+ const r = finalResult.forEachItemResults[idx];
8891
+ if (!r) return false;
8892
+ let hadFatal = this.hasFatal(r.issues || []);
8893
+ try {
8894
+ const ids = (r.issues || []).map((i) => i.ruleId).join(",");
8895
+ logger.debug(
8896
+ ` forEach: item ${idx + 1}/${forEachItems.length} issues=${(r.issues || []).length} ids=[${ids}]`
8897
+ );
8898
+ } catch {
8899
+ }
8900
+ if (!hadFatal && config && (config.fail_if || checkConfig.fail_if)) {
8901
+ try {
8902
+ const failures = await this.evaluateFailureConditions(
8903
+ checkName,
8904
+ r,
8905
+ config
8906
+ );
8907
+ hadFatal = failures.some((f) => f.failed);
8908
+ } catch {
8909
+ }
8910
+ }
8911
+ return hadFatal;
8912
+ })
8913
+ ) : [];
8914
+ finalResult.forEachFatalMask = mask;
8915
+ logger.debug(
8916
+ ` forEach: mask for "${checkName}" \u2192 fatals=${mask.filter(Boolean).length}/${mask.length}`
7716
8917
  );
7717
- if (failureResults.length > 0) {
7718
- const failureIssues = failureResults.filter((f) => f.failed).map((f) => ({
7719
- file: "system",
7720
- line: 0,
7721
- ruleId: f.conditionName,
7722
- message: f.message || `Failure condition met: ${f.expression}`,
7723
- severity: f.severity || "error",
7724
- category: "logic"
7725
- }));
7726
- finalResult.issues = [...finalResult.issues || [], ...failureIssues];
7727
- }
7728
- }
7729
- if (allOutputs.length > 0) {
7730
- finalResult.isForEach = true;
7731
- finalResult.forEachItems = allOutputs;
8918
+ } catch {
7732
8919
  }
7733
8920
  if (aggregatedContents.length > 0) {
7734
8921
  finalResult.content = aggregatedContents.join("\n");
7735
8922
  }
7736
- log2(
7737
- `\u{1F504} Debug: Completed forEach execution for check "${checkName}", total issues: ${allIssues.length}`
7738
- );
8923
+ for (const [childName, agg] of inlineAgg.entries()) {
8924
+ const childCfg = config.checks[childName];
8925
+ const childEnrichedIssues = (agg.issues || []).map((issue) => ({
8926
+ ...issue,
8927
+ checkName: childName,
8928
+ ruleId: `${childName}/${issue.ruleId}`,
8929
+ group: childCfg.group,
8930
+ schema: typeof childCfg.schema === "object" ? "custom" : childCfg.schema,
8931
+ template: childCfg.template,
8932
+ timestamp: Date.now()
8933
+ }));
8934
+ const childFinal = {
8935
+ issues: childEnrichedIssues,
8936
+ ...agg.outputs.length > 0 ? { output: agg.outputs } : {},
8937
+ isForEach: true,
8938
+ forEachItems: agg.outputs,
8939
+ forEachItemResults: agg.perItemResults,
8940
+ ...agg.contents.length > 0 ? { content: agg.contents.join("\n") } : {}
8941
+ };
8942
+ try {
8943
+ const mask = Array.from(
8944
+ { length: agg.perItemResults.length },
8945
+ (_, idx) => {
8946
+ const r = agg.perItemResults[idx];
8947
+ if (!r) return false;
8948
+ const hadFatal = (r.issues || []).some((issue) => {
8949
+ const id = issue.ruleId || "";
8950
+ 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");
8951
+ });
8952
+ return hadFatal;
8953
+ }
8954
+ );
8955
+ childFinal.forEachFatalMask = mask;
8956
+ } catch {
8957
+ }
8958
+ results.set(childName, childFinal);
8959
+ }
8960
+ if (debug && process.env.VISOR_OUTPUT_FORMAT !== "json" && process.env.VISOR_OUTPUT_FORMAT !== "sarif") {
8961
+ console.log(
8962
+ `\u{1F504} Debug: Completed forEach execution for check "${checkName}", total issues: ${allIssues.length}`
8963
+ );
8964
+ }
7739
8965
  }
7740
8966
  } else {
7741
8967
  if (checkConfig.if) {
@@ -7865,7 +9091,8 @@ ${expr}
7865
9091
  result: enrichedResult
7866
9092
  };
7867
9093
  } catch (error) {
7868
- const errorMessage = error instanceof Error ? error.message : String(error);
9094
+ const errorMessage = error instanceof Error ? `${error.message}
9095
+ ${error.stack || ""}` : String(error);
7869
9096
  const checkDuration = ((Date.now() - checkStartTime) / 1e3).toFixed(1);
7870
9097
  this.recordError(checkName, error instanceof Error ? error : new Error(String(error)));
7871
9098
  this.recordIterationComplete(checkName, checkStartTime, false, [], void 0);
@@ -7885,8 +9112,9 @@ ${expr}
7885
9112
  actualParallelism,
7886
9113
  effectiveFailFast
7887
9114
  );
9115
+ const levelChecksList = executionGroup.parallel.filter((name) => !results.has(name));
7888
9116
  for (let i = 0; i < levelResults.length; i++) {
7889
- const checkName = executionGroup.parallel[i];
9117
+ const checkName = levelChecksList[i];
7890
9118
  const result = levelResults[i];
7891
9119
  const checkConfig = config.checks[checkName];
7892
9120
  if (result.status === "fulfilled" && result.value.result && !result.value.error) {
@@ -9020,9 +10248,22 @@ ${result.value.result.debug.rawResponse}`;
9020
10248
  */
9021
10249
  recordForEachPreview(checkName, items) {
9022
10250
  const stats = this.executionStats.get(checkName);
9023
- if (!stats || !items.length) return;
10251
+ if (!stats) return;
10252
+ if (!Array.isArray(items) || items.length === 0) return;
9024
10253
  const preview = items.slice(0, 3).map((item) => {
9025
- const str = typeof item === "string" ? item : JSON.stringify(item);
10254
+ let str;
10255
+ if (typeof item === "string") {
10256
+ str = item;
10257
+ } else if (item === void 0 || item === null) {
10258
+ str = "(empty)";
10259
+ } else {
10260
+ try {
10261
+ const j = JSON.stringify(item);
10262
+ str = typeof j === "string" ? j : String(item);
10263
+ } catch {
10264
+ str = String(item);
10265
+ }
10266
+ }
9026
10267
  return str.length > 50 ? str.substring(0, 47) + "..." : str;
9027
10268
  });
9028
10269
  if (items.length > 3) {
@@ -9058,6 +10299,20 @@ ${result.value.result.debug.rawResponse}`;
9058
10299
  checks
9059
10300
  };
9060
10301
  }
10302
+ // Generic fatality helpers to avoid duplication
10303
+ isFatalRule(id, severity) {
10304
+ const sev = (severity || "").toLowerCase();
10305
+ 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");
10306
+ }
10307
+ hasFatal(issues) {
10308
+ if (!issues || issues.length === 0) return false;
10309
+ return issues.some((i) => this.isFatalRule(i.ruleId || "", i.severity));
10310
+ }
10311
+ async failIfTriggered(checkName, result, config) {
10312
+ if (!config) return false;
10313
+ const failures = await this.evaluateFailureConditions(checkName, result, config);
10314
+ return failures.some((f) => f.failed);
10315
+ }
9061
10316
  /**
9062
10317
  * Truncate a string to max length with ellipsis
9063
10318
  */