@bitcobblers/wod-wiki-lang 0.50.2187 → 0.50.2189

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
@@ -551,6 +551,9 @@ var EffortMetric = class {
551
551
  this.effort = effort;
552
552
  this.value = options?.resolvedSlug ?? effort;
553
553
  this.image = effort;
554
+ if (options?.canonicalLabel) {
555
+ this.name = options.canonicalLabel;
556
+ }
554
557
  if (options?.references) {
555
558
  this.metadata = { [wodWikiCore.REFERENCE_METADATA_KEY]: options.references };
556
559
  }
@@ -558,6 +561,7 @@ var EffortMetric = class {
558
561
  effort;
559
562
  value;
560
563
  image;
564
+ name;
561
565
  origin = "parser";
562
566
  metadata;
563
567
  type = wodWikiCore.MetricType.Effort;
@@ -1571,11 +1575,12 @@ var EquipmentMetric = class {
1571
1575
  /** Canonical slugs, ordered and deduplicated. */
1572
1576
  value;
1573
1577
  image;
1574
- name = "equipment";
1578
+ name;
1575
1579
  metadata;
1576
1580
  constructor(slugs, options) {
1577
1581
  this.value = [...new Set(slugs)];
1578
1582
  this.image = options?.source ?? this.value.join(" ");
1583
+ this.name = options?.refs?.length ? options.refs.map((r) => r.label).join(", ") : "equipment";
1579
1584
  this.origin = options?.origin ?? "parser";
1580
1585
  const reference = {
1581
1586
  matched: options?.matched ?? "whole",
@@ -1719,9 +1724,14 @@ function classifyStatements(facts, references) {
1719
1724
  metricPairs.push(slot.pair);
1720
1725
  }
1721
1726
  const mergedPairs = mergeFragments(metricPairs);
1722
- const finalPairs = references ? applyReferenceResolution(mergedPairs, fact, references) : mergedPairs;
1727
+ const finalPairs = references?.index ? applyReferenceResolution(mergedPairs, fact, references) : mergedPairs;
1723
1728
  statement.metrics = finalPairs.map((pair) => pair.metrics);
1724
1729
  statement.metricMeta = new Map(finalPairs.map((pair) => [pair.metrics, pair.meta]));
1730
+ const effortMetric = finalPairs.find((p) => p.metrics.type === wodWikiCore.MetricType.Effort)?.metrics;
1731
+ const effortRef = wodWikiCore.getReferenceMetadata(effortMetric?.metadata)?.refs?.find((r) => r.kind === "effort");
1732
+ if (effortRef?.slug) {
1733
+ statement.exerciseId = effortRef.slug;
1734
+ }
1725
1735
  statement.id = fact.id;
1726
1736
  statement.line = fact.line;
1727
1737
  statement.text = fact.meta.raw;
@@ -1749,33 +1759,34 @@ function equipmentMeta(resolution, authored, meta2) {
1749
1759
  };
1750
1760
  }
1751
1761
  function applyReferenceResolution(pairs, fact, references) {
1752
- const { index, defaults } = references;
1762
+ const index = references.index;
1763
+ const { defaults } = references;
1753
1764
  const result = [];
1754
1765
  const emitResolution = (resolution, authored, meta2) => {
1755
1766
  if (resolution.effortRef || resolution.matched !== "none" || resolution.diagnostics.length > 0) {
1756
1767
  result.push({
1757
1768
  metrics: new EffortMetric(authored, {
1758
1769
  resolvedSlug: resolution.effortRef?.slug,
1770
+ canonicalLabel: resolution.effortRef?.label,
1759
1771
  references: referenceMetadata(resolution)
1760
1772
  }),
1761
1773
  meta: meta2
1762
1774
  });
1763
1775
  }
1764
1776
  if (resolution.equipment.length > 0) {
1777
+ const eqMeta = equipmentMeta(resolution, authored, meta2);
1778
+ const eqSource = resolution.matched === "split" ? eqMeta.raw : resolution.matched === "explicit" ? resolution.equipment.map((ref) => ref.label).join(", ") : authored;
1765
1779
  result.push({
1766
1780
  metrics: new EquipmentMetric(
1767
1781
  resolution.equipment.map((ref) => ref.slug),
1768
1782
  {
1769
1783
  refs: resolution.equipment,
1770
- source: authored,
1784
+ source: eqSource,
1771
1785
  matched: resolution.matched,
1772
1786
  diagnostics: resolution.diagnostics
1773
1787
  }
1774
1788
  ),
1775
- // The equipment is the prefix of the authored label; the effort is
1776
- // what follows. Marking the whole phrase for both would let one mark
1777
- // hide the other.
1778
- meta: equipmentMeta(resolution, authored, meta2)
1789
+ meta: eqMeta
1779
1790
  });
1780
1791
  }
1781
1792
  };
@@ -1850,6 +1861,64 @@ function applyReferenceResolution(pairs, fact, references) {
1850
1861
  result.push(...malformedHints);
1851
1862
  return result;
1852
1863
  }
1864
+ function resolveFusedEffortReferences(statements, references) {
1865
+ if (!references?.index) return;
1866
+ const { index, defaults } = references;
1867
+ for (const statement of statements) {
1868
+ const current = statement.metrics.toArray();
1869
+ if (!current.some((m) => m.type === wodWikiCore.MetricType.Effort && !m.metadata)) continue;
1870
+ const nextMetrics = [];
1871
+ const nextMeta = new Map(statement.metricMeta);
1872
+ for (const metric of current) {
1873
+ if (metric.type !== wodWikiCore.MetricType.Effort || metric.metadata) {
1874
+ nextMetrics.push(metric);
1875
+ continue;
1876
+ }
1877
+ const authored = metric instanceof EffortMetric ? metric.effort : String(metric.value ?? "");
1878
+ const normalized = authored.trim().toLowerCase();
1879
+ if (STRUCTURAL_EFFORT_WORDS.has(normalized) || STANDARD_UNITS2.has(normalized)) {
1880
+ nextMetrics.push(metric);
1881
+ continue;
1882
+ }
1883
+ const meta2 = statement.metricMeta?.get(metric);
1884
+ const resolution = index.resolve(authored, defaults);
1885
+ if (resolution.matched === "none" && resolution.diagnostics.length === 0) {
1886
+ nextMetrics.push(metric);
1887
+ continue;
1888
+ }
1889
+ nextMeta.delete(metric);
1890
+ if (resolution.effortRef || resolution.matched !== "none" || resolution.diagnostics.length > 0) {
1891
+ const resolvedEffort = new EffortMetric(authored, {
1892
+ resolvedSlug: resolution.effortRef?.slug,
1893
+ canonicalLabel: resolution.effortRef?.label,
1894
+ references: referenceMetadata(resolution)
1895
+ });
1896
+ nextMetrics.push(resolvedEffort);
1897
+ if (meta2) nextMeta.set(resolvedEffort, meta2);
1898
+ if (resolution.effortRef?.slug) {
1899
+ statement.exerciseId = resolution.effortRef.slug;
1900
+ }
1901
+ }
1902
+ if (resolution.equipment.length > 0) {
1903
+ const eqMeta = meta2 ? equipmentMeta(resolution, authored, meta2) : void 0;
1904
+ const eqSource = resolution.matched === "split" && eqMeta ? eqMeta.raw : authored;
1905
+ const eqMetric = new EquipmentMetric(
1906
+ resolution.equipment.map((ref) => ref.slug),
1907
+ {
1908
+ refs: resolution.equipment,
1909
+ source: eqSource,
1910
+ matched: resolution.matched,
1911
+ diagnostics: resolution.diagnostics
1912
+ }
1913
+ );
1914
+ nextMetrics.push(eqMetric);
1915
+ if (eqMeta) nextMeta.set(eqMetric, eqMeta);
1916
+ }
1917
+ }
1918
+ statement.metrics.clear().add(...nextMetrics);
1919
+ statement.metricMeta = nextMeta;
1920
+ }
1921
+ }
1853
1922
  function classifyPrimitive(primitive) {
1854
1923
  switch (primitive.kind) {
1855
1924
  case "lap":
@@ -2467,6 +2536,7 @@ function extractStatements(sourceOrTree, sportOrTree, sportArg, references) {
2467
2536
  const facts = extractSyntaxFacts(resolved.source, resolved.tree);
2468
2537
  const statements = classifyStatements(facts, references);
2469
2538
  dialectStack.processAll(statements, sport);
2539
+ resolveFusedEffortReferences(statements, references);
2470
2540
  return normalizeInlineRounds(statements);
2471
2541
  }
2472
2542
  function isInlineRoundsPrescription(stmt) {
@@ -2588,557 +2658,87 @@ var WhiteboardScript = class {
2588
2658
  }
2589
2659
  };
2590
2660
 
2591
- // src/effort-registry/types.ts
2592
- var EFFORT_REGISTRY_ORIGINS = ["bundled", "user"];
2593
- var SEED_PRECEDENCE_RANK = {
2594
- core: 0,
2595
- community: 1,
2596
- imported: 2,
2597
- user: 3
2598
- };
2599
- function resolveSourceTier(sourceIdOrMetaKey, explicitTier) {
2600
- if (explicitTier) return explicitTier;
2601
- if (!sourceIdOrMetaKey) return "core";
2602
- if (sourceIdOrMetaKey === "user") return "user";
2603
- const id = sourceIdOrMetaKey.startsWith("seed:") ? sourceIdOrMetaKey.slice(5) : sourceIdOrMetaKey;
2604
- if (id === "core" || id === "efforts" || id === "equipment") return "core";
2605
- if (id === "community" || id.startsWith("community-") || id === "common") return "community";
2606
- return "imported";
2607
- }
2608
- function resolveSourceRank(sourceIdOrMetaKey, explicitTier) {
2609
- return SEED_PRECEDENCE_RANK[resolveSourceTier(sourceIdOrMetaKey, explicitTier)];
2610
- }
2611
- function resolveEntityTier(entity) {
2612
- if (entity.registrySource === "user") return "user";
2613
- return resolveSourceTier(entity.sourceId);
2614
- }
2615
- function resolveEntityRank(entity) {
2616
- return SEED_PRECEDENCE_RANK[resolveEntityTier(entity)];
2617
- }
2618
- function dedupeEntitiesByPrecedence(entities) {
2619
- const bySlug = /* @__PURE__ */ new Map();
2620
- for (const entity of entities) {
2621
- const key = entity.slug.toLowerCase();
2622
- const prior = bySlug.get(key);
2623
- if (!prior || resolveEntityRank(entity) >= resolveEntityRank(prior)) {
2624
- bySlug.set(key, entity);
2625
- }
2661
+ // src/parser/parseScript.ts
2662
+ function parseScript(inputText, options = {}) {
2663
+ if (!inputText || !inputText.trim()) {
2664
+ return new WhiteboardScript(inputText, [], []);
2665
+ }
2666
+ try {
2667
+ const doc = inputText.endsWith("\n") ? inputText : inputText + "\n";
2668
+ const tree = parser.parse(doc);
2669
+ const sport = options.sport ?? options.dialect;
2670
+ const references = options.references || options.tags ? {
2671
+ ...options.references ? { index: options.references } : {},
2672
+ defaults: options.defaults,
2673
+ tags: options.tags
2674
+ } : void 0;
2675
+ const statements = options.withoutDialects ? extractStatementsRaw(inputText, tree, references) : extractStatements(inputText, tree, sport, references);
2676
+ return new WhiteboardScript(inputText, statements, []);
2677
+ } catch (error) {
2678
+ const msg = error instanceof Error ? error.message : "Unknown parse error";
2679
+ return new WhiteboardScript(inputText, [], [{ message: msg }]);
2626
2680
  }
2627
- return Array.from(bySlug.values());
2628
2681
  }
2629
- var INTENSITY_TIERS = ["low", "moderate", "high"];
2630
- var DEFAULT_RESOLVER_OPTIONS = {
2631
- defaultMet: 5,
2632
- defaultThreshold: 2
2633
- };
2634
2682
 
2635
- // src/effort-registry/fuzzyMatch.ts
2636
- function levenshteinDistance(a, b) {
2637
- if (a === b) return 0;
2638
- if (a.length === 0) return b.length;
2639
- if (b.length === 0) return a.length;
2640
- if (a.length > b.length) {
2641
- const tmp = a;
2642
- a = b;
2643
- b = tmp;
2683
+ // src/parser/md-timer.ts
2684
+ var MdTimerRuntime = class {
2685
+ config;
2686
+ constructor(config) {
2687
+ this.config = config;
2644
2688
  }
2645
- const m = a.length;
2646
- const n = b.length;
2647
- let prev = new Uint32Array(m + 1);
2648
- let curr = new Uint32Array(m + 1);
2649
- for (let i = 0; i <= m; i++) {
2650
- prev[i] = i;
2689
+ /**
2690
+ * Parse WhiteboardScript source into a {@link WhiteboardScript}, running the
2691
+ * Dialect Stack on every statement.
2692
+ *
2693
+ * @param sport - The block's `:sport` fence suffix (` ```log:climbing `).
2694
+ */
2695
+ read(inputText, sport) {
2696
+ return parseScript(inputText, { sport, ...this.config });
2651
2697
  }
2652
- for (let j = 1; j <= n; j++) {
2653
- curr[0] = j;
2654
- const bj = b.charCodeAt(j - 1);
2655
- for (let i = 1; i <= m; i++) {
2656
- const cost = a.charCodeAt(i - 1) === bj ? 0 : 1;
2657
- curr[i] = Math.min(
2658
- prev[i] + 1,
2659
- // deletion
2660
- curr[i - 1] + 1,
2661
- // insertion
2662
- prev[i - 1] + cost
2663
- // substitution
2664
- );
2665
- }
2666
- const swap = prev;
2667
- prev = curr;
2668
- curr = swap;
2698
+ /**
2699
+ * Parse without running the Dialect Stack. Used by the parser test harness
2700
+ * which applies its own Dialect set. Production consumers should use {@link read}.
2701
+ */
2702
+ readWithoutDialects(inputText) {
2703
+ return parseScript(inputText, { withoutDialects: true, ...this.config });
2669
2704
  }
2670
- return prev[m];
2671
- }
2672
- function normalizeForFuzzy(input) {
2673
- return input.toLowerCase().replace(/[^\p{L}\p{N}\s-]/gu, " ").replace(/[-_]+/g, " ").trim().replace(/\s+/g, " ");
2674
- }
2675
- function isWithinThreshold(a, b, threshold) {
2676
- const na = normalizeForFuzzy(a);
2677
- const nb = normalizeForFuzzy(b);
2678
- if (na === nb) return true;
2679
- if (Math.abs(na.length - nb.length) > threshold) return false;
2680
- return levenshteinDistance(na, nb) <= threshold;
2705
+ };
2706
+
2707
+ // src/parser/parserInstance.ts
2708
+ function createParser(config) {
2709
+ return new MdTimerRuntime(config);
2681
2710
  }
2682
- function findBestFuzzyMatch(query, candidates, threshold) {
2683
- const nq = normalizeForFuzzy(query);
2684
- let best = null;
2685
- for (const candidate of candidates) {
2686
- const nc = normalizeForFuzzy(candidate);
2687
- if (Math.abs(nq.length - nc.length) > threshold) continue;
2688
- const distance = levenshteinDistance(nq, nc);
2689
- if (distance > threshold) continue;
2690
- if (!best || distance < best.distance) {
2691
- best = { target: candidate, distance };
2692
- if (distance === 0) break;
2693
- }
2711
+ var nextTagID = 0;
2712
+ var Tag = class _Tag {
2713
+ /**
2714
+ @internal
2715
+ */
2716
+ constructor(name2, set, base, modified) {
2717
+ this.name = name2;
2718
+ this.set = set;
2719
+ this.base = base;
2720
+ this.modified = modified;
2721
+ this.id = nextTagID++;
2694
2722
  }
2695
- return best;
2696
- }
2697
-
2698
- // src/effort-registry/reference-resolution.ts
2699
- function buildExactIndex(entities) {
2700
- const deduped = dedupeEntitiesByPrecedence(entities);
2701
- const byKey = /* @__PURE__ */ new Map();
2702
- const add = (key, entity) => {
2703
- if (!key) return;
2704
- const existing = byKey.get(key);
2705
- if (existing) {
2706
- if (!existing.includes(entity)) existing.push(entity);
2707
- } else {
2708
- byKey.set(key, [entity]);
2709
- }
2710
- };
2711
- for (const entity of deduped) {
2712
- add(entity.slug.toLowerCase(), entity);
2713
- add(normalizeForFuzzy(entity.label), entity);
2714
- for (const alias of entity.aliases) add(normalizeForFuzzy(alias), entity);
2723
+ toString() {
2724
+ let { name: name2 } = this;
2725
+ for (let mod of this.modified)
2726
+ if (mod.name)
2727
+ name2 = `${mod.name}(${name2})`;
2728
+ return name2;
2715
2729
  }
2716
- return {
2717
- byKey,
2718
- lookup(key) {
2719
- const direct = byKey.get(key) ?? byKey.get(normalizeForFuzzy(key));
2720
- if (!direct || direct.length === 0) return { entity: null, ambiguous: [] };
2721
- if (direct.length === 1) return { entity: direct[0], ambiguous: [] };
2722
- let maxRank = -1;
2723
- for (const candidate of direct) {
2724
- const rank = resolveEntityRank(candidate);
2725
- if (rank > maxRank) maxRank = rank;
2726
- }
2727
- const topTier = direct.filter((candidate) => resolveEntityRank(candidate) === maxRank);
2728
- if (topTier.length === 1) return { entity: topTier[0], ambiguous: [] };
2729
- return { entity: null, ambiguous: topTier };
2730
- }
2731
- };
2732
- }
2733
- function equipmentRef(entity) {
2734
- return { kind: "equipment", slug: entity.slug, label: entity.label };
2735
- }
2736
- function dedupeRefs(refs) {
2737
- const seen = /* @__PURE__ */ new Set();
2738
- const result = [];
2739
- for (const ref of refs) {
2740
- const key = `${ref.kind}:${ref.slug}`;
2741
- if (seen.has(key)) continue;
2742
- seen.add(key);
2743
- result.push(ref);
2744
- }
2745
- return result;
2746
- }
2747
- function createReferenceIndex(catalog, materialize) {
2748
- const dedupedEquipment = dedupeEntitiesByPrecedence(catalog.equipment ?? []);
2749
- const efforts = buildExactIndex(catalog.efforts ?? []);
2750
- const equipment = buildExactIndex(dedupedEquipment);
2751
- const prefixCandidates = dedupedEquipment.flatMap((entity) => [entity.label, ...entity.aliases].filter((name2) => !!name2 && !!name2.trim()).map((name2) => ({ lower: name2.trim().toLowerCase(), entity }))).sort((a, b) => b.lower.length - a.lower.length);
2752
- const materializer = materialize ?? ((effort) => ({
2753
- effort,
2754
- definition: effort,
2755
- slug: effort.slug,
2756
- label: effort.label,
2757
- met: effort.baseAttributes.met,
2758
- baseAttributes: effort.baseAttributes,
2759
- discipline: effort.baseAttributes.discipline,
2760
- disciplineFactor: effort.baseAttributes.disciplineFactor ?? 1,
2761
- intensityTier: effort.baseAttributes.intensityTier,
2762
- modifiers: {},
2763
- registrySource: effort.registrySource,
2764
- resolvedFrom: effort.registrySource === "user" ? "user" : effort.registrySource === "bundled" ? "bundled" : "default",
2765
- isEstimated: false
2766
- }));
2767
- const resolveEffortExact = (key) => {
2768
- const found = efforts.lookup(key.trim());
2769
- if (found.entity) return { entity: found.entity };
2770
- if (found.ambiguous.length > 0) {
2771
- return {
2772
- entity: null,
2773
- diagnostic: {
2774
- reason: "ambiguous-effort",
2775
- message: `"${key}" matches multiple efforts.`,
2776
- token: key,
2777
- candidates: found.ambiguous.map((e) => e.slug)
2778
- }
2779
- };
2780
- }
2781
- return {
2782
- entity: null,
2783
- diagnostic: { reason: "unknown-effort", message: `Unknown effort "${key}".`, token: key }
2784
- };
2785
- };
2786
- const resolveEquipmentValues = (values) => {
2787
- const refs = [];
2788
- const diagnostics = [];
2789
- for (const value of values) {
2790
- if (typeof value !== "string" || !value.trim()) {
2791
- diagnostics.push({
2792
- reason: "invalid-explicit",
2793
- message: `Equipment reference must be a slug string, got ${JSON.stringify(value) ?? "null"}.`,
2794
- token: String(value)
2795
- });
2796
- continue;
2797
- }
2798
- const found = equipment.lookup(value.trim());
2799
- if (found.entity) {
2800
- refs.push(equipmentRef(found.entity));
2801
- } else if (found.ambiguous.length > 0) {
2802
- diagnostics.push({
2803
- reason: "ambiguous-equipment",
2804
- message: `"${value}" matches multiple equipment entries.`,
2805
- token: value,
2806
- candidates: found.ambiguous.map((e) => e.slug)
2807
- });
2808
- } else {
2809
- diagnostics.push({
2810
- reason: "invalid-explicit",
2811
- message: `Unknown equipment "${value}".`,
2812
- token: value
2813
- });
2814
- }
2815
- }
2816
- return { refs: dedupeRefs(refs), diagnostics };
2817
- };
2818
- const index = {
2819
- resolveEffort(slugOrAlias) {
2820
- return efforts.lookup(slugOrAlias).entity;
2821
- },
2822
- resolveEquipment(slugOrAlias) {
2823
- return equipment.lookup(slugOrAlias).entity;
2824
- },
2825
- materialize(effort) {
2826
- return materializer(effort);
2827
- },
2828
- resolve(label, defaults) {
2829
- const source = (label ?? "").trim();
2830
- const diagnostics = [];
2831
- const push = (diagnostic) => {
2832
- if (diagnostic) diagnostics.push(diagnostic);
2833
- };
2834
- let matched = "none";
2835
- let effortEntity = null;
2836
- let remaining = "";
2837
- let equipmentRefs = [];
2838
- let equipmentAuthored = false;
2839
- const fillEquipment = () => {
2840
- if (equipmentRefs.length > 0 || equipmentAuthored) return;
2841
- if (effortEntity === null) return;
2842
- if (defaults?.equipment?.length) {
2843
- const resolvedDefaults = resolveEquipmentValues(defaults.equipment);
2844
- equipmentRefs = resolvedDefaults.refs;
2845
- for (const diagnostic of resolvedDefaults.diagnostics) {
2846
- push({ ...diagnostic, reason: "unknown-equipment" });
2847
- }
2848
- if (equipmentRefs.length > 0) return;
2849
- }
2850
- if (effortEntity.equipment?.length) {
2851
- const resolvedDefEquipment = resolveEquipmentValues(effortEntity.equipment);
2852
- equipmentRefs = resolvedDefEquipment.refs;
2853
- for (const diagnostic of resolvedDefEquipment.diagnostics) {
2854
- push({ ...diagnostic, reason: "unknown-equipment" });
2855
- }
2856
- }
2857
- };
2858
- if (!source) {
2859
- if (defaults?.effort) {
2860
- const found = resolveEffortExact(defaults.effort);
2861
- if (found.entity) {
2862
- effortEntity = found.entity;
2863
- matched = "default";
2864
- } else {
2865
- push({
2866
- reason: found.diagnostic?.reason === "ambiguous-effort" ? "ambiguous-effort" : "unknown-effort",
2867
- message: `Default effort "${defaults.effort}" is not in the catalog.`,
2868
- token: defaults.effort,
2869
- candidates: found.diagnostic?.candidates
2870
- });
2871
- }
2872
- }
2873
- fillEquipment();
2874
- return {
2875
- matched,
2876
- label: source,
2877
- effort: effortEntity ? materializer(effortEntity) : null,
2878
- effortRef: effortEntity ? { kind: "effort", slug: effortEntity.slug, label: effortEntity.label } : null,
2879
- equipment: equipmentRefs,
2880
- remaining: "",
2881
- diagnostics
2882
- };
2883
- }
2884
- if (source.startsWith("{")) {
2885
- let parsed;
2886
- try {
2887
- parsed = JSON.parse(source);
2888
- } catch {
2889
- return {
2890
- matched: "none",
2891
- label: source,
2892
- effort: null,
2893
- effortRef: null,
2894
- equipment: [],
2895
- remaining: "",
2896
- diagnostics: [{
2897
- reason: "malformed-json",
2898
- message: "Reference object is not valid JSON.",
2899
- token: source
2900
- }]
2901
- };
2902
- }
2903
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed) && ("effort" in parsed || "equipment" in parsed)) {
2904
- matched = "explicit";
2905
- const obj = parsed;
2906
- if (obj.effort !== void 0) {
2907
- if (typeof obj.effort === "string" && obj.effort.trim()) {
2908
- const found = resolveEffortExact(obj.effort);
2909
- effortEntity = found.entity;
2910
- if (found.diagnostic) {
2911
- push(found.diagnostic.reason === "ambiguous-effort" ? found.diagnostic : { ...found.diagnostic, reason: "invalid-explicit", message: `Explicit effort "${obj.effort}" is not in the catalog.` });
2912
- }
2913
- } else {
2914
- push({
2915
- reason: "invalid-explicit",
2916
- message: 'Explicit "effort" must be a slug string.',
2917
- token: String(obj.effort)
2918
- });
2919
- }
2920
- }
2921
- if (obj.equipment !== void 0) {
2922
- equipmentAuthored = true;
2923
- const values = Array.isArray(obj.equipment) ? obj.equipment : [obj.equipment];
2924
- const resolvedEquipment = resolveEquipmentValues(values);
2925
- equipmentRefs = resolvedEquipment.refs;
2926
- diagnostics.push(...resolvedEquipment.diagnostics);
2927
- if (effortEntity?.equipment?.length) {
2928
- const definitionSlugs = new Set(effortEntity.equipment);
2929
- const authoredSlugs = equipmentRefs.map((ref) => ref.slug);
2930
- if (authoredSlugs.length > 0 && authoredSlugs.some((slug) => !definitionSlugs.has(slug))) {
2931
- push({
2932
- reason: "conflict",
2933
- message: `Explicit equipment [${authoredSlugs.join(", ")}] contradicts effort "${effortEntity.slug}" definition equipment [${effortEntity.equipment.join(", ")}].`,
2934
- token: source
2935
- });
2936
- }
2937
- }
2938
- }
2939
- fillEquipment();
2940
- return {
2941
- matched,
2942
- label: source,
2943
- effort: effortEntity ? materializer(effortEntity) : null,
2944
- effortRef: effortEntity ? { kind: "effort", slug: effortEntity.slug, label: effortEntity.label } : null,
2945
- equipment: equipmentRefs,
2946
- remaining: "",
2947
- diagnostics
2948
- };
2949
- }
2950
- }
2951
- const lowerSource = source.toLowerCase();
2952
- for (const candidate of prefixCandidates) {
2953
- if (!lowerSource.startsWith(candidate.lower)) continue;
2954
- const after = source.slice(candidate.lower.length);
2955
- if (after && !/^\s/.test(after)) continue;
2956
- const candidateRemaining = after.trim();
2957
- if (!candidateRemaining) continue;
2958
- const equipmentFound = equipment.lookup(candidate.lower);
2959
- if (equipmentFound.ambiguous.length > 0) {
2960
- push({
2961
- reason: "ambiguous-equipment",
2962
- message: `"${candidate.lower}" matches multiple equipment entries.`,
2963
- token: source,
2964
- candidates: equipmentFound.ambiguous.map((e) => e.slug)
2965
- });
2966
- return {
2967
- matched: "none",
2968
- label: source,
2969
- effort: null,
2970
- effortRef: null,
2971
- equipment: [],
2972
- remaining: "",
2973
- diagnostics
2974
- };
2975
- }
2976
- if (!equipmentFound.entity) continue;
2977
- const effortFound = resolveEffortExact(candidateRemaining);
2978
- if (effortFound.entity) {
2979
- matched = "split";
2980
- effortEntity = effortFound.entity;
2981
- equipmentRefs = [equipmentRef(equipmentFound.entity)];
2982
- equipmentAuthored = true;
2983
- remaining = candidateRemaining;
2984
- break;
2985
- }
2986
- if (effortFound.diagnostic?.reason === "ambiguous-effort") {
2987
- push(effortFound.diagnostic);
2988
- return {
2989
- matched: "none",
2990
- label: source,
2991
- effort: null,
2992
- effortRef: null,
2993
- equipment: [],
2994
- remaining: "",
2995
- diagnostics
2996
- };
2997
- }
2998
- }
2999
- if (matched === "none") {
3000
- const found = resolveEffortExact(source);
3001
- if (found.entity) {
3002
- effortEntity = found.entity;
3003
- matched = "whole";
3004
- } else if (found.diagnostic?.reason === "ambiguous-effort") {
3005
- push(found.diagnostic);
3006
- return {
3007
- matched: "none",
3008
- label: source,
3009
- effort: null,
3010
- effortRef: null,
3011
- equipment: [],
3012
- remaining: "",
3013
- diagnostics
3014
- };
3015
- } else if (found.diagnostic) {
3016
- push(found.diagnostic);
3017
- }
3018
- }
3019
- fillEquipment();
3020
- return {
3021
- matched,
3022
- label: source,
3023
- effort: effortEntity ? materializer(effortEntity) : null,
3024
- effortRef: effortEntity ? { kind: "effort", slug: effortEntity.slug, label: effortEntity.label } : null,
3025
- equipment: equipmentRefs,
3026
- remaining,
3027
- diagnostics
3028
- };
3029
- }
3030
- };
3031
- return index;
3032
- }
3033
- function extractStatementRefs(statements) {
3034
- const efforts = [];
3035
- const equipment = [];
3036
- const seenEffort = /* @__PURE__ */ new Set();
3037
- const seenEquipment = /* @__PURE__ */ new Set();
3038
- const pushUnique = (list, seen, value) => {
3039
- if (!value) return;
3040
- if (seen.has(value)) return;
3041
- seen.add(value);
3042
- list.push(value);
3043
- };
3044
- for (const statement of statements ?? []) {
3045
- const metrics = Array.isArray(statement.metrics) ? statement.metrics : statement.metrics?.toArray?.() ?? [];
3046
- for (const metric of metrics) {
3047
- const refs = wodWikiCore.getReferenceMetadata(metric.metadata)?.refs;
3048
- if (!refs) continue;
3049
- for (const ref of refs) {
3050
- if (ref.kind === "effort") pushUnique(efforts, seenEffort, ref.slug);
3051
- if (ref.kind === "equipment") pushUnique(equipment, seenEquipment, ref.slug);
3052
- }
3053
- }
3054
- }
3055
- return { efforts, equipment };
3056
- }
3057
- function slugifyRefLabel(label) {
3058
- return normalizeForFuzzy(label).replace(/\s+/g, "-");
3059
- }
3060
-
3061
- // src/parser/parseScript.ts
3062
- function parseScript(inputText, options = {}) {
3063
- if (!inputText || !inputText.trim()) {
3064
- return new WhiteboardScript(inputText, [], []);
3065
- }
3066
- try {
3067
- const doc = inputText.endsWith("\n") ? inputText : inputText + "\n";
3068
- const tree = parser.parse(doc);
3069
- const sport = options.sport ?? options.dialect;
3070
- const references = options.references || options.tags ? {
3071
- index: options.references ?? createReferenceIndex({ efforts: [], equipment: [] }),
3072
- defaults: options.defaults,
3073
- tags: options.tags
3074
- } : void 0;
3075
- const statements = options.withoutDialects ? extractStatementsRaw(inputText, tree, references) : extractStatements(inputText, tree, sport, references);
3076
- return new WhiteboardScript(inputText, statements, []);
3077
- } catch (error) {
3078
- const msg = error instanceof Error ? error.message : "Unknown parse error";
3079
- return new WhiteboardScript(inputText, [], [{ message: msg }]);
3080
- }
3081
- }
3082
-
3083
- // src/parser/md-timer.ts
3084
- var MdTimerRuntime = class {
3085
- config;
3086
- constructor(config) {
3087
- this.config = config;
3088
- }
3089
- /**
3090
- * Parse WhiteboardScript source into a {@link WhiteboardScript}, running the
3091
- * Dialect Stack on every statement.
3092
- *
3093
- * @param sport - The block's `:sport` fence suffix (` ```log:climbing `).
3094
- */
3095
- read(inputText, sport) {
3096
- return parseScript(inputText, { sport, ...this.config });
3097
- }
3098
- /**
3099
- * Parse without running the Dialect Stack. Used by the parser test harness
3100
- * which applies its own Dialect set. Production consumers should use {@link read}.
3101
- */
3102
- readWithoutDialects(inputText) {
3103
- return parseScript(inputText, { withoutDialects: true, ...this.config });
3104
- }
3105
- };
3106
-
3107
- // src/parser/parserInstance.ts
3108
- function createParser(config) {
3109
- return new MdTimerRuntime(config);
3110
- }
3111
- var nextTagID = 0;
3112
- var Tag = class _Tag {
3113
- /**
3114
- @internal
3115
- */
3116
- constructor(name2, set, base, modified) {
3117
- this.name = name2;
3118
- this.set = set;
3119
- this.base = base;
3120
- this.modified = modified;
3121
- this.id = nextTagID++;
3122
- }
3123
- toString() {
3124
- let { name: name2 } = this;
3125
- for (let mod of this.modified)
3126
- if (mod.name)
3127
- name2 = `${mod.name}(${name2})`;
3128
- return name2;
3129
- }
3130
- static define(nameOrParent, parent) {
3131
- let name2 = typeof nameOrParent == "string" ? nameOrParent : "?";
3132
- if (nameOrParent instanceof _Tag)
3133
- parent = nameOrParent;
3134
- if (parent === null || parent === void 0 ? void 0 : parent.base)
3135
- throw new Error("Can not derive from a modified tag");
3136
- let tag = new _Tag(name2, [], null, []);
3137
- tag.set.push(tag);
3138
- if (parent)
3139
- for (let t2 of parent.set)
3140
- tag.set.push(t2);
3141
- return tag;
2730
+ static define(nameOrParent, parent) {
2731
+ let name2 = typeof nameOrParent == "string" ? nameOrParent : "?";
2732
+ if (nameOrParent instanceof _Tag)
2733
+ parent = nameOrParent;
2734
+ if (parent === null || parent === void 0 ? void 0 : parent.base)
2735
+ throw new Error("Can not derive from a modified tag");
2736
+ let tag = new _Tag(name2, [], null, []);
2737
+ tag.set.push(tag);
2738
+ if (parent)
2739
+ for (let t2 of parent.set)
2740
+ tag.set.push(t2);
2741
+ return tag;
3142
2742
  }
3143
2743
  /**
3144
2744
  Define a tag _modifier_, which is a function that, given a tag,
@@ -3851,17 +3451,16 @@ function structureKeywords() {
3851
3451
  }));
3852
3452
  }
3853
3453
  function toCompletions(values, detail, quoteValues = false) {
3854
- return values.map((value) => ({
3855
- label: value.label ?? value.value,
3856
- // `Property` values only lex as String | Number | Identifier, and
3857
- // Identifier is `[a-zA-Z][a-zA-Z0-9]*` — quote slugs it cannot represent
3858
- // (hyphens, spaces, leading digits) so they insert parse-valid text;
3859
- // `parsePropertyValue` unwraps the quotes when the property is consumed.
3860
- apply: quoteValues && !/^[A-Za-z][A-Za-z0-9]*$/.test(value.value) ? JSON.stringify(value.value) : value.value,
3861
- detail: value.detail ?? detail,
3862
- type: "text",
3863
- info: value.info
3864
- }));
3454
+ return values.map((value) => {
3455
+ const insertText = quoteValues ? !/^[A-Za-z][A-Za-z0-9]*$/.test(value.value) ? JSON.stringify(value.value) : value.value : value.label ?? value.value;
3456
+ return {
3457
+ label: value.label ?? value.value,
3458
+ apply: insertText,
3459
+ detail: value.detail ?? (value.label && value.label !== value.value ? value.value : detail),
3460
+ type: "text",
3461
+ info: value.info
3462
+ };
3463
+ });
3865
3464
  }
3866
3465
  function dialectWords(sport) {
3867
3466
  if (!sport) return [];
@@ -3944,7 +3543,13 @@ function whiteboardScriptCompletionSource(options = {}) {
3944
3543
  return null;
3945
3544
  }
3946
3545
  const efforts = options.efforts ? toCompletions(await options.efforts(), "effort") : [];
3947
- const effortOptions = [...keywords, ...efforts, ...sportWords];
3546
+ const equipmentPrefixes = options.equipment ? (await options.equipment()).map((eq) => ({
3547
+ label: eq.label ?? eq.value,
3548
+ apply: `${eq.label ?? eq.value} `,
3549
+ detail: eq.detail ?? "equipment",
3550
+ type: "text"
3551
+ })) : [];
3552
+ const effortOptions = [...keywords, ...efforts, ...equipmentPrefixes, ...sportWords];
3948
3553
  if (/^[ \t]*[A-Za-z][A-Za-z0-9]*$/.test(before)) {
3949
3554
  const supplied = suppliedPropertyKeys(context);
3950
3555
  return {
@@ -3987,7 +3592,7 @@ function whiteboardScriptCompletionSource(options = {}) {
3987
3592
  const typed = context.state.doc.sliceString(first.from, pos).toLowerCase();
3988
3593
  const compatible = effortOptions.filter((option) => {
3989
3594
  const inserted = typeof option.apply === "string" ? option.apply : option.label;
3990
- return inserted.toLowerCase().startsWith(typed);
3595
+ return inserted.toLowerCase().startsWith(typed) || option.label.toLowerCase().startsWith(typed);
3991
3596
  });
3992
3597
  return { from: first.from, options: compatible, validFor: /^[A-Za-z ]*$/ };
3993
3598
  }
@@ -4036,6 +3641,50 @@ function whiteboardScript(options) {
4036
3641
  return new language.LanguageSupport(whiteboardScriptLanguage, whiteboardScriptCompletion(options ?? {}));
4037
3642
  }
4038
3643
 
3644
+ // src/effort-registry/types.ts
3645
+ var EFFORT_REGISTRY_ORIGINS = ["bundled", "user"];
3646
+ var SEED_PRECEDENCE_RANK = {
3647
+ core: 0,
3648
+ community: 1,
3649
+ imported: 2,
3650
+ user: 3
3651
+ };
3652
+ function resolveSourceTier(sourceIdOrMetaKey, explicitTier) {
3653
+ if (explicitTier) return explicitTier;
3654
+ if (!sourceIdOrMetaKey) return "core";
3655
+ if (sourceIdOrMetaKey === "user") return "user";
3656
+ const id = sourceIdOrMetaKey.startsWith("seed:") ? sourceIdOrMetaKey.slice(5) : sourceIdOrMetaKey;
3657
+ if (id === "core" || id === "efforts" || id === "equipment") return "core";
3658
+ if (id === "community" || id.startsWith("community-") || id === "common") return "community";
3659
+ return "imported";
3660
+ }
3661
+ function resolveSourceRank(sourceIdOrMetaKey, explicitTier) {
3662
+ return SEED_PRECEDENCE_RANK[resolveSourceTier(sourceIdOrMetaKey, explicitTier)];
3663
+ }
3664
+ function resolveEntityTier(entity) {
3665
+ if (entity.registrySource === "user") return "user";
3666
+ return resolveSourceTier(entity.sourceId);
3667
+ }
3668
+ function resolveEntityRank(entity) {
3669
+ return SEED_PRECEDENCE_RANK[resolveEntityTier(entity)];
3670
+ }
3671
+ function dedupeEntitiesByPrecedence(entities) {
3672
+ const bySlug = /* @__PURE__ */ new Map();
3673
+ for (const entity of entities) {
3674
+ const key = entity.slug.toLowerCase();
3675
+ const prior = bySlug.get(key);
3676
+ if (!prior || resolveEntityRank(entity) >= resolveEntityRank(prior)) {
3677
+ bySlug.set(key, entity);
3678
+ }
3679
+ }
3680
+ return Array.from(bySlug.values());
3681
+ }
3682
+ var INTENSITY_TIERS = ["low", "moderate", "high"];
3683
+ var DEFAULT_RESOLVER_OPTIONS = {
3684
+ defaultMet: 5,
3685
+ defaultThreshold: 2
3686
+ };
3687
+
4039
3688
  // src/effort-registry/disciplines.ts
4040
3689
  var EFFORT_DISCIPLINES = [
4041
3690
  "bodyweight",
@@ -4641,6 +4290,71 @@ function tagCategoryToDocument(category) {
4641
4290
  }
4642
4291
  return lines.join("\n");
4643
4292
  }
4293
+
4294
+ // src/effort-registry/fuzzyMatch.ts
4295
+ function levenshteinDistance(a, b) {
4296
+ if (a === b) return 0;
4297
+ if (a.length === 0) return b.length;
4298
+ if (b.length === 0) return a.length;
4299
+ if (a.length > b.length) {
4300
+ const tmp = a;
4301
+ a = b;
4302
+ b = tmp;
4303
+ }
4304
+ const m = a.length;
4305
+ const n = b.length;
4306
+ let prev = new Uint32Array(m + 1);
4307
+ let curr = new Uint32Array(m + 1);
4308
+ for (let i = 0; i <= m; i++) {
4309
+ prev[i] = i;
4310
+ }
4311
+ for (let j = 1; j <= n; j++) {
4312
+ curr[0] = j;
4313
+ const bj = b.charCodeAt(j - 1);
4314
+ for (let i = 1; i <= m; i++) {
4315
+ const cost = a.charCodeAt(i - 1) === bj ? 0 : 1;
4316
+ curr[i] = Math.min(
4317
+ prev[i] + 1,
4318
+ // deletion
4319
+ curr[i - 1] + 1,
4320
+ // insertion
4321
+ prev[i - 1] + cost
4322
+ // substitution
4323
+ );
4324
+ }
4325
+ const swap = prev;
4326
+ prev = curr;
4327
+ curr = swap;
4328
+ }
4329
+ return prev[m];
4330
+ }
4331
+ function normalizeForFuzzy(input) {
4332
+ return input.toLowerCase().replace(/[^\p{L}\p{N}\s-]/gu, " ").replace(/[-_]+/g, " ").trim().replace(/\s+/g, " ");
4333
+ }
4334
+ function isWithinThreshold(a, b, threshold) {
4335
+ const na = normalizeForFuzzy(a);
4336
+ const nb = normalizeForFuzzy(b);
4337
+ if (na === nb) return true;
4338
+ if (Math.abs(na.length - nb.length) > threshold) return false;
4339
+ return levenshteinDistance(na, nb) <= threshold;
4340
+ }
4341
+ function findBestFuzzyMatch(query, candidates, threshold) {
4342
+ const nq = normalizeForFuzzy(query);
4343
+ let best = null;
4344
+ for (const candidate of candidates) {
4345
+ const nc = normalizeForFuzzy(candidate);
4346
+ if (Math.abs(nq.length - nc.length) > threshold) continue;
4347
+ const distance = levenshteinDistance(nq, nc);
4348
+ if (distance > threshold) continue;
4349
+ if (!best || distance < best.distance) {
4350
+ best = { target: candidate, distance };
4351
+ if (distance === 0) break;
4352
+ }
4353
+ }
4354
+ return best;
4355
+ }
4356
+
4357
+ // src/effort-registry/EffortResolver.ts
4644
4358
  function normalizeSlug(label) {
4645
4359
  return normalizeForFuzzy(label).replace(/\s+/g, "-");
4646
4360
  }
@@ -5002,148 +4716,519 @@ var CompositePropertyRegistry = class {
5002
4716
  for (const item of this.userItems.values()) merged.set(item.slug, item);
5003
4717
  return [...merged.values()];
5004
4718
  }
5005
- listByOrigin(origin) {
5006
- return origin === "user" ? [...this.userItems.values()] : [...this.bundledItems.values()];
4719
+ listByOrigin(origin) {
4720
+ return origin === "user" ? [...this.userItems.values()] : [...this.bundledItems.values()];
4721
+ }
4722
+ async upsert(item) {
4723
+ if (item.registrySource !== "user") {
4724
+ throw new Error(`Cannot upsert property with registrySource '${item.registrySource}'; clone it as 'user' first`);
4725
+ }
4726
+ this.userItems.set(item.slug, item);
4727
+ if (this.storage) await this.storage.save(item);
4728
+ }
4729
+ async delete(slug) {
4730
+ const item = this.userItems.get(slug);
4731
+ if (!item) throw new Error(`Cannot delete '${slug}': not a user-owned record`);
4732
+ this.userItems.delete(slug);
4733
+ if (this.storage) await this.storage.delete(slug);
4734
+ }
4735
+ };
4736
+ var CompositeEquipmentRegistry = class extends CompositePropertyRegistry {
4737
+ constructor(options) {
4738
+ super({ storage: options?.storage });
4739
+ }
4740
+ };
4741
+
4742
+ // src/effort-registry/CompositeEffortRegistry.ts
4743
+ var CompositeEffortRegistry = class extends CompositePropertyRegistry {
4744
+ constructor(options) {
4745
+ super({
4746
+ bundled: options?.bundled,
4747
+ storage: options?.storage
4748
+ });
4749
+ }
4750
+ async loadBundled(customBundled) {
4751
+ this.bundledItems.clear();
4752
+ const source = customBundled ?? this.initialBundled ?? bundledEfforts;
4753
+ for (const effort of source) {
4754
+ const prior = this.bundledItems.get(effort.slug);
4755
+ if (!prior || resolveEntityRank(effort) >= resolveEntityRank(prior)) {
4756
+ this.bundledItems.set(effort.slug, effort);
4757
+ }
4758
+ }
4759
+ this.userItems.clear();
4760
+ if (this.storage) {
4761
+ try {
4762
+ const userEfforts = await this.storage.load();
4763
+ for (const effort of userEfforts) {
4764
+ if (effort.registrySource === "user") {
4765
+ this.userItems.set(effort.slug, effort);
4766
+ }
4767
+ }
4768
+ } catch (err) {
4769
+ console.warn("[CompositeEffortRegistry] Failed to load user efforts from storage adapter:", err);
4770
+ }
4771
+ }
4772
+ this.initialized = true;
4773
+ }
4774
+ /** True after loadBundled() has resolved */
4775
+ isInitialized() {
4776
+ return this.initialized;
4777
+ }
4778
+ listByOrigin(origin) {
4779
+ if (origin === "user") {
4780
+ return Array.from(this.userItems.values());
4781
+ }
4782
+ if (origin === "bundled") {
4783
+ return Array.from(this.bundledItems.values());
4784
+ }
4785
+ return this.list().filter((e) => e.registrySource === origin);
4786
+ }
4787
+ async upsert(effort) {
4788
+ if (effort.registrySource !== "user") {
4789
+ throw new Error(
4790
+ `CompositeEffortRegistry.upsert: only user efforts can be written. Received registrySource="${effort.registrySource}"`
4791
+ );
4792
+ }
4793
+ this.userItems.set(effort.slug, effort);
4794
+ if (this.storage) {
4795
+ try {
4796
+ await this.storage.save(effort);
4797
+ } catch (err) {
4798
+ console.warn("[CompositeEffortRegistry] Failed to persist effort to storage adapter:", err);
4799
+ }
4800
+ }
4801
+ }
4802
+ async delete(slug) {
4803
+ const existing = this.resolve(slug);
4804
+ if (existing && existing.registrySource !== "user") {
4805
+ throw new Error(
4806
+ `CompositeEffortRegistry.delete: cannot delete non-user effort "${slug}"`
4807
+ );
4808
+ }
4809
+ this.userItems.delete(slug);
4810
+ if (this.storage) {
4811
+ try {
4812
+ await this.storage.delete(slug);
4813
+ } catch (err) {
4814
+ console.warn("[CompositeEffortRegistry] Failed to delete effort from storage adapter:", err);
4815
+ }
4816
+ }
4817
+ }
4818
+ };
4819
+
4820
+ // src/effort-registry/aliasManagement.ts
4821
+ async function addAliasToEffort(registry, targetSlug, newAlias) {
4822
+ const trimmed = newAlias.trim();
4823
+ if (!trimmed) {
4824
+ throw new Error("Alias cannot be empty");
4825
+ }
4826
+ const existing = registry.resolve(targetSlug);
4827
+ if (!existing) {
4828
+ throw new Error(`Effort "${targetSlug}" not found in registry`);
5007
4829
  }
5008
- async upsert(item) {
5009
- if (item.registrySource !== "user") {
5010
- throw new Error(`Cannot upsert property with registrySource '${item.registrySource}'; clone it as 'user' first`);
5011
- }
5012
- this.userItems.set(item.slug, item);
5013
- if (this.storage) await this.storage.save(item);
4830
+ const normalizedNew = normalizeForFuzzy(trimmed);
4831
+ const alreadyHas = normalizeForFuzzy(existing.label) === normalizedNew || existing.aliases.some((a) => normalizeForFuzzy(a) === normalizedNew);
4832
+ if (alreadyHas) {
4833
+ return existing;
5014
4834
  }
5015
- async delete(slug) {
5016
- const item = this.userItems.get(slug);
5017
- if (!item) throw new Error(`Cannot delete '${slug}': not a user-owned record`);
5018
- this.userItems.delete(slug);
5019
- if (this.storage) await this.storage.delete(slug);
4835
+ const updatedAliases = [...existing.aliases, trimmed];
4836
+ const now = (/* @__PURE__ */ new Date()).toISOString();
4837
+ if (existing.registrySource === "bundled") {
4838
+ const id = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? `effort-user-${crypto.randomUUID()}` : `effort-user-${Date.now()}`;
4839
+ const userEffort = {
4840
+ ...existing,
4841
+ id,
4842
+ registrySource: "user",
4843
+ aliases: updatedAliases,
4844
+ updatedAt: now,
4845
+ derivation: existing.derivation ?? {
4846
+ parentSlug: existing.slug,
4847
+ coefficients: {},
4848
+ hardOverrides: {}
4849
+ }
4850
+ };
4851
+ await registry.upsert(userEffort);
4852
+ return userEffort;
5020
4853
  }
5021
- };
5022
- var CompositeEquipmentRegistry = class extends CompositePropertyRegistry {
5023
- constructor(options) {
5024
- super({ storage: options?.storage });
4854
+ const updatedUserEffort = {
4855
+ ...existing,
4856
+ aliases: updatedAliases,
4857
+ updatedAt: now
4858
+ };
4859
+ await registry.upsert(updatedUserEffort);
4860
+ return updatedUserEffort;
4861
+ }
4862
+ function buildExactIndex(entities) {
4863
+ const deduped = dedupeEntitiesByPrecedence(entities);
4864
+ const byKey = /* @__PURE__ */ new Map();
4865
+ const add = (key, entity) => {
4866
+ if (!key) return;
4867
+ const existing = byKey.get(key);
4868
+ if (existing) {
4869
+ if (!existing.includes(entity)) existing.push(entity);
4870
+ } else {
4871
+ byKey.set(key, [entity]);
4872
+ }
4873
+ };
4874
+ for (const entity of deduped) {
4875
+ add(entity.slug.toLowerCase(), entity);
4876
+ add(normalizeForFuzzy(entity.label), entity);
4877
+ for (const alias of entity.aliases) add(normalizeForFuzzy(alias), entity);
5025
4878
  }
5026
- };
5027
-
5028
- // src/effort-registry/CompositeEffortRegistry.ts
5029
- var CompositeEffortRegistry = class extends CompositePropertyRegistry {
5030
- constructor(options) {
5031
- super({
5032
- bundled: options?.bundled,
5033
- storage: options?.storage
5034
- });
4879
+ return {
4880
+ byKey,
4881
+ lookup(key) {
4882
+ const direct = byKey.get(key) ?? byKey.get(normalizeForFuzzy(key));
4883
+ if (!direct || direct.length === 0) return { entity: null, ambiguous: [] };
4884
+ if (direct.length === 1) return { entity: direct[0], ambiguous: [] };
4885
+ let maxRank = -1;
4886
+ for (const candidate of direct) {
4887
+ const rank = resolveEntityRank(candidate);
4888
+ if (rank > maxRank) maxRank = rank;
4889
+ }
4890
+ const topTier = direct.filter((candidate) => resolveEntityRank(candidate) === maxRank);
4891
+ if (topTier.length === 1) return { entity: topTier[0], ambiguous: [] };
4892
+ return { entity: null, ambiguous: topTier };
4893
+ }
4894
+ };
4895
+ }
4896
+ function equipmentRef(entity) {
4897
+ return { kind: "equipment", slug: entity.slug, label: entity.label };
4898
+ }
4899
+ function dedupeRefs(refs) {
4900
+ const seen = /* @__PURE__ */ new Set();
4901
+ const result = [];
4902
+ for (const ref of refs) {
4903
+ const key = `${ref.kind}:${ref.slug}`;
4904
+ if (seen.has(key)) continue;
4905
+ seen.add(key);
4906
+ result.push(ref);
5035
4907
  }
5036
- async loadBundled(customBundled) {
5037
- this.bundledItems.clear();
5038
- const source = customBundled ?? this.initialBundled ?? bundledEfforts;
5039
- for (const effort of source) {
5040
- const prior = this.bundledItems.get(effort.slug);
5041
- if (!prior || resolveEntityRank(effort) >= resolveEntityRank(prior)) {
5042
- this.bundledItems.set(effort.slug, effort);
4908
+ return result;
4909
+ }
4910
+ function createReferenceIndex(catalog, materialize) {
4911
+ const dedupedEquipment = dedupeEntitiesByPrecedence(catalog.equipment ?? []);
4912
+ const efforts = buildExactIndex(catalog.efforts ?? []);
4913
+ const equipment = buildExactIndex(dedupedEquipment);
4914
+ const prefixCandidates = dedupedEquipment.flatMap((entity) => [entity.label, ...entity.aliases].filter((name2) => !!name2 && !!name2.trim()).map((name2) => ({ lower: name2.trim().toLowerCase(), entity }))).sort((a, b) => b.lower.length - a.lower.length);
4915
+ const materializer = materialize ?? ((effort) => ({
4916
+ effort,
4917
+ definition: effort,
4918
+ slug: effort.slug,
4919
+ label: effort.label,
4920
+ met: effort.baseAttributes.met,
4921
+ baseAttributes: effort.baseAttributes,
4922
+ discipline: effort.baseAttributes.discipline,
4923
+ disciplineFactor: effort.baseAttributes.disciplineFactor ?? 1,
4924
+ intensityTier: effort.baseAttributes.intensityTier,
4925
+ modifiers: {},
4926
+ registrySource: effort.registrySource,
4927
+ resolvedFrom: effort.registrySource === "user" ? "user" : effort.registrySource === "bundled" ? "bundled" : "default",
4928
+ isEstimated: false
4929
+ }));
4930
+ const resolveEffortExact = (key) => {
4931
+ const found = efforts.lookup(key.trim());
4932
+ if (found.entity) return { entity: found.entity };
4933
+ if (found.ambiguous.length > 0) {
4934
+ return {
4935
+ entity: null,
4936
+ diagnostic: {
4937
+ reason: "ambiguous-effort",
4938
+ message: `"${key}" matches multiple efforts.`,
4939
+ token: key,
4940
+ candidates: found.ambiguous.map((e) => e.slug)
4941
+ }
4942
+ };
4943
+ }
4944
+ return {
4945
+ entity: null,
4946
+ diagnostic: { reason: "unknown-effort", message: `Unknown effort "${key}".`, token: key }
4947
+ };
4948
+ };
4949
+ const resolveEquipmentValues = (values) => {
4950
+ const refs = [];
4951
+ const diagnostics = [];
4952
+ for (const value of values) {
4953
+ if (typeof value !== "string" || !value.trim()) {
4954
+ diagnostics.push({
4955
+ reason: "invalid-explicit",
4956
+ message: `Equipment reference must be a slug string, got ${JSON.stringify(value) ?? "null"}.`,
4957
+ token: String(value)
4958
+ });
4959
+ continue;
4960
+ }
4961
+ const found = equipment.lookup(value.trim());
4962
+ if (found.entity) {
4963
+ refs.push(equipmentRef(found.entity));
4964
+ } else if (found.ambiguous.length > 0) {
4965
+ diagnostics.push({
4966
+ reason: "ambiguous-equipment",
4967
+ message: `"${value}" matches multiple equipment entries.`,
4968
+ token: value,
4969
+ candidates: found.ambiguous.map((e) => e.slug)
4970
+ });
4971
+ } else {
4972
+ diagnostics.push({
4973
+ reason: "invalid-explicit",
4974
+ message: `Unknown equipment "${value}".`,
4975
+ token: value
4976
+ });
5043
4977
  }
5044
4978
  }
5045
- this.userItems.clear();
5046
- if (this.storage) {
5047
- try {
5048
- const userEfforts = await this.storage.load();
5049
- for (const effort of userEfforts) {
5050
- if (effort.registrySource === "user") {
5051
- this.userItems.set(effort.slug, effort);
4979
+ return { refs: dedupeRefs(refs), diagnostics };
4980
+ };
4981
+ const index = {
4982
+ resolveEffort(slugOrAlias) {
4983
+ return efforts.lookup(slugOrAlias).entity;
4984
+ },
4985
+ resolveEquipment(slugOrAlias) {
4986
+ return equipment.lookup(slugOrAlias).entity;
4987
+ },
4988
+ materialize(effort) {
4989
+ return materializer(effort);
4990
+ },
4991
+ resolve(label, defaults) {
4992
+ const source = (label ?? "").trim();
4993
+ const diagnostics = [];
4994
+ const push = (diagnostic) => {
4995
+ if (diagnostic) diagnostics.push(diagnostic);
4996
+ };
4997
+ let matched = "none";
4998
+ let effortEntity = null;
4999
+ let remaining = "";
5000
+ let equipmentRefs = [];
5001
+ let equipmentAuthored = false;
5002
+ const fillEquipment = () => {
5003
+ if (equipmentRefs.length > 0 || equipmentAuthored) return;
5004
+ if (effortEntity === null) return;
5005
+ if (defaults?.equipment?.length) {
5006
+ const resolvedDefaults = resolveEquipmentValues(defaults.equipment);
5007
+ equipmentRefs = resolvedDefaults.refs;
5008
+ for (const diagnostic of resolvedDefaults.diagnostics) {
5009
+ push({ ...diagnostic, reason: "unknown-equipment" });
5010
+ }
5011
+ if (equipmentRefs.length > 0) return;
5012
+ }
5013
+ if (effortEntity.equipment?.length) {
5014
+ const resolvedDefEquipment = resolveEquipmentValues(effortEntity.equipment);
5015
+ equipmentRefs = resolvedDefEquipment.refs;
5016
+ for (const diagnostic of resolvedDefEquipment.diagnostics) {
5017
+ push({ ...diagnostic, reason: "unknown-equipment" });
5018
+ }
5019
+ }
5020
+ };
5021
+ if (!source) {
5022
+ if (defaults?.effort) {
5023
+ const found = resolveEffortExact(defaults.effort);
5024
+ if (found.entity) {
5025
+ effortEntity = found.entity;
5026
+ matched = "default";
5027
+ } else {
5028
+ push({
5029
+ reason: found.diagnostic?.reason === "ambiguous-effort" ? "ambiguous-effort" : "unknown-effort",
5030
+ message: `Default effort "${defaults.effort}" is not in the catalog.`,
5031
+ token: defaults.effort,
5032
+ candidates: found.diagnostic?.candidates
5033
+ });
5034
+ }
5035
+ }
5036
+ fillEquipment();
5037
+ return {
5038
+ matched,
5039
+ label: source,
5040
+ effort: effortEntity ? materializer(effortEntity) : null,
5041
+ effortRef: effortEntity ? { kind: "effort", slug: effortEntity.slug, label: effortEntity.label } : null,
5042
+ equipment: equipmentRefs,
5043
+ remaining: "",
5044
+ diagnostics
5045
+ };
5046
+ }
5047
+ if (source.startsWith("{")) {
5048
+ let parsed;
5049
+ try {
5050
+ parsed = JSON.parse(source);
5051
+ } catch {
5052
+ return {
5053
+ matched: "none",
5054
+ label: source,
5055
+ effort: null,
5056
+ effortRef: null,
5057
+ equipment: [],
5058
+ remaining: "",
5059
+ diagnostics: [{
5060
+ reason: "malformed-json",
5061
+ message: "Reference object is not valid JSON.",
5062
+ token: source
5063
+ }]
5064
+ };
5065
+ }
5066
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed) && ("effort" in parsed || "equipment" in parsed)) {
5067
+ matched = "explicit";
5068
+ const obj = parsed;
5069
+ if (obj.effort !== void 0) {
5070
+ if (typeof obj.effort === "string" && obj.effort.trim()) {
5071
+ const found = resolveEffortExact(obj.effort);
5072
+ effortEntity = found.entity;
5073
+ if (found.diagnostic) {
5074
+ push(found.diagnostic.reason === "ambiguous-effort" ? found.diagnostic : { ...found.diagnostic, reason: "invalid-explicit", message: `Explicit effort "${obj.effort}" is not in the catalog.` });
5075
+ }
5076
+ } else {
5077
+ push({
5078
+ reason: "invalid-explicit",
5079
+ message: 'Explicit "effort" must be a slug string.',
5080
+ token: String(obj.effort)
5081
+ });
5082
+ }
5083
+ }
5084
+ if (obj.equipment !== void 0) {
5085
+ equipmentAuthored = true;
5086
+ const values = Array.isArray(obj.equipment) ? obj.equipment : [obj.equipment];
5087
+ const resolvedEquipment = resolveEquipmentValues(values);
5088
+ equipmentRefs = resolvedEquipment.refs;
5089
+ diagnostics.push(...resolvedEquipment.diagnostics);
5090
+ if (effortEntity?.equipment?.length) {
5091
+ const definitionSlugs = new Set(effortEntity.equipment);
5092
+ const authoredSlugs = equipmentRefs.map((ref) => ref.slug);
5093
+ if (authoredSlugs.length > 0 && authoredSlugs.some((slug) => !definitionSlugs.has(slug))) {
5094
+ push({
5095
+ reason: "conflict",
5096
+ message: `Explicit equipment [${authoredSlugs.join(", ")}] contradicts effort "${effortEntity.slug}" definition equipment [${effortEntity.equipment.join(", ")}].`,
5097
+ token: source
5098
+ });
5099
+ }
5100
+ }
5052
5101
  }
5102
+ fillEquipment();
5103
+ return {
5104
+ matched,
5105
+ label: source,
5106
+ effort: effortEntity ? materializer(effortEntity) : null,
5107
+ effortRef: effortEntity ? { kind: "effort", slug: effortEntity.slug, label: effortEntity.label } : null,
5108
+ equipment: equipmentRefs,
5109
+ remaining: "",
5110
+ diagnostics
5111
+ };
5053
5112
  }
5054
- } catch (err) {
5055
- console.warn("[CompositeEffortRegistry] Failed to load user efforts from storage adapter:", err);
5056
5113
  }
5057
- }
5058
- this.initialized = true;
5059
- }
5060
- /** True after loadBundled() has resolved */
5061
- isInitialized() {
5062
- return this.initialized;
5063
- }
5064
- listByOrigin(origin) {
5065
- if (origin === "user") {
5066
- return Array.from(this.userItems.values());
5067
- }
5068
- if (origin === "bundled") {
5069
- return Array.from(this.bundledItems.values());
5070
- }
5071
- return this.list().filter((e) => e.registrySource === origin);
5072
- }
5073
- async upsert(effort) {
5074
- if (effort.registrySource !== "user") {
5075
- throw new Error(
5076
- `CompositeEffortRegistry.upsert: only user efforts can be written. Received registrySource="${effort.registrySource}"`
5077
- );
5078
- }
5079
- this.userItems.set(effort.slug, effort);
5080
- if (this.storage) {
5081
- try {
5082
- await this.storage.save(effort);
5083
- } catch (err) {
5084
- console.warn("[CompositeEffortRegistry] Failed to persist effort to storage adapter:", err);
5114
+ const lowerSource = source.toLowerCase();
5115
+ for (const candidate of prefixCandidates) {
5116
+ if (!lowerSource.startsWith(candidate.lower)) continue;
5117
+ const after = source.slice(candidate.lower.length);
5118
+ if (after && !/^\s/.test(after)) continue;
5119
+ const candidateRemaining = after.trim();
5120
+ if (!candidateRemaining) continue;
5121
+ const equipmentFound = equipment.lookup(candidate.lower);
5122
+ if (equipmentFound.ambiguous.length > 0) {
5123
+ push({
5124
+ reason: "ambiguous-equipment",
5125
+ message: `"${candidate.lower}" matches multiple equipment entries.`,
5126
+ token: source,
5127
+ candidates: equipmentFound.ambiguous.map((e) => e.slug)
5128
+ });
5129
+ return {
5130
+ matched: "none",
5131
+ label: source,
5132
+ effort: null,
5133
+ effortRef: null,
5134
+ equipment: [],
5135
+ remaining: "",
5136
+ diagnostics
5137
+ };
5138
+ }
5139
+ if (!equipmentFound.entity) continue;
5140
+ const effortFound = resolveEffortExact(candidateRemaining);
5141
+ if (effortFound.entity) {
5142
+ matched = "split";
5143
+ effortEntity = effortFound.entity;
5144
+ const rawPrefix = source.slice(0, candidate.lower.length).trim();
5145
+ equipmentRefs = [{
5146
+ ...equipmentRef(equipmentFound.entity),
5147
+ ...rawPrefix.toLowerCase() !== equipmentFound.entity.label.toLowerCase() ? { matchedAlias: rawPrefix } : {}
5148
+ }];
5149
+ equipmentAuthored = true;
5150
+ remaining = candidateRemaining;
5151
+ break;
5152
+ }
5153
+ if (effortFound.diagnostic?.reason === "ambiguous-effort") {
5154
+ push(effortFound.diagnostic);
5155
+ return {
5156
+ matched: "none",
5157
+ label: source,
5158
+ effort: null,
5159
+ effortRef: null,
5160
+ equipment: [],
5161
+ remaining: "",
5162
+ diagnostics
5163
+ };
5164
+ }
5085
5165
  }
5086
- }
5087
- }
5088
- async delete(slug) {
5089
- const existing = this.resolve(slug);
5090
- if (existing && existing.registrySource !== "user") {
5091
- throw new Error(
5092
- `CompositeEffortRegistry.delete: cannot delete non-user effort "${slug}"`
5093
- );
5094
- }
5095
- this.userItems.delete(slug);
5096
- if (this.storage) {
5097
- try {
5098
- await this.storage.delete(slug);
5099
- } catch (err) {
5100
- console.warn("[CompositeEffortRegistry] Failed to delete effort from storage adapter:", err);
5166
+ if (matched === "none") {
5167
+ const found = resolveEffortExact(source);
5168
+ if (found.entity) {
5169
+ effortEntity = found.entity;
5170
+ matched = "whole";
5171
+ } else if (found.diagnostic?.reason === "ambiguous-effort") {
5172
+ push(found.diagnostic);
5173
+ return {
5174
+ matched: "none",
5175
+ label: source,
5176
+ effort: null,
5177
+ effortRef: null,
5178
+ equipment: [],
5179
+ remaining: "",
5180
+ diagnostics
5181
+ };
5182
+ } else if (found.diagnostic) {
5183
+ push(found.diagnostic);
5184
+ }
5101
5185
  }
5186
+ fillEquipment();
5187
+ const effortMatchText = matched === "split" ? remaining : source;
5188
+ return {
5189
+ matched,
5190
+ label: source,
5191
+ effort: effortEntity ? materializer(effortEntity) : null,
5192
+ effortRef: effortEntity ? {
5193
+ kind: "effort",
5194
+ slug: effortEntity.slug,
5195
+ label: effortEntity.label,
5196
+ ...effortMatchText && effortMatchText.toLowerCase() !== effortEntity.label.toLowerCase() ? { matchedAlias: effortMatchText } : {}
5197
+ } : null,
5198
+ equipment: equipmentRefs,
5199
+ remaining,
5200
+ diagnostics
5201
+ };
5102
5202
  }
5103
- }
5104
- };
5105
-
5106
- // src/effort-registry/aliasManagement.ts
5107
- async function addAliasToEffort(registry, targetSlug, newAlias) {
5108
- const trimmed = newAlias.trim();
5109
- if (!trimmed) {
5110
- throw new Error("Alias cannot be empty");
5111
- }
5112
- const existing = registry.resolve(targetSlug);
5113
- if (!existing) {
5114
- throw new Error(`Effort "${targetSlug}" not found in registry`);
5115
- }
5116
- const normalizedNew = normalizeForFuzzy(trimmed);
5117
- const alreadyHas = normalizeForFuzzy(existing.label) === normalizedNew || existing.aliases.some((a) => normalizeForFuzzy(a) === normalizedNew);
5118
- if (alreadyHas) {
5119
- return existing;
5120
- }
5121
- const updatedAliases = [...existing.aliases, trimmed];
5122
- const now = (/* @__PURE__ */ new Date()).toISOString();
5123
- if (existing.registrySource === "bundled") {
5124
- const id = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? `effort-user-${crypto.randomUUID()}` : `effort-user-${Date.now()}`;
5125
- const userEffort = {
5126
- ...existing,
5127
- id,
5128
- registrySource: "user",
5129
- aliases: updatedAliases,
5130
- updatedAt: now,
5131
- derivation: existing.derivation ?? {
5132
- parentSlug: existing.slug,
5133
- coefficients: {},
5134
- hardOverrides: {}
5203
+ };
5204
+ return index;
5205
+ }
5206
+ function extractStatementRefs(statements) {
5207
+ const efforts = [];
5208
+ const equipment = [];
5209
+ const seenEffort = /* @__PURE__ */ new Set();
5210
+ const seenEquipment = /* @__PURE__ */ new Set();
5211
+ const pushUnique = (list, seen, value) => {
5212
+ if (!value) return;
5213
+ if (seen.has(value)) return;
5214
+ seen.add(value);
5215
+ list.push(value);
5216
+ };
5217
+ for (const statement of statements ?? []) {
5218
+ const metrics = Array.isArray(statement.metrics) ? statement.metrics : statement.metrics?.toArray?.() ?? [];
5219
+ for (const metric of metrics) {
5220
+ const refs = wodWikiCore.getReferenceMetadata(metric.metadata)?.refs;
5221
+ if (!refs) continue;
5222
+ for (const ref of refs) {
5223
+ if (ref.kind === "effort") pushUnique(efforts, seenEffort, ref.slug);
5224
+ if (ref.kind === "equipment") pushUnique(equipment, seenEquipment, ref.slug);
5135
5225
  }
5136
- };
5137
- await registry.upsert(userEffort);
5138
- return userEffort;
5226
+ }
5139
5227
  }
5140
- const updatedUserEffort = {
5141
- ...existing,
5142
- aliases: updatedAliases,
5143
- updatedAt: now
5144
- };
5145
- await registry.upsert(updatedUserEffort);
5146
- return updatedUserEffort;
5228
+ return { efforts, equipment };
5229
+ }
5230
+ function slugifyRefLabel(label) {
5231
+ return normalizeForFuzzy(label).replace(/\s+/g, "-");
5147
5232
  }
5148
5233
 
5149
5234
  // src/runtime/contracts/IRuntimeOptions.ts
@@ -10177,8 +10262,11 @@ function tryEnrichBlock(block, resolver, references, overwrite, debug, propertyS
10177
10262
  if (normalizedBlockType !== "effort" && normalizedBlockType !== "exercise") {
10178
10263
  return;
10179
10264
  }
10180
- const label = block.label;
10181
- if (!label) {
10265
+ const statementEffortMetric = block.getAllMemory().flatMap((loc) => loc.metrics.toArray()).find((m) => m.type === wodWikiCore.MetricType.Effort);
10266
+ const preResolvedMeta = wodWikiCore.getReferenceMetadata(statementEffortMetric?.metadata);
10267
+ const preResolvedEffortRef = preResolvedMeta?.refs?.find((r) => r.kind === "effort");
10268
+ const label = block.label || preResolvedMeta?.source || (typeof statementEffortMetric?.image === "string" ? statementEffortMetric.image : "");
10269
+ if (!label && !preResolvedEffortRef && !block.context?.exerciseId) {
10182
10270
  if (debug) logTrace(`[EffortEnrichment] Skipping block with no label: ${block.key}`);
10183
10271
  return;
10184
10272
  }
@@ -10195,13 +10283,23 @@ function tryEnrichBlock(block, resolver, references, overwrite, debug, propertyS
10195
10283
  let equipmentRefs = [];
10196
10284
  let diagnostics = [];
10197
10285
  if (references) {
10198
- const resolution = references.resolve(label);
10199
- resolved = resolution.effort ?? void 0;
10200
- matchedKind = resolution.matched;
10201
- equipmentRefs = resolution.equipment;
10202
- diagnostics = resolution.diagnostics;
10286
+ const directSlug = preResolvedEffortRef?.slug || block.context?.exerciseId;
10287
+ const directEntity = directSlug ? references.resolveEffort(directSlug) : null;
10288
+ if (directEntity && preResolvedMeta) {
10289
+ resolved = references.materialize(directEntity);
10290
+ matchedKind = preResolvedMeta.matched;
10291
+ equipmentRefs = preResolvedMeta.refs?.filter((r) => r.kind === "equipment") ?? [];
10292
+ diagnostics = [...preResolvedMeta.diagnostics ?? []];
10293
+ } else {
10294
+ const resolution = references.resolve(label);
10295
+ resolved = resolution.effort ?? void 0;
10296
+ matchedKind = resolution.matched;
10297
+ equipmentRefs = resolution.equipment;
10298
+ diagnostics = resolution.diagnostics;
10299
+ }
10203
10300
  } else {
10204
- resolved = resolver.resolveEffort(label);
10301
+ const lookupKey = preResolvedEffortRef?.slug || block.context?.exerciseId || label;
10302
+ resolved = resolver.resolveEffort(lookupKey) ?? (lookupKey !== label ? resolver.resolveEffort(label) : void 0);
10205
10303
  }
10206
10304
  if (!resolved) {
10207
10305
  if (diagnostics.length > 0) {
@@ -16209,6 +16307,7 @@ exports.renderMetric = renderMetric;
16209
16307
  exports.resolveDominantOrigin = resolveDominantOrigin;
16210
16308
  exports.resolveEntityRank = resolveEntityRank;
16211
16309
  exports.resolveEntityTier = resolveEntityTier;
16310
+ exports.resolveFusedEffortReferences = resolveFusedEffortReferences;
16212
16311
  exports.resolveProfileRules = resolveProfileRules;
16213
16312
  exports.resolveSoundProfile = resolveSoundProfile;
16214
16313
  exports.resolveSourceRank = resolveSourceRank;