@skillstech/thunderlang 0.1.7 → 0.3.0

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.cjs CHANGED
@@ -450,6 +450,60 @@ function parseDecision(name, node2) {
450
450
  }
451
451
  return dec;
452
452
  }
453
+ function parseProperty(name, node2) {
454
+ const prop = { name, vars: [], decide: null, expects: [], line: node2.line };
455
+ for (const c of node2.children.filter((x) => !isNote(x))) {
456
+ const k = firstWord(c.text);
457
+ const a = rest(c.text);
458
+ if (k === "forAll" || k === "forall") {
459
+ for (const v of c.children.filter((x) => !isNote(x))) {
460
+ const m = v.text.trim().match(/^([A-Za-z_]\w*)\s*:\s*(\w+)(?:\s+where\s+(.+))?$/);
461
+ if (!m) continue;
462
+ const whereChild = (v.children || []).find((x) => firstWord(x.text) === "where");
463
+ prop.vars.push({ name: m[1], type: m[2], where: m[3] || (whereChild ? rest(whereChild.text) : null) });
464
+ }
465
+ } else if (k === "decide") prop.decide = a || c.children[0] && c.children[0].text.trim() || null;
466
+ else if (k === "when") {
467
+ const dm = a.match(/(?:decide|execute)\s+(\w+)/);
468
+ if (dm) prop.decide = dm[1];
469
+ } else if (k === "expect") for (const e of c.children.filter((x) => !isNote(x))) prop.expects.push(e.text.trim());
470
+ }
471
+ return prop;
472
+ }
473
+ function parseEvaluation(name, node2) {
474
+ const ev = { name, dataset: null, requires: [], line: node2.line };
475
+ for (const c of node2.children.filter((x) => !isNote(x))) {
476
+ const k = firstWord(c.text);
477
+ const a = rest(c.text);
478
+ if (k === "dataset") ev.dataset = a || c.children[0] && c.children[0].text.trim() || null;
479
+ else if (k === "require") {
480
+ for (const r of c.children.filter((x) => !isNote(x))) {
481
+ const m = r.text.trim().match(/^([A-Za-z_][\w.]*)\s*(>=|<=|==|!=|>|<)\s*(-?[\d.]+)$/);
482
+ if (m) ev.requires.push({ metric: m[1], op: m[2], threshold: parseFloat(m[3]) });
483
+ }
484
+ }
485
+ }
486
+ return ev;
487
+ }
488
+ function parseScenario(name, node2) {
489
+ const sc = { name, given: [], when: [], then: [], never: [], eventually: [], line: node2.line };
490
+ const clausesOf = (c) => {
491
+ const items = leafItems(c);
492
+ return items.length ? items : rest(c.text) ? [rest(c.text)] : [];
493
+ };
494
+ for (const c of node2.children.filter((x) => !isNote(x))) {
495
+ const k = firstWord(c.text);
496
+ if (k === "given") sc.given.push(...clausesOf(c));
497
+ else if (k === "when") sc.when.push(...clausesOf(c));
498
+ else if (k === "then") sc.then.push(...clausesOf(c));
499
+ else if (k === "never") sc.never.push(...clausesOf(c));
500
+ else if (k === "eventually") {
501
+ const within = (rest(c.text).match(/within\s+(.+)/) || [])[1] || null;
502
+ sc.eventually.push({ within, clauses: leafItems(c) });
503
+ }
504
+ }
505
+ return sc;
506
+ }
453
507
  function parseLifecycle(name, node2) {
454
508
  const lc = { name, states: [], transitions: [], terminals: [], line: node2.line };
455
509
  for (const c of node2.children.filter((x) => !isNote(x))) {
@@ -482,7 +536,13 @@ function parseInvariant(name, node2) {
482
536
  for (const c of kids(node2)) {
483
537
  const k = firstWord(c.text);
484
538
  if (k === "verify") inv.verify.push(rest(c.text));
485
- else if (k === "because") inv.because = rest(c.text);
539
+ else if (k === "id") {
540
+ const v = rest(c.text);
541
+ if (v) {
542
+ inv.id = v;
543
+ inv.stableId = v;
544
+ }
545
+ } else if (k === "because") inv.because = rest(c.text);
486
546
  else if (k === "statement") inv.statement = rest(c.text) || leafItems(c).join(" ");
487
547
  else if (k === "scope") inv.scope = (rest(c.text) || leafItems(c).join(" ")).toLowerCase();
488
548
  else if (k === "severity") inv.severity = (rest(c.text) || leafItems(c).join(" ")).toLowerCase();
@@ -568,11 +628,12 @@ function parseHandler(trigger, node2) {
568
628
  actions
569
629
  };
570
630
  }
631
+ var VERIFY_KINDS = /* @__PURE__ */ new Set(["assertion", "static", "runtime", "evidence", "tool", "approval", "formal"]);
571
632
  function upsertRule(list, statement, line) {
572
633
  const id = slug(statement);
573
634
  let r = list.find((x) => x.id === id);
574
635
  if (!r) {
575
- r = { id, statement, because: null, verify: [], notes: [], line };
636
+ r = { id, statement, because: null, verify: [], verifications: [], notes: [], line };
576
637
  list.push(r);
577
638
  }
578
639
  return r;
@@ -581,8 +642,28 @@ function applyDetail(rule, node2) {
581
642
  for (const c of node2.children) {
582
643
  const kw = firstWord(c.text);
583
644
  if (kw === "because") rule.because = rest(c.text) || c.children[0] && c.children[0].text || null;
584
- else if (kw === "verify") rule.verify.push(rest(c.text) || c.children[0] && c.children[0].text || "");
585
- else if (isNote(c)) rule.notes.push(parseNoteNode(c));
645
+ else if (kw === "verify") {
646
+ const a = rest(c.text);
647
+ const m = a && a.match(/^by\s+(\w+)/i);
648
+ if (m) {
649
+ const kind = m[1].toLowerCase();
650
+ const details = (c.children || []).filter((x) => !isNote(x)).map((x) => x.text.trim()).filter(Boolean);
651
+ rule.verifications.push({ kind: VERIFY_KINDS.has(kind) ? kind : "unknown", declared: kind, details });
652
+ rule.verify.push(details.length ? `${kind}: ${details.join("; ")}` : `by ${kind}`);
653
+ } else {
654
+ const v = a || c.children[0] && c.children[0].text || "";
655
+ if (v) {
656
+ rule.verify.push(v);
657
+ rule.verifications.push({ kind: "unclassified", declared: null, details: [v] });
658
+ }
659
+ }
660
+ } else if (kw === "id") {
661
+ const v = rest(c.text) || c.children[0] && c.children[0].text;
662
+ if (v) {
663
+ rule.id = v;
664
+ rule.stableId = v;
665
+ }
666
+ } else if (isNote(c)) rule.notes.push(parseNoteNode(c));
586
667
  }
587
668
  rule.verify = rule.verify.filter(Boolean);
588
669
  }
@@ -647,6 +728,9 @@ function parseIntent(source) {
647
728
  handlers: [],
648
729
  // Decisions, rules, process (Gap 4)
649
730
  decisions: [],
731
+ properties: [],
732
+ scenarios: [],
733
+ evaluations: [],
650
734
  // Governance: waivers , governed exceptions to blocking diagnostics (Gap 5)
651
735
  waivers: [],
652
736
  // Data purpose + privacy , governed data elements (Gap 6)
@@ -875,6 +959,15 @@ function parseIntent(source) {
875
959
  case "decision":
876
960
  ast.decisions.push(parseDecision(arg, node2));
877
961
  break;
962
+ case "property":
963
+ ast.properties.push(parseProperty(arg, node2));
964
+ break;
965
+ case "scenario":
966
+ ast.scenarios.push(parseScenario(arg, node2));
967
+ break;
968
+ case "evaluation":
969
+ ast.evaluations.push(parseEvaluation(arg, node2));
970
+ break;
878
971
  // ── System profile ──
879
972
  case "capability": {
880
973
  const cap = { name: arg, description: null, implements: [], line: node2.line };
@@ -2194,7 +2287,7 @@ function notesSummary(ast) {
2194
2287
  byLens
2195
2288
  };
2196
2289
  }
2197
- var COMPILER_VERSION = "0.1.0";
2290
+ var COMPILER_VERSION = "0.3.0";
2198
2291
  var PROOF_SCHEMA_VERSION = "0.1.0";
2199
2292
  var SOURCE_PRODUCT = "skillstech-compiler";
2200
2293
  function buildContractGraph(ast, generatedAt) {
@@ -2687,13 +2780,15 @@ function buildProof(ast, { sourceFile, sourceHash, targetsRequested, targetsGene
2687
2780
  id: g.id,
2688
2781
  text: g.statement,
2689
2782
  status: g.verify.length > 0 || verifiedText ? "planned" : "needs_verification",
2690
- evidence: g.verify
2783
+ evidence: g.verify,
2784
+ verifications: g.verifications || []
2691
2785
  })),
2692
2786
  neverRules: ast.neverRules.map((n) => ({
2693
2787
  id: n.id,
2694
2788
  text: n.statement,
2695
2789
  status: n.verify.length > 0 ? "planned" : "needs_verification",
2696
- evidence: n.verify
2790
+ evidence: n.verify,
2791
+ verifications: n.verifications || []
2697
2792
  })),
2698
2793
  errors: (ast.errors || []).map((e) => ({ name: e.name })),
2699
2794
  examples: (ast.examples || []).map((ex) => ({ given: ex.given, expect: ex.expect })),
@@ -3658,6 +3753,112 @@ function extractFactsRuby(source, file = "input.rb") {
3658
3753
  }
3659
3754
  return { schemaVersion: IR_SCHEMA_VERSION, sourceLanguage: "ruby", sourceRoot: file, functions, tests, errors };
3660
3755
  }
3756
+ function parseNameColonTypeParams(raw) {
3757
+ return splitTopLevel(raw, ",").map((p) => p.trim()).filter(Boolean).map((p) => {
3758
+ const cleaned = p.replace(/@\w+(\([^)]*\))?/g, "").replace(/^(?:val|var|vararg|implicit|final|lazy)\s+/, "").replace(/=.*$/, "").trim();
3759
+ const mm = cleaned.match(/^([A-Za-z_]\w*)\s*:\s*(.+)$/);
3760
+ if (mm) return { name: mm[1], type: mm[2].trim() };
3761
+ return { name: cleaned.replace(/[^\w].*$/, "") || cleaned, type: null };
3762
+ });
3763
+ }
3764
+ function extractFactsKotlin(source, file = "input.kt") {
3765
+ let m;
3766
+ const functions = [];
3767
+ const tests = [];
3768
+ const seen = /* @__PURE__ */ new Set();
3769
+ const testMethods = /* @__PURE__ */ new Set();
3770
+ const ta = /@Test\b[\s\S]{0,120}?\bfun\s+(?:`([^`]+)`|(\w+))\s*\(/g;
3771
+ while (m = ta.exec(source)) testMethods.add(m[1] || m[2]);
3772
+ const fnRe = /\bfun\s+(?:<[^>]*>\s*)?(?:[A-Za-z_][\w.]*\.)?(?:`([^`]+)`|([A-Za-z_]\w*))\s*\(([^)]*)\)\s*(?::\s*([^{=\n]+))?/g;
3773
+ while (m = fnRe.exec(source)) {
3774
+ const name = m[1] || m[2];
3775
+ if (testMethods.has(name)) {
3776
+ if (!tests.some((t) => t.name === name)) tests.push({ name, file, line: lineOf(source, m.index) });
3777
+ continue;
3778
+ }
3779
+ if (seen.has(name)) continue;
3780
+ seen.add(name);
3781
+ functions.push({ name, file, line: lineOf(source, m.index), parameters: parseNameColonTypeParams(m[3] || ""), returnType: m[4] ? m[4].trim() : null, evidence: [{ kind: "fun", file, line: lineOf(source, m.index) }] });
3782
+ }
3783
+ const errors = [];
3784
+ const addErr = addErrOf(errors, /* @__PURE__ */ new Set(), { _src: source, _file: file });
3785
+ let mm;
3786
+ const ce = /class\s+(\w*(?:Exception|Error))\b/g;
3787
+ while (mm = ce.exec(source)) addErr(mm[1], mm.index);
3788
+ const th = /throw\s+(\w+)\s*\(/g;
3789
+ while (mm = th.exec(source)) addErr(mm[1], mm.index);
3790
+ return { schemaVersion: IR_SCHEMA_VERSION, sourceLanguage: "kotlin", sourceRoot: file, functions, tests, errors };
3791
+ }
3792
+ function extractFactsScala(source, file = "input.scala") {
3793
+ let m;
3794
+ const functions = [];
3795
+ const tests = [];
3796
+ const seen = /* @__PURE__ */ new Set();
3797
+ const fnRe = /\bdef\s+([A-Za-z_]\w*)\s*(?:\[[^\]]*\])?\s*\(([^)]*)\)\s*(?::\s*([^={\n]+))?/g;
3798
+ while (m = fnRe.exec(source)) {
3799
+ const name = m[1];
3800
+ if (seen.has(name)) continue;
3801
+ seen.add(name);
3802
+ functions.push({ name, file, line: lineOf(source, m.index), parameters: parseNameColonTypeParams(m[2] || ""), returnType: m[3] ? m[3].trim() : null, evidence: [{ kind: "def", file, line: lineOf(source, m.index) }] });
3803
+ }
3804
+ const seenT = /* @__PURE__ */ new Set();
3805
+ const addTest = (n, idx) => {
3806
+ const k = String(n).toLowerCase();
3807
+ if (n && !seenT.has(k)) {
3808
+ seenT.add(k);
3809
+ tests.push({ name: n, file, line: lineOf(source, idx) });
3810
+ }
3811
+ };
3812
+ const tr = /\btest\s*\(\s*"([^"]+)"/g;
3813
+ while (m = tr.exec(source)) addTest(m[1], m.index);
3814
+ const inRe = /"([^"]+)"\s+(?:in|should|must)\b/g;
3815
+ while (m = inRe.exec(source)) addTest(m[1], m.index);
3816
+ const errors = [];
3817
+ const addErr = addErrOf(errors, /* @__PURE__ */ new Set(), { _src: source, _file: file });
3818
+ let mm;
3819
+ const ce = /class\s+(\w*(?:Exception|Error))\b/g;
3820
+ while (mm = ce.exec(source)) addErr(mm[1], mm.index);
3821
+ const th = /throw\s+new\s+(\w+)\s*\(/g;
3822
+ while (mm = th.exec(source)) addErr(mm[1], mm.index);
3823
+ return { schemaVersion: IR_SCHEMA_VERSION, sourceLanguage: "scala", sourceRoot: file, functions, tests, errors };
3824
+ }
3825
+ function extractFactsElixir(source, file = "input.ex") {
3826
+ let m;
3827
+ const functions = [];
3828
+ const tests = [];
3829
+ const seen = /* @__PURE__ */ new Set();
3830
+ const seenT = /* @__PURE__ */ new Set();
3831
+ const addTest = (n, idx) => {
3832
+ const k = String(n).toLowerCase();
3833
+ if (n && !seenT.has(k)) {
3834
+ seenT.add(k);
3835
+ tests.push({ name: n, file, line: lineOf(source, idx) });
3836
+ }
3837
+ };
3838
+ const tr = /\btest\s+"([^"]+)"/g;
3839
+ while (m = tr.exec(source)) addTest(m[1], m.index);
3840
+ const fnRe = /^[ \t]*defp?\s+([a-z_]\w*[!?]?)\s*(?:\(([^)]*)\))?/gm;
3841
+ while (m = fnRe.exec(source)) {
3842
+ const name = m[1];
3843
+ if (seen.has(name)) continue;
3844
+ seen.add(name);
3845
+ const parameters = splitTopLevel(m[2] || "", ",").map((p) => p.trim()).filter(Boolean).map((p) => ({ name: p.split(/\\\\/)[0].trim().replace(/^%\{?/, "").replace(/[^\w].*$/, "") || p, type: null }));
3846
+ functions.push({ name, file, line: lineOf(source, m.index), indent: (m[0].match(/^[ \t]*/) || [""])[0].length, parameters, returnType: null, evidence: [{ kind: "def", file, line: lineOf(source, m.index) }] });
3847
+ }
3848
+ const errors = [];
3849
+ const seenErr = /* @__PURE__ */ new Set();
3850
+ const addErr = (n, idx) => {
3851
+ if (n && !seenErr.has(n)) {
3852
+ seenErr.add(n);
3853
+ errors.push({ name: n, file, line: lineOf(source, idx) });
3854
+ }
3855
+ };
3856
+ const modErr = /defmodule\s+([\w.]*(?:Error|Exception))\b/g;
3857
+ while (m = modErr.exec(source)) addErr(m[1].split(".").pop(), m.index);
3858
+ const raiseRe = /raise\s+([A-Z][\w.]*)/g;
3859
+ while (m = raiseRe.exec(source)) addErr(m[1].split(".").pop(), m.index);
3860
+ return { schemaVersion: IR_SCHEMA_VERSION, sourceLanguage: "elixir", sourceRoot: file, functions, tests, errors };
3861
+ }
3661
3862
  var ADAPTERS = {
3662
3863
  typescript: extractFactsTypeScript,
3663
3864
  ts: extractFactsTypeScript,
@@ -3681,10 +3882,18 @@ var ADAPTERS = {
3681
3882
  cc: extractFactsCpp,
3682
3883
  php: extractFactsPhp,
3683
3884
  ruby: extractFactsRuby,
3684
- rb: extractFactsRuby
3885
+ rb: extractFactsRuby,
3886
+ kotlin: extractFactsKotlin,
3887
+ kt: extractFactsKotlin,
3888
+ kts: extractFactsKotlin,
3889
+ scala: extractFactsScala,
3890
+ sc: extractFactsScala,
3891
+ elixir: extractFactsElixir,
3892
+ ex: extractFactsElixir,
3893
+ exs: extractFactsElixir
3685
3894
  };
3686
- var SUPPORTED_LANGUAGES = ["typescript", "javascript", "python", "java", "csharp", "go", "rust", "cpp", "php", "ruby", "perl"];
3687
- var DYNAMIC_LANGUAGES = /* @__PURE__ */ new Set(["perl", "javascript", "python", "ruby", "php"]);
3895
+ var SUPPORTED_LANGUAGES = ["typescript", "javascript", "python", "java", "csharp", "go", "rust", "cpp", "php", "ruby", "perl", "kotlin", "scala", "elixir"];
3896
+ var DYNAMIC_LANGUAGES = /* @__PURE__ */ new Set(["perl", "javascript", "python", "ruby", "php", "elixir"]);
3688
3897
  var LANG_DISPLAY = {
3689
3898
  typescript: "TypeScript",
3690
3899
  javascript: "JavaScript",
@@ -3696,7 +3905,10 @@ var LANG_DISPLAY = {
3696
3905
  cpp: "C++",
3697
3906
  php: "PHP",
3698
3907
  ruby: "Ruby",
3699
- perl: "Perl"
3908
+ perl: "Perl",
3909
+ kotlin: "Kotlin",
3910
+ scala: "Scala",
3911
+ elixir: "Elixir"
3700
3912
  };
3701
3913
  function unwrapReturn(ret) {
3702
3914
  if (!ret) return { output: null, error: null };
@@ -3820,6 +4032,9 @@ function languageForFile(file) {
3820
4032
  if (/\.(cpp|cc|cxx|hpp|hh|c|h)$/i.test(file)) return "cpp";
3821
4033
  if (/\.php$/i.test(file)) return "php";
3822
4034
  if (/\.rb$/i.test(file)) return "ruby";
4035
+ if (/\.kts?$/i.test(file)) return "kotlin";
4036
+ if (/\.(scala|sc)$/i.test(file)) return "scala";
4037
+ if (/\.exs?$/i.test(file)) return "elixir";
3823
4038
  if (/\.(mjs|cjs|jsx?)$/i.test(file)) return "javascript";
3824
4039
  return "typescript";
3825
4040
  }
@@ -3827,7 +4042,7 @@ function isPublicFn(fn, language) {
3827
4042
  const name = fn.name || "";
3828
4043
  if (/(?:Internal|Impl|_impl|_helper|_test|Helper)$/.test(name)) return false;
3829
4044
  if (language === "go" || language === "golang") return /^[A-Z]/.test(name) && name !== "Test";
3830
- if (language === "python" || language === "ruby") return !name.startsWith("_") && (fn.indent == null || fn.indent <= 4);
4045
+ if (language === "python" || language === "ruby" || language === "elixir") return !name.startsWith("_") && (fn.indent == null || fn.indent <= 4);
3831
4046
  return !name.startsWith("_") && name !== "init" && name !== "constructor";
3832
4047
  }
3833
4048
  function liftAll(source, { language = "typescript", file = "", publicOnly = true } = {}) {
@@ -3902,7 +4117,10 @@ var LANG_EXT = {
3902
4117
  cpp: "cpp",
3903
4118
  php: "php",
3904
4119
  ruby: "rb",
3905
- perl: "pl"
4120
+ perl: "pl",
4121
+ kotlin: "kt",
4122
+ scala: "scala",
4123
+ elixir: "ex"
3906
4124
  };
3907
4125
  function liftSource(source, { language = "typescript", file = "", seeds = void 0 } = {}) {
3908
4126
  const key = String(language).toLowerCase();
@@ -6088,21 +6306,23 @@ function compileExpr(src) {
6088
6306
  function evalExpr(src, inputs = {}) {
6089
6307
  return compileExpr(src)(inputs);
6090
6308
  }
6309
+ var C_LOGIC = { and: (a, b) => `(${a} && ${b})`, or: (a, b) => `(${a} || ${b})`, not: (a) => `!${a}`, bool: (v) => String(v) };
6091
6310
  var DIALECTS = {
6092
- js: { eq: (a, b) => `${a} === ${b}`, neq: (a, b) => `${a} !== ${b}`, inList: (list, x) => `[${list.join(", ")}].includes(${x})`, nil: "undefined" },
6093
- csharp: { eq: (a, b) => `${a} == ${b}`, neq: (a, b) => `${a} != ${b}`, inList: (list, x) => `new[]{${list.join(", ")}}.Contains(${x})`, nil: "null" },
6094
- java: { eq: (a, b) => `java.util.Objects.equals(${a}, ${b})`, neq: (a, b) => `!java.util.Objects.equals(${a}, ${b})`, inList: (list, x) => `java.util.List.of(${list.join(", ")}).contains(${x})`, nil: "null" }
6311
+ js: { ...C_LOGIC, eq: (a, b) => `${a} === ${b}`, neq: (a, b) => `${a} !== ${b}`, inList: (list, x) => `[${list.join(", ")}].includes(${x})`, nil: "undefined" },
6312
+ csharp: { ...C_LOGIC, eq: (a, b) => `${a} == ${b}`, neq: (a, b) => `${a} != ${b}`, inList: (list, x) => `new[]{${list.join(", ")}}.Contains(${x})`, nil: "null" },
6313
+ java: { ...C_LOGIC, eq: (a, b) => `java.util.Objects.equals(${a}, ${b})`, neq: (a, b) => `!java.util.Objects.equals(${a}, ${b})`, inList: (list, x) => `java.util.List.of(${list.join(", ")}).contains(${x})`, nil: "null" },
6314
+ python: { and: (a, b) => `(${a} and ${b})`, or: (a, b) => `(${a} or ${b})`, not: (a) => `(not ${a})`, bool: (v) => v ? "True" : "False", eq: (a, b) => `${a} == ${b}`, neq: (a, b) => `${a} != ${b}`, inList: (list, x) => `${x} in [${list.join(", ")}]`, nil: "None" }
6095
6315
  };
6096
6316
  function renderExpr(src, inputs, D) {
6097
6317
  const known = new Set(inputs);
6098
6318
  const r = (n) => {
6099
6319
  switch (n.k) {
6100
6320
  case "or":
6101
- return `(${r(n.a)} || ${r(n.b)})`;
6321
+ return D.or(r(n.a), r(n.b));
6102
6322
  case "and":
6103
- return `(${r(n.a)} && ${r(n.b)})`;
6323
+ return D.and(r(n.a), r(n.b));
6104
6324
  case "not":
6105
- return `!${r(n.a)}`;
6325
+ return D.not(r(n.a));
6106
6326
  case "neg":
6107
6327
  return `-${r(n.a)}`;
6108
6328
  case "arith":
@@ -6116,7 +6336,7 @@ function renderExpr(src, inputs, D) {
6116
6336
  case "list":
6117
6337
  return `[${n.items.map(r).join(", ")}]`;
6118
6338
  case "lit":
6119
- return typeof n.v === "string" ? JSON.stringify(n.v) : String(n.v);
6339
+ return typeof n.v === "string" ? JSON.stringify(n.v) : typeof n.v === "boolean" ? D.bool(n.v) : String(n.v);
6120
6340
  case "ref":
6121
6341
  return known.has(n.path) || known.has(n.path.split(".")[0]) ? n.path : JSON.stringify(n.path);
6122
6342
  default:
@@ -6237,7 +6457,7 @@ function runTests(ast) {
6237
6457
  const inputs = coerce(c.given);
6238
6458
  const run = evaluateDecision(dec, inputs);
6239
6459
  const pass = c.expect == null || String(run.result) === String(c.expect);
6240
- results.push({ ...label, kind: "decision", expected: c.expect, actual: run.result, pass, ...run.ok ? {} : { note: "a condition failed to evaluate" } });
6460
+ results.push({ ...label, kind: "decision", expected: c.expect, actual: run.result, matched: run.matched, pass, ...run.ok ? {} : { note: "a condition failed to evaluate" } });
6241
6461
  } else if (lc) {
6242
6462
  const sim = simulateLifecycle(lc, c.events || []);
6243
6463
  const passState = c.expect == null || sim.finalState === c.expect;