jetic-cli 0.2.5 → 0.2.6

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/index.js CHANGED
@@ -269150,6 +269150,7 @@ var require_yaml_io = __commonJS({
269150
269150
  exports2.loadModelFromDir = loadModelFromDir5;
269151
269151
  exports2.readWorkflowDefinitionFile = readWorkflowDefinitionFile2;
269152
269152
  exports2.listWorkflowFiles = listWorkflowFiles2;
269153
+ exports2.loadWorkflowByName = loadWorkflowByName3;
269153
269154
  exports2.hasYamlModel = hasYamlModel5;
269154
269155
  exports2.loadModelCompat = loadModelCompat;
269155
269156
  var fs10 = __importStar(require("fs"));
@@ -269245,6 +269246,29 @@ var require_yaml_io = __commonJS({
269245
269246
  return [];
269246
269247
  return fs10.readdirSync(workflowsDir).filter((f) => f.endsWith(".json") || f.endsWith(".yaml") || f.endsWith(".yml")).sort();
269247
269248
  }
269249
+ function loadWorkflowByName3(workflowsDir, slugOrName) {
269250
+ const wanted = slugOrName.trim().toLowerCase();
269251
+ if (!wanted)
269252
+ return null;
269253
+ const files = listWorkflowFiles2(workflowsDir);
269254
+ const bySlug = files.find((f) => f.replace(/.(json|ya?ml)$/, "").toLowerCase() === wanted);
269255
+ if (bySlug) {
269256
+ try {
269257
+ return readWorkflowDefinitionFile2(path15.join(workflowsDir, bySlug));
269258
+ } catch {
269259
+ return null;
269260
+ }
269261
+ }
269262
+ for (const file of files) {
269263
+ try {
269264
+ const def = readWorkflowDefinitionFile2(path15.join(workflowsDir, file));
269265
+ if (def?.name && def.name.trim().toLowerCase() === wanted)
269266
+ return def;
269267
+ } catch {
269268
+ }
269269
+ }
269270
+ return null;
269271
+ }
269248
269272
  function hasYamlModel5(modelDir) {
269249
269273
  return fs10.existsSync(path15.join(modelDir, "api.yaml"));
269250
269274
  }
@@ -272353,12 +272377,15 @@ var require_dist12 = __commonJS({
272353
272377
  exports2.JeticMemory = void 0;
272354
272378
  var fs10 = __importStar(require("fs"));
272355
272379
  var path15 = __importStar(require("path"));
272380
+ function memoryFilePath(projectRoot) {
272381
+ return path15.join(projectRoot ?? process.cwd(), ".jetic", "memory.json");
272382
+ }
272356
272383
  var JeticMemory4 = class {
272357
272384
  scope;
272358
272385
  filePath;
272359
272386
  constructor(options) {
272360
272387
  this.scope = options.scope;
272361
- this.filePath = path15.join(process.cwd(), ".jetic", "memory.json");
272388
+ this.filePath = memoryFilePath(options.projectRoot);
272362
272389
  }
272363
272390
  ensureDirectory() {
272364
272391
  const dir = path15.dirname(this.filePath);
@@ -272451,8 +272478,8 @@ var require_dist12 = __commonJS({
272451
272478
  this.writeData(data);
272452
272479
  }
272453
272480
  }
272454
- static getAllMemory() {
272455
- const filePath = path15.join(process.cwd(), ".jetic", "memory.json");
272481
+ static getAllMemory(projectRoot) {
272482
+ const filePath = memoryFilePath(projectRoot);
272456
272483
  if (!fs10.existsSync(filePath))
272457
272484
  return {};
272458
272485
  try {
@@ -272474,8 +272501,8 @@ var require_dist12 = __commonJS({
272474
272501
  return {};
272475
272502
  }
272476
272503
  }
272477
- static clearAllMemory() {
272478
- const filePath = path15.join(process.cwd(), ".jetic", "memory.json");
272504
+ static clearAllMemory(projectRoot) {
272505
+ const filePath = memoryFilePath(projectRoot);
272479
272506
  if (fs10.existsSync(filePath)) {
272480
272507
  fs10.unlinkSync(filePath);
272481
272508
  }
@@ -272485,6 +272512,158 @@ var require_dist12 = __commonJS({
272485
272512
  }
272486
272513
  });
272487
272514
 
272515
+ // ../../packages/simulator/dist/conditions.js
272516
+ var require_conditions = __commonJS({
272517
+ "../../packages/simulator/dist/conditions.js"(exports2) {
272518
+ "use strict";
272519
+ init_cjs_shims();
272520
+ Object.defineProperty(exports2, "__esModule", { value: true });
272521
+ exports2.UNARY_OPERATORS = exports2.CONDITION_OPERATORS = void 0;
272522
+ exports2.conditionKind = conditionKind3;
272523
+ exports2.isRulesCondition = isRulesCondition3;
272524
+ exports2.isLegacyCondition = isLegacyCondition;
272525
+ exports2.describeInvalidCondition = describeInvalidCondition3;
272526
+ exports2.evaluateRules = evaluateRules3;
272527
+ exports2.evaluateLegacyCondition = evaluateLegacyCondition3;
272528
+ exports2.CONDITION_OPERATORS = [
272529
+ "equals",
272530
+ "not_equals",
272531
+ "greater_than",
272532
+ "greater_than_or_equal",
272533
+ "less_than",
272534
+ "less_than_or_equal",
272535
+ "exists",
272536
+ "not_exists",
272537
+ "is_empty",
272538
+ "is_not_empty",
272539
+ "contains",
272540
+ "not_contains",
272541
+ "starts_with",
272542
+ "ends_with"
272543
+ ];
272544
+ exports2.UNARY_OPERATORS = ["exists", "not_exists", "is_empty", "is_not_empty"];
272545
+ function isObject(value) {
272546
+ return typeof value === "object" && value !== null && !Array.isArray(value);
272547
+ }
272548
+ function conditionKind3(condition) {
272549
+ if (!isObject(condition))
272550
+ return "invalid";
272551
+ if (isObject(condition.rules))
272552
+ return "rules";
272553
+ if (typeof condition.if === "string")
272554
+ return "legacy";
272555
+ return "invalid";
272556
+ }
272557
+ function isRulesCondition3(condition) {
272558
+ return conditionKind3(condition) === "rules";
272559
+ }
272560
+ function isLegacyCondition(condition) {
272561
+ return conditionKind3(condition) === "legacy";
272562
+ }
272563
+ function describeInvalidCondition3(condition) {
272564
+ if (!isObject(condition))
272565
+ return "condition must be an object";
272566
+ const keys = Object.keys(condition);
272567
+ return `unrecognized condition (keys: ${keys.join(", ") || "none"}). Use { "rules": { "all": [{ "left", "operator", "right" }] }, "onFail": "abort|continue|switch" }`;
272568
+ }
272569
+ async function evaluateRule(rule, resolve2) {
272570
+ const left = await resolve2(String(rule.left ?? ""));
272571
+ const right = rule.right !== void 0 ? await resolve2(String(rule.right)) : void 0;
272572
+ switch (rule.operator) {
272573
+ case "equals":
272574
+ return { ok: left === right };
272575
+ case "not_equals":
272576
+ return { ok: left !== right };
272577
+ case "greater_than":
272578
+ return { ok: Number(left) > Number(right) };
272579
+ case "greater_than_or_equal":
272580
+ return { ok: Number(left) >= Number(right) };
272581
+ case "less_than":
272582
+ return { ok: Number(left) < Number(right) };
272583
+ case "less_than_or_equal":
272584
+ return { ok: Number(left) <= Number(right) };
272585
+ case "exists":
272586
+ return { ok: left !== "" && left !== void 0 };
272587
+ case "not_exists":
272588
+ return { ok: left === "" || left === void 0 };
272589
+ case "is_empty":
272590
+ return { ok: left === "" };
272591
+ case "is_not_empty":
272592
+ return { ok: left !== "" };
272593
+ case "contains":
272594
+ return { ok: right !== void 0 && left.includes(right) };
272595
+ case "not_contains":
272596
+ return { ok: right !== void 0 && !left.includes(right) };
272597
+ case "starts_with":
272598
+ return { ok: right !== void 0 && left.startsWith(right) };
272599
+ case "ends_with":
272600
+ return { ok: right !== void 0 && left.endsWith(right) };
272601
+ default:
272602
+ return { ok: false, unknownOperator: true };
272603
+ }
272604
+ }
272605
+ function describeRule(rule) {
272606
+ return `${rule.left} ${rule.operator}${rule.right !== void 0 ? ` ${rule.right}` : ""}`;
272607
+ }
272608
+ async function evaluateRules3(group, resolve2) {
272609
+ const all = Array.isArray(group.all) ? group.all : void 0;
272610
+ const any = Array.isArray(group.any) ? group.any : void 0;
272611
+ if ((!all || all.length === 0) && (!any || any.length === 0)) {
272612
+ return { passed: true, reason: "No rules defined" };
272613
+ }
272614
+ const failing = [];
272615
+ let passed = true;
272616
+ if (all && all.length > 0) {
272617
+ const results = [];
272618
+ for (const rule of all)
272619
+ results.push({ rule, ...await evaluateRule(rule, resolve2) });
272620
+ const bad = results.filter((r) => !r.ok);
272621
+ if (bad.length > 0) {
272622
+ passed = false;
272623
+ failing.push(...bad.map((r) => r.unknownOperator ? `${describeRule(r.rule)} (unknown operator)` : describeRule(r.rule)));
272624
+ }
272625
+ }
272626
+ if (any && any.length > 0) {
272627
+ const results = [];
272628
+ for (const rule of any)
272629
+ results.push({ rule, ...await evaluateRule(rule, resolve2) });
272630
+ if (!results.some((r) => r.ok)) {
272631
+ passed = false;
272632
+ failing.push(`none of: ${results.map((r) => describeRule(r.rule)).join(" | ")}`);
272633
+ }
272634
+ }
272635
+ const mode = all && any ? "AND+OR" : all ? "AND" : "OR";
272636
+ return {
272637
+ passed,
272638
+ reason: passed ? `All conditions met (${mode})` : `Failed: ${failing.join(", ")}`
272639
+ };
272640
+ }
272641
+ async function evaluateLegacyCondition3(condition, resolve2) {
272642
+ const evaluated = await resolve2(condition.if);
272643
+ let matched;
272644
+ if (condition.exists !== void 0) {
272645
+ const hasValue = evaluated !== "" && evaluated !== "null" && evaluated !== "undefined";
272646
+ matched = condition.exists ? hasValue : !hasValue;
272647
+ } else if (condition.equals !== void 0) {
272648
+ matched = evaluated === condition.equals;
272649
+ } else if (condition.notEquals !== void 0) {
272650
+ matched = evaluated !== condition.notEquals;
272651
+ } else if (condition.contains !== void 0) {
272652
+ matched = evaluated.includes(condition.contains);
272653
+ } else if (condition.greaterThan !== void 0) {
272654
+ matched = Number(evaluated) > condition.greaterThan;
272655
+ } else if (condition.lessThan !== void 0) {
272656
+ matched = Number(evaluated) < condition.lessThan;
272657
+ } else if (condition.in !== void 0) {
272658
+ matched = condition.in.includes(evaluated);
272659
+ } else {
272660
+ matched = evaluated !== "" && evaluated !== "false" && evaluated !== "0" && evaluated !== "null" && evaluated !== "undefined";
272661
+ }
272662
+ return { matched, evaluated };
272663
+ }
272664
+ }
272665
+ });
272666
+
272488
272667
  // ../../packages/simulator/dist/workflow-simulator.js
272489
272668
  var require_workflow_simulator = __commonJS({
272490
272669
  "../../packages/simulator/dist/workflow-simulator.js"(exports2) {
@@ -272494,6 +272673,7 @@ var require_workflow_simulator = __commonJS({
272494
272673
  exports2.WorkflowSimulator = void 0;
272495
272674
  var memory_1 = require_dist12();
272496
272675
  var faker_1 = require("@faker-js/faker");
272676
+ var conditions_1 = require_conditions();
272497
272677
  function deepGet2(obj, dotPath) {
272498
272678
  if (obj === null || obj === void 0)
272499
272679
  return void 0;
@@ -272509,27 +272689,88 @@ var require_workflow_simulator = __commonJS({
272509
272689
  var WorkflowSimulator2 = class {
272510
272690
  model;
272511
272691
  environment;
272512
- constructor(model, environment) {
272692
+ runtime;
272693
+ constructor(model, environment, runtime = {}) {
272513
272694
  this.model = model;
272514
272695
  this.environment = environment;
272696
+ this.runtime = runtime;
272697
+ }
272698
+ memory(scope) {
272699
+ return new memory_1.JeticMemory({ scope, projectRoot: this.runtime.projectRoot });
272515
272700
  }
272516
272701
  async simulateWorkflow(workflow, options) {
272517
272702
  if (options?.clearMemory) {
272518
- memory_1.JeticMemory.clearAllMemory();
272703
+ memory_1.JeticMemory.clearAllMemory(this.runtime.projectRoot);
272519
272704
  }
272520
- const baseUrl = this.environment.baseUrl;
272521
272705
  const startTime = Date.now();
272522
- const stepResults = [];
272523
- let allPassed = true;
272706
+ const state = { results: [], allPassed: true, halted: false };
272707
+ await this.runSteps(workflow, options, state, [workflow.name]);
272708
+ const stepResults = state.results;
272709
+ const totalTimeMs = Date.now() - startTime;
272710
+ return {
272711
+ success: state.allPassed,
272712
+ workflow: {
272713
+ name: workflow.name,
272714
+ description: workflow.description,
272715
+ stepsCount: workflow.steps.length
272716
+ },
272717
+ environment: {
272718
+ name: this.environment.name,
272719
+ baseUrl: this.environment.baseUrl
272720
+ },
272721
+ stats: {
272722
+ totalSteps: workflow.steps.length,
272723
+ executedSteps: stepResults.length,
272724
+ passedSteps: stepResults.filter((r) => r.passed).length,
272725
+ failedSteps: stepResults.filter((r) => !r.passed).length,
272726
+ totalTimeMs
272727
+ },
272728
+ results: stepResults
272729
+ };
272730
+ }
272731
+ /**
272732
+ * Runs one workflow's steps, appending to `state`. Returns `'stop'` when the remaining steps
272733
+ * of THIS workflow must not run (failure, `onFail: continue|switch`), else `'done'`.
272734
+ * `state.halted` is set only for failures, so a caller resuming after a switch knows whether to go on.
272735
+ */
272736
+ async runSteps(workflow, options, state, lineage) {
272737
+ const baseUrl = this.environment.baseUrl;
272738
+ const resolve2 = (value) => this.resolveTemplateString(value);
272524
272739
  for (let i = 0; i < workflow.steps.length; i++) {
272525
272740
  const step = workflow.steps[i];
272526
272741
  const stepStartTime = Date.now();
272527
- if (step.condition) {
272528
- const condResult = await this.evaluateCondition(step.condition);
272742
+ const stepName = step.name || `${step.method} ${step.path}`;
272743
+ const kind = step.condition === void 0 ? void 0 : (0, conditions_1.conditionKind)(step.condition);
272744
+ if (kind === "invalid") {
272745
+ const reason = (0, conditions_1.describeInvalidCondition)(step.condition);
272746
+ state.results.push({
272747
+ stepIndex: i + 1,
272748
+ name: stepName,
272749
+ method: step.method,
272750
+ path: step.path,
272751
+ targetUrl: "",
272752
+ expectedStatus: step.expectStatus ?? 200,
272753
+ actualStatus: 0,
272754
+ durationMs: Date.now() - stepStartTime,
272755
+ passed: false,
272756
+ skipped: false,
272757
+ retryCount: 0,
272758
+ requestHeaders: {},
272759
+ requestBody: {},
272760
+ responseBody: null,
272761
+ capturedMemory: [],
272762
+ error: `Step ${i + 1} "${stepName}": ${reason}`
272763
+ });
272764
+ state.allPassed = false;
272765
+ state.halted = true;
272766
+ return "stop";
272767
+ }
272768
+ if (kind === "legacy") {
272769
+ const condResult = await (0, conditions_1.evaluateLegacyCondition)(step.condition, resolve2);
272529
272770
  if (!condResult.matched) {
272530
- stepResults.push({
272771
+ state.results.push({
272531
272772
  stepIndex: i + 1,
272532
- name: step.name || `${step.method} ${step.path}`,
272773
+ name: stepName,
272533
272774
  method: step.method,
272534
272775
  path: step.path,
272535
272776
  targetUrl: "",
@@ -272609,97 +272850,72 @@ var require_workflow_simulator = __commonJS({
272609
272850
  }
272610
272851
  }
272611
272852
  const responseCaptured = stepPassed ? await this.captureToMemory(step.capture, responseBody) : [];
272612
- if (!stepPassed) {
272613
- allPassed = false;
272614
- const onFailure = step.onFailure ?? "abort";
272615
- stepResults.push({
272616
- stepIndex: i + 1,
272617
- name: step.name || `${step.method} ${step.path}`,
272618
- method: step.method,
272619
- path: step.path,
272620
- targetUrl: fullUrl,
272621
- expectedStatus,
272622
- actualStatus: responseStatus,
272623
- durationMs: Date.now() - stepStartTime,
272624
- passed: false,
272625
- skipped: false,
272626
- retryCount: attempts - 1,
272627
- failureHandling: onFailure === "continue" ? "continued" : "aborted",
272628
- requestHeaders: injectedHeaders,
272629
- requestBody: finalBody,
272630
- responseBody,
272631
- capturedMemory: [...inputCaptured],
272632
- error: errorMsg
272633
- });
272634
- if (onFailure === "abort")
272635
- break;
272636
- continue;
272637
- }
272638
- stepResults.push({
272853
+ const onFailure = step.onFailure ?? "abort";
272854
+ const result = {
272639
272855
  stepIndex: i + 1,
272640
- name: step.name || `${step.method} ${step.path}`,
272856
+ name: stepName,
272641
272857
  method: step.method,
272642
272858
  path: step.path,
272643
272859
  targetUrl: fullUrl,
272644
272860
  expectedStatus,
272645
272861
  actualStatus: responseStatus,
272646
272862
  durationMs: Date.now() - stepStartTime,
272647
- passed: true,
272863
+ passed: stepPassed,
272648
272864
  skipped: false,
272649
272865
  retryCount: attempts - 1,
272650
272866
  requestHeaders: injectedHeaders,
272651
272867
  requestBody: finalBody,
272652
272868
  responseBody,
272653
- capturedMemory: [...inputCaptured, ...responseCaptured]
272654
- });
272655
- }
272656
- const totalTimeMs = Date.now() - startTime;
272657
- return {
272658
- success: allPassed,
272659
- workflow: {
272660
- name: workflow.name,
272661
- description: workflow.description,
272662
- stepsCount: workflow.steps.length
272663
- },
272664
- environment: {
272665
- name: this.environment.name,
272666
- baseUrl
272667
- },
272668
- stats: {
272669
- totalSteps: workflow.steps.length,
272670
- executedSteps: stepResults.length,
272671
- passedSteps: stepResults.filter((r) => r.passed).length,
272672
- failedSteps: stepResults.filter((r) => !r.passed).length,
272673
- totalTimeMs
272674
- },
272675
- results: stepResults
272676
- };
272677
- }
272678
- // ── Condition evaluator ────────────────────────────────────────────────────
272679
- // Resolves the `if` template and applies the declared operator. Returns
272680
- // `matched: true` when the condition passes (step should execute).
272681
- async evaluateCondition(condition) {
272682
- const evaluated = await this.resolveTemplateString(condition.if);
272683
- let matched;
272684
- if (condition.exists !== void 0) {
272685
- const hasValue = evaluated !== "" && evaluated !== "null" && evaluated !== "undefined";
272686
- matched = condition.exists ? hasValue : !hasValue;
272687
- } else if (condition.equals !== void 0) {
272688
- matched = evaluated === condition.equals;
272689
- } else if (condition.notEquals !== void 0) {
272690
- matched = evaluated !== condition.notEquals;
272691
- } else if (condition.contains !== void 0) {
272692
- matched = evaluated.includes(condition.contains);
272693
- } else if (condition.greaterThan !== void 0) {
272694
- matched = Number(evaluated) > condition.greaterThan;
272695
- } else if (condition.lessThan !== void 0) {
272696
- matched = Number(evaluated) < condition.lessThan;
272697
- } else if (condition.in !== void 0) {
272698
- matched = condition.in.includes(evaluated);
272699
- } else {
272700
- matched = evaluated !== "" && evaluated !== "false" && evaluated !== "0" && evaluated !== "null" && evaluated !== "undefined";
272869
+ capturedMemory: stepPassed ? [...inputCaptured, ...responseCaptured] : [...inputCaptured],
272870
+ ...stepPassed ? {} : {
272871
+ failureHandling: onFailure === "continue" ? "continued" : "aborted",
272872
+ error: errorMsg
272873
+ }
272874
+ };
272875
+ state.results.push(result);
272876
+ if (!stepPassed) {
272877
+ state.allPassed = false;
272878
+ if (onFailure === "abort") {
272879
+ state.halted = true;
272880
+ return "stop";
272881
+ }
272882
+ }
272883
+ if (kind === "rules") {
272884
+ const condition = step.condition;
272885
+ const outcome = await (0, conditions_1.evaluateRules)(condition.rules, resolve2);
272886
+ result.conditionResult = { evaluated: outcome.reason, matched: outcome.passed };
272887
+ if (outcome.passed)
272888
+ continue;
272889
+ result.conditionOutcome = condition.onFail;
272890
+ const fail = (message) => {
272891
+ result.passed = false;
272892
+ result.error = result.error ? `${result.error}; ${message}` : message;
272893
+ state.allPassed = false;
272894
+ state.halted = true;
272895
+ return "stop";
272896
+ };
272897
+ if (condition.onFail === "continue")
272898
+ return "stop";
272899
+ if (condition.onFail !== "switch")
272900
+ return fail(`Condition failed at step ${i + 1}: ${outcome.reason}`);
272901
+ const target = condition.switchToWorkflow;
272902
+ if (!target)
272903
+ return fail(`Condition failed at step ${i + 1} (${outcome.reason}) and onFail "switch" has no switchToWorkflow`);
272904
+ if (!options?.loadWorkflow) {
272905
+ return fail(`Condition failed at step ${i + 1} (${outcome.reason}); cannot switch to "${target}": this runner was not given a workflow loader`);
272906
+ }
272907
+ const switched = await options.loadWorkflow(target);
272908
+ if (!switched)
272909
+ return fail(`Condition failed at step ${i + 1} (${outcome.reason}); switch target workflow "${target}" was not found`);
272910
+ if (lineage.includes(switched.name)) {
272911
+ return fail(`Condition failed at step ${i + 1}; switching to "${switched.name}" would loop (${[...lineage, switched.name].join(" \u2192 ")})`);
272912
+ }
272913
+ await this.runSteps(switched, options, state, [...lineage, switched.name]);
272914
+ if (state.halted || !condition.returnOnComplete)
272915
+ return "stop";
272916
+ }
272701
272917
  }
272702
- return { matched, evaluated };
272918
+ return "done";
272703
272919
  }
272704
272920
  async resolveTemplateString(value) {
272705
272921
  const templateRe = /\{\{([^}]+)\}\}/g;
@@ -272718,7 +272934,7 @@ var require_workflow_simulator = __commonJS({
272718
272934
  continue;
272719
272935
  }
272720
272936
  }
272721
- const mem = new memory_1.JeticMemory({ scope });
272937
+ const mem = this.memory(scope);
272722
272938
  const memVal = await mem.get(key);
272723
272939
  replacements.push({ placeholder: match[0], resolved: memVal != null ? String(memVal) : "" });
272724
272940
  continue;
@@ -272762,7 +272978,7 @@ var require_workflow_simulator = __commonJS({
272762
272978
  strValue = await this.resolveTemplateString(memKeyOrTemplate);
272763
272979
  } else {
272764
272980
  const [scope, key] = memKeyOrTemplate.includes(":") ? memKeyOrTemplate.split(":", 2) : ["workflow", memKeyOrTemplate];
272765
- const scopedMemory = new memory_1.JeticMemory({ scope });
272981
+ const scopedMemory = this.memory(scope);
272766
272982
  const value = await scopedMemory.get(key);
272767
272983
  if (value === null)
272768
272984
  continue;
@@ -272787,7 +273003,7 @@ var require_workflow_simulator = __commonJS({
272787
273003
  if (value === void 0 || value === null)
272788
273004
  continue;
272789
273005
  const [scope, key] = memKey.includes(":") ? memKey.split(":", 2) : ["workflow", memKey];
272790
- const memory = new memory_1.JeticMemory({ scope });
273006
+ const memory = this.memory(scope);
272791
273007
  await memory.set(key, value);
272792
273008
  captured.push(`${scope}:${key} = ${JSON.stringify(value)}`);
272793
273009
  }
@@ -272802,7 +273018,7 @@ var require_workflow_simulator = __commonJS({
272802
273018
  if (value === void 0 || value === null)
272803
273019
  continue;
272804
273020
  const [scope, key] = memKey.includes(":") ? memKey.split(":", 2) : ["workflow", memKey];
272805
- const memory = new memory_1.JeticMemory({ scope });
273021
+ const memory = this.memory(scope);
272806
273022
  await memory.set(key, value);
272807
273023
  captured.push(`${scope}:${key} = ${JSON.stringify(value)}`);
272808
273024
  }
@@ -272818,7 +273034,7 @@ var require_workflow_simulator = __commonJS({
272818
273034
  if (body[paramName] !== void 0) {
272819
273035
  result = result.replace(p, String(body[paramName]));
272820
273036
  } else {
272821
- const memory = new memory_1.JeticMemory({ scope: "workflow" });
273037
+ const memory = this.memory("workflow");
272822
273038
  const memVal = await memory.get(paramName);
272823
273039
  if (memVal !== null) {
272824
273040
  result = result.replace(p, String(memVal));
@@ -272837,6 +273053,22 @@ var require_dist13 = __commonJS({
272837
273053
  "../../packages/simulator/dist/index.js"(exports2) {
272838
273054
  "use strict";
272839
273055
  init_cjs_shims();
273056
+ var __createBinding = exports2 && exports2.__createBinding || (Object.create ? (function(o, m, k, k2) {
273057
+ if (k2 === void 0) k2 = k;
273058
+ var desc = Object.getOwnPropertyDescriptor(m, k);
273059
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
273060
+ desc = { enumerable: true, get: function() {
273061
+ return m[k];
273062
+ } };
273063
+ }
273064
+ Object.defineProperty(o, k2, desc);
273065
+ }) : (function(o, m, k, k2) {
273066
+ if (k2 === void 0) k2 = k;
273067
+ o[k2] = m[k];
273068
+ }));
273069
+ var __exportStar = exports2 && exports2.__exportStar || function(m, exports3) {
273070
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports3, p)) __createBinding(exports3, m, p);
273071
+ };
272840
273072
  Object.defineProperty(exports2, "__esModule", { value: true });
272841
273073
  exports2.WorkflowSimulator = exports2.ResponseValidator = exports2.DataGenerator = exports2.EndpointSimulator = void 0;
272842
273074
  var simulator_1 = require_simulator();
@@ -272855,6 +273087,7 @@ var require_dist13 = __commonJS({
272855
273087
  Object.defineProperty(exports2, "WorkflowSimulator", { enumerable: true, get: function() {
272856
273088
  return workflow_simulator_1.WorkflowSimulator;
272857
273089
  } });
273090
+ __exportStar(require_conditions(), exports2);
272858
273091
  }
272859
273092
  });
272860
273093
 
@@ -273238,8 +273471,13 @@ var require_workflow_tools = __commonJS({
273238
273471
  ...targetEnv,
273239
273472
  baseUrl: args.baseUrl || targetEnv.baseUrl
273240
273473
  };
273241
- const simulator = new simulator_1.WorkflowSimulator(model, effectiveEnv);
273242
- const result = await simulator.simulateWorkflow(workflowDef, { clearMemory: args.clearMemory ?? false });
273474
+ const projectRoot = (0, types_1.projectRootOfModel)(filePath);
273475
+ const workflowsDir = path15.join(path15.dirname(filePath), "workflows");
273476
+ const simulator = new simulator_1.WorkflowSimulator(model, effectiveEnv, { projectRoot });
273477
+ const result = await simulator.simulateWorkflow(workflowDef, {
273478
+ clearMemory: args.clearMemory ?? false,
273479
+ loadWorkflow: (slugOrName) => (0, model_1.loadWorkflowByName)(workflowsDir, slugOrName)
273480
+ });
273243
273481
  return {
273244
273482
  ...result,
273245
273483
  filePath,
@@ -273304,7 +273542,23 @@ var require_workflow_authoring_tools = __commonJS({
273304
273542
  var path15 = __importStar(require("path"));
273305
273543
  var types_1 = require_types();
273306
273544
  var workflow_tools_1 = require_workflow_tools();
273307
- var stepConditionSchema = zod_1.z.object({
273545
+ var simulator_1 = require_dist13();
273546
+ var model_1 = require_dist2();
273547
+ var conditionRuleSchema = zod_1.z.object({
273548
+ left: zod_1.z.string().min(1).describe('Value to test, usually a template: "{{workflow:role}}"'),
273549
+ operator: zod_1.z.enum(simulator_1.CONDITION_OPERATORS).describe("equals, not_equals, greater_than(_or_equal), less_than(_or_equal), exists, not_exists, is_empty, is_not_empty, contains, not_contains, starts_with, ends_with"),
273550
+ right: zod_1.z.string().optional().describe("Value to compare against (omit for exists/not_exists/is_empty/is_not_empty)")
273551
+ });
273552
+ var rulesConditionSchema = zod_1.z.object({
273553
+ rules: zod_1.z.object({
273554
+ all: zod_1.z.array(conditionRuleSchema).optional().describe("AND \u2014 every rule must hold"),
273555
+ any: zod_1.z.array(conditionRuleSchema).optional().describe("OR \u2014 at least one rule must hold")
273556
+ }).describe("Rule group evaluated after the step runs"),
273557
+ onFail: zod_1.z.enum(["abort", "continue", "switch"]).describe("When the rules do not hold: abort = fail the workflow; continue = stop remaining steps without failing; switch = run switchToWorkflow"),
273558
+ switchToWorkflow: zod_1.z.string().optional().describe('Workflow slug or name to run when onFail is "switch"'),
273559
+ returnOnComplete: zod_1.z.boolean().optional().describe('With onFail "switch": resume this workflow after the target finishes')
273560
+ }).describe("Canonical condition: evaluated after the step; onFail steers the run");
273561
+ var legacyConditionSchema = zod_1.z.object({
273308
273562
  if: zod_1.z.string().min(1).describe('Template expression to resolve, e.g. "{{workflow:role}}"'),
273309
273563
  equals: zod_1.z.string().optional().describe("Resolved value must equal this string"),
273310
273564
  notEquals: zod_1.z.string().optional().describe("Resolved value must NOT equal this string"),
@@ -273313,7 +273567,8 @@ var require_workflow_authoring_tools = __commonJS({
273313
273567
  greaterThan: zod_1.z.number().optional().describe("Numeric greater-than comparison"),
273314
273568
  lessThan: zod_1.z.number().optional().describe("Numeric less-than comparison"),
273315
273569
  in: zod_1.z.array(zod_1.z.string()).optional().describe("Resolved value must be one of these strings")
273316
- }).describe("Skip this step when the condition is not met");
273570
+ }).describe("Legacy guard: evaluated before the step; if not met the step is skipped");
273571
+ var stepConditionSchema = zod_1.z.union([rulesConditionSchema, legacyConditionSchema]);
273317
273572
  var stepRetrySchema = zod_1.z.object({
273318
273573
  times: zod_1.z.number().int().positive().max(10).describe("Max retry attempts (1\u201310)"),
273319
273574
  delayMs: zod_1.z.number().int().nonnegative().optional().describe("Delay between retries in ms (default 0)")
@@ -273328,7 +273583,7 @@ var require_workflow_authoring_tools = __commonJS({
273328
273583
  capture: zod_1.z.record(zod_1.z.string()).optional().describe('Save RESPONSE fields to memory: "scope:key" \u2192 "dot.path.in.response"'),
273329
273584
  captureInput: zod_1.z.record(zod_1.z.string()).optional().describe('Save REQUEST body fields to memory BEFORE sending: "scope:key" \u2192 "bodyField"'),
273330
273585
  expectStatus: zod_1.z.number().int().positive().optional().describe("Expected HTTP status (default 200; any 2xx passes when expecting 2xx)"),
273331
- condition: stepConditionSchema.optional().describe('Skip this step when condition is not met. Use {{workflow:key}} in the "if" field.'),
273586
+ condition: stepConditionSchema.optional().describe("Preferred: { rules: { all|any: [{ left, operator, right }] }, onFail: abort|continue|switch } \u2014 evaluated after the step. Legacy: { if, equals\u2026 } skips the step when not met."),
273332
273587
  onFailure: zod_1.z.enum(["abort", "continue"]).optional().describe('"abort" (default): stop the workflow on failure. "continue": log failure and proceed (captures skipped).'),
273333
273588
  continueOnStatus: zod_1.z.array(zod_1.z.number().int()).optional().describe("Additional HTTP status codes treated as pass for this step (e.g. [404])."),
273334
273589
  retry: stepRetrySchema.optional().describe("Retry on failure \u2014 overrides the workflow-level global retry.")
@@ -273411,6 +273666,58 @@ var require_workflow_authoring_tools = __commonJS({
273411
273666
  const prefix = wfPath.split("/").slice(0, 3).join("/");
273412
273667
  return model.filter((e) => e.path.startsWith(prefix)).map((e) => `${e.method} ${e.path}`).slice(0, 3);
273413
273668
  }
273669
+ function checkRulesCondition(cond, stepNo, sName, errors, warnings, ownName, workflowsDir2) {
273670
+ const refs = [];
273671
+ const rules = cond.rules;
273672
+ let ruleCount = 0;
273673
+ for (const group of ["all", "any"]) {
273674
+ const list = rules[group];
273675
+ if (list === void 0)
273676
+ continue;
273677
+ if (!Array.isArray(list)) {
273678
+ err(errors, stepNo, sName, "BAD_CONDITION", `Step ${stepNo} "condition.rules.${group}" must be an array of rules.`, `Use "${group}": [{ "left": "{{workflow:role}}", "operator": "equals", "right": "admin" }].`);
273679
+ continue;
273680
+ }
273681
+ list.forEach((rule, ri) => {
273682
+ ruleCount++;
273683
+ const where = `Step ${stepNo} condition.rules.${group}[${ri}]`;
273684
+ if (!rule || typeof rule !== "object" || Array.isArray(rule)) {
273685
+ err(errors, stepNo, sName, "BAD_CONDITION", `${where} must be an object { left, operator, right? }.`, 'Use { "left": "{{workflow:role}}", "operator": "equals", "right": "admin" }.');
273686
+ return;
273687
+ }
273688
+ if (typeof rule.left !== "string" || !rule.left.trim()) {
273689
+ err(errors, stepNo, sName, "BAD_CONDITION", `${where} needs a non-empty "left".`, 'Set "left" to a value or template like "{{workflow:role}}".');
273690
+ } else if (!/\{\{[^}]+\}\}/.test(rule.left)) {
273691
+ warn(warnings, stepNo, sName, "CONDITION_BARE_IF", `${where} "left" ("${rule.left}") contains no {{template}} and is compared literally.`, "Use {{workflow:key}} or {{scope:key}} to reference captured memory values.");
273692
+ }
273693
+ if (!simulator_1.CONDITION_OPERATORS.includes(rule.operator)) {
273694
+ err(errors, stepNo, sName, "BAD_CONDITION_OPERATOR", `${where} has unknown operator ${JSON.stringify(rule.operator)}.`, `Use one of: ${simulator_1.CONDITION_OPERATORS.join(", ")}.`);
273695
+ } else if (!simulator_1.UNARY_OPERATORS.includes(rule.operator) && rule.right === void 0) {
273696
+ err(errors, stepNo, sName, "CONDITION_MISSING_RIGHT", `${where} uses "${rule.operator}" but has no "right" operand.`, 'Add "right", or use exists / not_exists / is_empty / is_not_empty.');
273697
+ }
273698
+ collectTemplateRefs(rule.left, refs);
273699
+ collectTemplateRefs(rule.right, refs);
273700
+ });
273701
+ }
273702
+ if (ruleCount === 0 && !errors.some((e) => e.stepIndex === stepNo && e.code === "BAD_CONDITION")) {
273703
+ warn(warnings, stepNo, sName, "CONDITION_NO_RULES", `Step ${stepNo} condition has no rules, so it always passes.`, 'Add rules under "rules.all" or "rules.any".');
273704
+ }
273705
+ if (!["abort", "continue", "switch"].includes(cond.onFail)) {
273706
+ err(errors, stepNo, sName, "BAD_CONDITION_ONFAIL", `Step ${stepNo} condition needs "onFail" of "abort", "continue" or "switch" (got ${JSON.stringify(cond.onFail)}).`, '"abort" fails the workflow, "continue" stops the remaining steps quietly, "switch" runs switchToWorkflow.');
273707
+ } else if (cond.onFail === "switch") {
273708
+ const target = typeof cond.switchToWorkflow === "string" ? cond.switchToWorkflow.trim() : "";
273709
+ if (!target) {
273710
+ err(errors, stepNo, sName, "CONDITION_SWITCH_NO_TARGET", `Step ${stepNo} uses onFail "switch" but has no "switchToWorkflow".`, 'Set "switchToWorkflow" to the slug or name of the workflow to run.');
273711
+ } else if (ownName && target.toLowerCase() === ownName.trim().toLowerCase()) {
273712
+ err(errors, stepNo, sName, "CONDITION_SWITCH_SELF", `Step ${stepNo} switches to this same workflow ("${target}"), which would loop.`, 'Point "switchToWorkflow" at a different workflow.');
273713
+ } else if (!(0, model_1.loadWorkflowByName)(workflowsDir2, target)) {
273714
+ warn(warnings, stepNo, sName, "CONDITION_SWITCH_TARGET_MISSING", `Step ${stepNo} switches to "${target}", which does not exist in .jetic/workflows yet.`, "Create that workflow before running this one, or the run fails when the condition triggers.");
273715
+ }
273716
+ } else if (cond.returnOnComplete !== void 0 || cond.switchToWorkflow !== void 0) {
273717
+ warn(warnings, stepNo, sName, "CONDITION_UNUSED_SWITCH_FIELDS", `Step ${stepNo} sets switchToWorkflow/returnOnComplete but onFail is "${cond.onFail}", so they are ignored.`, 'Use onFail "switch" or remove those fields.');
273718
+ }
273719
+ return refs;
273720
+ }
273414
273721
  function validateWorkflowDefinition2(wf, opts) {
273415
273722
  const errors = [];
273416
273723
  const warnings = [];
@@ -273447,6 +273754,7 @@ var require_workflow_authoring_tools = __commonJS({
273447
273754
  return;
273448
273755
  }
273449
273756
  const step = rawStep;
273757
+ let rulesRefs = [];
273450
273758
  for (const key of Object.keys(step)) {
273451
273759
  if (MODEL_DIALECT_KEYS.has(key)) {
273452
273760
  err(errors, stepNo, sName, "MODEL_DIALECT_KEY", `Step ${stepNo} uses "${key}" from the model-embedded workflow dialect, which file workflows ignore.`, 'File-workflow equivalent: split "call" into method+path; replace auth/bind with inject/capture.');
@@ -273457,9 +273765,11 @@ var require_workflow_authoring_tools = __commonJS({
273457
273765
  if ("condition" in step && step.condition !== void 0) {
273458
273766
  const cond = step.condition;
273459
273767
  if (!cond || typeof cond !== "object" || Array.isArray(cond)) {
273460
- err(errors, stepNo, sName, "BAD_CONDITION", `Step ${stepNo} "condition" must be an object.`, 'Use { "condition": { "if": "{{workflow:role}}", "equals": "admin" } }.');
273768
+ err(errors, stepNo, sName, "BAD_CONDITION", `Step ${stepNo} "condition" must be an object.`, 'Use { "condition": { "rules": { "all": [{ "left": "{{workflow:role}}", "operator": "equals", "right": "admin" }] }, "onFail": "abort" } }.');
273769
+ } else if ((0, simulator_1.conditionKind)(cond) === "rules") {
273770
+ rulesRefs = checkRulesCondition(cond, stepNo, sName, errors, warnings, wf.name, path15.join(projectRoot, ".jetic", "workflows"));
273461
273771
  } else if (!cond.if || typeof cond.if !== "string" || !cond.if.trim()) {
273462
- err(errors, stepNo, sName, "BAD_CONDITION", `Step ${stepNo} "condition" must have a non-empty "if" field.`, 'Set "if" to a template expression like "{{workflow:role}}".');
273772
+ err(errors, stepNo, sName, "BAD_CONDITION", `Step ${stepNo} "condition" is neither a rules condition (needs "rules" + "onFail") nor a legacy guard (needs a non-empty "if"). The runners fail steps with an unrecognized condition.`, 'Use { "rules": { "all": [{ "left": "{{workflow:role}}", "operator": "equals", "right": "admin" }] }, "onFail": "abort" }.');
273463
273773
  } else {
273464
273774
  const operators = ["equals", "notEquals", "contains", "exists", "greaterThan", "lessThan", "in"];
273465
273775
  const usedOps = operators.filter((op) => op in cond && cond[op] !== void 0);
@@ -273658,6 +273968,25 @@ var require_workflow_authoring_tools = __commonJS({
273658
273968
  }
273659
273969
  }
273660
273970
  }
273971
+ for (const ref of rulesRefs) {
273972
+ if (ref.startsWith("faker."))
273973
+ continue;
273974
+ if (!ref.includes(":")) {
273975
+ err(errors, stepNo, sName, "BAD_TEMPLATE", `"{{${ref}}}" in the condition of step ${stepNo} needs a scope ("{{workflow:key}}").`, "Bare {{key}} is left literal \u2014 add the scope prefix.");
273976
+ continue;
273977
+ }
273978
+ const { scope, key } = splitMemKey(ref);
273979
+ if (!key)
273980
+ continue;
273981
+ if (scope === "human") {
273982
+ humanKeys.add(key);
273983
+ continue;
273984
+ }
273985
+ const full = `${scope}:${key}`;
273986
+ if (!defined.has(full) && !persistedMemory.has(full)) {
273987
+ err(errors, stepNo, sName, "UNDEFINED_MEMORY_REF", `"{{${ref}}}" in the condition of step ${stepNo} is not captured by this or an earlier step and is not in memory.json.`, `Capture "${full}" in this step or an earlier one before referencing it in a condition.`);
273988
+ }
273989
+ }
273661
273990
  });
273662
273991
  return toReport(errors, warnings, humanKeys);
273663
273992
  }
@@ -288859,11 +289188,20 @@ var require_server3 = __commonJS({
288859
289188
  " The scaffold tool already handles faker mapping \u2014 only override to {{human:key}} when a",
288860
289189
  " field appears in missingFields AND genuinely requires interactive user input.",
288861
289190
  "",
288862
- "STEP 4c \u2014 CONDITIONS (branching / skipping steps)",
288863
- " Add `condition` to skip a step when a memory value does not match:",
288864
- ' { "condition": { "if": "{{workflow:userRole}}", "equals": "admin" } }',
288865
- " Operators: equals, notEquals, contains, exists (bool), greaterThan, lessThan, in (string[]).",
288866
- " Skipped steps count as passed \u2014 the workflow continues.",
289191
+ "STEP 4c \u2014 CONDITIONS (branching / assertions on memory)",
289192
+ " Preferred form \u2014 checked AFTER the step runs, so it can use values that step just captured:",
289193
+ ' { "condition": { "rules": { "all": [ { "left": "{{workflow:userRole}}", "operator": "equals", "right": "admin" } ] },',
289194
+ ' "onFail": "abort" } }',
289195
+ " rules.all = AND, rules.any = OR. Operators: equals, not_equals, greater_than(_or_equal), less_than(_or_equal),",
289196
+ " exists, not_exists, is_empty, is_not_empty, contains, not_contains, starts_with, ends_with.",
289197
+ " onFail when the rules do NOT hold:",
289198
+ ' \u2022 "abort" \u2014 fail the workflow',
289199
+ ' \u2022 "continue" \u2014 stop the remaining steps, workflow still passes (branch not applicable)',
289200
+ ' \u2022 "switch" \u2014 run another saved workflow: add "switchToWorkflow": "<slug or name>";',
289201
+ ' add "returnOnComplete": true to resume this workflow afterwards',
289202
+ " Legacy form (still supported) \u2014 a guard checked BEFORE the step; a miss skips just that step (counts as passed):",
289203
+ ' { "condition": { "if": "{{workflow:userRole}}", "equals": "admin" } } // equals, notEquals, contains, exists, greaterThan, lessThan, in',
289204
+ " A condition that is neither shape FAILS the run \u2014 it is never ignored.",
288867
289205
  ' Pair with onFailure:"continue" for negative/branching flows.',
288868
289206
  "",
288869
289207
  "STEP 4d \u2014 FAILURE MODES & RETRY",
@@ -288952,8 +289290,8 @@ var require_server3 = __commonJS({
288952
289290
  register("jetic_simulate_workflow", "Simulate workflow", "Executes a full end-to-end multi-step workflow simulation with dynamic memory capture, header injection, and schema validation against the live server.", workflow_tools_1.simulateWorkflowSchema.shape, READ_LIVE, workflow_tools_1.handleSimulateWorkflow);
288953
289291
  register("jetic_simulate", "Simulate workflow (alias)", "Alias of jetic_simulate_workflow: runs a multi-step user-journey workflow against the live server. Prefer jetic_simulate_workflow in new prompts.", workflow_tools_1.simulateWorkflowSchema.shape, READ_LIVE, workflow_tools_1.handleSimulateWorkflow);
288954
289292
  register("jetic_scaffold_workflow", "Scaffold workflow steps from model", "PHASE 4a \u2014 FIRST STEP when building a workflow. Given an ordered endpoint list, reads model.json and returns ready-made `draftSteps`: body fields faker-mapped by type/format/name, inject derived from securitySchemes.obtainedFrom token-producer chains, capture from endpoint.produces, captureInput for reusable credentials. Returns missingFields (fields needing human input or manual fix) and securityChain analysis. Does NOT save. Review draftSteps, then call jetic_validate_workflow \u2192 jetic_create_workflow.", workflow_designer_tools_1.scaffoldWorkflowSchema.shape, READ_ONLY, workflow_designer_tools_1.handleScaffoldWorkflow);
288955
- register("jetic_validate_workflow", "Validate workflow (dry run)", "Deep-validates a workflow WITHOUT saving: step structure, endpoint cross-checks against model.json, full memory data-flow analysis. Now also validates condition (including condition.if template refs), onFailure, continueOnStatus, and retry fields. Accepts a saved workflow slug/name or an inline definition.", workflow_authoring_tools_1.validateWorkflowSchema.shape, READ_ONLY, workflow_authoring_tools_1.handleValidateWorkflow);
288956
- register("jetic_create_workflow", "Create workflow", "Creates a .jetic/workflows/<slug>.json workflow. Supports: condition (skip steps based on memory), onFailure (abort|continue), continueOnStatus (extra pass codes), retry (per-step or global default). Refuses to save on validation errors.", workflow_authoring_tools_1.createWorkflowSchema.shape, WRITES_MODEL, workflow_authoring_tools_1.handleCreateWorkflow);
289293
+ register("jetic_validate_workflow", "Validate workflow (dry run)", "Deep-validates a workflow WITHOUT saving: step structure, endpoint cross-checks against model.json, full memory data-flow analysis. Also validates conditions (rules + onFail/switch targets, or legacy if guards; template refs), onFailure, continueOnStatus, and retry fields. Accepts a saved workflow slug/name or an inline definition.", workflow_authoring_tools_1.validateWorkflowSchema.shape, READ_ONLY, workflow_authoring_tools_1.handleValidateWorkflow);
289294
+ register("jetic_create_workflow", "Create workflow", "Creates a .jetic/workflows/<slug>.json workflow. Supports: condition (rules with onFail abort|continue|switch, or legacy if-guards), onFailure (abort|continue), continueOnStatus (extra pass codes), retry (per-step or global default). Refuses to save on validation errors.", workflow_authoring_tools_1.createWorkflowSchema.shape, WRITES_MODEL, workflow_authoring_tools_1.handleCreateWorkflow);
288957
289295
  register("jetic_update_workflow", "Update workflow", "Updates a saved file workflow: rename, description/environment, full steps replacement, or appendSteps. Validates before writing and refuses to save on errors.", workflow_authoring_tools_1.updateWorkflowSchema.shape, WRITES_MODEL, workflow_authoring_tools_1.handleUpdateWorkflow);
288958
289296
  register("jetic_delete_workflow", "Delete workflow", "Deletes a saved .jetic/workflows/*.json workflow file by slug, name, or path. Destructive: confirm with the user before calling. Cannot delete model.json-embedded workflows.", workflow_authoring_tools_1.deleteWorkflowSchema.shape, DESTRUCTIVE, workflow_authoring_tools_1.handleDeleteWorkflow);
288959
289297
  register("jetic_list_agents", "List agents", "Lists registered deterministic agents (model-watcher, workflow-impact) available to jetic_run_agent.", agent_tools_1.listAgentsSchema.shape, READ_ONLY, agent_tools_1.handleListAgents);
@@ -289697,9 +290035,12 @@ Running ${files.length} workflow(s)...
289697
290035
  for (const file of files) {
289698
290036
  const workflow = (0, import_model2.readWorkflowDefinitionFile)(path9.join(config.workflowsDir, file));
289699
290037
  import_core11.jeticEvents.emit("workflow.started", { workflowId: workflow.name });
289700
- const simulator = new import_simulator.WorkflowSimulator(model, targetEnv);
289701
- const result = await simulator.simulateWorkflow(workflow);
289702
- results.push({ name: workflow.name, passed: result.success, durationMs: result.stats.totalTimeMs });
290038
+ const simulator = new import_simulator.WorkflowSimulator(model, targetEnv, { projectRoot: config.projectRoot });
290039
+ const result = await simulator.simulateWorkflow(workflow, {
290040
+ loadWorkflow: (slugOrName) => (0, import_model2.loadWorkflowByName)(config.workflowsDir, slugOrName)
290041
+ });
290042
+ const failures = result.results.filter((r) => !r.passed).map((r) => ({ step: r.name, status: r.actualStatus, error: r.error }));
290043
+ results.push({ name: workflow.name, passed: result.success, durationMs: result.stats.totalTimeMs, failures });
289703
290044
  if (result.success) {
289704
290045
  import_core11.jeticEvents.emit("workflow.completed", { workflowId: workflow.name, passed: true });
289705
290046
  } else {
@@ -289707,6 +290048,10 @@ Running ${files.length} workflow(s)...
289707
290048
  }
289708
290049
  if (!options.ci) {
289709
290050
  console.log(` ${result.success ? "\x1B[32m\u2713\x1B[0m" : "\x1B[31m\u2717\x1B[0m"} ${workflow.name}`);
290051
+ for (const failure of failures) {
290052
+ const detail = failure.error ?? (failure.status ? `HTTP ${failure.status}` : "failed");
290053
+ console.log(` \x1B[2m\u21B3 ${failure.step}: ${detail}\x1B[0m`);
290054
+ }
289710
290055
  }
289711
290056
  }
289712
290057
  const passed = results.filter((r) => r.passed).length;
@@ -289830,7 +290175,7 @@ memoryCommand.command("clear").description("Clear memory").action(() => {
289830
290175
  init_cjs_shims();
289831
290176
  var import_commander15 = require("commander");
289832
290177
  var import_core14 = __toESM(require_dist());
289833
- var import_simulator2 = __toESM(require_dist13());
290178
+ var import_simulator3 = __toESM(require_dist13());
289834
290179
  var path12 = __toESM(require("path"));
289835
290180
 
289836
290181
  // src/commands/simulate-workflow.ts
@@ -289839,7 +290184,9 @@ var path11 = __toESM(require("path"));
289839
290184
  var fs7 = __toESM(require("fs"));
289840
290185
  var import_commander14 = require("commander");
289841
290186
  var import_core13 = __toESM(require_dist());
290187
+ var import_model5 = __toESM(require_dist2());
289842
290188
  var import_memory2 = __toESM(require_dist12());
290189
+ var import_simulator2 = __toESM(require_dist13());
289843
290190
  var import_faker = require("@faker-js/faker");
289844
290191
  var c = {
289845
290192
  reset: "\x1B[0m",
@@ -289896,58 +290243,32 @@ function deepGet(obj, dotPath) {
289896
290243
  async function resolveConditionRef(ref, context = "condition") {
289897
290244
  return ref.replace(/\{\{([^}]+)\}\}/g, () => "").trim() === ref.trim() ? ref : await resolveTemplateString(ref, context);
289898
290245
  }
289899
- async function evalConditionRule(rule, context = "condition") {
289900
- const left = await resolveConditionRef(rule.left, context);
289901
- const right = rule.right !== void 0 ? await resolveConditionRef(rule.right, context) : void 0;
289902
- const op = rule.operator;
289903
- switch (op) {
289904
- case "equals":
289905
- return left === right;
289906
- case "not_equals":
289907
- return left !== right;
289908
- case "greater_than":
289909
- return Number(left) > Number(right);
289910
- case "greater_than_or_equal":
289911
- return Number(left) >= Number(right);
289912
- case "less_than":
289913
- return Number(left) < Number(right);
289914
- case "less_than_or_equal":
289915
- return Number(left) <= Number(right);
289916
- case "exists":
289917
- return left !== "" && left !== void 0;
289918
- case "not_exists":
289919
- return left === "" || left === void 0;
289920
- case "is_empty":
289921
- return left === "";
289922
- case "is_not_empty":
289923
- return left !== "";
289924
- case "contains":
289925
- return right !== void 0 && left.includes(right);
289926
- case "not_contains":
289927
- return right !== void 0 && !left.includes(right);
289928
- case "starts_with":
289929
- return right !== void 0 && left.startsWith(right);
289930
- case "ends_with":
289931
- return right !== void 0 && left.endsWith(right);
289932
- default:
289933
- return false;
289934
- }
289935
- }
289936
290246
  async function evaluateStepCondition(condition, context = "condition") {
289937
- const { rules } = condition;
289938
- const ruleList = rules.all ?? rules.any ?? [];
289939
- const isAnd = !!rules.all;
289940
- if (ruleList.length === 0) return { passed: true, reason: "No rules defined" };
289941
- const results = [];
289942
- for (const rule of ruleList) {
289943
- results.push({ rule, ok: await evalConditionRule(rule, context) });
290247
+ return (0, import_simulator2.evaluateRules)(condition.rules, (value) => resolveConditionRef(value, context));
290248
+ }
290249
+ async function preStepCondition(step) {
290250
+ if (!step.condition) return null;
290251
+ const label = step.name || `${step.method} ${step.path}`;
290252
+ const base = { step, status: 0, responseBody: null, durationMs: 0, captured: [], injected: {} };
290253
+ const kind = (0, import_simulator2.conditionKind)(step.condition);
290254
+ if (kind === "invalid") {
290255
+ return { ...base, passed: false, error: `Step "${label}": ${(0, import_simulator2.describeInvalidCondition)(step.condition)}` };
290256
+ }
290257
+ if (kind === "legacy") {
290258
+ const outcome = await (0, import_simulator2.evaluateLegacyCondition)(
290259
+ step.condition,
290260
+ (value) => resolveConditionRef(value, `condition of step "${label}"`)
290261
+ );
290262
+ if (!outcome.matched) {
290263
+ return {
290264
+ ...base,
290265
+ passed: true,
290266
+ skipped: true,
290267
+ conditionResult: { passed: false, reason: `legacy guard not met (resolved "${outcome.evaluated}")` }
290268
+ };
290269
+ }
289944
290270
  }
289945
- const passed = isAnd ? results.every((r) => r.ok) : results.some((r) => r.ok);
289946
- const failing = results.filter((r) => !r.ok);
289947
- const reason = passed ? `All conditions met (${isAnd ? "AND" : "OR"})` : `Failed: ${failing.map(
289948
- (r) => `${r.rule.left} ${r.rule.operator}${r.rule.right !== void 0 ? " " + r.rule.right : ""}`
289949
- ).join(", ")}`;
289950
- return { passed, reason };
290271
+ return null;
289951
290272
  }
289952
290273
  function envKeyForHuman(key) {
289953
290274
  return `JETIC_HUMAN_${key.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
@@ -290236,12 +290557,14 @@ function renderStepResult(result, index, total) {
290236
290557
  console.log(` ${CHAIN} ${c.green}\u{1F4BE} captured ${cap}${c.reset}`);
290237
290558
  }
290238
290559
  }
290239
- if (result.conditionResult) {
290560
+ if (result.skipped) {
290561
+ console.log(` ${CHAIN} ${c.yellow}\u2298 skipped${c.reset} ${c.dim}${result.conditionResult?.reason ?? ""}${c.reset}`);
290562
+ } else if (result.conditionResult) {
290240
290563
  const { passed: condPassed, reason } = result.conditionResult;
290241
290564
  const condIcon = condPassed ? `${c.magenta}\u25C7${c.reset}` : `${c.yellow}\u25C8${c.reset}`;
290242
290565
  const condColor = condPassed ? c.magenta : c.yellow;
290243
290566
  console.log(` ${CHAIN} ${condIcon} ${condColor}condition ${condPassed ? "passed" : "failed"}${c.reset} ${c.dim}${reason}${c.reset}`);
290244
- if (!condPassed && result.step.condition) {
290567
+ if (!condPassed && (0, import_simulator2.isRulesCondition)(result.step.condition)) {
290245
290568
  const { onFail, switchToWorkflow } = result.step.condition;
290246
290569
  const actionStr = onFail === "switch" ? `switching to workflow: ${switchToWorkflow ?? "unknown"}` : onFail === "continue" ? "continuing without remaining steps" : "aborting workflow";
290247
290570
  console.log(` ${CHAIN} ${c.yellow} \u2192 ${actionStr}${c.reset}`);
@@ -290458,12 +290781,17 @@ var simulateWorkflowCommand = new import_commander14.Command("workflow").descrip
290458
290781
  spinner.start(`${stepLabel} ${c.dim}${step.name}${c.reset}...`);
290459
290782
  let result;
290460
290783
  try {
290461
- result = await executeStep(step, baseUrl, {});
290462
- if (step.condition) {
290784
+ const preempted = await preStepCondition(step);
290785
+ result = preempted ?? await executeStep(step, baseUrl, {});
290786
+ if (!preempted && (0, import_simulator2.isRulesCondition)(step.condition)) {
290463
290787
  const condResult = await evaluateStepCondition(step.condition, `condition of step "${step.name || step.path}"`);
290464
290788
  result.conditionResult = condResult;
290465
290789
  if (!condResult.passed) {
290466
290790
  const { onFail, switchToWorkflow } = step.condition;
290791
+ if (onFail === "abort") {
290792
+ result.passed = false;
290793
+ result.error = `Condition failed: ${condResult.reason}`;
290794
+ }
290467
290795
  results.push(result);
290468
290796
  const icon2 = result.passed ? TICK : CROSS;
290469
290797
  spinner.stop(` ${icon2} ${stepLabel} ${formatStatus(result.status)} ${c.dim}${result.durationMs}ms${c.reset}`);
@@ -290481,10 +290809,9 @@ var simulateWorkflowCommand = new import_commander14.Command("workflow").descrip
290481
290809
  } else if (onFail === "switch" && switchToWorkflow) {
290482
290810
  console.log(` ${ARROW} Condition failed \u2014 switching to workflow: ${c.bold}${switchToWorkflow}${c.reset}
290483
290811
  `);
290484
- const switchPath = path11.join(workflowsDir, `${switchToWorkflow}.json`);
290485
- if (fs7.existsSync(switchPath)) {
290812
+ const switchedWorkflow = (0, import_model5.loadWorkflowByName)(workflowsDir, switchToWorkflow);
290813
+ if (switchedWorkflow) {
290486
290814
  try {
290487
- const switchedWorkflow = JSON.parse(fs7.readFileSync(switchPath, "utf-8"));
290488
290815
  console.log(` ${TICK} Loaded switch workflow: ${c.bold}${switchedWorkflow.name}${c.reset}
290489
290816
  `);
290490
290817
  renderWorkflowHeader(switchedWorkflow);
@@ -290495,8 +290822,9 @@ var simulateWorkflowCommand = new import_commander14.Command("workflow").descrip
290495
290822
  const switchLabel = `${c.dim}${si + 1}/${switchedWorkflow.steps.length}${c.reset} ${getMethodColor(switchStep.method)}${switchStep.method}${c.reset} ${switchStep.path}`;
290496
290823
  const switchSpinner = new Spinner();
290497
290824
  switchSpinner.start(`${switchLabel} ${c.dim}${switchStep.name}${c.reset}...`);
290498
- const switchResult = await executeStep(switchStep, baseUrl, {});
290499
- if (switchStep.condition) {
290825
+ const switchPre = await preStepCondition(switchStep);
290826
+ const switchResult = switchPre ?? await executeStep(switchStep, baseUrl, {});
290827
+ if (!switchPre && (0, import_simulator2.isRulesCondition)(switchStep.condition)) {
290500
290828
  switchResult.conditionResult = await evaluateStepCondition(
290501
290829
  switchStep.condition,
290502
290830
  `condition of step "${switchStep.name || switchStep.path}"`
@@ -290508,12 +290836,16 @@ var simulateWorkflowCommand = new import_commander14.Command("workflow").descrip
290508
290836
  renderStepResult(switchResult, si, switchedWorkflow.steps.length);
290509
290837
  }
290510
290838
  } catch (e) {
290511
- console.log(` ${c.red}\u2717 Failed to load switch workflow: ${e.message}${c.reset}
290839
+ console.log(` ${c.red}\u2717 Failed to run switch workflow: ${e.message}${c.reset}
290512
290840
  `);
290841
+ result.passed = false;
290842
+ result.error = `Switch workflow "${switchToWorkflow}" failed: ${e.message}`;
290513
290843
  }
290514
290844
  } else {
290515
- console.log(` ${c.yellow}\u26A0 Switch workflow not found: ${switchPath}${c.reset}
290845
+ console.log(` ${c.red}\u2717 Switch workflow not found: ${switchToWorkflow}${c.reset}
290516
290846
  `);
290847
+ result.passed = false;
290848
+ result.error = `Switch workflow "${switchToWorkflow}" was not found in ${workflowsDir}`;
290517
290849
  }
290518
290850
  if (step.condition.returnOnComplete) {
290519
290851
  console.log(` ${c.green}\u21A9 Sub-workflow complete. Returning to main workflow...${c.reset}
@@ -290524,6 +290856,14 @@ var simulateWorkflowCommand = new import_commander14.Command("workflow").descrip
290524
290856
  break;
290525
290857
  }
290526
290858
  }
290859
+ if (onFail === "switch" && !switchToWorkflow) {
290860
+ console.log(` ${c.red}\u2717 Condition failed and onFail "switch" has no switchToWorkflow.${c.reset}
290861
+ `);
290862
+ result.passed = false;
290863
+ result.error = 'onFail "switch" has no switchToWorkflow';
290864
+ stopOnFailure = true;
290865
+ break;
290866
+ }
290527
290867
  continue;
290528
290868
  }
290529
290869
  }
@@ -290538,9 +290878,15 @@ var simulateWorkflowCommand = new import_commander14.Command("workflow").descrip
290538
290878
  throw err;
290539
290879
  }
290540
290880
  results.push(result);
290541
- const icon = result.passed ? TICK : CROSS;
290881
+ const icon = result.skipped ? SKIP : result.passed ? TICK : CROSS;
290542
290882
  spinner.stop(` ${icon} ${stepLabel} ${formatStatus(result.status)} ${c.dim}${result.durationMs}ms${c.reset}`);
290543
290883
  renderStepResult(result, i, workflow.steps.length);
290884
+ if (!result.passed && result.error && !result.status && (0, import_simulator2.conditionKind)(step.condition) === "invalid") {
290885
+ console.log(` ${c.red}${c.bold}\u26A0 Unrecognized condition \u2014 stopping workflow.${c.reset}
290886
+ `);
290887
+ stopOnFailure = true;
290888
+ break;
290889
+ }
290544
290890
  if (!result.passed && (step.path.includes("/login") || step.path.includes("/register") || step.path.includes("/auth"))) {
290545
290891
  console.log(` ${c.red}${c.bold}\u26A0 Auth step failed \u2014 stopping workflow to prevent cascading failures.${c.reset}
290546
290892
  `);
@@ -290663,15 +291009,19 @@ var Spinner2 = class {
290663
291009
  }
290664
291010
  }
290665
291011
  };
290666
- async function selectEnvironment(environments) {
291012
+ function defaultEnvironment(model, environments) {
291013
+ const defaultName = model.defaultEnvironment ?? environments.find((e) => e.default)?.name;
291014
+ return environments.find((e) => e.name === defaultName) ?? environments[0];
291015
+ }
291016
+ async function selectEnvironment(model, environments) {
290667
291017
  if (environments.length === 0) {
290668
291018
  console.log(` ${c2.yellow}\u26A0${c2.reset} No environments defined in model.json`);
290669
291019
  console.log(` ${c2.dim}Using default: http://localhost:3000${c2.reset}
290670
291020
  `);
290671
291021
  return { name: "default", baseUrl: "http://localhost:3000" };
290672
291022
  }
290673
- if (environments.length === 1) {
290674
- const env = environments[0];
291023
+ if (environments.length === 1 || !process.stdin.isTTY) {
291024
+ const env = defaultEnvironment(model, environments);
290675
291025
  console.log(` ${c2.cyan}\u{1F30D}${c2.reset} Environment: ${c2.bold}${env.name}${c2.reset} ${c2.dim}\u2192 ${env.baseUrl}${c2.reset}
290676
291026
  `);
290677
291027
  return env;
@@ -290914,7 +291264,7 @@ function loadModel3() {
290914
291264
  }
290915
291265
  var simulateCommand = new import_commander15.Command("simulate").description("Simulate API endpoints against a live server");
290916
291266
  simulateCommand.addCommand(simulateWorkflowCommand);
290917
- simulateCommand.command("endpoint [method] [endpointPath]").description("Simulate a specific endpoint or all endpoints").option("--all", "Simulate all endpoints").option("--verbose", "Show detailed request/response info", false).action(async (method, endpointPath, options) => {
291267
+ simulateCommand.command("endpoint [method] [endpointPath]").description("Simulate a specific endpoint or all endpoints").option("--all", "Simulate all endpoints").option("--verbose", "Show detailed request/response info", false).option("--env <name>", "Environment name to use from model.json (skips the interactive picker)").action(async (method, endpointPath, options) => {
290918
291268
  console.log("");
290919
291269
  console.log(` ${c2.bgCyan}${c2.black}${c2.bold} JETIC ${c2.reset} ${c2.cyan}${c2.bold}Endpoint Simulator${c2.reset}`);
290920
291270
  console.log("");
@@ -290928,7 +291278,20 @@ simulateCommand.command("endpoint [method] [endpointPath]").description("Simulat
290928
291278
  console.log(` ${c2.dim}Model v${model.version} \u2022 ${model.endpoints.length} endpoints${c2.reset}
290929
291279
  `);
290930
291280
  const environments = model.environments || [];
290931
- const selectedEnv = await selectEnvironment(environments);
291281
+ let selectedEnv;
291282
+ if (options.env) {
291283
+ const named = environments.find((e) => e.name === options.env);
291284
+ if (!named) {
291285
+ console.log(` ${c2.red}\u2717${c2.reset} Environment ${c2.bold}${options.env}${c2.reset} not found. Available: ${environments.map((e) => e.name).join(", ") || "(none)"}
291286
+ `);
291287
+ process.exit(2);
291288
+ }
291289
+ selectedEnv = named;
291290
+ console.log(` ${c2.cyan}\u{1F30D}${c2.reset} Environment: ${c2.bold}${named.name}${c2.reset} ${c2.dim}\u2192 ${named.baseUrl}${c2.reset}
291291
+ `);
291292
+ } else {
291293
+ selectedEnv = await selectEnvironment(model, environments);
291294
+ }
290932
291295
  const checkSpinner = new Spinner2();
290933
291296
  checkSpinner.start(`Checking if backend is active at ${selectedEnv.baseUrl}...`);
290934
291297
  try {
@@ -290942,7 +291305,7 @@ simulateCommand.command("endpoint [method] [endpointPath]").description("Simulat
290942
291305
  `);
290943
291306
  process.exit(1);
290944
291307
  }
290945
- const simulator = new import_simulator2.EndpointSimulator(model, selectedEnv);
291308
+ const simulator = new import_simulator3.EndpointSimulator(model, selectedEnv);
290946
291309
  const spinner = new Spinner2();
290947
291310
  if (options.all) {
290948
291311
  console.log(` ${c2.magenta}\u{1F680}${c2.reset} Simulating ${c2.bold}${model.endpoints.length}${c2.reset} endpoints...
@@ -291005,8 +291368,9 @@ var import_commander16 = require("commander");
291005
291368
  var import_express = __toESM(require("express"));
291006
291369
  var import_path = __toESM(require("path"));
291007
291370
  var import_fs = __toESM(require("fs"));
291008
- var import_model5 = __toESM(require_dist2());
291371
+ var import_model6 = __toESM(require_dist2());
291009
291372
  var import_memory3 = __toESM(require_dist12());
291373
+ var import_simulator4 = __toESM(require_dist13());
291010
291374
  var IGNORED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".cache", "__pycache__", ".next", ".nuxt", ".venv"]);
291011
291375
  var IGNORED_EXTS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".svg", ".ico", ".mp4", ".webm", ".woff", ".woff2", ".ttf", ".eot", ".map"]);
291012
291376
  var SOURCE_EXTS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go", ".rs", ".java", ".kt", ".cs", ".rb", ".php", ".json", ".yaml", ".yml", ".toml", ".env", ".graphql", ".gql", ".sql", ".prisma", ".proto"]);
@@ -291155,7 +291519,7 @@ var ChangeScanner = class {
291155
291519
  this.saveChanges(data);
291156
291520
  }
291157
291521
  };
291158
- var devCommand = new import_commander16.Command("dev").description("Start the Jetic local dashboard").option("-p, --port <number>", "Port to run the dashboard on", "8787").action(async (options) => {
291522
+ var devCommand = new import_commander16.Command("dev").description("Start the Jetic local dashboard").option("-p, --port <number>", "Port to run the dashboard on", "8787").option("--no-open", "Do not open the dashboard in a browser (also: JETIC_NO_OPEN=1)").action(async (options) => {
291159
291523
  const port = parseInt(options.port, 10);
291160
291524
  const app = (0, import_express.default)();
291161
291525
  const projectRoot = process.cwd();
@@ -291183,7 +291547,7 @@ var devCommand = new import_commander16.Command("dev").description("Start the Je
291183
291547
  const jeticDir2 = import_path.default.join(process.cwd(), ".jetic");
291184
291548
  const modelDir = import_path.default.join(jeticDir2, "model");
291185
291549
  try {
291186
- if ((0, import_model5.hasYamlModel)(modelDir)) return res.json((0, import_model5.loadModelFromDir)(modelDir));
291550
+ if ((0, import_model6.hasYamlModel)(modelDir)) return res.json((0, import_model6.loadModelFromDir)(modelDir));
291187
291551
  const modelPath = import_path.default.join(jeticDir2, "model.json");
291188
291552
  if (!import_fs.default.existsSync(modelPath)) return res.json(null);
291189
291553
  res.json(JSON.parse(import_fs.default.readFileSync(modelPath, "utf8")));
@@ -291793,57 +292157,43 @@ var devCommand = new import_commander16.Command("dev").description("Start the Je
291793
292157
  }
291794
292158
  return result;
291795
292159
  }
291796
- async function evalCondRule(rule, stepIndex, stepName) {
291797
- const left = await resolveCondRef(rule.left, stepIndex, stepName);
291798
- const right = rule.right !== void 0 ? await resolveCondRef(rule.right, stepIndex, stepName) : void 0;
291799
- switch (rule.operator) {
291800
- case "equals":
291801
- return left === right;
291802
- case "not_equals":
291803
- return left !== right;
291804
- case "greater_than":
291805
- return Number(left) > Number(right);
291806
- case "greater_than_or_equal":
291807
- return Number(left) >= Number(right);
291808
- case "less_than":
291809
- return Number(left) < Number(right);
291810
- case "less_than_or_equal":
291811
- return Number(left) <= Number(right);
291812
- case "exists":
291813
- return left !== "" && left !== void 0;
291814
- case "not_exists":
291815
- return left === "" || left === void 0;
291816
- case "is_empty":
291817
- return left === "";
291818
- case "is_not_empty":
291819
- return left !== "";
291820
- case "contains":
291821
- return right !== void 0 && left.includes(right);
291822
- case "not_contains":
291823
- return right !== void 0 && !left.includes(right);
291824
- case "starts_with":
291825
- return right !== void 0 && left.startsWith(right);
291826
- case "ends_with":
291827
- return right !== void 0 && left.endsWith(right);
291828
- default:
291829
- return false;
291830
- }
291831
- }
291832
292160
  async function evalCondition(condition, stepIndex, stepName) {
291833
- const rules = condition.rules?.all ?? condition.rules?.any ?? [];
291834
- const isAnd = !!condition.rules?.all;
291835
- if (rules.length === 0) return { passed: true, reason: "No rules" };
291836
- const results = [];
291837
- for (const rule of rules) results.push({ rule, ok: await evalCondRule(rule, stepIndex, stepName) });
291838
- const passed2 = isAnd ? results.every((r) => r.ok) : results.some((r) => r.ok);
291839
- const failing = results.filter((r) => !r.ok);
291840
- const reason = passed2 ? `All conditions met (${isAnd ? "AND" : "OR"})` : `Failed: ${failing.map((r) => `${r.rule.left} ${r.rule.operator}${r.rule.right !== void 0 ? " " + r.rule.right : ""}`).join(", ")}`;
291841
- return { passed: passed2, reason };
292161
+ return (0, import_simulator4.evaluateRules)(condition.rules ?? {}, (value) => resolveCondRef(value, stepIndex, stepName));
291842
292162
  }
291843
292163
  for (let i = 0; i < workflow.steps.length; i++) {
291844
292164
  if (aborted) break;
291845
292165
  const step = workflow.steps[i];
291846
292166
  send("step_start", { index: i, step: { name: step.name, method: step.method, path: step.path } });
292167
+ const condKind = step.condition ? (0, import_simulator4.conditionKind)(step.condition) : void 0;
292168
+ const stepSummary = { name: step.name, method: step.method, path: step.path, description: step.description };
292169
+ if (condKind === "invalid") {
292170
+ const reason = `Step ${i + 1} "${step.name || `${step.method} ${step.path}`}": ${(0, import_simulator4.describeInvalidCondition)(step.condition)}`;
292171
+ failed++;
292172
+ send("step_result", { index: i, step: stepSummary, status: 0, passed: false, durationMs: 0, captured: [], injected: {}, responseBody: null, error: reason });
292173
+ send("aborted", { index: i, reason });
292174
+ res.end();
292175
+ return;
292176
+ }
292177
+ if (condKind === "legacy") {
292178
+ const guard = await (0, import_simulator4.evaluateLegacyCondition)(step.condition, (value) => resolveCondRef(value, i, step.name || ""));
292179
+ if (!guard.matched) {
292180
+ passed++;
292181
+ send("step_result", {
292182
+ index: i,
292183
+ step: stepSummary,
292184
+ status: 0,
292185
+ passed: true,
292186
+ skipped: true,
292187
+ durationMs: 0,
292188
+ captured: [],
292189
+ injected: {},
292190
+ responseBody: null,
292191
+ error: null,
292192
+ skipReason: `legacy guard not met (resolved "${guard.evaluated}")`
292193
+ });
292194
+ continue;
292195
+ }
292196
+ }
291847
292197
  let result;
291848
292198
  try {
291849
292199
  result = await executeStep2(step, i);
@@ -291868,7 +292218,7 @@ var devCommand = new import_commander16.Command("dev").description("Start the Je
291868
292218
  responseBody: result.responseBody,
291869
292219
  error: result.error
291870
292220
  });
291871
- if (step.condition && !aborted) {
292221
+ if ((0, import_simulator4.isRulesCondition)(step.condition) && !aborted) {
291872
292222
  const condResult = await evalCondition(step.condition, i, step.name || `${step.method} ${step.path}`);
291873
292223
  send("condition_result", {
291874
292224
  index: i,
@@ -292050,6 +292400,8 @@ var devCommand = new import_commander16.Command("dev").description("Start the Je
292050
292400
  console.log(`
292051
292401
  \u{1F680} Jetic Studio is running at ${url}
292052
292402
  `);
292403
+ const noOpenEnv = process.env.JETIC_NO_OPEN;
292404
+ if (!options.open || noOpenEnv && noOpenEnv !== "0" && noOpenEnv.toLowerCase() !== "false") return;
292053
292405
  const { exec } = require("child_process");
292054
292406
  const start = process.platform == "darwin" ? "open" : process.platform == "win32" ? "start" : "xdg-open";
292055
292407
  exec(`${start} ${url}`);