@probelabs/visor 0.1.89 → 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/check-execution-engine.d.ts +3 -0
- package/dist/check-execution-engine.d.ts.map +1 -1
- package/dist/failure-condition-evaluator.d.ts +1 -0
- package/dist/failure-condition-evaluator.d.ts.map +1 -1
- package/dist/index.js +1624 -168
- package/dist/logger.d.ts.map +1 -1
- package/dist/providers/command-check-provider.d.ts +2 -0
- package/dist/providers/command-check-provider.d.ts.map +1 -1
- package/dist/sdk/{check-execution-engine-D6FPIIKR.mjs → check-execution-engine-Z2USLMN5.mjs} +2 -2
- package/dist/sdk/{chunk-N34GS4A5.mjs → chunk-N2PPFOSF.mjs} +1511 -246
- package/dist/sdk/chunk-N2PPFOSF.mjs.map +1 -0
- package/dist/sdk/sdk.js +1398 -143
- package/dist/sdk/sdk.js.map +1 -1
- package/dist/sdk/sdk.mjs +3 -1
- package/dist/sdk/sdk.mjs.map +1 -1
- package/package.json +2 -2
- package/dist/sdk/chunk-N34GS4A5.mjs.map +0 -1
- /package/dist/sdk/{check-execution-engine-D6FPIIKR.mjs.map → check-execution-engine-Z2USLMN5.mjs.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
process.env.VISOR_VERSION = '0.1.
|
|
2
|
+
process.env.VISOR_VERSION = '0.1.90';
|
|
3
3
|
process.env.PROBE_VERSION = '0.6.0-rc125';
|
|
4
4
|
/******/ (() => { // webpackBootstrap
|
|
5
5
|
/******/ var __webpack_modules__ = ({
|
|
@@ -101913,6 +101913,9 @@ class CheckExecutionEngine {
|
|
|
101913
101913
|
if (Array.isArray(output)) {
|
|
101914
101914
|
normalizedOutput = output;
|
|
101915
101915
|
}
|
|
101916
|
+
else if (output && typeof output === 'object' && Array.isArray(output.items)) {
|
|
101917
|
+
normalizedOutput = output.items;
|
|
101918
|
+
}
|
|
101916
101919
|
else if (typeof output === 'string') {
|
|
101917
101920
|
try {
|
|
101918
101921
|
const parsed = JSON.parse(output);
|
|
@@ -102139,8 +102142,10 @@ class CheckExecutionEngine {
|
|
|
102139
102142
|
templateContent = await fs.readFile(templatePath, 'utf-8');
|
|
102140
102143
|
}
|
|
102141
102144
|
// Prepare template data
|
|
102145
|
+
// Filter out system-level issues (fail_if conditions, internal errors) which should not appear in output
|
|
102146
|
+
const filteredIssues = (reviewSummary.issues || []).filter(issue => !(issue.file === 'system' && issue.line === 0));
|
|
102142
102147
|
const templateData = {
|
|
102143
|
-
issues:
|
|
102148
|
+
issues: filteredIssues,
|
|
102144
102149
|
checkName: checkName,
|
|
102145
102150
|
};
|
|
102146
102151
|
const rendered = await liquid.parseAndRender(templateContent, templateData);
|
|
@@ -102254,6 +102259,15 @@ class CheckExecutionEngine {
|
|
|
102254
102259
|
],
|
|
102255
102260
|
};
|
|
102256
102261
|
}
|
|
102262
|
+
// Build children-by-parent mapping for inline branch-first execution
|
|
102263
|
+
const childrenByParent = new Map();
|
|
102264
|
+
for (const [child, depsArr] of Object.entries(dependencies)) {
|
|
102265
|
+
for (const p of depsArr || []) {
|
|
102266
|
+
if (!childrenByParent.has(p))
|
|
102267
|
+
childrenByParent.set(p, []);
|
|
102268
|
+
childrenByParent.get(p).push(child);
|
|
102269
|
+
}
|
|
102270
|
+
}
|
|
102257
102271
|
// Log execution plan
|
|
102258
102272
|
const stats = dependency_resolver_1.DependencyResolver.getExecutionStats(dependencyGraph);
|
|
102259
102273
|
if (debug) {
|
|
@@ -102287,8 +102301,15 @@ class CheckExecutionEngine {
|
|
|
102287
102301
|
if (debug) {
|
|
102288
102302
|
log(`🔧 Debug: Executing level ${executionGroup.level} with ${executionGroup.parallel.length} checks (parallelism: ${actualParallelism})`);
|
|
102289
102303
|
}
|
|
102290
|
-
// Create task functions for checks in this level
|
|
102291
|
-
const
|
|
102304
|
+
// Create task functions for checks in this level, skip those already completed inline
|
|
102305
|
+
const levelChecks = executionGroup.parallel.filter(name => !results.has(name));
|
|
102306
|
+
const levelTaskFunctions = levelChecks.map(checkName => async () => {
|
|
102307
|
+
// Skip if this check was already completed by item-level branch scheduler
|
|
102308
|
+
if (results.has(checkName)) {
|
|
102309
|
+
if (debug)
|
|
102310
|
+
log(`🔧 Debug: Skipping ${checkName} (already satisfied earlier)`);
|
|
102311
|
+
return { checkName, error: null, result: results.get(checkName) };
|
|
102312
|
+
}
|
|
102292
102313
|
const checkConfig = config.checks[checkName];
|
|
102293
102314
|
if (!checkConfig) {
|
|
102294
102315
|
return {
|
|
@@ -102297,6 +102318,7 @@ class CheckExecutionEngine {
|
|
|
102297
102318
|
result: null,
|
|
102298
102319
|
};
|
|
102299
102320
|
}
|
|
102321
|
+
// (no early gating; rely on per-item scheduler after parents run)
|
|
102300
102322
|
const checkStartTime = Date.now();
|
|
102301
102323
|
completedChecksCount++;
|
|
102302
102324
|
logger_1.logger.step(`Running check: ${checkName} [${completedChecksCount}/${totalChecksCount}]`);
|
|
@@ -102365,30 +102387,41 @@ class CheckExecutionEngine {
|
|
|
102365
102387
|
const id = issue.ruleId || '';
|
|
102366
102388
|
return id.endsWith('/__skipped');
|
|
102367
102389
|
});
|
|
102368
|
-
//
|
|
102390
|
+
// If dependency is a forEach parent, do NOT apply global fatal gating here.
|
|
102391
|
+
// We'll gate per-item inside the forEach loop to avoid stopping other branches.
|
|
102392
|
+
const depExtended = depRes;
|
|
102393
|
+
const isDepForEachParent = !!depExtended.isForEach;
|
|
102394
|
+
// Treat these as fatal in direct dependencies (non-forEach only):
|
|
102369
102395
|
// - command provider execution/transform failures
|
|
102370
102396
|
// - forEach validation/iteration errors
|
|
102371
102397
|
// - fail_if conditions (global or check-specific)
|
|
102372
|
-
|
|
102373
|
-
|
|
102374
|
-
|
|
102375
|
-
|
|
102376
|
-
|
|
102377
|
-
id.
|
|
102378
|
-
id === 'command/
|
|
102379
|
-
|
|
102380
|
-
|
|
102381
|
-
|
|
102382
|
-
|
|
102383
|
-
|
|
102384
|
-
|
|
102385
|
-
|
|
102386
|
-
|
|
102387
|
-
|
|
102388
|
-
|
|
102389
|
-
|
|
102390
|
-
|
|
102391
|
-
|
|
102398
|
+
// For non-forEach parents, only provider-fatal or fail_if/global_fail_if should gate.
|
|
102399
|
+
let hasFatalFailure = false;
|
|
102400
|
+
if (!isDepForEachParent) {
|
|
102401
|
+
const issues = depRes.issues || [];
|
|
102402
|
+
hasFatalFailure = issues.some(issue => {
|
|
102403
|
+
const id = issue.ruleId || '';
|
|
102404
|
+
return (id === 'command/execution_error' ||
|
|
102405
|
+
id.endsWith('/command/execution_error') ||
|
|
102406
|
+
id === 'command/timeout' ||
|
|
102407
|
+
id.endsWith('/command/timeout') ||
|
|
102408
|
+
id === 'command/transform_js_error' ||
|
|
102409
|
+
id.endsWith('/command/transform_js_error') ||
|
|
102410
|
+
id === 'command/transform_error' ||
|
|
102411
|
+
id.endsWith('/command/transform_error') ||
|
|
102412
|
+
id === 'forEach/undefined_output' ||
|
|
102413
|
+
id.endsWith('/forEach/undefined_output') ||
|
|
102414
|
+
id.endsWith('/forEach/iteration_error') ||
|
|
102415
|
+
id.endsWith('_fail_if') ||
|
|
102416
|
+
id.endsWith('/global_fail_if'));
|
|
102417
|
+
});
|
|
102418
|
+
// As a fallback, evaluate fail_if on the dependency result now
|
|
102419
|
+
if (!hasFatalFailure && config && (config.fail_if || config.checks[depId]?.fail_if)) {
|
|
102420
|
+
try {
|
|
102421
|
+
hasFatalFailure = await this.failIfTriggered(depId, depRes, config);
|
|
102422
|
+
}
|
|
102423
|
+
catch { }
|
|
102424
|
+
}
|
|
102392
102425
|
}
|
|
102393
102426
|
if (debug) {
|
|
102394
102427
|
log(`🔧 Debug: gating check '${checkName}' against dep '${depId}': wasSkipped=${wasSkipped} hasFatalFailure=${hasFatalFailure}`);
|
|
@@ -102413,11 +102446,17 @@ class CheckExecutionEngine {
|
|
|
102413
102446
|
const depResult = results.get(depId);
|
|
102414
102447
|
// Check if this dependency has forEach enabled
|
|
102415
102448
|
const depForEachResult = depResult;
|
|
102416
|
-
if (depForEachResult.isForEach
|
|
102449
|
+
if (depForEachResult.isForEach ||
|
|
102450
|
+
Array.isArray(depForEachResult.forEachItemResults) ||
|
|
102451
|
+
Array.isArray(depForEachResult.forEachItems)) {
|
|
102417
102452
|
if (!isForEachDependent) {
|
|
102418
102453
|
// First forEach dependency found - use it as the primary
|
|
102419
102454
|
isForEachDependent = true;
|
|
102420
|
-
forEachItems = depForEachResult.forEachItems
|
|
102455
|
+
forEachItems = Array.isArray(depForEachResult.forEachItems)
|
|
102456
|
+
? depForEachResult.forEachItems
|
|
102457
|
+
: new Array(Array.isArray(depForEachResult.forEachItemResults)
|
|
102458
|
+
? depForEachResult.forEachItemResults.length
|
|
102459
|
+
: 0).fill(undefined);
|
|
102421
102460
|
forEachParentName = depId;
|
|
102422
102461
|
}
|
|
102423
102462
|
// Track all forEach parents for unwrapping
|
|
@@ -102460,6 +102499,18 @@ class CheckExecutionEngine {
|
|
|
102460
102499
|
// Handle forEach dependent execution
|
|
102461
102500
|
let finalResult;
|
|
102462
102501
|
if (isForEachDependent && forEachParentName) {
|
|
102502
|
+
if (!Array.isArray(forEachItems)) {
|
|
102503
|
+
forEachItems = [];
|
|
102504
|
+
}
|
|
102505
|
+
if (!Array.isArray(forEachItems)) {
|
|
102506
|
+
this.recordSkip(checkName, 'dependency_failed');
|
|
102507
|
+
return {
|
|
102508
|
+
checkName,
|
|
102509
|
+
error: null,
|
|
102510
|
+
result: { issues: [] },
|
|
102511
|
+
skipped: true,
|
|
102512
|
+
};
|
|
102513
|
+
}
|
|
102463
102514
|
// Record forEach preview items
|
|
102464
102515
|
this.recordForEachPreview(checkName, forEachItems);
|
|
102465
102516
|
// If the forEach parent returned an empty array, skip this check entirely
|
|
@@ -102468,6 +102519,7 @@ class CheckExecutionEngine {
|
|
|
102468
102519
|
log(`🔄 Debug: Skipping check "${checkName}" - forEach check "${forEachParentName}" returned 0 items`);
|
|
102469
102520
|
}
|
|
102470
102521
|
logger_1.logger.info(` forEach: no items from "${forEachParentName}", skipping check...`);
|
|
102522
|
+
this.recordSkip(checkName, 'dependency_failed');
|
|
102471
102523
|
// Return a special marker result so that dependent checks can detect the skip
|
|
102472
102524
|
finalResult = {
|
|
102473
102525
|
issues: [],
|
|
@@ -102479,14 +102531,176 @@ class CheckExecutionEngine {
|
|
|
102479
102531
|
// Skip to the end - don't execute this check
|
|
102480
102532
|
}
|
|
102481
102533
|
else {
|
|
102534
|
+
// Emit explicit debug to stdout so CLI e2e can assert it
|
|
102482
102535
|
if (debug) {
|
|
102483
|
-
log(`🔄 Debug: Check "${checkName}" depends on forEach check "${forEachParentName}", executing ${forEachItems.length} times`);
|
|
102536
|
+
console.log(`🔄 Debug: Check "${checkName}" depends on forEach check "${forEachParentName}", executing ${forEachItems.length} times`);
|
|
102484
102537
|
}
|
|
102485
102538
|
// Log forEach processing start (non-debug)
|
|
102486
|
-
|
|
102539
|
+
const __itemCount = Array.isArray(forEachItems) ? forEachItems.length : 0;
|
|
102540
|
+
logger_1.logger.info(` forEach: processing ${__itemCount} items from "${forEachParentName}"...`);
|
|
102487
102541
|
const allIssues = [];
|
|
102488
|
-
const allOutputs =
|
|
102542
|
+
const allOutputs = new Array(forEachItems.length);
|
|
102489
102543
|
const aggregatedContents = [];
|
|
102544
|
+
const perItemResults = new Array(forEachItems.length);
|
|
102545
|
+
// Aggregators for inline descendant execution (branch-first mode for simple chains)
|
|
102546
|
+
const inlineAgg = new Map();
|
|
102547
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
102548
|
+
const execInlineDescendants = async (parentName, itemIndex, baseDeps) => {
|
|
102549
|
+
const children = (childrenByParent.get(parentName) || []).filter(child => {
|
|
102550
|
+
const deps = dependencies[child] || [];
|
|
102551
|
+
// Only handle simple chains inline: exactly one dependency which is the parent
|
|
102552
|
+
return deps.length === 1 && deps[0] === parentName;
|
|
102553
|
+
});
|
|
102554
|
+
for (const childName of children) {
|
|
102555
|
+
const childCfg = config.checks[childName];
|
|
102556
|
+
const childProviderType = childCfg.type || 'ai';
|
|
102557
|
+
const childProv = this.providerRegistry.getProviderOrThrow(childProviderType);
|
|
102558
|
+
this.setProviderWebhookContext(childProv);
|
|
102559
|
+
const childProviderConfig = {
|
|
102560
|
+
type: childProviderType,
|
|
102561
|
+
prompt: childCfg.prompt,
|
|
102562
|
+
exec: childCfg.exec,
|
|
102563
|
+
focus: childCfg.focus || this.mapCheckNameToFocus(childName),
|
|
102564
|
+
schema: childCfg.schema,
|
|
102565
|
+
group: childCfg.group,
|
|
102566
|
+
checkName: childName,
|
|
102567
|
+
eventContext: prInfo.eventContext,
|
|
102568
|
+
transform: childCfg.transform,
|
|
102569
|
+
transform_js: childCfg.transform_js,
|
|
102570
|
+
env: childCfg.env,
|
|
102571
|
+
forEach: childCfg.forEach,
|
|
102572
|
+
ai: {
|
|
102573
|
+
timeout: timeout || 600000,
|
|
102574
|
+
debug: debug,
|
|
102575
|
+
...(childCfg.ai || {}),
|
|
102576
|
+
},
|
|
102577
|
+
};
|
|
102578
|
+
// Build per-item dependency results for child, including transitive ancestors
|
|
102579
|
+
const childDepResults = new Map();
|
|
102580
|
+
const childAllDeps = dependency_resolver_1.DependencyResolver.getAllDependencies(childName, dependencyGraph.nodes);
|
|
102581
|
+
for (const dep of childAllDeps) {
|
|
102582
|
+
const baseRes = baseDeps.get(dep);
|
|
102583
|
+
if (baseRes) {
|
|
102584
|
+
childDepResults.set(dep, baseRes);
|
|
102585
|
+
continue;
|
|
102586
|
+
}
|
|
102587
|
+
const globalRes = results.get(dep);
|
|
102588
|
+
if (!globalRes)
|
|
102589
|
+
continue;
|
|
102590
|
+
if (globalRes &&
|
|
102591
|
+
(globalRes.isForEach ||
|
|
102592
|
+
Array.isArray(globalRes.forEachItemResults) ||
|
|
102593
|
+
Array.isArray(globalRes.output))) {
|
|
102594
|
+
// Prefer precise per-item result when available
|
|
102595
|
+
if (Array.isArray(globalRes.forEachItemResults) &&
|
|
102596
|
+
globalRes.forEachItemResults[itemIndex]) {
|
|
102597
|
+
childDepResults.set(dep, globalRes.forEachItemResults[itemIndex]);
|
|
102598
|
+
}
|
|
102599
|
+
else if (Array.isArray(globalRes.output) &&
|
|
102600
|
+
globalRes.output[itemIndex] !== undefined) {
|
|
102601
|
+
childDepResults.set(dep, {
|
|
102602
|
+
issues: [],
|
|
102603
|
+
output: globalRes.output[itemIndex],
|
|
102604
|
+
});
|
|
102605
|
+
}
|
|
102606
|
+
else {
|
|
102607
|
+
childDepResults.set(dep, globalRes);
|
|
102608
|
+
}
|
|
102609
|
+
}
|
|
102610
|
+
else {
|
|
102611
|
+
childDepResults.set(dep, globalRes);
|
|
102612
|
+
}
|
|
102613
|
+
}
|
|
102614
|
+
// If the parent item had a fatal failure, skip this child for this branch
|
|
102615
|
+
const parentItemRes = childDepResults.get(parentName);
|
|
102616
|
+
if (parentItemRes) {
|
|
102617
|
+
// Skip when parent explicitly signaled error in its output for this item
|
|
102618
|
+
try {
|
|
102619
|
+
const pout = parentItemRes.output;
|
|
102620
|
+
if (pout && typeof pout === 'object' && pout.error === true) {
|
|
102621
|
+
continue;
|
|
102622
|
+
}
|
|
102623
|
+
}
|
|
102624
|
+
catch { }
|
|
102625
|
+
const fatal = (parentItemRes.issues || []).some(issue => {
|
|
102626
|
+
const id = issue.ruleId || '';
|
|
102627
|
+
const sev = issue.severity || 'error';
|
|
102628
|
+
return (sev === 'error' ||
|
|
102629
|
+
sev === 'critical' ||
|
|
102630
|
+
id === 'command/execution_error' ||
|
|
102631
|
+
id.endsWith('/command/execution_error') ||
|
|
102632
|
+
id === 'command/timeout' ||
|
|
102633
|
+
id.endsWith('/command/timeout') ||
|
|
102634
|
+
id === 'command/transform_js_error' ||
|
|
102635
|
+
id.endsWith('/command/transform_js_error') ||
|
|
102636
|
+
id === 'command/transform_error' ||
|
|
102637
|
+
id.endsWith('/command/transform_error') ||
|
|
102638
|
+
id.endsWith('/forEach/iteration_error') ||
|
|
102639
|
+
id === 'forEach/undefined_output' ||
|
|
102640
|
+
id.endsWith('/forEach/undefined_output') ||
|
|
102641
|
+
id.endsWith('_fail_if') ||
|
|
102642
|
+
id.endsWith('/global_fail_if'));
|
|
102643
|
+
});
|
|
102644
|
+
if (fatal) {
|
|
102645
|
+
continue;
|
|
102646
|
+
}
|
|
102647
|
+
}
|
|
102648
|
+
// Evaluate per-item if condition
|
|
102649
|
+
if (childCfg.if) {
|
|
102650
|
+
const condResults = new Map(results);
|
|
102651
|
+
for (const [k, v] of childDepResults)
|
|
102652
|
+
condResults.set(k, v);
|
|
102653
|
+
const shouldRunChild = await this.evaluateCheckCondition(childName, childCfg.if, prInfo, condResults, debug);
|
|
102654
|
+
if (!shouldRunChild) {
|
|
102655
|
+
continue;
|
|
102656
|
+
}
|
|
102657
|
+
}
|
|
102658
|
+
// Execute child for this item (record stats)
|
|
102659
|
+
const childIterStart = this.recordIterationStart(childName);
|
|
102660
|
+
const childItemRes = await this.executeWithRouting(childName, childCfg, childProv, childProviderConfig, prInfo, childDepResults, sessionInfo, config, dependencyGraph, debug, results, { index: itemIndex, total: forEachItems.length, parent: parentName });
|
|
102661
|
+
// Per-item fail_if
|
|
102662
|
+
if (config && (config.fail_if || childCfg.fail_if)) {
|
|
102663
|
+
const fRes = await this.evaluateFailureConditions(childName, childItemRes, config);
|
|
102664
|
+
if (fRes.length > 0) {
|
|
102665
|
+
const fIssues = fRes
|
|
102666
|
+
.filter(f => f.failed)
|
|
102667
|
+
.map(f => ({
|
|
102668
|
+
file: 'system',
|
|
102669
|
+
line: 0,
|
|
102670
|
+
ruleId: f.conditionName,
|
|
102671
|
+
message: f.message || `Failure condition met: ${f.expression}`,
|
|
102672
|
+
severity: (f.severity || 'error'),
|
|
102673
|
+
category: 'logic',
|
|
102674
|
+
}));
|
|
102675
|
+
childItemRes.issues = [...(childItemRes.issues || []), ...fIssues];
|
|
102676
|
+
}
|
|
102677
|
+
}
|
|
102678
|
+
if (!inlineAgg.has(childName)) {
|
|
102679
|
+
inlineAgg.set(childName, {
|
|
102680
|
+
issues: [],
|
|
102681
|
+
outputs: new Array(forEachItems.length),
|
|
102682
|
+
contents: [],
|
|
102683
|
+
perItemResults: new Array(forEachItems.length),
|
|
102684
|
+
});
|
|
102685
|
+
}
|
|
102686
|
+
const agg = inlineAgg.get(childName);
|
|
102687
|
+
if (childItemRes.issues)
|
|
102688
|
+
agg.issues.push(...childItemRes.issues);
|
|
102689
|
+
const out = childItemRes.output;
|
|
102690
|
+
agg.outputs[itemIndex] = out;
|
|
102691
|
+
agg.perItemResults[itemIndex] = childItemRes;
|
|
102692
|
+
const c = childItemRes.content;
|
|
102693
|
+
if (typeof c === 'string' && c.trim())
|
|
102694
|
+
agg.contents.push(c.trim());
|
|
102695
|
+
// Record iteration completion for stats
|
|
102696
|
+
const childHadFatal = this.hasFatal(childItemRes.issues || []);
|
|
102697
|
+
this.recordIterationComplete(childName, childIterStart, !childHadFatal, childItemRes.issues || [], childItemRes.output);
|
|
102698
|
+
// Recurse further for simple chains
|
|
102699
|
+
const nextBase = new Map(baseDeps);
|
|
102700
|
+
nextBase.set(childName, childItemRes);
|
|
102701
|
+
await execInlineDescendants(childName, itemIndex, nextBase);
|
|
102702
|
+
}
|
|
102703
|
+
};
|
|
102490
102704
|
// Create task functions (not executed yet) - these will be executed with controlled concurrency
|
|
102491
102705
|
// via executeWithLimitedParallelism to respect maxParallelism setting
|
|
102492
102706
|
const itemTasks = forEachItems.map((item, itemIndex) => async () => {
|
|
@@ -102497,15 +102711,25 @@ class CheckExecutionEngine {
|
|
|
102497
102711
|
if (forEachParents.includes(depName)) {
|
|
102498
102712
|
// This is a forEach parent - unwrap its output for this iteration
|
|
102499
102713
|
const depForEachResult = depResult;
|
|
102500
|
-
if (Array.isArray(depForEachResult.
|
|
102714
|
+
if (Array.isArray(depForEachResult.forEachItemResults) &&
|
|
102715
|
+
depForEachResult.forEachItemResults[itemIndex]) {
|
|
102716
|
+
// Use precise per-item result (includes issues + output)
|
|
102717
|
+
forEachDependencyResults.set(depName, depForEachResult.forEachItemResults[itemIndex]);
|
|
102718
|
+
// Also provide -raw access to the full array
|
|
102719
|
+
const rawResult = {
|
|
102720
|
+
issues: [],
|
|
102721
|
+
output: depForEachResult.output,
|
|
102722
|
+
};
|
|
102723
|
+
forEachDependencyResults.set(`${depName}-raw`, rawResult);
|
|
102724
|
+
}
|
|
102725
|
+
else if (Array.isArray(depForEachResult.output) &&
|
|
102501
102726
|
depForEachResult.output[itemIndex] !== undefined) {
|
|
102502
|
-
//
|
|
102727
|
+
// Fallback to output-only unwrapping
|
|
102503
102728
|
const modifiedResult = {
|
|
102504
102729
|
issues: [],
|
|
102505
102730
|
output: depForEachResult.output[itemIndex],
|
|
102506
102731
|
};
|
|
102507
102732
|
forEachDependencyResults.set(depName, modifiedResult);
|
|
102508
|
-
// Also provide -raw access to the full array
|
|
102509
102733
|
const rawResult = {
|
|
102510
102734
|
issues: [],
|
|
102511
102735
|
output: depForEachResult.output,
|
|
@@ -102521,6 +102745,55 @@ class CheckExecutionEngine {
|
|
|
102521
102745
|
forEachDependencyResults.set(depName, depResult);
|
|
102522
102746
|
}
|
|
102523
102747
|
}
|
|
102748
|
+
// Per-item dependency gating for forEach parents: if a dependency failed for this item, skip this iteration
|
|
102749
|
+
if ((checkConfig.depends_on || []).length > 0) {
|
|
102750
|
+
const directDeps = checkConfig.depends_on || [];
|
|
102751
|
+
for (const depId of directDeps) {
|
|
102752
|
+
if (!forEachParents.includes(depId))
|
|
102753
|
+
continue;
|
|
102754
|
+
const depItemRes = forEachDependencyResults.get(depId);
|
|
102755
|
+
if (!depItemRes)
|
|
102756
|
+
continue;
|
|
102757
|
+
const wasSkippedDep = (depItemRes.issues || []).some(i => (i.ruleId || '').endsWith('/__skipped'));
|
|
102758
|
+
let hasFatalDepFailure = (depItemRes.issues || []).some(issue => {
|
|
102759
|
+
const id = issue.ruleId || '';
|
|
102760
|
+
return (id === 'command/execution_error' ||
|
|
102761
|
+
id.endsWith('/command/execution_error') ||
|
|
102762
|
+
id === 'command/timeout' ||
|
|
102763
|
+
id.endsWith('/command/timeout') ||
|
|
102764
|
+
id === 'command/transform_js_error' ||
|
|
102765
|
+
id.endsWith('/command/transform_js_error') ||
|
|
102766
|
+
id === 'command/transform_error' ||
|
|
102767
|
+
id.endsWith('/command/transform_error') ||
|
|
102768
|
+
id.endsWith('/forEach/iteration_error') ||
|
|
102769
|
+
id === 'forEach/undefined_output' ||
|
|
102770
|
+
id.endsWith('/forEach/undefined_output') ||
|
|
102771
|
+
id.endsWith('_fail_if') ||
|
|
102772
|
+
id.endsWith('/global_fail_if'));
|
|
102773
|
+
});
|
|
102774
|
+
if (!hasFatalDepFailure &&
|
|
102775
|
+
config &&
|
|
102776
|
+
(config.fail_if || config.checks[depId]?.fail_if)) {
|
|
102777
|
+
try {
|
|
102778
|
+
const depFailures = await this.evaluateFailureConditions(depId, depItemRes, config);
|
|
102779
|
+
hasFatalDepFailure = depFailures.some(f => f.failed);
|
|
102780
|
+
}
|
|
102781
|
+
catch { }
|
|
102782
|
+
}
|
|
102783
|
+
const depAgg = dependencyResults.get(depId);
|
|
102784
|
+
const maskFatal = !!depAgg?.forEachFatalMask && depAgg.forEachFatalMask[itemIndex] === true;
|
|
102785
|
+
if (wasSkippedDep || hasFatalDepFailure || maskFatal) {
|
|
102786
|
+
if (debug) {
|
|
102787
|
+
log(`🔄 Debug: Skipping item ${itemIndex + 1}/${forEachItems.length} for check "${checkName}" due to failed dependency '${depId}'`);
|
|
102788
|
+
}
|
|
102789
|
+
return {
|
|
102790
|
+
index: itemIndex,
|
|
102791
|
+
itemResult: { issues: [] },
|
|
102792
|
+
skipped: true,
|
|
102793
|
+
};
|
|
102794
|
+
}
|
|
102795
|
+
}
|
|
102796
|
+
}
|
|
102524
102797
|
// Evaluate if condition for this forEach item
|
|
102525
102798
|
if (checkConfig.if) {
|
|
102526
102799
|
// Merge current results with forEach-specific dependency results for condition evaluation
|
|
@@ -102553,6 +102826,24 @@ class CheckExecutionEngine {
|
|
|
102553
102826
|
total: forEachItems.length,
|
|
102554
102827
|
parent: forEachParentName,
|
|
102555
102828
|
});
|
|
102829
|
+
// no-op
|
|
102830
|
+
// Evaluate fail_if per item so a single failing branch does not stop others
|
|
102831
|
+
if (config && (config.fail_if || checkConfig.fail_if)) {
|
|
102832
|
+
const itemFailures = await this.evaluateFailureConditions(checkName, itemResult, config);
|
|
102833
|
+
if (itemFailures.length > 0) {
|
|
102834
|
+
const failureIssues = itemFailures
|
|
102835
|
+
.filter(f => f.failed)
|
|
102836
|
+
.map(f => ({
|
|
102837
|
+
file: 'system',
|
|
102838
|
+
line: 0,
|
|
102839
|
+
ruleId: f.conditionName,
|
|
102840
|
+
message: f.message || `Failure condition met: ${f.expression}`,
|
|
102841
|
+
severity: (f.severity || 'error'),
|
|
102842
|
+
category: 'logic',
|
|
102843
|
+
}));
|
|
102844
|
+
itemResult.issues = [...(itemResult.issues || []), ...failureIssues];
|
|
102845
|
+
}
|
|
102846
|
+
}
|
|
102556
102847
|
// Record iteration completion
|
|
102557
102848
|
// Check if this iteration had fatal errors
|
|
102558
102849
|
const hadFatalError = (itemResult.issues || []).some(issue => {
|
|
@@ -102569,15 +102860,299 @@ class CheckExecutionEngine {
|
|
|
102569
102860
|
const iterationDuration = (Date.now() - iterationStart) / 1000;
|
|
102570
102861
|
this.recordIterationComplete(checkName, iterationStart, !hadFatalError, // Success if no fatal errors
|
|
102571
102862
|
itemResult.issues || [], itemResult.output);
|
|
102863
|
+
// General branch-first scheduling for this item: execute all descendants (from current node only) when ready
|
|
102864
|
+
const descendantSet = (() => {
|
|
102865
|
+
const visited = new Set();
|
|
102866
|
+
const stack = [checkName];
|
|
102867
|
+
while (stack.length) {
|
|
102868
|
+
const p = stack.pop();
|
|
102869
|
+
const kids = childrenByParent.get(p) || [];
|
|
102870
|
+
for (const k of kids) {
|
|
102871
|
+
if (!visited.has(k)) {
|
|
102872
|
+
visited.add(k);
|
|
102873
|
+
stack.push(k);
|
|
102874
|
+
}
|
|
102875
|
+
}
|
|
102876
|
+
}
|
|
102877
|
+
return visited;
|
|
102878
|
+
})();
|
|
102879
|
+
const perItemDone = new Set([...forEachParents, checkName]);
|
|
102880
|
+
const perItemDepMap = new Map();
|
|
102881
|
+
for (const [k, v] of forEachDependencyResults)
|
|
102882
|
+
perItemDepMap.set(k, v);
|
|
102883
|
+
perItemDepMap.set(checkName, itemResult);
|
|
102884
|
+
const isFatal = (r) => {
|
|
102885
|
+
if (!r)
|
|
102886
|
+
return true;
|
|
102887
|
+
return this.hasFatal(r.issues || []);
|
|
102888
|
+
};
|
|
102889
|
+
while (true) {
|
|
102890
|
+
let progressed = false;
|
|
102891
|
+
for (const node of descendantSet) {
|
|
102892
|
+
if (perItemDone.has(node))
|
|
102893
|
+
continue;
|
|
102894
|
+
const nodeCfg = config.checks[node];
|
|
102895
|
+
if (!nodeCfg)
|
|
102896
|
+
continue;
|
|
102897
|
+
const deps = dependencies[node] || [];
|
|
102898
|
+
// Are all deps satisfied for this item?
|
|
102899
|
+
let ready = true;
|
|
102900
|
+
const childDepsMap = new Map();
|
|
102901
|
+
for (const d of deps) {
|
|
102902
|
+
// Prefer per-item result if already computed in this branch
|
|
102903
|
+
const perItemRes = perItemDepMap.get(d);
|
|
102904
|
+
if (perItemRes) {
|
|
102905
|
+
if (isFatal(perItemRes)) {
|
|
102906
|
+
ready = false;
|
|
102907
|
+
break;
|
|
102908
|
+
}
|
|
102909
|
+
childDepsMap.set(d, perItemRes);
|
|
102910
|
+
continue;
|
|
102911
|
+
}
|
|
102912
|
+
const agg = results.get(d);
|
|
102913
|
+
// If dependency is a forEach-capable result, require per-item unwrap
|
|
102914
|
+
if (agg && (agg.isForEach || Array.isArray(agg.forEachItemResults))) {
|
|
102915
|
+
const r = (agg.forEachItemResults && agg.forEachItemResults[itemIndex]) ||
|
|
102916
|
+
undefined;
|
|
102917
|
+
const maskFatal = !!agg.forEachFatalMask && agg.forEachFatalMask[itemIndex] === true;
|
|
102918
|
+
if (!r || maskFatal || isFatal(r)) {
|
|
102919
|
+
ready = false;
|
|
102920
|
+
break;
|
|
102921
|
+
}
|
|
102922
|
+
childDepsMap.set(d, r);
|
|
102923
|
+
continue;
|
|
102924
|
+
}
|
|
102925
|
+
// Fallback: use global dependency result (non-forEach)
|
|
102926
|
+
if (!agg || isFatal(agg)) {
|
|
102927
|
+
ready = false;
|
|
102928
|
+
break;
|
|
102929
|
+
}
|
|
102930
|
+
childDepsMap.set(d, agg);
|
|
102931
|
+
}
|
|
102932
|
+
if (!ready)
|
|
102933
|
+
continue;
|
|
102934
|
+
// if condition per item
|
|
102935
|
+
if (nodeCfg.if) {
|
|
102936
|
+
const condResults = new Map(results);
|
|
102937
|
+
for (const [k, v] of childDepsMap)
|
|
102938
|
+
condResults.set(k, v);
|
|
102939
|
+
const shouldRun = await this.evaluateCheckCondition(node, nodeCfg.if, prInfo, condResults, debug);
|
|
102940
|
+
if (!shouldRun) {
|
|
102941
|
+
perItemDone.add(node);
|
|
102942
|
+
progressed = true;
|
|
102943
|
+
continue;
|
|
102944
|
+
}
|
|
102945
|
+
}
|
|
102946
|
+
// Execute node for this item
|
|
102947
|
+
const nodeProvType = nodeCfg.type || 'ai';
|
|
102948
|
+
const nodeProv = this.providerRegistry.getProviderOrThrow(nodeProvType);
|
|
102949
|
+
this.setProviderWebhookContext(nodeProv);
|
|
102950
|
+
const nodeProviderConfig = {
|
|
102951
|
+
type: nodeProvType,
|
|
102952
|
+
prompt: nodeCfg.prompt,
|
|
102953
|
+
exec: nodeCfg.exec,
|
|
102954
|
+
focus: nodeCfg.focus || this.mapCheckNameToFocus(node),
|
|
102955
|
+
schema: nodeCfg.schema,
|
|
102956
|
+
group: nodeCfg.group,
|
|
102957
|
+
checkName: node,
|
|
102958
|
+
eventContext: prInfo.eventContext,
|
|
102959
|
+
transform: nodeCfg.transform,
|
|
102960
|
+
transform_js: nodeCfg.transform_js,
|
|
102961
|
+
env: nodeCfg.env,
|
|
102962
|
+
forEach: nodeCfg.forEach,
|
|
102963
|
+
ai: { timeout: timeout || 600000, debug: debug, ...(nodeCfg.ai || {}) },
|
|
102964
|
+
};
|
|
102965
|
+
const iterStart = this.recordIterationStart(node);
|
|
102966
|
+
// Expand dependency map for execution context to include transitive ancestors
|
|
102967
|
+
const execDepMap = new Map(childDepsMap);
|
|
102968
|
+
const nodeAllDeps = dependency_resolver_1.DependencyResolver.getAllDependencies(node, dependencyGraph.nodes);
|
|
102969
|
+
for (const dep of nodeAllDeps) {
|
|
102970
|
+
if (execDepMap.has(dep))
|
|
102971
|
+
continue;
|
|
102972
|
+
// Prefer per-item map first
|
|
102973
|
+
const perItemRes = perItemDepMap.get(dep);
|
|
102974
|
+
if (perItemRes) {
|
|
102975
|
+
execDepMap.set(dep, perItemRes);
|
|
102976
|
+
continue;
|
|
102977
|
+
}
|
|
102978
|
+
const agg = results.get(dep);
|
|
102979
|
+
if (!agg)
|
|
102980
|
+
continue;
|
|
102981
|
+
if (agg &&
|
|
102982
|
+
(agg.isForEach ||
|
|
102983
|
+
Array.isArray(agg.forEachItemResults) ||
|
|
102984
|
+
Array.isArray(agg.output))) {
|
|
102985
|
+
if (Array.isArray(agg.forEachItemResults) &&
|
|
102986
|
+
agg.forEachItemResults[itemIndex]) {
|
|
102987
|
+
execDepMap.set(dep, agg.forEachItemResults[itemIndex]);
|
|
102988
|
+
}
|
|
102989
|
+
else if (Array.isArray(agg.output) &&
|
|
102990
|
+
agg.output[itemIndex] !== undefined) {
|
|
102991
|
+
execDepMap.set(dep, {
|
|
102992
|
+
issues: [],
|
|
102993
|
+
output: agg.output[itemIndex],
|
|
102994
|
+
});
|
|
102995
|
+
}
|
|
102996
|
+
else {
|
|
102997
|
+
execDepMap.set(dep, agg);
|
|
102998
|
+
}
|
|
102999
|
+
}
|
|
103000
|
+
else {
|
|
103001
|
+
execDepMap.set(dep, agg);
|
|
103002
|
+
}
|
|
103003
|
+
}
|
|
103004
|
+
const nodeItemRes = await this.executeWithRouting(node, nodeCfg, nodeProv, nodeProviderConfig, prInfo, execDepMap, sessionInfo, config, dependencyGraph, debug, results, { index: itemIndex, total: forEachItems.length, parent: forEachParentName });
|
|
103005
|
+
if (config && (config.fail_if || nodeCfg.fail_if)) {
|
|
103006
|
+
const fRes = await this.evaluateFailureConditions(node, nodeItemRes, config);
|
|
103007
|
+
if (fRes.length > 0) {
|
|
103008
|
+
const fIssues = fRes
|
|
103009
|
+
.filter(f => f.failed)
|
|
103010
|
+
.map(f => ({
|
|
103011
|
+
file: 'system',
|
|
103012
|
+
line: 0,
|
|
103013
|
+
ruleId: f.conditionName,
|
|
103014
|
+
message: f.message || `Failure condition met: ${f.expression}`,
|
|
103015
|
+
severity: (f.severity || 'error'),
|
|
103016
|
+
category: 'logic',
|
|
103017
|
+
}));
|
|
103018
|
+
nodeItemRes.issues = [...(nodeItemRes.issues || []), ...fIssues];
|
|
103019
|
+
}
|
|
103020
|
+
}
|
|
103021
|
+
const hadFatal = isFatal(nodeItemRes);
|
|
103022
|
+
this.recordIterationComplete(node, iterStart, !hadFatal, nodeItemRes.issues || [], nodeItemRes.output);
|
|
103023
|
+
// Aggregate results for this node across items
|
|
103024
|
+
if (!inlineAgg.has(node))
|
|
103025
|
+
inlineAgg.set(node, {
|
|
103026
|
+
issues: [],
|
|
103027
|
+
outputs: [],
|
|
103028
|
+
contents: [],
|
|
103029
|
+
perItemResults: [],
|
|
103030
|
+
});
|
|
103031
|
+
const agg = inlineAgg.get(node);
|
|
103032
|
+
if (nodeItemRes.issues)
|
|
103033
|
+
agg.issues.push(...nodeItemRes.issues);
|
|
103034
|
+
const nout = nodeItemRes.output;
|
|
103035
|
+
if (nout !== undefined)
|
|
103036
|
+
agg.outputs.push(nout);
|
|
103037
|
+
agg.perItemResults.push(nodeItemRes);
|
|
103038
|
+
const ncontent = nodeItemRes.content;
|
|
103039
|
+
if (typeof ncontent === 'string' && ncontent.trim())
|
|
103040
|
+
agg.contents.push(ncontent.trim());
|
|
103041
|
+
perItemDepMap.set(node, nodeItemRes);
|
|
103042
|
+
perItemDone.add(node);
|
|
103043
|
+
progressed = true;
|
|
103044
|
+
}
|
|
103045
|
+
if (!progressed)
|
|
103046
|
+
break;
|
|
103047
|
+
}
|
|
102572
103048
|
// Log iteration progress
|
|
102573
103049
|
logger_1.logger.info(` ✔ ${itemIndex + 1}/${forEachItems.length} (${iterationDuration.toFixed(1)}s)`);
|
|
103050
|
+
perItemResults[itemIndex] = itemResult;
|
|
102574
103051
|
return { index: itemIndex, itemResult };
|
|
102575
103052
|
});
|
|
102576
|
-
|
|
103053
|
+
// Determine runnable indices by intersecting masks across all direct forEach parents
|
|
103054
|
+
const directForEachParents = (checkConfig.depends_on || []).filter(dep => {
|
|
103055
|
+
const r = results.get(dep);
|
|
103056
|
+
return (!!r &&
|
|
103057
|
+
(r.isForEach ||
|
|
103058
|
+
Array.isArray(r.forEachItemResults) ||
|
|
103059
|
+
Array.isArray(r.forEachItems)));
|
|
103060
|
+
});
|
|
103061
|
+
if (directForEachParents.length > 0) {
|
|
103062
|
+
logger_1.logger.debug(` forEach: direct parents for "${checkName}": ${directForEachParents.join(', ')}`);
|
|
103063
|
+
}
|
|
103064
|
+
const isIndexFatalForParent = async (parent, idx) => {
|
|
103065
|
+
const agg = results.get(parent);
|
|
103066
|
+
if (!agg)
|
|
103067
|
+
return false; // if missing, do not gate
|
|
103068
|
+
if (agg.forEachFatalMask && agg.forEachFatalMask[idx] === true)
|
|
103069
|
+
return true;
|
|
103070
|
+
const r = (agg.forEachItemResults && agg.forEachItemResults[idx]) || undefined;
|
|
103071
|
+
if (!r)
|
|
103072
|
+
return false;
|
|
103073
|
+
// 1) Issues-based fatality (provider/transform/timeout/fail_if markers)
|
|
103074
|
+
const hadFatalByIssues = this.hasFatal(r.issues || []);
|
|
103075
|
+
if (hadFatalByIssues)
|
|
103076
|
+
return true;
|
|
103077
|
+
// 2) Fail_if based fatality evaluated directly on the parent per-item result
|
|
103078
|
+
try {
|
|
103079
|
+
if (config && (config.fail_if || config.checks[parent]?.fail_if)) {
|
|
103080
|
+
// If output is a string, try parsing JSON (full or tail) to honor fail_if semantics
|
|
103081
|
+
let rForEval = r;
|
|
103082
|
+
const rawOut = r?.output;
|
|
103083
|
+
if (typeof rawOut === 'string') {
|
|
103084
|
+
const parseTail = (text) => {
|
|
103085
|
+
try {
|
|
103086
|
+
const lines = text.split('\n');
|
|
103087
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
103088
|
+
const t = lines[i].trim();
|
|
103089
|
+
if (t.startsWith('{') || t.startsWith('[')) {
|
|
103090
|
+
const candidate = lines.slice(i).join('\n').trim();
|
|
103091
|
+
if ((candidate.startsWith('{') && candidate.endsWith('}')) ||
|
|
103092
|
+
(candidate.startsWith('[') && candidate.endsWith(']'))) {
|
|
103093
|
+
return JSON.parse(candidate);
|
|
103094
|
+
}
|
|
103095
|
+
}
|
|
103096
|
+
}
|
|
103097
|
+
}
|
|
103098
|
+
catch { }
|
|
103099
|
+
try {
|
|
103100
|
+
return JSON.parse(text);
|
|
103101
|
+
}
|
|
103102
|
+
catch {
|
|
103103
|
+
return null;
|
|
103104
|
+
}
|
|
103105
|
+
};
|
|
103106
|
+
const parsed = parseTail(rawOut);
|
|
103107
|
+
if (parsed && typeof parsed === 'object') {
|
|
103108
|
+
rForEval = { ...r, output: parsed };
|
|
103109
|
+
}
|
|
103110
|
+
}
|
|
103111
|
+
const failures = await this.evaluateFailureConditions(parent, rForEval, config);
|
|
103112
|
+
if (failures.some(f => f.failed)) {
|
|
103113
|
+
// Temporary: surface why index is gated
|
|
103114
|
+
}
|
|
103115
|
+
if (failures.some(f => f.failed))
|
|
103116
|
+
return true;
|
|
103117
|
+
}
|
|
103118
|
+
}
|
|
103119
|
+
catch { }
|
|
103120
|
+
return false;
|
|
103121
|
+
};
|
|
103122
|
+
const runnableIndices = [];
|
|
103123
|
+
for (let idx = 0; idx < forEachItems.length; idx++) {
|
|
103124
|
+
let ok = true;
|
|
103125
|
+
for (const p of directForEachParents) {
|
|
103126
|
+
if (await isIndexFatalForParent(p, idx)) {
|
|
103127
|
+
ok = false;
|
|
103128
|
+
break;
|
|
103129
|
+
}
|
|
103130
|
+
}
|
|
103131
|
+
// Only schedule indices that have a corresponding task function
|
|
103132
|
+
if (ok && typeof itemTasks[idx] === 'function')
|
|
103133
|
+
runnableIndices.push(idx);
|
|
103134
|
+
}
|
|
103135
|
+
// no-op
|
|
103136
|
+
// Early skip if no runnable items after intersecting masks across all direct forEach parents
|
|
103137
|
+
if (runnableIndices.length === 0) {
|
|
103138
|
+
this.recordSkip(checkName, 'dependency_failed');
|
|
103139
|
+
logger_1.logger.info(`⏭ Skipped (dependency failed: no runnable items)`);
|
|
103140
|
+
return {
|
|
103141
|
+
checkName,
|
|
103142
|
+
error: null,
|
|
103143
|
+
result: { issues: [] },
|
|
103144
|
+
skipped: true,
|
|
103145
|
+
};
|
|
103146
|
+
}
|
|
103147
|
+
const forEachConcurrency = Math.max(1, Math.min(runnableIndices.length, effectiveMaxParallelism));
|
|
102577
103148
|
if (debug && forEachConcurrency > 1) {
|
|
102578
103149
|
log(`🔄 Debug: Limiting forEach concurrency for check "${checkName}" to ${forEachConcurrency}`);
|
|
102579
103150
|
}
|
|
102580
|
-
const
|
|
103151
|
+
const scheduledTasks = runnableIndices
|
|
103152
|
+
.map(i => itemTasks[i])
|
|
103153
|
+
.filter(fn => typeof fn === 'function');
|
|
103154
|
+
const forEachResults = await this.executeWithLimitedParallelism(scheduledTasks, forEachConcurrency, false);
|
|
103155
|
+
let processedCount = 0;
|
|
102581
103156
|
for (const result of forEachResults) {
|
|
102582
103157
|
if (result.status === 'rejected') {
|
|
102583
103158
|
// Instead of throwing, record the failure and continue with other iterations
|
|
@@ -102597,56 +103172,137 @@ class CheckExecutionEngine {
|
|
|
102597
103172
|
}
|
|
102598
103173
|
continue;
|
|
102599
103174
|
}
|
|
102600
|
-
// Skip results from skipped items (those
|
|
103175
|
+
// Skip results from skipped items (those gated by dependencies/if)
|
|
102601
103176
|
if (result.value.skipped) {
|
|
102602
103177
|
continue;
|
|
102603
103178
|
}
|
|
102604
|
-
const { itemResult } = result.value;
|
|
103179
|
+
const { index: finishedIndex, itemResult } = result.value;
|
|
103180
|
+
processedCount++;
|
|
102605
103181
|
if (itemResult.issues) {
|
|
102606
103182
|
allIssues.push(...itemResult.issues);
|
|
102607
103183
|
}
|
|
102608
103184
|
const resultWithOutput = itemResult;
|
|
102609
|
-
|
|
102610
|
-
allOutputs.push(resultWithOutput.output);
|
|
102611
|
-
}
|
|
103185
|
+
allOutputs[finishedIndex] = resultWithOutput.output;
|
|
102612
103186
|
const itemContent = resultWithOutput.content;
|
|
102613
103187
|
if (typeof itemContent === 'string' && itemContent.trim()) {
|
|
102614
103188
|
aggregatedContents.push(itemContent.trim());
|
|
102615
103189
|
}
|
|
103190
|
+
else {
|
|
103191
|
+
const outStr = typeof resultWithOutput.output === 'string'
|
|
103192
|
+
? resultWithOutput.output.trim()
|
|
103193
|
+
: '';
|
|
103194
|
+
if (outStr)
|
|
103195
|
+
aggregatedContents.push(outStr);
|
|
103196
|
+
}
|
|
103197
|
+
}
|
|
103198
|
+
// If no items were processed (all gated), mark this check as skipped for dependency_failed
|
|
103199
|
+
if (processedCount === 0) {
|
|
103200
|
+
this.recordSkip(checkName, 'dependency_failed');
|
|
103201
|
+
logger_1.logger.info(`⏭ Skipped (dependency failed for all items)`);
|
|
103202
|
+
return {
|
|
103203
|
+
checkName,
|
|
103204
|
+
error: null,
|
|
103205
|
+
result: { issues: [] },
|
|
103206
|
+
skipped: true,
|
|
103207
|
+
};
|
|
102616
103208
|
}
|
|
102617
103209
|
const finalOutput = allOutputs.length > 0 ? allOutputs : undefined;
|
|
102618
103210
|
finalResult = {
|
|
102619
103211
|
issues: allIssues,
|
|
102620
103212
|
...(finalOutput !== undefined ? { output: finalOutput } : {}),
|
|
102621
103213
|
};
|
|
102622
|
-
//
|
|
102623
|
-
|
|
102624
|
-
|
|
102625
|
-
|
|
102626
|
-
|
|
102627
|
-
|
|
102628
|
-
|
|
102629
|
-
|
|
102630
|
-
|
|
102631
|
-
|
|
102632
|
-
|
|
102633
|
-
|
|
102634
|
-
|
|
102635
|
-
|
|
102636
|
-
|
|
102637
|
-
|
|
102638
|
-
|
|
102639
|
-
|
|
102640
|
-
|
|
102641
|
-
|
|
102642
|
-
|
|
102643
|
-
|
|
103214
|
+
// Mark this result as forEach-capable and attach per-item results for precise downstream gating
|
|
103215
|
+
finalResult.isForEach = true;
|
|
103216
|
+
finalResult.forEachItems = allOutputs;
|
|
103217
|
+
finalResult.forEachItemResults =
|
|
103218
|
+
perItemResults;
|
|
103219
|
+
// Compute fatal mask
|
|
103220
|
+
try {
|
|
103221
|
+
const mask = finalResult.forEachItemResults
|
|
103222
|
+
? await Promise.all(Array.from({ length: forEachItems.length }, async (_, idx) => {
|
|
103223
|
+
const r = finalResult.forEachItemResults[idx];
|
|
103224
|
+
if (!r)
|
|
103225
|
+
return false; // no result (skipped) → not fatal for descendants
|
|
103226
|
+
let hadFatal = this.hasFatal(r.issues || []);
|
|
103227
|
+
try {
|
|
103228
|
+
const ids = (r.issues || []).map(i => i.ruleId).join(',');
|
|
103229
|
+
logger_1.logger.debug(` forEach: item ${idx + 1}/${forEachItems.length} issues=${(r.issues || []).length} ids=[${ids}]`);
|
|
103230
|
+
}
|
|
103231
|
+
catch { }
|
|
103232
|
+
if (!hadFatal && config && (config.fail_if || checkConfig.fail_if)) {
|
|
103233
|
+
try {
|
|
103234
|
+
const failures = await this.evaluateFailureConditions(checkName, r, config);
|
|
103235
|
+
hadFatal = failures.some(f => f.failed);
|
|
103236
|
+
}
|
|
103237
|
+
catch { }
|
|
103238
|
+
}
|
|
103239
|
+
return hadFatal;
|
|
103240
|
+
}))
|
|
103241
|
+
: [];
|
|
103242
|
+
finalResult.forEachFatalMask = mask;
|
|
103243
|
+
logger_1.logger.debug(` forEach: mask for "${checkName}" → fatals=${mask.filter(Boolean).length}/${mask.length}`);
|
|
102644
103244
|
}
|
|
103245
|
+
catch { }
|
|
102645
103246
|
if (aggregatedContents.length > 0) {
|
|
102646
103247
|
finalResult.content =
|
|
102647
103248
|
aggregatedContents.join('\n');
|
|
102648
103249
|
}
|
|
102649
|
-
|
|
103250
|
+
// Finalize inline descendant aggregations to full results, so later levels skip them
|
|
103251
|
+
for (const [childName, agg] of inlineAgg.entries()) {
|
|
103252
|
+
const childCfg = config.checks[childName];
|
|
103253
|
+
const childEnrichedIssues = (agg.issues || []).map(issue => ({
|
|
103254
|
+
...issue,
|
|
103255
|
+
checkName: childName,
|
|
103256
|
+
ruleId: `${childName}/${issue.ruleId}`,
|
|
103257
|
+
group: childCfg.group,
|
|
103258
|
+
schema: typeof childCfg.schema === 'object' ? 'custom' : childCfg.schema,
|
|
103259
|
+
template: childCfg.template,
|
|
103260
|
+
timestamp: Date.now(),
|
|
103261
|
+
}));
|
|
103262
|
+
const childFinal = {
|
|
103263
|
+
issues: childEnrichedIssues,
|
|
103264
|
+
...(agg.outputs.length > 0 ? { output: agg.outputs } : {}),
|
|
103265
|
+
isForEach: true,
|
|
103266
|
+
forEachItems: agg.outputs,
|
|
103267
|
+
forEachItemResults: agg.perItemResults,
|
|
103268
|
+
...(agg.contents.length > 0 ? { content: agg.contents.join('\n') } : {}),
|
|
103269
|
+
};
|
|
103270
|
+
// Compute fatal mask for child aggregate
|
|
103271
|
+
try {
|
|
103272
|
+
const mask = Array.from({ length: agg.perItemResults.length }, (_, idx) => {
|
|
103273
|
+
const r = agg.perItemResults[idx];
|
|
103274
|
+
if (!r)
|
|
103275
|
+
return false; // skipped item is not fatal for descendants
|
|
103276
|
+
const hadFatal = (r.issues || []).some(issue => {
|
|
103277
|
+
const id = issue.ruleId || '';
|
|
103278
|
+
return (issue.severity === 'error' ||
|
|
103279
|
+
issue.severity === 'critical' ||
|
|
103280
|
+
id === 'command/execution_error' ||
|
|
103281
|
+
id.endsWith('/command/execution_error') ||
|
|
103282
|
+
id === 'command/timeout' ||
|
|
103283
|
+
id.endsWith('/command/timeout') ||
|
|
103284
|
+
id === 'command/transform_js_error' ||
|
|
103285
|
+
id.endsWith('/command/transform_js_error') ||
|
|
103286
|
+
id === 'command/transform_error' ||
|
|
103287
|
+
id.endsWith('/command/transform_error') ||
|
|
103288
|
+
id.endsWith('/forEach/iteration_error') ||
|
|
103289
|
+
id === 'forEach/undefined_output' ||
|
|
103290
|
+
id.endsWith('/forEach/undefined_output') ||
|
|
103291
|
+
id.endsWith('_fail_if') ||
|
|
103292
|
+
id.endsWith('/global_fail_if'));
|
|
103293
|
+
});
|
|
103294
|
+
return hadFatal;
|
|
103295
|
+
});
|
|
103296
|
+
childFinal.forEachFatalMask = mask;
|
|
103297
|
+
}
|
|
103298
|
+
catch { }
|
|
103299
|
+
results.set(childName, childFinal);
|
|
103300
|
+
}
|
|
103301
|
+
if (debug &&
|
|
103302
|
+
process.env.VISOR_OUTPUT_FORMAT !== 'json' &&
|
|
103303
|
+
process.env.VISOR_OUTPUT_FORMAT !== 'sarif') {
|
|
103304
|
+
console.log(`🔄 Debug: Completed forEach execution for check "${checkName}", total issues: ${allIssues.length}`);
|
|
103305
|
+
}
|
|
102650
103306
|
} // End of else block for forEachItems.length > 0
|
|
102651
103307
|
}
|
|
102652
103308
|
else {
|
|
@@ -102767,7 +103423,7 @@ class CheckExecutionEngine {
|
|
|
102767
103423
|
};
|
|
102768
103424
|
}
|
|
102769
103425
|
catch (error) {
|
|
102770
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
103426
|
+
const errorMessage = error instanceof Error ? `${error.message}\n${error.stack || ''}` : String(error);
|
|
102771
103427
|
const checkDuration = ((Date.now() - checkStartTime) / 1000).toFixed(1);
|
|
102772
103428
|
// Record error in stats
|
|
102773
103429
|
this.recordError(checkName, error instanceof Error ? error : new Error(String(error)));
|
|
@@ -102786,8 +103442,9 @@ class CheckExecutionEngine {
|
|
|
102786
103442
|
// Execute checks in this level with controlled parallelism
|
|
102787
103443
|
const levelResults = await this.executeWithLimitedParallelism(levelTaskFunctions, actualParallelism, effectiveFailFast);
|
|
102788
103444
|
// Process results and store them for next level
|
|
103445
|
+
const levelChecksList = executionGroup.parallel.filter(name => !results.has(name));
|
|
102789
103446
|
for (let i = 0; i < levelResults.length; i++) {
|
|
102790
|
-
const checkName =
|
|
103447
|
+
const checkName = levelChecksList[i];
|
|
102791
103448
|
const result = levelResults[i];
|
|
102792
103449
|
const checkConfig = config.checks[checkName];
|
|
102793
103450
|
if (result.status === 'fulfilled' && result.value.result && !result.value.error) {
|
|
@@ -103920,11 +104577,28 @@ class CheckExecutionEngine {
|
|
|
103920
104577
|
*/
|
|
103921
104578
|
recordForEachPreview(checkName, items) {
|
|
103922
104579
|
const stats = this.executionStats.get(checkName);
|
|
103923
|
-
if (!stats
|
|
104580
|
+
if (!stats)
|
|
104581
|
+
return;
|
|
104582
|
+
if (!Array.isArray(items) || items.length === 0)
|
|
103924
104583
|
return;
|
|
103925
104584
|
// Store preview of first 3 items
|
|
103926
104585
|
const preview = items.slice(0, 3).map(item => {
|
|
103927
|
-
|
|
104586
|
+
let str;
|
|
104587
|
+
if (typeof item === 'string') {
|
|
104588
|
+
str = item;
|
|
104589
|
+
}
|
|
104590
|
+
else if (item === undefined || item === null) {
|
|
104591
|
+
str = '(empty)';
|
|
104592
|
+
}
|
|
104593
|
+
else {
|
|
104594
|
+
try {
|
|
104595
|
+
const j = JSON.stringify(item);
|
|
104596
|
+
str = typeof j === 'string' ? j : String(item);
|
|
104597
|
+
}
|
|
104598
|
+
catch {
|
|
104599
|
+
str = String(item);
|
|
104600
|
+
}
|
|
104601
|
+
}
|
|
103928
104602
|
return str.length > 50 ? str.substring(0, 47) + '...' : str;
|
|
103929
104603
|
});
|
|
103930
104604
|
if (items.length > 3) {
|
|
@@ -103961,6 +104635,36 @@ class CheckExecutionEngine {
|
|
|
103961
104635
|
checks,
|
|
103962
104636
|
};
|
|
103963
104637
|
}
|
|
104638
|
+
// Generic fatality helpers to avoid duplication
|
|
104639
|
+
isFatalRule(id, severity) {
|
|
104640
|
+
const sev = (severity || '').toLowerCase();
|
|
104641
|
+
return (sev === 'error' ||
|
|
104642
|
+
sev === 'critical' ||
|
|
104643
|
+
id === 'command/execution_error' ||
|
|
104644
|
+
id.endsWith('/command/execution_error') ||
|
|
104645
|
+
id === 'command/timeout' ||
|
|
104646
|
+
id.endsWith('/command/timeout') ||
|
|
104647
|
+
id === 'command/transform_js_error' ||
|
|
104648
|
+
id.endsWith('/command/transform_js_error') ||
|
|
104649
|
+
id === 'command/transform_error' ||
|
|
104650
|
+
id.endsWith('/command/transform_error') ||
|
|
104651
|
+
id.endsWith('/forEach/iteration_error') ||
|
|
104652
|
+
id === 'forEach/undefined_output' ||
|
|
104653
|
+
id.endsWith('/forEach/undefined_output') ||
|
|
104654
|
+
id.endsWith('_fail_if') ||
|
|
104655
|
+
id.endsWith('/global_fail_if'));
|
|
104656
|
+
}
|
|
104657
|
+
hasFatal(issues) {
|
|
104658
|
+
if (!issues || issues.length === 0)
|
|
104659
|
+
return false;
|
|
104660
|
+
return issues.some(i => this.isFatalRule(i.ruleId || '', i.severity));
|
|
104661
|
+
}
|
|
104662
|
+
async failIfTriggered(checkName, result, config) {
|
|
104663
|
+
if (!config)
|
|
104664
|
+
return false;
|
|
104665
|
+
const failures = await this.evaluateFailureConditions(checkName, result, config);
|
|
104666
|
+
return failures.some(f => f.failed);
|
|
104667
|
+
}
|
|
103964
104668
|
/**
|
|
103965
104669
|
* Truncate a string to max length with ellipsis
|
|
103966
104670
|
*/
|
|
@@ -106196,7 +106900,17 @@ class FailureConditionEvaluator {
|
|
|
106196
106900
|
async evaluateSimpleCondition(checkName, checkSchema, checkGroup, reviewSummary, expression, previousOutputs) {
|
|
106197
106901
|
const context = this.buildEvaluationContext(checkName, checkSchema, checkGroup, reviewSummary, previousOutputs);
|
|
106198
106902
|
try {
|
|
106199
|
-
|
|
106903
|
+
try {
|
|
106904
|
+
const isObj = context.output && typeof context.output === 'object';
|
|
106905
|
+
const keys = isObj ? Object.keys(context.output).join(',') : typeof context.output;
|
|
106906
|
+
let errorVal = undefined;
|
|
106907
|
+
if (isObj && context.output.error !== undefined)
|
|
106908
|
+
errorVal = context.output.error;
|
|
106909
|
+
(__nccwpck_require__(86999).logger).debug(` fail_if: evaluating '${expression}' with output keys=${keys} error=${String(errorVal)}`);
|
|
106910
|
+
}
|
|
106911
|
+
catch { }
|
|
106912
|
+
const res = this.evaluateExpression(expression, context);
|
|
106913
|
+
return res;
|
|
106200
106914
|
}
|
|
106201
106915
|
catch (error) {
|
|
106202
106916
|
console.warn(`Failed to evaluate fail_if expression: ${error}`);
|
|
@@ -106303,6 +107017,12 @@ class FailureConditionEvaluator {
|
|
|
106303
107017
|
results.length = 0;
|
|
106304
107018
|
results.push(...filteredResults, ...checkResults);
|
|
106305
107019
|
}
|
|
107020
|
+
try {
|
|
107021
|
+
if (checkName === 'B') {
|
|
107022
|
+
console.error(`🔧 Debug: fail_if results for ${checkName}: ${JSON.stringify(results)} context.output=${JSON.stringify(context.output)}`);
|
|
107023
|
+
}
|
|
107024
|
+
}
|
|
107025
|
+
catch { }
|
|
106306
107026
|
return results;
|
|
106307
107027
|
}
|
|
106308
107028
|
/**
|
|
@@ -106521,6 +107241,10 @@ class FailureConditionEvaluator {
|
|
|
106521
107241
|
exec = this.sandbox.compile(`return (${normalizedExpr});`);
|
|
106522
107242
|
}
|
|
106523
107243
|
const result = exec(scope).run();
|
|
107244
|
+
try {
|
|
107245
|
+
(__nccwpck_require__(86999).logger).debug(` fail_if: result=${Boolean(result)}`);
|
|
107246
|
+
}
|
|
107247
|
+
catch { }
|
|
106524
107248
|
// Ensure we return a boolean
|
|
106525
107249
|
return Boolean(result);
|
|
106526
107250
|
}
|
|
@@ -106592,6 +107316,73 @@ class FailureConditionEvaluator {
|
|
|
106592
107316
|
else if (extractedOutput && typeof extractedOutput === 'object') {
|
|
106593
107317
|
Object.assign(aggregatedOutput, extractedOutput);
|
|
106594
107318
|
}
|
|
107319
|
+
// If provider attached a raw transform snapshot, merge its fields generically.
|
|
107320
|
+
try {
|
|
107321
|
+
const raw = reviewSummaryWithOutput.__raw;
|
|
107322
|
+
if (raw && typeof raw === 'object') {
|
|
107323
|
+
Object.assign(aggregatedOutput, raw);
|
|
107324
|
+
}
|
|
107325
|
+
}
|
|
107326
|
+
catch { }
|
|
107327
|
+
// If output is a string, try to parse JSON (full or from end) to enrich context,
|
|
107328
|
+
// and also derive common boolean flags generically (e.g., key:true/false) for fail_if usage.
|
|
107329
|
+
try {
|
|
107330
|
+
if (typeof extractedOutput === 'string') {
|
|
107331
|
+
const parsed = this.tryExtractJsonFromEnd(extractedOutput) ??
|
|
107332
|
+
(() => {
|
|
107333
|
+
try {
|
|
107334
|
+
return JSON.parse(extractedOutput);
|
|
107335
|
+
}
|
|
107336
|
+
catch {
|
|
107337
|
+
return null;
|
|
107338
|
+
}
|
|
107339
|
+
})();
|
|
107340
|
+
if (parsed !== null) {
|
|
107341
|
+
if (Array.isArray(parsed)) {
|
|
107342
|
+
aggregatedOutput.items = parsed;
|
|
107343
|
+
}
|
|
107344
|
+
else if (typeof parsed === 'object') {
|
|
107345
|
+
Object.assign(aggregatedOutput, parsed);
|
|
107346
|
+
}
|
|
107347
|
+
}
|
|
107348
|
+
// Generic boolean key extraction for simple text outputs (no special provider cases)
|
|
107349
|
+
const lower = extractedOutput.toLowerCase();
|
|
107350
|
+
const boolFrom = (key) => {
|
|
107351
|
+
const reTrue = new RegExp(`(?:^|[^a-z0-9_])${key}[^a-z0-9_]*[:=][^a-z0-9_]*true(?:[^a-z0-9_]|$)`);
|
|
107352
|
+
const reFalse = new RegExp(`(?:^|[^a-z0-9_])${key}[^a-z0-9_]*[:=][^a-z0-9_]*false(?:[^a-z0-9_]|$)`);
|
|
107353
|
+
if (reTrue.test(lower))
|
|
107354
|
+
return true;
|
|
107355
|
+
if (reFalse.test(lower))
|
|
107356
|
+
return false;
|
|
107357
|
+
return null;
|
|
107358
|
+
};
|
|
107359
|
+
const keys = ['error'];
|
|
107360
|
+
for (const k of keys) {
|
|
107361
|
+
const v = boolFrom(k);
|
|
107362
|
+
if (v !== null && aggregatedOutput[k] === undefined) {
|
|
107363
|
+
aggregatedOutput[k] = v;
|
|
107364
|
+
}
|
|
107365
|
+
}
|
|
107366
|
+
}
|
|
107367
|
+
}
|
|
107368
|
+
catch { }
|
|
107369
|
+
// Try to parse JSON from content as a last resort when no structured output is present
|
|
107370
|
+
try {
|
|
107371
|
+
const rsAny = reviewSummaryWithOutput;
|
|
107372
|
+
const hasStructuredOutput = extractedOutput !== undefined && extractedOutput !== null;
|
|
107373
|
+
if (!hasStructuredOutput && typeof rsAny?.content === 'string') {
|
|
107374
|
+
const parsedFromContent = this.tryExtractJsonFromEnd(rsAny.content);
|
|
107375
|
+
if (parsedFromContent !== null && parsedFromContent !== undefined) {
|
|
107376
|
+
if (Array.isArray(parsedFromContent)) {
|
|
107377
|
+
aggregatedOutput.items = parsedFromContent;
|
|
107378
|
+
}
|
|
107379
|
+
else if (typeof parsedFromContent === 'object') {
|
|
107380
|
+
Object.assign(aggregatedOutput, parsedFromContent);
|
|
107381
|
+
}
|
|
107382
|
+
}
|
|
107383
|
+
}
|
|
107384
|
+
}
|
|
107385
|
+
catch { }
|
|
106595
107386
|
const context = {
|
|
106596
107387
|
output: aggregatedOutput,
|
|
106597
107388
|
outputs: (() => {
|
|
@@ -106622,6 +107413,24 @@ class FailureConditionEvaluator {
|
|
|
106622
107413
|
}
|
|
106623
107414
|
return context;
|
|
106624
107415
|
}
|
|
107416
|
+
// Minimal JSON-from-end extractor for fail_if context fallback
|
|
107417
|
+
tryExtractJsonFromEnd(text) {
|
|
107418
|
+
try {
|
|
107419
|
+
const lines = text.split('\n');
|
|
107420
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
107421
|
+
const t = lines[i].trim();
|
|
107422
|
+
if (t.startsWith('{') || t.startsWith('[')) {
|
|
107423
|
+
const candidate = lines.slice(i).join('\n').trim();
|
|
107424
|
+
if ((candidate.startsWith('{') && candidate.endsWith('}')) ||
|
|
107425
|
+
(candidate.startsWith('[') && candidate.endsWith(']'))) {
|
|
107426
|
+
return JSON.parse(candidate);
|
|
107427
|
+
}
|
|
107428
|
+
}
|
|
107429
|
+
}
|
|
107430
|
+
}
|
|
107431
|
+
catch { }
|
|
107432
|
+
return null;
|
|
107433
|
+
}
|
|
106625
107434
|
/**
|
|
106626
107435
|
* Check if any failure condition requires halting execution
|
|
106627
107436
|
*/
|
|
@@ -110435,6 +111244,18 @@ function configureLoggerFromCli(options) {
|
|
|
110435
111244
|
verbose: options.verbose,
|
|
110436
111245
|
quiet: options.quiet,
|
|
110437
111246
|
});
|
|
111247
|
+
// Expose output format and debug to process env for modules that need to gate
|
|
111248
|
+
// stdout emissions without plumbing the value through every call site.
|
|
111249
|
+
try {
|
|
111250
|
+
if (options.output)
|
|
111251
|
+
process.env.VISOR_OUTPUT_FORMAT = String(options.output);
|
|
111252
|
+
if (typeof options.debug === 'boolean') {
|
|
111253
|
+
process.env.VISOR_DEBUG = options.debug ? 'true' : 'false';
|
|
111254
|
+
}
|
|
111255
|
+
}
|
|
111256
|
+
catch {
|
|
111257
|
+
// ignore
|
|
111258
|
+
}
|
|
110438
111259
|
}
|
|
110439
111260
|
|
|
110440
111261
|
|
|
@@ -112689,6 +113510,10 @@ class CommandCheckProvider extends check_provider_interface_1.CheckProvider {
|
|
|
112689
113510
|
return true;
|
|
112690
113511
|
}
|
|
112691
113512
|
async execute(prInfo, config, dependencyResults) {
|
|
113513
|
+
try {
|
|
113514
|
+
logger_1.logger.info(` command provider: executing check=${String(config.checkName || config.type)} hasTransformJs=${Boolean(config.transform_js)}`);
|
|
113515
|
+
}
|
|
113516
|
+
catch { }
|
|
112692
113517
|
const command = config.exec;
|
|
112693
113518
|
const transform = config.transform;
|
|
112694
113519
|
const transformJs = config.transform_js;
|
|
@@ -112746,6 +113571,7 @@ class CommandCheckProvider extends check_provider_interface_1.CheckProvider {
|
|
|
112746
113571
|
// Keep raw output for transforms
|
|
112747
113572
|
const rawOutput = stdout.trim();
|
|
112748
113573
|
// Try to parse output as JSON for default behavior
|
|
113574
|
+
// no debug
|
|
112749
113575
|
let output = rawOutput;
|
|
112750
113576
|
try {
|
|
112751
113577
|
// Attempt to parse as JSON
|
|
@@ -112755,38 +113581,40 @@ class CommandCheckProvider extends check_provider_interface_1.CheckProvider {
|
|
|
112755
113581
|
}
|
|
112756
113582
|
catch {
|
|
112757
113583
|
// Try to extract JSON from the end of output (for commands with debug logs)
|
|
112758
|
-
const
|
|
112759
|
-
if (
|
|
113584
|
+
const extractedTail = this.extractJsonFromEnd(rawOutput);
|
|
113585
|
+
if (extractedTail) {
|
|
112760
113586
|
try {
|
|
112761
|
-
output = JSON.parse(
|
|
112762
|
-
logger_1.logger.debug(`🔧 Debug: Extracted and parsed JSON from end of output (${extracted.length} chars from ${rawOutput.length} total)`);
|
|
112763
|
-
logger_1.logger.debug(`🔧 Debug: Extracted JSON content: ${extracted.slice(0, 200)}`);
|
|
113587
|
+
output = JSON.parse(extractedTail);
|
|
112764
113588
|
}
|
|
112765
|
-
catch
|
|
112766
|
-
// Extraction found something but it's not valid JSON
|
|
112767
|
-
logger_1.logger.debug(`🔧 Debug: Extracted text is not valid JSON: ${parseError instanceof Error ? parseError.message : 'Unknown error'}`);
|
|
113589
|
+
catch {
|
|
112768
113590
|
output = rawOutput;
|
|
112769
113591
|
}
|
|
112770
113592
|
}
|
|
112771
113593
|
else {
|
|
112772
|
-
//
|
|
112773
|
-
|
|
112774
|
-
|
|
112775
|
-
|
|
112776
|
-
|
|
112777
|
-
|
|
112778
|
-
|
|
112779
|
-
|
|
112780
|
-
|
|
112781
|
-
|
|
112782
|
-
|
|
112783
|
-
|
|
113594
|
+
// Try to extract any balanced JSON substring anywhere
|
|
113595
|
+
const extractedAny = this.extractJsonAnywhere(rawOutput);
|
|
113596
|
+
if (extractedAny) {
|
|
113597
|
+
try {
|
|
113598
|
+
output = JSON.parse(extractedAny);
|
|
113599
|
+
}
|
|
113600
|
+
catch {
|
|
113601
|
+
output = rawOutput;
|
|
113602
|
+
}
|
|
113603
|
+
}
|
|
113604
|
+
else {
|
|
113605
|
+
// Last resort: detect common boolean flags like error:true or error=false for fail_if gating
|
|
113606
|
+
const m = /\berror\b\s*[:=]\s*(true|false)/i.exec(rawOutput);
|
|
113607
|
+
if (m) {
|
|
113608
|
+
output = { error: m[1].toLowerCase() === 'true' };
|
|
113609
|
+
}
|
|
113610
|
+
else {
|
|
113611
|
+
output = rawOutput;
|
|
113612
|
+
}
|
|
112784
113613
|
}
|
|
112785
|
-
}
|
|
112786
|
-
catch {
|
|
112787
|
-
// Ignore logging errors
|
|
112788
113614
|
}
|
|
112789
113615
|
}
|
|
113616
|
+
// Log the parsed structure for debugging
|
|
113617
|
+
// no debug
|
|
112790
113618
|
// Apply transform if specified (Liquid or JavaScript)
|
|
112791
113619
|
let finalOutput = output;
|
|
112792
113620
|
// First apply Liquid transform if present
|
|
@@ -112839,64 +113667,147 @@ class CommandCheckProvider extends check_provider_interface_1.CheckProvider {
|
|
|
112839
113667
|
// Compile and execute the JavaScript expression
|
|
112840
113668
|
// Use direct property access instead of destructuring to avoid syntax issues
|
|
112841
113669
|
const trimmedTransform = transformJs.trim();
|
|
112842
|
-
|
|
112843
|
-
|
|
112844
|
-
|
|
112845
|
-
|
|
112846
|
-
|
|
112847
|
-
|
|
112848
|
-
|
|
112849
|
-
|
|
112850
|
-
|
|
112851
|
-
|
|
112852
|
-
|
|
112853
|
-
|
|
112854
|
-
|
|
112855
|
-
else {
|
|
112856
|
-
transformExpression = `(${trimmedTransform})`;
|
|
112857
|
-
}
|
|
113670
|
+
// Build a safe function body that supports statements + implicit last-expression return.
|
|
113671
|
+
const buildBodyWithReturn = (raw) => {
|
|
113672
|
+
const t = raw.trim();
|
|
113673
|
+
// Find last non-empty line
|
|
113674
|
+
const lines = t.split(/\n/);
|
|
113675
|
+
let i = lines.length - 1;
|
|
113676
|
+
while (i >= 0 && lines[i].trim().length === 0)
|
|
113677
|
+
i--;
|
|
113678
|
+
if (i < 0)
|
|
113679
|
+
return 'return undefined;';
|
|
113680
|
+
const lastLine = lines[i].trim();
|
|
113681
|
+
if (/^return\b/i.test(lastLine)) {
|
|
113682
|
+
return t;
|
|
112858
113683
|
}
|
|
112859
|
-
|
|
112860
|
-
|
|
112861
|
-
|
|
112862
|
-
|
|
113684
|
+
const idx = t.lastIndexOf(lastLine);
|
|
113685
|
+
const head = idx >= 0 ? t.slice(0, idx) : '';
|
|
113686
|
+
const lastExpr = lastLine.replace(/;\s*$/, '');
|
|
113687
|
+
return `${head}\nreturn (${lastExpr});`;
|
|
113688
|
+
};
|
|
113689
|
+
const bodyWithReturn = buildBodyWithReturn(trimmedTransform);
|
|
112863
113690
|
const code = `
|
|
112864
113691
|
const output = scope.output;
|
|
112865
113692
|
const pr = scope.pr;
|
|
112866
113693
|
const files = scope.files;
|
|
112867
113694
|
const outputs = scope.outputs;
|
|
112868
113695
|
const env = scope.env;
|
|
112869
|
-
const log = (...args) => {
|
|
112870
|
-
|
|
112871
|
-
|
|
112872
|
-
|
|
113696
|
+
const log = (...args) => { console.log('🔍 Debug:', ...args); };
|
|
113697
|
+
const __result = (function(){
|
|
113698
|
+
${bodyWithReturn}
|
|
113699
|
+
})();
|
|
113700
|
+
return __result;
|
|
112873
113701
|
`;
|
|
113702
|
+
// Execute user code exclusively inside the sandbox
|
|
113703
|
+
if (!this.sandbox) {
|
|
113704
|
+
this.sandbox = this.createSecureSandbox();
|
|
113705
|
+
}
|
|
113706
|
+
// Try to serialize result to JSON string inside sandbox to preserve primitives like booleans
|
|
113707
|
+
let parsedFromSandboxJson = undefined;
|
|
112874
113708
|
try {
|
|
112875
|
-
|
|
112876
|
-
|
|
113709
|
+
const stringifyCode = `
|
|
113710
|
+
const output = scope.output;
|
|
113711
|
+
const pr = scope.pr;
|
|
113712
|
+
const files = scope.files;
|
|
113713
|
+
const outputs = scope.outputs;
|
|
113714
|
+
const env = scope.env;
|
|
113715
|
+
const log = (...args) => { console.log('🔍 Debug:', ...args); };
|
|
113716
|
+
const __ret = (function(){
|
|
113717
|
+
${bodyWithReturn}
|
|
113718
|
+
})();
|
|
113719
|
+
return typeof __ret === 'object' && __ret !== null ? JSON.stringify(__ret) : null;
|
|
113720
|
+
`;
|
|
113721
|
+
const stringifyExec = this.sandbox.compile(stringifyCode);
|
|
113722
|
+
const jsonStr = stringifyExec({ scope: jsContext }).run();
|
|
113723
|
+
if (typeof jsonStr === 'string' && jsonStr.trim().startsWith('{')) {
|
|
113724
|
+
parsedFromSandboxJson = JSON.parse(jsonStr);
|
|
113725
|
+
}
|
|
112877
113726
|
}
|
|
112878
|
-
catch {
|
|
112879
|
-
|
|
113727
|
+
catch { }
|
|
113728
|
+
if (parsedFromSandboxJson !== undefined) {
|
|
113729
|
+
finalOutput = parsedFromSandboxJson;
|
|
112880
113730
|
}
|
|
112881
|
-
|
|
112882
|
-
|
|
113731
|
+
else {
|
|
113732
|
+
const exec = this.sandbox.compile(code);
|
|
113733
|
+
finalOutput = exec({ scope: jsContext }).run();
|
|
112883
113734
|
}
|
|
112884
|
-
|
|
112885
|
-
|
|
112886
|
-
logger_1.logger.verbose(`✓ Applied JavaScript transform successfully`);
|
|
113735
|
+
// Fallback: if sandbox could not preserve primitives (e.g., booleans lost),
|
|
113736
|
+
// attempt to re-evaluate the transform in a locked Node VM context to get plain JS values.
|
|
112887
113737
|
try {
|
|
112888
|
-
|
|
112889
|
-
|
|
113738
|
+
if (finalOutput &&
|
|
113739
|
+
typeof finalOutput === 'object' &&
|
|
113740
|
+
!Array.isArray(finalOutput) &&
|
|
113741
|
+
(finalOutput.error === undefined ||
|
|
113742
|
+
finalOutput.issues === undefined)) {
|
|
113743
|
+
const vm = await Promise.resolve().then(() => __importStar(__nccwpck_require__(30714)));
|
|
113744
|
+
const vmContext = vm.createContext({ scope: jsContext });
|
|
113745
|
+
const vmCode = `
|
|
113746
|
+
(function(){
|
|
113747
|
+
const output = scope.output; const pr = scope.pr; const files = scope.files; const outputs = scope.outputs; const env = scope.env; const log = ()=>{};
|
|
113748
|
+
${bodyWithReturn}
|
|
113749
|
+
})()
|
|
113750
|
+
`;
|
|
113751
|
+
const vmResult = vm.runInContext(vmCode, vmContext, { timeout: 1000 });
|
|
113752
|
+
if (vmResult && typeof vmResult === 'object') {
|
|
113753
|
+
finalOutput = vmResult;
|
|
113754
|
+
}
|
|
113755
|
+
}
|
|
112890
113756
|
}
|
|
112891
|
-
catch {
|
|
112892
|
-
|
|
112893
|
-
|
|
112894
|
-
|
|
113757
|
+
catch { }
|
|
113758
|
+
// Create a plain JSON snapshot of the transform result to avoid proxy/getter surprises
|
|
113759
|
+
// Prefer JSON stringify inside the sandbox realm (so it knows how to serialize its own objects),
|
|
113760
|
+
// then fall back to host-side JSON clone and finally to a shallow copy of own enumerable properties.
|
|
113761
|
+
let finalSnapshot = null;
|
|
113762
|
+
try {
|
|
113763
|
+
if (finalOutput && typeof finalOutput === 'object' && !Array.isArray(finalOutput)) {
|
|
113764
|
+
// Try realm-local stringify first
|
|
113765
|
+
try {
|
|
113766
|
+
const stringifyExec = this.sandbox.compile('return JSON.stringify(scope.obj);');
|
|
113767
|
+
const jsonStr = stringifyExec({ obj: finalOutput }).run();
|
|
113768
|
+
if (typeof jsonStr === 'string' && jsonStr.trim().startsWith('{')) {
|
|
113769
|
+
finalSnapshot = JSON.parse(jsonStr);
|
|
113770
|
+
}
|
|
113771
|
+
}
|
|
113772
|
+
catch { }
|
|
113773
|
+
if (!finalSnapshot) {
|
|
113774
|
+
try {
|
|
113775
|
+
finalSnapshot = JSON.parse(JSON.stringify(finalOutput));
|
|
113776
|
+
}
|
|
113777
|
+
catch { }
|
|
113778
|
+
}
|
|
113779
|
+
if (!finalSnapshot) {
|
|
113780
|
+
const tmp = {};
|
|
113781
|
+
for (const k of Object.keys(finalOutput)) {
|
|
113782
|
+
tmp[k] = finalOutput[k];
|
|
113783
|
+
}
|
|
113784
|
+
finalSnapshot = tmp;
|
|
113785
|
+
}
|
|
112895
113786
|
}
|
|
112896
|
-
|
|
112897
|
-
|
|
113787
|
+
}
|
|
113788
|
+
catch { }
|
|
113789
|
+
// @ts-ignore store for later extraction path
|
|
113790
|
+
this.__lastTransformSnapshot = finalSnapshot;
|
|
113791
|
+
try {
|
|
113792
|
+
const isObj = finalOutput && typeof finalOutput === 'object' && !Array.isArray(finalOutput);
|
|
113793
|
+
const keys = isObj
|
|
113794
|
+
? Object.keys(finalOutput).join(',')
|
|
113795
|
+
: typeof finalOutput;
|
|
113796
|
+
logger_1.logger.debug(` transform_js: output typeof=${Array.isArray(finalOutput) ? 'array' : typeof finalOutput} keys=${keys}`);
|
|
113797
|
+
if (isObj && finalOutput.issues) {
|
|
113798
|
+
const mi = finalOutput.issues;
|
|
113799
|
+
logger_1.logger.debug(` transform_js: issues typeof=${Array.isArray(mi) ? 'array' : typeof mi} len=${(mi && mi.length) || 0}`);
|
|
112898
113800
|
}
|
|
113801
|
+
try {
|
|
113802
|
+
if (isObj)
|
|
113803
|
+
logger_1.logger.debug(` transform_js: error value=${String(finalOutput.error)}`);
|
|
113804
|
+
}
|
|
113805
|
+
catch { }
|
|
112899
113806
|
}
|
|
113807
|
+
catch { }
|
|
113808
|
+
logger_1.logger.verbose(`✓ Applied JavaScript transform successfully`);
|
|
113809
|
+
// Already normalized in sandbox result
|
|
113810
|
+
// no debug
|
|
112900
113811
|
}
|
|
112901
113812
|
catch (error) {
|
|
112902
113813
|
logger_1.logger.error(`✗ Failed to apply JavaScript transform: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
@@ -112915,15 +113826,205 @@ class CommandCheckProvider extends check_provider_interface_1.CheckProvider {
|
|
|
112915
113826
|
}
|
|
112916
113827
|
}
|
|
112917
113828
|
// Extract structured issues when the command returns them (skip for forEach parents)
|
|
113829
|
+
// no debug
|
|
112918
113830
|
let issues = [];
|
|
112919
113831
|
let outputForDependents = finalOutput;
|
|
113832
|
+
// Capture a shallow snapshot created earlier if available (within transform_js path)
|
|
113833
|
+
// @ts-ignore - finalSnapshot is defined in the transform_js scope above when applicable
|
|
113834
|
+
// @ts-ignore retrieve snapshot captured after transform_js (if any)
|
|
113835
|
+
const snapshotForExtraction = this.__lastTransformSnapshot || null;
|
|
113836
|
+
try {
|
|
113837
|
+
if (snapshotForExtraction) {
|
|
113838
|
+
logger_1.logger.debug(` provider: snapshot keys=${Object.keys(snapshotForExtraction).join(',')}`);
|
|
113839
|
+
}
|
|
113840
|
+
else {
|
|
113841
|
+
logger_1.logger.debug(` provider: snapshot is null`);
|
|
113842
|
+
}
|
|
113843
|
+
}
|
|
113844
|
+
catch { }
|
|
113845
|
+
// Some shells may wrap JSON output inside a one-element array due to quoting.
|
|
113846
|
+
// If we see a single-element array containing a JSON string or object, unwrap it.
|
|
113847
|
+
try {
|
|
113848
|
+
if (Array.isArray(outputForDependents) && outputForDependents.length === 1) {
|
|
113849
|
+
const first = outputForDependents[0];
|
|
113850
|
+
if (typeof first === 'string') {
|
|
113851
|
+
try {
|
|
113852
|
+
outputForDependents = JSON.parse(first);
|
|
113853
|
+
}
|
|
113854
|
+
catch { }
|
|
113855
|
+
}
|
|
113856
|
+
else if (first && typeof first === 'object') {
|
|
113857
|
+
outputForDependents = first;
|
|
113858
|
+
}
|
|
113859
|
+
}
|
|
113860
|
+
}
|
|
113861
|
+
catch { }
|
|
112920
113862
|
let content;
|
|
112921
113863
|
let extracted = null;
|
|
112922
113864
|
const trimmedRawOutput = typeof rawOutput === 'string' ? rawOutput.trim() : undefined;
|
|
112923
113865
|
const commandConfig = config;
|
|
112924
113866
|
const isForEachParent = commandConfig.forEach === true;
|
|
112925
113867
|
if (!isForEachParent) {
|
|
112926
|
-
|
|
113868
|
+
// Generic: if transform output is an object and contains an 'issues' field,
|
|
113869
|
+
// expose all other fields to dependents regardless of whether we successfully
|
|
113870
|
+
// normalized the issues array. This preserves flags like 'error' for fail_if.
|
|
113871
|
+
try {
|
|
113872
|
+
const baseObj = (snapshotForExtraction || finalOutput);
|
|
113873
|
+
if (baseObj &&
|
|
113874
|
+
typeof baseObj === 'object' &&
|
|
113875
|
+
Object.prototype.hasOwnProperty.call(baseObj, 'issues')) {
|
|
113876
|
+
const remaining = { ...baseObj };
|
|
113877
|
+
delete remaining.issues;
|
|
113878
|
+
outputForDependents = Object.keys(remaining).length > 0 ? remaining : undefined;
|
|
113879
|
+
try {
|
|
113880
|
+
const k = outputForDependents && typeof outputForDependents === 'object'
|
|
113881
|
+
? Object.keys(outputForDependents).join(',')
|
|
113882
|
+
: String(outputForDependents);
|
|
113883
|
+
logger_1.logger.debug(` provider: generic-remaining keys=${k}`);
|
|
113884
|
+
}
|
|
113885
|
+
catch { }
|
|
113886
|
+
}
|
|
113887
|
+
}
|
|
113888
|
+
catch { }
|
|
113889
|
+
// Fast path for transform_js objects that include an issues array (realm-agnostic)
|
|
113890
|
+
const objForExtraction = (snapshotForExtraction || finalOutput);
|
|
113891
|
+
if (objForExtraction && typeof objForExtraction === 'object') {
|
|
113892
|
+
try {
|
|
113893
|
+
const rec = objForExtraction;
|
|
113894
|
+
const maybeIssues = rec.issues;
|
|
113895
|
+
const toPlainArray = (v) => {
|
|
113896
|
+
if (Array.isArray(v))
|
|
113897
|
+
return v;
|
|
113898
|
+
try {
|
|
113899
|
+
if (v && typeof v === 'object' && typeof v[Symbol.iterator] === 'function') {
|
|
113900
|
+
return Array.from(v);
|
|
113901
|
+
}
|
|
113902
|
+
}
|
|
113903
|
+
catch { }
|
|
113904
|
+
const len = Number((v || {}).length);
|
|
113905
|
+
if (Number.isFinite(len) && len >= 0) {
|
|
113906
|
+
const arr = [];
|
|
113907
|
+
for (let i = 0; i < len; i++)
|
|
113908
|
+
arr.push(v[i]);
|
|
113909
|
+
return arr;
|
|
113910
|
+
}
|
|
113911
|
+
try {
|
|
113912
|
+
const cloned = JSON.parse(JSON.stringify(v));
|
|
113913
|
+
return Array.isArray(cloned) ? cloned : null;
|
|
113914
|
+
}
|
|
113915
|
+
catch {
|
|
113916
|
+
return null;
|
|
113917
|
+
}
|
|
113918
|
+
};
|
|
113919
|
+
try {
|
|
113920
|
+
const ctor = maybeIssues && maybeIssues.constructor
|
|
113921
|
+
? maybeIssues.constructor.name
|
|
113922
|
+
: 'unknown';
|
|
113923
|
+
logger_1.logger.debug(` provider: issues inspect typeof=${typeof maybeIssues} Array.isArray=${Array.isArray(maybeIssues)} ctor=${ctor} keys=${Object.keys((maybeIssues || {})).join(',')}`);
|
|
113924
|
+
}
|
|
113925
|
+
catch { }
|
|
113926
|
+
const arr = toPlainArray(maybeIssues);
|
|
113927
|
+
if (arr) {
|
|
113928
|
+
const norm = this.normalizeIssueArray(arr);
|
|
113929
|
+
if (norm) {
|
|
113930
|
+
issues = norm;
|
|
113931
|
+
const remaining = { ...rec };
|
|
113932
|
+
delete remaining.issues;
|
|
113933
|
+
outputForDependents = Object.keys(remaining).length > 0 ? remaining : undefined;
|
|
113934
|
+
try {
|
|
113935
|
+
const keys = outputForDependents && typeof outputForDependents === 'object'
|
|
113936
|
+
? Object.keys(outputForDependents).join(',')
|
|
113937
|
+
: String(outputForDependents);
|
|
113938
|
+
logger_1.logger.info(` provider: fast-path issues=${issues.length} remaining keys=${keys}`);
|
|
113939
|
+
}
|
|
113940
|
+
catch { }
|
|
113941
|
+
}
|
|
113942
|
+
else {
|
|
113943
|
+
try {
|
|
113944
|
+
logger_1.logger.info(' provider: fast-path norm failed');
|
|
113945
|
+
}
|
|
113946
|
+
catch { }
|
|
113947
|
+
}
|
|
113948
|
+
}
|
|
113949
|
+
else {
|
|
113950
|
+
try {
|
|
113951
|
+
logger_1.logger.info(' provider: fast-path arr unavailable');
|
|
113952
|
+
}
|
|
113953
|
+
catch { }
|
|
113954
|
+
}
|
|
113955
|
+
}
|
|
113956
|
+
catch { }
|
|
113957
|
+
}
|
|
113958
|
+
// Normalize extraction target: unwrap one-element arrays like ["{...}"] or [{...}]
|
|
113959
|
+
let extractionTarget = snapshotForExtraction || finalOutput;
|
|
113960
|
+
try {
|
|
113961
|
+
if (Array.isArray(extractionTarget) && extractionTarget.length === 1) {
|
|
113962
|
+
const first = extractionTarget[0];
|
|
113963
|
+
if (typeof first === 'string') {
|
|
113964
|
+
try {
|
|
113965
|
+
extractionTarget = JSON.parse(first);
|
|
113966
|
+
}
|
|
113967
|
+
catch {
|
|
113968
|
+
extractionTarget = first;
|
|
113969
|
+
}
|
|
113970
|
+
}
|
|
113971
|
+
else if (first && typeof first === 'object') {
|
|
113972
|
+
extractionTarget = first;
|
|
113973
|
+
}
|
|
113974
|
+
}
|
|
113975
|
+
}
|
|
113976
|
+
catch { }
|
|
113977
|
+
extracted = this.extractIssuesFromOutput(extractionTarget);
|
|
113978
|
+
try {
|
|
113979
|
+
if (extractionTarget !== (snapshotForExtraction || finalOutput)) {
|
|
113980
|
+
finalOutput = extractionTarget;
|
|
113981
|
+
}
|
|
113982
|
+
}
|
|
113983
|
+
catch { }
|
|
113984
|
+
// no debug
|
|
113985
|
+
// Handle cross-realm Arrays from sandbox: issues may look like an array but fail Array.isArray
|
|
113986
|
+
if (!extracted && finalOutput && typeof finalOutput === 'object') {
|
|
113987
|
+
try {
|
|
113988
|
+
const rec = finalOutput;
|
|
113989
|
+
const maybeIssues = rec.issues;
|
|
113990
|
+
if (maybeIssues && typeof maybeIssues === 'object') {
|
|
113991
|
+
let arr = null;
|
|
113992
|
+
// Prefer iterator if present
|
|
113993
|
+
try {
|
|
113994
|
+
if (typeof maybeIssues[Symbol.iterator] === 'function') {
|
|
113995
|
+
arr = Array.from(maybeIssues);
|
|
113996
|
+
}
|
|
113997
|
+
}
|
|
113998
|
+
catch { }
|
|
113999
|
+
// Fallback to length-based copy
|
|
114000
|
+
if (!arr) {
|
|
114001
|
+
const len = Number(maybeIssues.length);
|
|
114002
|
+
if (Number.isFinite(len) && len >= 0) {
|
|
114003
|
+
arr = [];
|
|
114004
|
+
for (let i = 0; i < len; i++)
|
|
114005
|
+
arr.push(maybeIssues[i]);
|
|
114006
|
+
}
|
|
114007
|
+
}
|
|
114008
|
+
// Last resort: JSON clone
|
|
114009
|
+
if (!arr) {
|
|
114010
|
+
try {
|
|
114011
|
+
arr = JSON.parse(JSON.stringify(maybeIssues));
|
|
114012
|
+
}
|
|
114013
|
+
catch { }
|
|
114014
|
+
}
|
|
114015
|
+
if (arr && Array.isArray(arr)) {
|
|
114016
|
+
const norm = this.normalizeIssueArray(arr);
|
|
114017
|
+
if (norm) {
|
|
114018
|
+
issues = norm;
|
|
114019
|
+
const remaining = { ...rec };
|
|
114020
|
+
delete remaining.issues;
|
|
114021
|
+
outputForDependents = Object.keys(remaining).length > 0 ? remaining : undefined;
|
|
114022
|
+
}
|
|
114023
|
+
}
|
|
114024
|
+
}
|
|
114025
|
+
}
|
|
114026
|
+
catch { }
|
|
114027
|
+
}
|
|
112927
114028
|
if (!extracted && typeof finalOutput === 'string') {
|
|
112928
114029
|
// Attempt to parse string output as JSON and extract issues again
|
|
112929
114030
|
try {
|
|
@@ -112932,15 +114033,52 @@ class CommandCheckProvider extends check_provider_interface_1.CheckProvider {
|
|
|
112932
114033
|
if (extracted) {
|
|
112933
114034
|
issues = extracted.issues;
|
|
112934
114035
|
outputForDependents = extracted.remainingOutput;
|
|
114036
|
+
// If remainingOutput carries a content field, pick it up
|
|
114037
|
+
if (typeof extracted.remainingOutput === 'object' &&
|
|
114038
|
+
extracted.remainingOutput !== null &&
|
|
114039
|
+
typeof extracted.remainingOutput.content === 'string') {
|
|
114040
|
+
const c = String(extracted.remainingOutput.content).trim();
|
|
114041
|
+
if (c)
|
|
114042
|
+
content = c;
|
|
114043
|
+
}
|
|
112935
114044
|
}
|
|
112936
114045
|
}
|
|
112937
114046
|
catch {
|
|
112938
|
-
//
|
|
114047
|
+
// Try to salvage JSON from anywhere within the string (stripped logs/ansi)
|
|
114048
|
+
try {
|
|
114049
|
+
const any = this.extractJsonAnywhere(finalOutput);
|
|
114050
|
+
if (any) {
|
|
114051
|
+
const parsed = JSON.parse(any);
|
|
114052
|
+
extracted = this.extractIssuesFromOutput(parsed);
|
|
114053
|
+
if (extracted) {
|
|
114054
|
+
issues = extracted.issues;
|
|
114055
|
+
outputForDependents = extracted.remainingOutput;
|
|
114056
|
+
if (typeof extracted.remainingOutput === 'object' &&
|
|
114057
|
+
extracted.remainingOutput !== null &&
|
|
114058
|
+
typeof extracted.remainingOutput.content === 'string') {
|
|
114059
|
+
const c = String(extracted.remainingOutput.content).trim();
|
|
114060
|
+
if (c)
|
|
114061
|
+
content = c;
|
|
114062
|
+
}
|
|
114063
|
+
}
|
|
114064
|
+
}
|
|
114065
|
+
}
|
|
114066
|
+
catch {
|
|
114067
|
+
// leave as-is
|
|
114068
|
+
}
|
|
112939
114069
|
}
|
|
112940
114070
|
}
|
|
112941
114071
|
else if (extracted) {
|
|
112942
114072
|
issues = extracted.issues;
|
|
112943
114073
|
outputForDependents = extracted.remainingOutput;
|
|
114074
|
+
// Also propagate embedded content when remainingOutput is an object { content, ... }
|
|
114075
|
+
if (typeof extracted.remainingOutput === 'object' &&
|
|
114076
|
+
extracted.remainingOutput !== null &&
|
|
114077
|
+
typeof extracted.remainingOutput.content === 'string') {
|
|
114078
|
+
const c = String(extracted.remainingOutput.content).trim();
|
|
114079
|
+
if (c)
|
|
114080
|
+
content = c;
|
|
114081
|
+
}
|
|
112944
114082
|
}
|
|
112945
114083
|
if (!issues.length && this.shouldTreatAsTextOutput(trimmedRawOutput)) {
|
|
112946
114084
|
content = trimmedRawOutput;
|
|
@@ -112951,26 +114089,204 @@ class CommandCheckProvider extends check_provider_interface_1.CheckProvider {
|
|
|
112951
114089
|
content = trimmed;
|
|
112952
114090
|
}
|
|
112953
114091
|
}
|
|
114092
|
+
// Generic fallback: if issues are still empty, try to parse raw stdout as JSON and extract issues.
|
|
114093
|
+
if (!issues.length && typeof trimmedRawOutput === 'string') {
|
|
114094
|
+
try {
|
|
114095
|
+
const tryParsed = JSON.parse(trimmedRawOutput);
|
|
114096
|
+
const reextract = this.extractIssuesFromOutput(tryParsed);
|
|
114097
|
+
if (reextract && reextract.issues && reextract.issues.length) {
|
|
114098
|
+
issues = reextract.issues;
|
|
114099
|
+
if (!outputForDependents && reextract.remainingOutput) {
|
|
114100
|
+
outputForDependents = reextract.remainingOutput;
|
|
114101
|
+
}
|
|
114102
|
+
}
|
|
114103
|
+
else if (Array.isArray(tryParsed)) {
|
|
114104
|
+
// Treat parsed array as potential issues array or array of { issues: [...] }
|
|
114105
|
+
const first = tryParsed[0];
|
|
114106
|
+
if (first && typeof first === 'object' && Array.isArray(first.issues)) {
|
|
114107
|
+
const merged = [];
|
|
114108
|
+
for (const el of tryParsed) {
|
|
114109
|
+
if (el && typeof el === 'object' && Array.isArray(el.issues)) {
|
|
114110
|
+
merged.push(...el.issues);
|
|
114111
|
+
}
|
|
114112
|
+
}
|
|
114113
|
+
const flat = this.normalizeIssueArray(merged);
|
|
114114
|
+
if (flat)
|
|
114115
|
+
issues = flat;
|
|
114116
|
+
}
|
|
114117
|
+
else {
|
|
114118
|
+
// Try to parse string elements into JSON objects and extract
|
|
114119
|
+
const converted = [];
|
|
114120
|
+
for (const el of tryParsed) {
|
|
114121
|
+
if (typeof el === 'string') {
|
|
114122
|
+
try {
|
|
114123
|
+
const obj = JSON.parse(el);
|
|
114124
|
+
converted.push(obj);
|
|
114125
|
+
}
|
|
114126
|
+
catch {
|
|
114127
|
+
// keep as-is
|
|
114128
|
+
}
|
|
114129
|
+
}
|
|
114130
|
+
else {
|
|
114131
|
+
converted.push(el);
|
|
114132
|
+
}
|
|
114133
|
+
}
|
|
114134
|
+
const flat = this.normalizeIssueArray(converted);
|
|
114135
|
+
if (flat)
|
|
114136
|
+
issues = flat;
|
|
114137
|
+
}
|
|
114138
|
+
}
|
|
114139
|
+
}
|
|
114140
|
+
catch { }
|
|
114141
|
+
if (!issues.length) {
|
|
114142
|
+
try {
|
|
114143
|
+
const any = this.extractJsonAnywhere(trimmedRawOutput);
|
|
114144
|
+
if (any) {
|
|
114145
|
+
const tryParsed = JSON.parse(any);
|
|
114146
|
+
const reextract = this.extractIssuesFromOutput(tryParsed);
|
|
114147
|
+
if (reextract && reextract.issues && reextract.issues.length) {
|
|
114148
|
+
issues = reextract.issues;
|
|
114149
|
+
if (!outputForDependents && reextract.remainingOutput) {
|
|
114150
|
+
outputForDependents = reextract.remainingOutput;
|
|
114151
|
+
}
|
|
114152
|
+
}
|
|
114153
|
+
}
|
|
114154
|
+
}
|
|
114155
|
+
catch { }
|
|
114156
|
+
}
|
|
114157
|
+
}
|
|
114158
|
+
// Preserve all primitive flags (boolean/number/string) from original transform output
|
|
114159
|
+
try {
|
|
114160
|
+
const srcObj = (snapshotForExtraction || finalOutput);
|
|
114161
|
+
if (outputForDependents &&
|
|
114162
|
+
typeof outputForDependents === 'object' &&
|
|
114163
|
+
srcObj &&
|
|
114164
|
+
typeof srcObj === 'object') {
|
|
114165
|
+
for (const k of Object.keys(srcObj)) {
|
|
114166
|
+
const v = srcObj[k];
|
|
114167
|
+
if (typeof v === 'boolean' || typeof v === 'number' || typeof v === 'string') {
|
|
114168
|
+
outputForDependents[k] = v;
|
|
114169
|
+
}
|
|
114170
|
+
}
|
|
114171
|
+
}
|
|
114172
|
+
}
|
|
114173
|
+
catch { }
|
|
114174
|
+
// Normalize output object to a plain shallow object (avoid JSON stringify drop of false booleans)
|
|
114175
|
+
try {
|
|
114176
|
+
if (outputForDependents &&
|
|
114177
|
+
typeof outputForDependents === 'object' &&
|
|
114178
|
+
!Array.isArray(outputForDependents)) {
|
|
114179
|
+
const plain = {};
|
|
114180
|
+
for (const k of Object.keys(outputForDependents)) {
|
|
114181
|
+
plain[k] = outputForDependents[k];
|
|
114182
|
+
}
|
|
114183
|
+
outputForDependents = plain;
|
|
114184
|
+
}
|
|
114185
|
+
}
|
|
114186
|
+
catch { }
|
|
112954
114187
|
}
|
|
112955
114188
|
if (!content && this.shouldTreatAsTextOutput(trimmedRawOutput) && !isForEachParent) {
|
|
112956
114189
|
content = trimmedRawOutput;
|
|
112957
114190
|
}
|
|
114191
|
+
// Normalize output object to plain JSON to avoid cross-realm proxy quirks
|
|
114192
|
+
try {
|
|
114193
|
+
if (outputForDependents && typeof outputForDependents === 'object') {
|
|
114194
|
+
outputForDependents = JSON.parse(JSON.stringify(outputForDependents));
|
|
114195
|
+
}
|
|
114196
|
+
}
|
|
114197
|
+
catch { }
|
|
114198
|
+
// Promote primitive flags from original transform output to top-level result fields (schema-agnostic)
|
|
114199
|
+
const promoted = {};
|
|
114200
|
+
try {
|
|
114201
|
+
const srcObj = (snapshotForExtraction || finalOutput);
|
|
114202
|
+
if (srcObj && typeof srcObj === 'object') {
|
|
114203
|
+
for (const k of Object.keys(srcObj)) {
|
|
114204
|
+
const v = srcObj[k];
|
|
114205
|
+
if (typeof v === 'boolean') {
|
|
114206
|
+
if (v === true && promoted[k] === undefined)
|
|
114207
|
+
promoted[k] = true;
|
|
114208
|
+
}
|
|
114209
|
+
else if ((typeof v === 'number' || typeof v === 'string') &&
|
|
114210
|
+
promoted[k] === undefined) {
|
|
114211
|
+
promoted[k] = v;
|
|
114212
|
+
}
|
|
114213
|
+
}
|
|
114214
|
+
}
|
|
114215
|
+
}
|
|
114216
|
+
catch { }
|
|
112958
114217
|
// Return the output and issues as part of the review summary so dependent checks can use them
|
|
112959
114218
|
const result = {
|
|
112960
114219
|
issues,
|
|
112961
114220
|
output: outputForDependents,
|
|
112962
114221
|
...(content ? { content } : {}),
|
|
114222
|
+
...promoted,
|
|
112963
114223
|
};
|
|
112964
|
-
|
|
112965
|
-
|
|
112966
|
-
|
|
112967
|
-
const
|
|
112968
|
-
|
|
114224
|
+
// Attach raw transform object only when transform_js was used (avoid polluting plain command outputs)
|
|
114225
|
+
try {
|
|
114226
|
+
if (transformJs) {
|
|
114227
|
+
const rawObj = (snapshotForExtraction || finalOutput);
|
|
114228
|
+
if (rawObj && typeof rawObj === 'object') {
|
|
114229
|
+
result.__raw = rawObj;
|
|
114230
|
+
}
|
|
112969
114231
|
}
|
|
112970
|
-
|
|
112971
|
-
|
|
114232
|
+
}
|
|
114233
|
+
catch { }
|
|
114234
|
+
// Final safeguard: ensure primitive flags from original transform output are present in result.output.
|
|
114235
|
+
// Do this without dropping explicit false values (important for fail_if like `output.error`).
|
|
114236
|
+
try {
|
|
114237
|
+
const srcObj = (snapshotForExtraction || finalOutput);
|
|
114238
|
+
const srcErr = (() => {
|
|
114239
|
+
try {
|
|
114240
|
+
if (snapshotForExtraction &&
|
|
114241
|
+
typeof snapshotForExtraction === 'object' &&
|
|
114242
|
+
snapshotForExtraction.error !== undefined) {
|
|
114243
|
+
return Boolean(snapshotForExtraction.error);
|
|
114244
|
+
}
|
|
114245
|
+
if (finalOutput &&
|
|
114246
|
+
typeof finalOutput === 'object' &&
|
|
114247
|
+
finalOutput.error !== undefined) {
|
|
114248
|
+
return Boolean(finalOutput.error);
|
|
114249
|
+
}
|
|
114250
|
+
}
|
|
114251
|
+
catch { }
|
|
114252
|
+
return undefined;
|
|
114253
|
+
})();
|
|
114254
|
+
const dst = result.output;
|
|
114255
|
+
if (srcObj && typeof srcObj === 'object' && dst && typeof dst === 'object') {
|
|
114256
|
+
try {
|
|
114257
|
+
logger_1.logger.debug(` provider: safeguard src.error typeof=${typeof srcObj.error} val=${String(srcObj.error)} dst.hasErrorBefore=${String(dst.error !== undefined)}`);
|
|
114258
|
+
}
|
|
114259
|
+
catch { }
|
|
114260
|
+
for (const k of Object.keys(srcObj)) {
|
|
114261
|
+
const v = srcObj[k];
|
|
114262
|
+
if (typeof v === 'boolean' || typeof v === 'number' || typeof v === 'string') {
|
|
114263
|
+
dst[k] = v;
|
|
114264
|
+
}
|
|
114265
|
+
}
|
|
114266
|
+
// Explicitly normalize a common flag used in tests/pipelines
|
|
114267
|
+
if (srcErr !== undefined && dst.error === undefined) {
|
|
114268
|
+
dst.error = srcErr;
|
|
114269
|
+
try {
|
|
114270
|
+
const k = Object.keys(dst).join(',');
|
|
114271
|
+
logger_1.logger.debug(` provider: safeguard merged error -> output keys=${k} val=${String(dst.error)}`);
|
|
114272
|
+
}
|
|
114273
|
+
catch { }
|
|
114274
|
+
}
|
|
114275
|
+
}
|
|
114276
|
+
}
|
|
114277
|
+
catch { }
|
|
114278
|
+
try {
|
|
114279
|
+
const out = result.output;
|
|
114280
|
+
if (out && typeof out === 'object') {
|
|
114281
|
+
const k = Object.keys(out).join(',');
|
|
114282
|
+
logger_1.logger.debug(` provider: return output keys=${k}`);
|
|
114283
|
+
}
|
|
114284
|
+
else {
|
|
114285
|
+
logger_1.logger.debug(` provider: return output type=${typeof out}`);
|
|
112972
114286
|
}
|
|
112973
114287
|
}
|
|
114288
|
+
catch { }
|
|
114289
|
+
// no debug
|
|
112974
114290
|
return result;
|
|
112975
114291
|
}
|
|
112976
114292
|
catch (error) {
|
|
@@ -113134,25 +114450,106 @@ class CommandCheckProvider extends check_provider_interface_1.CheckProvider {
|
|
|
113134
114450
|
* Looks for the last occurrence of { or [ and tries to parse from there
|
|
113135
114451
|
*/
|
|
113136
114452
|
extractJsonFromEnd(text) {
|
|
113137
|
-
//
|
|
113138
|
-
|
|
113139
|
-
|
|
113140
|
-
|
|
113141
|
-
|
|
113142
|
-
|
|
113143
|
-
|
|
113144
|
-
|
|
113145
|
-
|
|
113146
|
-
|
|
113147
|
-
|
|
113148
|
-
|
|
113149
|
-
|
|
113150
|
-
|
|
114453
|
+
// Robust strategy: find the last closing brace/bracket, then walk backwards to the matching opener
|
|
114454
|
+
const lastBrace = Math.max(text.lastIndexOf('}'), text.lastIndexOf(']'));
|
|
114455
|
+
if (lastBrace === -1)
|
|
114456
|
+
return null;
|
|
114457
|
+
// Scan backwards to find matching opener with a simple counter
|
|
114458
|
+
let open = 0;
|
|
114459
|
+
for (let i = lastBrace; i >= 0; i--) {
|
|
114460
|
+
const ch = text[i];
|
|
114461
|
+
if (ch === '}' || ch === ']')
|
|
114462
|
+
open++;
|
|
114463
|
+
else if (ch === '{' || ch === '[')
|
|
114464
|
+
open--;
|
|
114465
|
+
if (open === 0 && (ch === '{' || ch === '[')) {
|
|
114466
|
+
const candidate = text.slice(i, lastBrace + 1).trim();
|
|
114467
|
+
try {
|
|
114468
|
+
JSON.parse(candidate);
|
|
114469
|
+
return candidate;
|
|
114470
|
+
}
|
|
114471
|
+
catch {
|
|
114472
|
+
return null;
|
|
113151
114473
|
}
|
|
113152
114474
|
}
|
|
113153
114475
|
}
|
|
113154
114476
|
return null;
|
|
113155
114477
|
}
|
|
114478
|
+
// Extract any balanced JSON object/array substring from anywhere in the text
|
|
114479
|
+
extractJsonAnywhere(text) {
|
|
114480
|
+
const n = text.length;
|
|
114481
|
+
let best = null;
|
|
114482
|
+
for (let i = 0; i < n; i++) {
|
|
114483
|
+
const start = text[i];
|
|
114484
|
+
if (start !== '{' && start !== '[')
|
|
114485
|
+
continue;
|
|
114486
|
+
let open = 0;
|
|
114487
|
+
let inString = false;
|
|
114488
|
+
let escape = false;
|
|
114489
|
+
for (let j = i; j < n; j++) {
|
|
114490
|
+
const ch = text[j];
|
|
114491
|
+
if (escape) {
|
|
114492
|
+
escape = false;
|
|
114493
|
+
continue;
|
|
114494
|
+
}
|
|
114495
|
+
if (ch === '\\') {
|
|
114496
|
+
escape = true;
|
|
114497
|
+
continue;
|
|
114498
|
+
}
|
|
114499
|
+
if (ch === '"') {
|
|
114500
|
+
inString = !inString;
|
|
114501
|
+
continue;
|
|
114502
|
+
}
|
|
114503
|
+
if (inString)
|
|
114504
|
+
continue;
|
|
114505
|
+
if (ch === '{' || ch === '[')
|
|
114506
|
+
open++;
|
|
114507
|
+
else if (ch === '}' || ch === ']')
|
|
114508
|
+
open--;
|
|
114509
|
+
if (open === 0 && (ch === '}' || ch === ']')) {
|
|
114510
|
+
const candidate = text.slice(i, j + 1).trim();
|
|
114511
|
+
try {
|
|
114512
|
+
JSON.parse(candidate);
|
|
114513
|
+
best = candidate; // keep the last valid one we find
|
|
114514
|
+
}
|
|
114515
|
+
catch {
|
|
114516
|
+
// Try a loose-to-strict conversion (quote keys and barewords)
|
|
114517
|
+
const strict = this.looseJsonToStrict(candidate);
|
|
114518
|
+
if (strict) {
|
|
114519
|
+
try {
|
|
114520
|
+
JSON.parse(strict);
|
|
114521
|
+
best = strict;
|
|
114522
|
+
}
|
|
114523
|
+
catch { }
|
|
114524
|
+
}
|
|
114525
|
+
}
|
|
114526
|
+
break;
|
|
114527
|
+
}
|
|
114528
|
+
}
|
|
114529
|
+
}
|
|
114530
|
+
return best;
|
|
114531
|
+
}
|
|
114532
|
+
// Best-effort conversion of object-literal-like strings to strict JSON
|
|
114533
|
+
looseJsonToStrict(candidate) {
|
|
114534
|
+
try {
|
|
114535
|
+
let s = candidate.trim();
|
|
114536
|
+
// Convert single quotes to double quotes conservatively
|
|
114537
|
+
s = s.replace(/'/g, '"');
|
|
114538
|
+
// Quote unquoted keys: {key: ...} or ,key: ...
|
|
114539
|
+
s = s.replace(/([\{,]\s*)([A-Za-z_][A-Za-z0-9_-]*)\s*:/g, '$1"$2":');
|
|
114540
|
+
// Quote bareword values except true/false/null and numbers
|
|
114541
|
+
s = s.replace(/:\s*([A-Za-z_][A-Za-z0-9_-]*)\s*(?=[,}])/g, (m, word) => {
|
|
114542
|
+
const lw = String(word).toLowerCase();
|
|
114543
|
+
if (lw === 'true' || lw === 'false' || lw === 'null')
|
|
114544
|
+
return `:${lw}`;
|
|
114545
|
+
return `:"${word}"`;
|
|
114546
|
+
});
|
|
114547
|
+
return s;
|
|
114548
|
+
}
|
|
114549
|
+
catch {
|
|
114550
|
+
return null;
|
|
114551
|
+
}
|
|
114552
|
+
}
|
|
113156
114553
|
/**
|
|
113157
114554
|
* Recursively apply JSON-smart wrapper to outputs object values
|
|
113158
114555
|
*/
|
|
@@ -113202,6 +114599,14 @@ class CommandCheckProvider extends check_provider_interface_1.CheckProvider {
|
|
|
113202
114599
|
];
|
|
113203
114600
|
}
|
|
113204
114601
|
extractIssuesFromOutput(output) {
|
|
114602
|
+
try {
|
|
114603
|
+
logger_1.logger.info(` extractIssuesFromOutput: typeof=${Array.isArray(output) ? 'array' : typeof output}`);
|
|
114604
|
+
if (typeof output === 'object' && output) {
|
|
114605
|
+
const rec = output;
|
|
114606
|
+
logger_1.logger.info(` extractIssuesFromOutput: keys=${Object.keys(rec).join(',')} issuesIsArray=${Array.isArray(rec.issues)}`);
|
|
114607
|
+
}
|
|
114608
|
+
}
|
|
114609
|
+
catch { }
|
|
113205
114610
|
if (output === null || output === undefined) {
|
|
113206
114611
|
return null;
|
|
113207
114612
|
}
|
|
@@ -113210,9 +114615,30 @@ class CommandCheckProvider extends check_provider_interface_1.CheckProvider {
|
|
|
113210
114615
|
return null;
|
|
113211
114616
|
}
|
|
113212
114617
|
if (Array.isArray(output)) {
|
|
113213
|
-
|
|
113214
|
-
|
|
113215
|
-
|
|
114618
|
+
// Two supported shapes:
|
|
114619
|
+
// 1) Array<ReviewIssue-like>
|
|
114620
|
+
// 2) Array<{ issues: Array<ReviewIssue-like> }>
|
|
114621
|
+
const first = output[0];
|
|
114622
|
+
if (first &&
|
|
114623
|
+
typeof first === 'object' &&
|
|
114624
|
+
!Array.isArray(first.message) &&
|
|
114625
|
+
Array.isArray(first.issues)) {
|
|
114626
|
+
// flatten nested issues arrays
|
|
114627
|
+
const merged = [];
|
|
114628
|
+
for (const el of output) {
|
|
114629
|
+
if (el && typeof el === 'object' && Array.isArray(el.issues)) {
|
|
114630
|
+
merged.push(...el.issues);
|
|
114631
|
+
}
|
|
114632
|
+
}
|
|
114633
|
+
const flat = this.normalizeIssueArray(merged);
|
|
114634
|
+
if (flat)
|
|
114635
|
+
return { issues: flat, remainingOutput: undefined };
|
|
114636
|
+
}
|
|
114637
|
+
else {
|
|
114638
|
+
const issues = this.normalizeIssueArray(output);
|
|
114639
|
+
if (issues) {
|
|
114640
|
+
return { issues, remainingOutput: undefined };
|
|
114641
|
+
}
|
|
113216
114642
|
}
|
|
113217
114643
|
return null;
|
|
113218
114644
|
}
|
|
@@ -113343,11 +114769,36 @@ class CommandCheckProvider extends check_provider_interface_1.CheckProvider {
|
|
|
113343
114769
|
}
|
|
113344
114770
|
async renderCommandTemplate(template, context) {
|
|
113345
114771
|
try {
|
|
113346
|
-
|
|
114772
|
+
// Best-effort compatibility: allow double-quoted bracket keys inside Liquid tags.
|
|
114773
|
+
// e.g., {{ outputs["fetch-tickets"].key }} → {{ outputs['fetch-tickets'].key }}
|
|
114774
|
+
let tpl = template;
|
|
114775
|
+
if (tpl.includes('{{')) {
|
|
114776
|
+
tpl = tpl.replace(/\{\{([\s\S]*?)\}\}/g, (_m, inner) => {
|
|
114777
|
+
const fixed = String(inner).replace(/\[\"/g, "['").replace(/\"\]/g, "']");
|
|
114778
|
+
return `{{ ${fixed} }}`;
|
|
114779
|
+
});
|
|
114780
|
+
}
|
|
114781
|
+
let rendered = await this.liquid.parseAndRender(tpl, context);
|
|
114782
|
+
// If Liquid left unresolved tags (common when users write JS expressions inside {{ }}),
|
|
114783
|
+
// fall back to a safe JS-expression renderer for the remaining tags.
|
|
114784
|
+
if (/\{\{[\s\S]*?\}\}/.test(rendered)) {
|
|
114785
|
+
try {
|
|
114786
|
+
rendered = this.renderWithJsExpressions(rendered, context);
|
|
114787
|
+
}
|
|
114788
|
+
catch {
|
|
114789
|
+
// keep Liquid-rendered result as-is
|
|
114790
|
+
}
|
|
114791
|
+
}
|
|
114792
|
+
return rendered;
|
|
113347
114793
|
}
|
|
113348
114794
|
catch (error) {
|
|
113349
|
-
logger_1.logger.debug(`🔧 Debug: Liquid
|
|
113350
|
-
|
|
114795
|
+
logger_1.logger.debug(`🔧 Debug: Liquid templating failed, trying JS-expression fallback: ${error}`);
|
|
114796
|
+
try {
|
|
114797
|
+
return this.renderWithJsExpressions(template, context);
|
|
114798
|
+
}
|
|
114799
|
+
catch {
|
|
114800
|
+
return template;
|
|
114801
|
+
}
|
|
113351
114802
|
}
|
|
113352
114803
|
}
|
|
113353
114804
|
renderWithJsExpressions(template, context) {
|
|
@@ -113360,9 +114811,8 @@ class CommandCheckProvider extends check_provider_interface_1.CheckProvider {
|
|
|
113360
114811
|
const expressionRegex = /\{\{\s*([^{}]+?)\s*\}\}/g;
|
|
113361
114812
|
return template.replace(expressionRegex, (_match, expr) => {
|
|
113362
114813
|
const expression = String(expr).trim();
|
|
113363
|
-
if (!expression)
|
|
114814
|
+
if (!expression)
|
|
113364
114815
|
return '';
|
|
113365
|
-
}
|
|
113366
114816
|
try {
|
|
113367
114817
|
const evalCode = `
|
|
113368
114818
|
const pr = scope.pr;
|
|
@@ -113371,15 +114821,13 @@ class CommandCheckProvider extends check_provider_interface_1.CheckProvider {
|
|
|
113371
114821
|
const env = scope.env;
|
|
113372
114822
|
return (${expression});
|
|
113373
114823
|
`;
|
|
113374
|
-
if (!this.sandbox)
|
|
114824
|
+
if (!this.sandbox)
|
|
113375
114825
|
this.sandbox = this.createSecureSandbox();
|
|
113376
|
-
}
|
|
113377
114826
|
const evaluator = this.sandbox.compile(evalCode);
|
|
113378
114827
|
const result = evaluator({ scope }).run();
|
|
113379
114828
|
return result === undefined || result === null ? '' : String(result);
|
|
113380
114829
|
}
|
|
113381
|
-
catch
|
|
113382
|
-
logger_1.logger.debug(`🔧 Debug: Failed to evaluate expression: ${expression} - ${evaluationError}`);
|
|
114830
|
+
catch {
|
|
113383
114831
|
return '';
|
|
113384
114832
|
}
|
|
113385
114833
|
});
|
|
@@ -115856,6 +117304,14 @@ module.exports = require("node:util");
|
|
|
115856
117304
|
|
|
115857
117305
|
/***/ }),
|
|
115858
117306
|
|
|
117307
|
+
/***/ 30714:
|
|
117308
|
+
/***/ ((module) => {
|
|
117309
|
+
|
|
117310
|
+
"use strict";
|
|
117311
|
+
module.exports = require("node:vm");
|
|
117312
|
+
|
|
117313
|
+
/***/ }),
|
|
117314
|
+
|
|
115859
117315
|
/***/ 70857:
|
|
115860
117316
|
/***/ ((module) => {
|
|
115861
117317
|
|
|
@@ -231814,7 +233270,7 @@ module.exports = /*#__PURE__*/JSON.parse('{"application/1d-interleaved-parityfec
|
|
|
231814
233270
|
/***/ ((module) => {
|
|
231815
233271
|
|
|
231816
233272
|
"use strict";
|
|
231817
|
-
module.exports = {"rE":"0.1.
|
|
233273
|
+
module.exports = {"rE":"0.1.90"};
|
|
231818
233274
|
|
|
231819
233275
|
/***/ })
|
|
231820
233276
|
|