@bitcobblers/wod-wiki-lang 0.37.2137 → 0.37.2140

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.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { wallClockNow, formatDurationSmart } from './chunk-PFQDVQQO.mjs';
2
2
  export { createBrowserRuntimeNow, frozenNow, getRuntimeNowMs, wallClockNow } from './chunk-PFQDVQQO.mjs';
3
- import { MetricType, MetricContainer, BlockKey, ParsedCodeStatement, OutputStatement, valueKindOf, normalizeFieldPath } from '@bitcobblers/wod-wiki-core';
3
+ import { MetricType, MetricContainer, BlockKey, REFERENCE_METADATA_KEY, ParsedCodeStatement, getReferenceMetadata, OutputStatement, valueKindOf, normalizeFieldPath } from '@bitcobblers/wod-wiki-core';
4
4
  import { LRParser } from '@lezer/lr';
5
5
  import { LRLanguage, LanguageSupport } from '@codemirror/language';
6
6
  import { NodeProp } from '@lezer/common';
@@ -378,15 +378,19 @@ function createPolicy() {
378
378
  }
379
379
  var metricPresentation = createPolicy();
380
380
  var EffortMetric = class {
381
- constructor(effort) {
381
+ constructor(effort, options) {
382
382
  this.effort = effort;
383
- this.value = effort;
383
+ this.value = options?.resolvedSlug ?? effort;
384
384
  this.image = effort;
385
+ if (options?.references) {
386
+ this.metadata = { [REFERENCE_METADATA_KEY]: options.references };
387
+ }
385
388
  }
386
389
  effort;
387
390
  value;
388
391
  image;
389
392
  origin = "parser";
393
+ metadata;
390
394
  type = MetricType.Effort;
391
395
  };
392
396
  var DistanceMetric = class {
@@ -1354,6 +1358,27 @@ var DurationMetric = class {
1354
1358
  return this.value > 0 ? "down" : "up";
1355
1359
  }
1356
1360
  };
1361
+ var EquipmentMetric = class {
1362
+ type = MetricType.Equipment;
1363
+ origin;
1364
+ /** Canonical slugs, ordered and deduplicated. */
1365
+ value;
1366
+ image;
1367
+ name = "equipment";
1368
+ metadata;
1369
+ constructor(slugs, options) {
1370
+ this.value = [...new Set(slugs)];
1371
+ this.image = options?.source ?? this.value.join(" ");
1372
+ this.origin = options?.origin ?? "parser";
1373
+ const reference = {
1374
+ matched: options?.matched ?? "whole",
1375
+ source: options?.source ?? this.image,
1376
+ ...options?.refs ? { refs: options.refs } : {},
1377
+ ...options?.diagnostics?.length ? { diagnostics: options.diagnostics } : {}
1378
+ };
1379
+ this.metadata = { [REFERENCE_METADATA_KEY]: reference };
1380
+ }
1381
+ };
1357
1382
  var GroupMetric = class {
1358
1383
  constructor(group, image) {
1359
1384
  this.group = group;
@@ -1450,16 +1475,29 @@ var PROPERTY_KEY_TO_METRIC_TYPE = {
1450
1475
  volume: MetricType.Volume,
1451
1476
  work: MetricType.Work
1452
1477
  };
1453
- function classifyStatements(facts) {
1478
+ var STRUCTURAL_EFFORT_WORDS = /* @__PURE__ */ new Set(["rest", "amrap", "emom", "tabata", "for time"]);
1479
+ var STANDARD_UNITS2 = UnitRegistry.standard();
1480
+ var REFERENCE_PAIR_KEYS = /* @__PURE__ */ new Set(["effort", "equipment"]);
1481
+ function referenceMetadata(resolution) {
1482
+ return {
1483
+ matched: resolution.matched,
1484
+ source: resolution.label,
1485
+ ...resolution.effortRef || resolution.equipment.length > 0 ? { refs: [...resolution.effortRef ? [resolution.effortRef] : [], ...resolution.equipment] } : {},
1486
+ ...resolution.remaining ? { remaining: resolution.remaining } : {},
1487
+ ...resolution.diagnostics.length > 0 ? { diagnostics: resolution.diagnostics } : {}
1488
+ };
1489
+ }
1490
+ function classifyStatements(facts, references) {
1454
1491
  return facts.statements.map((fact) => {
1455
1492
  const statement = new ParsedCodeStatement();
1456
- const metricPairs = [];
1493
+ let metricPairs = [];
1457
1494
  for (const primitive of fact.primitives) {
1458
1495
  metricPairs.push(...classifyPrimitive(primitive));
1459
1496
  }
1460
1497
  const mergedPairs = mergeFragments(metricPairs);
1461
- statement.metrics = mergedPairs.map((pair) => pair.metrics);
1462
- statement.metricMeta = new Map(mergedPairs.map((pair) => [pair.metrics, pair.meta]));
1498
+ const finalPairs = references ? applyReferenceResolution(mergedPairs, fact, references) : mergedPairs;
1499
+ statement.metrics = finalPairs.map((pair) => pair.metrics);
1500
+ statement.metricMeta = new Map(finalPairs.map((pair) => [pair.metrics, pair.meta]));
1463
1501
  statement.id = fact.id;
1464
1502
  statement.line = fact.line;
1465
1503
  statement.text = fact.meta.raw;
@@ -1471,6 +1509,105 @@ function classifyStatements(facts) {
1471
1509
  return statement;
1472
1510
  });
1473
1511
  }
1512
+ function applyReferenceResolution(pairs, fact, references) {
1513
+ const { index, defaults } = references;
1514
+ const result = [];
1515
+ const emitResolution = (resolution, authored, meta2) => {
1516
+ if (resolution.effortRef || resolution.matched !== "none" || resolution.diagnostics.length > 0) {
1517
+ result.push({
1518
+ metrics: new EffortMetric(authored, {
1519
+ resolvedSlug: resolution.effortRef?.slug,
1520
+ references: referenceMetadata(resolution)
1521
+ }),
1522
+ meta: meta2
1523
+ });
1524
+ }
1525
+ if (resolution.equipment.length > 0) {
1526
+ result.push({
1527
+ metrics: new EquipmentMetric(
1528
+ resolution.equipment.map((ref) => ref.slug),
1529
+ {
1530
+ refs: resolution.equipment,
1531
+ source: authored,
1532
+ matched: resolution.matched,
1533
+ diagnostics: resolution.diagnostics
1534
+ }
1535
+ ),
1536
+ meta: meta2
1537
+ });
1538
+ }
1539
+ };
1540
+ const handledRawObjects = /* @__PURE__ */ new Set();
1541
+ const malformedHints = [];
1542
+ for (const primitive of fact.primitives) {
1543
+ if (primitive.kind !== "metric_object") continue;
1544
+ if (handledRawObjects.has(primitive.raw)) continue;
1545
+ handledRawObjects.add(primitive.raw);
1546
+ if (primitive.pairs.length === 0) {
1547
+ malformedHints.push({
1548
+ metrics: {
1549
+ ...hintMetric("reference.malformed_json", "parser"),
1550
+ metadata: referenceMetadata(index.resolve(primitive.raw))
1551
+ },
1552
+ meta: primitive.meta
1553
+ });
1554
+ continue;
1555
+ }
1556
+ const hasReferenceKeys = primitive.pairs.some((pair) => REFERENCE_PAIR_KEYS.has(pair.key.toLowerCase()));
1557
+ if (!hasReferenceKeys) continue;
1558
+ const resolution = index.resolve(primitive.raw);
1559
+ if (resolution.matched === "explicit") {
1560
+ emitResolution(resolution, primitive.raw, primitive.meta);
1561
+ }
1562
+ for (const pair of primitive.pairs) {
1563
+ if (REFERENCE_PAIR_KEYS.has(pair.key.toLowerCase())) continue;
1564
+ const metricType = PROPERTY_KEY_TO_METRIC_TYPE[pair.key.toLowerCase()] ?? MetricType.Custom;
1565
+ const rawValue = Array.isArray(pair.value) ? pair.value.join(", ") : pair.value;
1566
+ result.push({
1567
+ metrics: new PropertyMetric(pair.key, rawValue, { type: metricType, image: `${pair.key}: ${rawValue}` }),
1568
+ meta: primitive.meta
1569
+ });
1570
+ }
1571
+ }
1572
+ let sawEffortMetric = false;
1573
+ let sawDurationMetric = false;
1574
+ let sawMovementQuantity = false;
1575
+ let statementMeta;
1576
+ for (const pair of pairs) {
1577
+ const metric = pair.metrics;
1578
+ if (metric.type === MetricType.Custom && handledRawObjects.has(pair.meta.raw)) continue;
1579
+ if (metric.type === MetricType.Effort) {
1580
+ const authored = metric instanceof EffortMetric ? metric.effort : String(metric.value ?? "");
1581
+ const normalized = authored.trim().toLowerCase();
1582
+ if (STRUCTURAL_EFFORT_WORDS.has(normalized) || STANDARD_UNITS2.has(normalized)) {
1583
+ result.push(pair);
1584
+ continue;
1585
+ }
1586
+ sawEffortMetric = true;
1587
+ const resolution = index.resolve(authored, defaults);
1588
+ if (resolution.matched === "none" && resolution.diagnostics.length === 0) {
1589
+ result.push(pair);
1590
+ continue;
1591
+ }
1592
+ emitResolution(resolution, authored, pair.meta);
1593
+ continue;
1594
+ }
1595
+ if (metric.type === MetricType.Duration) sawDurationMetric = true;
1596
+ if (metric.type === MetricType.Rep || metric.type === MetricType.Rounds || metric.type === MetricType.Resistance || metric.type === MetricType.Distance) {
1597
+ sawMovementQuantity = true;
1598
+ }
1599
+ result.push(pair);
1600
+ statementMeta ??= pair.meta;
1601
+ }
1602
+ if (defaults && !sawEffortMetric && fact.isLeaf && sawMovementQuantity && !sawDurationMetric) {
1603
+ const resolution = index.resolve("", defaults);
1604
+ if (resolution.effortRef) {
1605
+ emitResolution(resolution, resolution.effortRef.label, statementMeta ?? fact.meta);
1606
+ }
1607
+ }
1608
+ result.push(...malformedHints);
1609
+ return result;
1610
+ }
1474
1611
  function classifyPrimitive(primitive) {
1475
1612
  switch (primitive.kind) {
1476
1613
  case "lap":
@@ -1539,10 +1676,11 @@ function classifyPrimitive(primitive) {
1539
1676
  const pairs = primitive.pairs;
1540
1677
  return pairs.map((pair) => {
1541
1678
  const metricType = PROPERTY_KEY_TO_METRIC_TYPE[pair.key.toLowerCase()] ?? MetricType.Custom;
1679
+ const rawValue = Array.isArray(pair.value) ? pair.value.join(", ") : pair.value;
1542
1680
  return {
1543
- metrics: new PropertyMetric(pair.key, pair.value, {
1681
+ metrics: new PropertyMetric(pair.key, rawValue, {
1544
1682
  type: metricType,
1545
- image: `${pair.key}: ${pair.value}`
1683
+ image: `${pair.key}: ${rawValue}`
1546
1684
  }),
1547
1685
  meta: primitive.meta
1548
1686
  };
@@ -1580,6 +1718,23 @@ function mergeFragments(pairs) {
1580
1718
  continue;
1581
1719
  }
1582
1720
  }
1721
+ if (current.metrics instanceof RepMetric && next.metrics instanceof EffortMetric && /^(reps?)$/i.test(next.metrics.effort)) {
1722
+ const gap = next.meta.startOffset - current.meta.endOffset;
1723
+ if (gap <= 1) {
1724
+ const mergedMeta = {
1725
+ ...current.meta,
1726
+ endOffset: next.meta.endOffset,
1727
+ columnEnd: next.meta.columnEnd,
1728
+ length: next.meta.endOffset - current.meta.startOffset,
1729
+ raw: current.meta.raw + (gap === 1 ? " " : "") + next.meta.raw
1730
+ };
1731
+ current = {
1732
+ metrics: current.metrics,
1733
+ meta: mergedMeta
1734
+ };
1735
+ continue;
1736
+ }
1737
+ }
1583
1738
  if (current.metrics instanceof RepMetric && (next.metrics instanceof ResistanceMetric || next.metrics instanceof DistanceMetric)) {
1584
1739
  const gap = next.meta.startOffset - current.meta.endOffset;
1585
1740
  if (gap <= 1) {
@@ -1955,12 +2110,12 @@ function resolveSourceInput(sourceOrTree, treeArg) {
1955
2110
  }
1956
2111
  return { source: "" };
1957
2112
  }
1958
- function extractStatementsRaw(sourceOrTree, treeArg) {
2113
+ function extractStatementsRaw(sourceOrTree, treeArg, references) {
1959
2114
  const { source, tree } = resolveSourceInput(sourceOrTree, treeArg);
1960
2115
  const facts = extractSyntaxFacts(source, tree);
1961
- return normalizeInlineRounds(classifyStatements(facts));
2116
+ return normalizeInlineRounds(classifyStatements(facts, references));
1962
2117
  }
1963
- function extractStatements(sourceOrTree, sportOrTree, sportArg) {
2118
+ function extractStatements(sourceOrTree, sportOrTree, sportArg, references) {
1964
2119
  let sport;
1965
2120
  let tree;
1966
2121
  if (typeof sportOrTree === "string") {
@@ -1971,7 +2126,7 @@ function extractStatements(sourceOrTree, sportOrTree, sportArg) {
1971
2126
  }
1972
2127
  const resolved = resolveSourceInput(sourceOrTree, tree);
1973
2128
  const facts = extractSyntaxFacts(resolved.source, resolved.tree);
1974
- const statements = classifyStatements(facts);
2129
+ const statements = classifyStatements(facts, references);
1975
2130
  dialectStack.processAll(statements, sport);
1976
2131
  return normalizeInlineRounds(statements);
1977
2132
  }
@@ -2103,7 +2258,8 @@ function parseScript(inputText, options = {}) {
2103
2258
  const doc = inputText.endsWith("\n") ? inputText : inputText + "\n";
2104
2259
  const tree = parser.parse(doc);
2105
2260
  const sport = options.sport ?? options.dialect;
2106
- const statements = options.withoutDialects ? extractStatementsRaw(inputText, tree) : extractStatements(inputText, tree, sport);
2261
+ const references = options.references ? { index: options.references, defaults: options.defaults } : void 0;
2262
+ const statements = options.withoutDialects ? extractStatementsRaw(inputText, tree, references) : extractStatements(inputText, tree, sport, references);
2107
2263
  return new WhiteboardScript(inputText, statements, []);
2108
2264
  } catch (error) {
2109
2265
  const msg = error instanceof Error ? error.message : "Unknown parse error";
@@ -2113,6 +2269,10 @@ function parseScript(inputText, options = {}) {
2113
2269
 
2114
2270
  // src/parser/md-timer.ts
2115
2271
  var MdTimerRuntime = class {
2272
+ config;
2273
+ constructor(config) {
2274
+ this.config = config;
2275
+ }
2116
2276
  /**
2117
2277
  * Parse WhiteboardScript source into a {@link WhiteboardScript}, running the
2118
2278
  * Dialect Stack on every statement.
@@ -2120,22 +2280,21 @@ var MdTimerRuntime = class {
2120
2280
  * @param sport - The block's `:sport` fence suffix (` ```log:climbing `).
2121
2281
  */
2122
2282
  read(inputText, sport) {
2123
- return parseScript(inputText, { sport });
2283
+ return parseScript(inputText, { sport, ...this.config });
2124
2284
  }
2125
2285
  /**
2126
2286
  * Parse without running the Dialect Stack. Used by the parser test harness
2127
2287
  * which applies its own Dialect set. Production consumers should use {@link read}.
2128
2288
  */
2129
2289
  readWithoutDialects(inputText) {
2130
- return parseScript(inputText, { withoutDialects: true });
2290
+ return parseScript(inputText, { withoutDialects: true, ...this.config });
2131
2291
  }
2132
2292
  };
2133
2293
 
2134
2294
  // src/parser/parserInstance.ts
2135
- function createParser() {
2136
- return new MdTimerRuntime();
2295
+ function createParser(config) {
2296
+ return new MdTimerRuntime(config);
2137
2297
  }
2138
- var sharedParser = /* @__PURE__ */ createParser();
2139
2298
  var nextTagID = 0;
2140
2299
  var Tag = class _Tag {
2141
2300
  /**
@@ -2799,6 +2958,42 @@ function whiteboardScript() {
2799
2958
 
2800
2959
  // src/effort-registry/types.ts
2801
2960
  var EFFORT_REGISTRY_ORIGINS = ["bundled", "user"];
2961
+ var CATALOG_PRECEDENCE_RANK = {
2962
+ core: 0,
2963
+ community: 1,
2964
+ imported: 2,
2965
+ user: 3
2966
+ };
2967
+ function resolveSourceTier(sourceIdOrMetaKey, explicitTier) {
2968
+ if (explicitTier) return explicitTier;
2969
+ if (!sourceIdOrMetaKey) return "core";
2970
+ if (sourceIdOrMetaKey === "user") return "user";
2971
+ const id = sourceIdOrMetaKey.startsWith("seed:") ? sourceIdOrMetaKey.slice(5) : sourceIdOrMetaKey;
2972
+ if (id === "core" || id === "efforts" || id === "equipment") return "core";
2973
+ if (id === "community" || id.startsWith("community-") || id === "common") return "community";
2974
+ return "imported";
2975
+ }
2976
+ function resolveSourceRank(sourceIdOrMetaKey, explicitTier) {
2977
+ return CATALOG_PRECEDENCE_RANK[resolveSourceTier(sourceIdOrMetaKey, explicitTier)];
2978
+ }
2979
+ function resolveEntityTier(entity) {
2980
+ if (entity.registrySource === "user") return "user";
2981
+ return resolveSourceTier(entity.sourceId);
2982
+ }
2983
+ function resolveEntityRank(entity) {
2984
+ return CATALOG_PRECEDENCE_RANK[resolveEntityTier(entity)];
2985
+ }
2986
+ function dedupeEntitiesByPrecedence(entities) {
2987
+ const bySlug = /* @__PURE__ */ new Map();
2988
+ for (const entity of entities) {
2989
+ const key = entity.slug.toLowerCase();
2990
+ const prior = bySlug.get(key);
2991
+ if (!prior || resolveEntityRank(entity) >= resolveEntityRank(prior)) {
2992
+ bySlug.set(key, entity);
2993
+ }
2994
+ }
2995
+ return Array.from(bySlug.values());
2996
+ }
2802
2997
  var INTENSITY_TIERS = ["low", "moderate", "high"];
2803
2998
  var DEFAULT_RESOLVER_OPTIONS = {
2804
2999
  defaultMet: 5,
@@ -2838,6 +3033,426 @@ function disciplineFactorFor(discipline) {
2838
3033
  const key = discipline.toLowerCase();
2839
3034
  return isEffortDiscipline(key) ? DISCIPLINE_FACTORS[key] : 1;
2840
3035
  }
3036
+ var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
3037
+ var SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
3038
+ function unquote(value) {
3039
+ return value.replace(
3040
+ /^(['"])(.*)\1$/,
3041
+ (_match, quote, inner) => quote === '"' ? inner.replace(
3042
+ /\\(["\\nrt])/g,
3043
+ (_all, c) => c === "n" ? "\n" : c === "r" ? "\r" : c === "t" ? " " : c
3044
+ ) : inner
3045
+ );
3046
+ }
3047
+ function quoteYaml(val) {
3048
+ if (!val) return '""';
3049
+ if (/[:"'\n#{}[\],&*?|\-<>=%!@`]/.test(val) || val !== val.trim()) {
3050
+ return `"${val.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t")}"`;
3051
+ }
3052
+ return val;
3053
+ }
3054
+ function parseFrontmatter(raw) {
3055
+ const match = raw.match(FRONTMATTER_RE);
3056
+ if (!match) return { meta: {}, body: raw };
3057
+ const lines = match[1].split(/\r?\n/);
3058
+ const meta2 = {};
3059
+ for (let i = 0; i < lines.length; i++) {
3060
+ const keyMatch = lines[i].match(/^([A-Za-z][\w.-]*)\s*:(.*)$/);
3061
+ if (!keyMatch) continue;
3062
+ const key = keyMatch[1];
3063
+ const rawVal = keyMatch[2].trim();
3064
+ if (rawVal === "") {
3065
+ const items = [];
3066
+ while (i + 1 < lines.length) {
3067
+ const itemMatch = lines[i + 1].match(/^\s+-\s+(.+)$/);
3068
+ if (!itemMatch) break;
3069
+ items.push(unquote(itemMatch[1].trim()));
3070
+ i++;
3071
+ }
3072
+ meta2[key] = items.length > 0 ? items : "";
3073
+ } else {
3074
+ const wasQuoted = /^(['"])(.*)\1$/.test(rawVal);
3075
+ const value = unquote(rawVal);
3076
+ const num2 = Number(value);
3077
+ meta2[key] = !wasQuoted && value !== "" && !isNaN(num2) ? num2 : value;
3078
+ }
3079
+ }
3080
+ return { meta: meta2, body: raw.slice(match[0].length) };
3081
+ }
3082
+ function getScalar(meta2, key) {
3083
+ const value = meta2[key];
3084
+ return typeof value === "string" || typeof value === "number" ? value : void 0;
3085
+ }
3086
+ function getListStrict(meta2, key) {
3087
+ const value = meta2[key];
3088
+ if (value === void 0) return [];
3089
+ if (typeof value === "string" && value === "[]") return [];
3090
+ return Array.isArray(value) ? value : void 0;
3091
+ }
3092
+ function coerceNestedScalar(raw) {
3093
+ if (raw === "true") return true;
3094
+ if (raw === "false") return false;
3095
+ if (raw !== "" && !Number.isNaN(Number(raw))) return Number(raw);
3096
+ return unquote(raw);
3097
+ }
3098
+ function readNestedSection(lines, key) {
3099
+ const start = lines.findIndex((l) => new RegExp(`^${key}:\\s*$`).test(l));
3100
+ if (start === -1) return void 0;
3101
+ const result = {};
3102
+ let subKey = null;
3103
+ for (let i = start + 1; i < lines.length; i++) {
3104
+ const line = lines[i];
3105
+ if (/^\S/.test(line)) break;
3106
+ const trimmed = line.trim();
3107
+ if (!trimmed || trimmed.startsWith("#")) continue;
3108
+ const indent = line.length - line.trimStart().length;
3109
+ const m = trimmed.match(/^([\w.-]+):\s*(.*)$/);
3110
+ if (!m) continue;
3111
+ const [, k, rawVal] = m;
3112
+ if (indent === 2) {
3113
+ if (rawVal === "") {
3114
+ subKey = k;
3115
+ result[k] = {};
3116
+ } else {
3117
+ subKey = null;
3118
+ result[k] = coerceNestedScalar(rawVal);
3119
+ }
3120
+ } else if (indent >= 4 && subKey !== null) {
3121
+ result[subKey][k] = coerceNestedScalar(rawVal);
3122
+ }
3123
+ }
3124
+ return Object.keys(result).length > 0 ? result : void 0;
3125
+ }
3126
+ function parseDiscipline(val) {
3127
+ if (!val) return void 0;
3128
+ const normalized = unquote(val.trim()).toLowerCase();
3129
+ if (isEffortDiscipline(normalized)) return normalized;
3130
+ console.warn(`[definition-markdown] unknown discipline '${val}' \u2014 expected one of the canonical values; treating as unset`);
3131
+ return void 0;
3132
+ }
3133
+ function identityProblems(slug, label) {
3134
+ const problems = [];
3135
+ const slugText = slug === void 0 ? "" : String(slug);
3136
+ if (!slugText) problems.push("Missing required field: slug");
3137
+ else if (!SLUG_RE.test(slugText)) problems.push("Invalid slug: must be lowercase letters, numbers, and hyphens only");
3138
+ if (label === void 0 || !String(label).trim()) problems.push("Missing required field: label");
3139
+ return problems;
3140
+ }
3141
+ function stringListProblems(values, kind) {
3142
+ const problems = [];
3143
+ for (const value of values) {
3144
+ if (!value.trim()) problems.push(`Invalid ${kind}: blank entries are not allowed`);
3145
+ else if (kind === "equipment" && !SLUG_RE.test(value)) {
3146
+ problems.push(`Invalid ${kind}: '${value}' must be a canonical slug (lowercase letters, numbers, hyphens)`);
3147
+ }
3148
+ }
3149
+ const duplicate = values.find((v, i) => values.indexOf(v) !== i);
3150
+ if (duplicate !== void 0) problems.push(`Invalid ${kind}: duplicate entry '${duplicate}'`);
3151
+ return problems;
3152
+ }
3153
+ function metProblem(value) {
3154
+ if (value === void 0) return "Missing required field: baseAttributes.met";
3155
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
3156
+ return "Invalid baseAttributes.met: must be a positive number";
3157
+ }
3158
+ return null;
3159
+ }
3160
+ function parseEffortFile(raw) {
3161
+ const blockMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
3162
+ if (!blockMatch) return null;
3163
+ const innerLines = blockMatch[1].split(/\r?\n/);
3164
+ const { meta: meta2, body } = parseFrontmatter(raw);
3165
+ const type = getScalar(meta2, "type");
3166
+ if (type !== void 0 && type !== "effort") return null;
3167
+ const id = getScalar(meta2, "id");
3168
+ const slug = getScalar(meta2, "slug");
3169
+ const label = getScalar(meta2, "label");
3170
+ const aliases = getListStrict(meta2, "aliases");
3171
+ const equipment = getListStrict(meta2, "equipment");
3172
+ if (aliases === void 0 || equipment === void 0) return null;
3173
+ if (identityProblems(slug, label).length > 0) return null;
3174
+ if (stringListProblems(aliases, "aliases").length > 0) return null;
3175
+ if (equipment.length > 0 && stringListProblems(equipment, "equipment").length > 0) return null;
3176
+ const baseAttrs = readNestedSection(innerLines, "baseAttributes") ?? {};
3177
+ if (metProblem(baseAttrs.met) !== null) return null;
3178
+ const met = baseAttrs.met;
3179
+ const discipline = parseDiscipline(
3180
+ typeof baseAttrs.discipline === "string" ? baseAttrs.discipline : void 0
3181
+ );
3182
+ const intensityTier = baseAttrs.intensityTier;
3183
+ const disciplineFactor = typeof baseAttrs.disciplineFactor === "number" && Number.isFinite(baseAttrs.disciplineFactor) ? baseAttrs.disciplineFactor : void 0;
3184
+ const hints = readNestedSection(innerLines, "hints");
3185
+ const derivation = readNestedSection(innerLines, "derivation");
3186
+ const trimmedBody = body.trim();
3187
+ return {
3188
+ id: String(id ?? ""),
3189
+ slug: String(slug),
3190
+ label: String(label),
3191
+ aliases,
3192
+ baseAttributes: {
3193
+ met,
3194
+ ...discipline ? { discipline } : {},
3195
+ ...disciplineFactor !== void 0 ? { disciplineFactor } : {},
3196
+ ...intensityTier ? { intensityTier } : {}
3197
+ },
3198
+ registrySource: "bundled",
3199
+ ...equipment.length > 0 ? { equipment } : {},
3200
+ ...trimmedBody ? { body: trimmedBody } : {},
3201
+ ...hints ? { hints } : {},
3202
+ ...derivation ? { derivation } : {}
3203
+ };
3204
+ }
3205
+ function parseEquipmentFile(raw) {
3206
+ const { meta: meta2, body } = parseFrontmatter(raw);
3207
+ if (getScalar(meta2, "type") !== "equipment") return null;
3208
+ const id = getScalar(meta2, "id");
3209
+ const slug = getScalar(meta2, "slug");
3210
+ const label = getScalar(meta2, "label");
3211
+ const aliases = getListStrict(meta2, "aliases");
3212
+ if (id === void 0 || aliases === void 0) return null;
3213
+ if (identityProblems(slug, label).length > 0) return null;
3214
+ if (stringListProblems(aliases, "aliases").length > 0) return null;
3215
+ const trimmedBody = body.trim();
3216
+ return {
3217
+ id: String(id),
3218
+ slug: String(slug),
3219
+ label: String(label),
3220
+ aliases,
3221
+ registrySource: "bundled",
3222
+ ...trimmedBody ? { body: trimmedBody } : {}
3223
+ };
3224
+ }
3225
+ function createDefaultEffort() {
3226
+ return {
3227
+ id: `effort-user-${v7()}`,
3228
+ slug: "",
3229
+ label: "",
3230
+ aliases: [],
3231
+ baseAttributes: { met: 5 },
3232
+ registrySource: "user"
3233
+ };
3234
+ }
3235
+ function documentToEffort(doc, baseEffort) {
3236
+ const errors = [];
3237
+ const blockMatch = doc.match(/^---\r?\n([\s\S]*?)\r?\n---/);
3238
+ if (!blockMatch) {
3239
+ errors.push("Missing YAML frontmatter delimiters (---)");
3240
+ return { effort: baseEffort ?? createDefaultEffort(), errors };
3241
+ }
3242
+ const innerLines = blockMatch[1].split(/\r?\n/);
3243
+ const { meta: meta2, body } = parseFrontmatter(doc);
3244
+ const bodyText = body.replace(/^\r?\n+/, "") || void 0;
3245
+ const id = getScalar(meta2, "id");
3246
+ const slug = getScalar(meta2, "slug");
3247
+ const label = getScalar(meta2, "label");
3248
+ const registrySource = getScalar(meta2, "registrySource");
3249
+ const createdAt = getScalar(meta2, "createdAt");
3250
+ const updatedAt = getScalar(meta2, "updatedAt");
3251
+ const aliases = getListStrict(meta2, "aliases");
3252
+ const equipmentRaw = meta2["equipment"];
3253
+ const equipment = getListStrict(meta2, "equipment");
3254
+ if (aliases === void 0) {
3255
+ errors.push("Invalid aliases: must be a list (`aliases:` followed by `- item` lines)");
3256
+ }
3257
+ if (equipment === void 0) {
3258
+ errors.push("Invalid equipment: must be a list (`equipment:` followed by `- slug` lines)");
3259
+ }
3260
+ const baseAttrs = readNestedSection(innerLines, "baseAttributes") ?? {};
3261
+ const baseAttributes = {};
3262
+ const metRaw = baseAttrs.met;
3263
+ if (metRaw === void 0) {
3264
+ const fallbackMet = baseEffort?.baseAttributes.met;
3265
+ const fallbackProblem = fallbackMet === void 0 ? null : metProblem(fallbackMet);
3266
+ if (fallbackProblem) errors.push(fallbackProblem);
3267
+ else if (fallbackMet !== void 0) baseAttributes.met = fallbackMet;
3268
+ else errors.push("Missing required field: baseAttributes.met");
3269
+ } else {
3270
+ const problem = metProblem(metRaw);
3271
+ if (problem) errors.push(problem);
3272
+ else baseAttributes.met = Number(metRaw);
3273
+ }
3274
+ if (getScalar(meta2, "met") !== void 0) {
3275
+ errors.push(
3276
+ "Invalid met placement: 'met' must be nested under baseAttributes (missing baseAttributes.met)"
3277
+ );
3278
+ }
3279
+ const discipline = parseDiscipline(
3280
+ typeof baseAttrs.discipline === "string" ? baseAttrs.discipline : void 0
3281
+ );
3282
+ if (discipline) baseAttributes.discipline = discipline;
3283
+ if (typeof baseAttrs.disciplineFactor === "number" && Number.isFinite(baseAttrs.disciplineFactor)) {
3284
+ baseAttributes.disciplineFactor = baseAttrs.disciplineFactor;
3285
+ }
3286
+ if (baseAttrs.intensityTier) baseAttributes.intensityTier = baseAttrs.intensityTier;
3287
+ const hints = readNestedSection(innerLines, "hints") ?? baseEffort?.hints;
3288
+ const derivation = readNestedSection(innerLines, "derivation");
3289
+ errors.push(...identityProblems(slug, label));
3290
+ if (aliases !== void 0) errors.push(...stringListProblems(aliases, "aliases"));
3291
+ if (equipment !== void 0 && equipment.length > 0) {
3292
+ errors.push(...stringListProblems(equipment, "equipment"));
3293
+ }
3294
+ const resolvedEquipment = equipmentRaw !== void 0 ? equipment ?? [] : baseEffort?.equipment?.length ? baseEffort.equipment : void 0;
3295
+ const effort = {
3296
+ id: String(id ?? "") || baseEffort?.id || `effort-user-${v7()}`,
3297
+ slug: String(slug ?? ""),
3298
+ label: String(label ?? ""),
3299
+ aliases: aliases ?? [],
3300
+ baseAttributes,
3301
+ registrySource: registrySource || baseEffort?.registrySource || "user",
3302
+ ...resolvedEquipment ? { equipment: resolvedEquipment } : {},
3303
+ ...createdAt ? { createdAt: String(createdAt) } : {},
3304
+ ...updatedAt ? { updatedAt: String(updatedAt) } : {},
3305
+ ...bodyText ? { body: bodyText } : {},
3306
+ ...hints && Object.keys(hints).length > 0 ? { hints } : {},
3307
+ ...derivation ? { derivation } : {}
3308
+ };
3309
+ return { effort, errors };
3310
+ }
3311
+ function effortToDocument(effort) {
3312
+ const lines = ["---"];
3313
+ lines.push(`id: ${effort.id}`);
3314
+ lines.push(`slug: ${effort.slug}`);
3315
+ lines.push(`label: ${quoteYaml(effort.label)}`);
3316
+ if (effort.aliases.length > 0) {
3317
+ lines.push("aliases:");
3318
+ for (const alias of effort.aliases) {
3319
+ lines.push(` - ${quoteYaml(alias)}`);
3320
+ }
3321
+ } else {
3322
+ lines.push("aliases: []");
3323
+ }
3324
+ lines.push("baseAttributes:");
3325
+ lines.push(` met: ${effort.baseAttributes.met}`);
3326
+ if (effort.baseAttributes.discipline) {
3327
+ lines.push(` discipline: ${effort.baseAttributes.discipline}`);
3328
+ }
3329
+ if (effort.baseAttributes.disciplineFactor !== void 0) {
3330
+ lines.push(` disciplineFactor: ${effort.baseAttributes.disciplineFactor}`);
3331
+ }
3332
+ if (effort.baseAttributes.intensityTier) {
3333
+ lines.push(` intensityTier: ${effort.baseAttributes.intensityTier}`);
3334
+ }
3335
+ lines.push(`registrySource: ${effort.registrySource}`);
3336
+ if (effort.equipment && effort.equipment.length > 0) {
3337
+ lines.push("equipment:");
3338
+ for (const slug of effort.equipment) {
3339
+ lines.push(` - ${quoteYaml(slug)}`);
3340
+ }
3341
+ }
3342
+ if (effort.derivation) {
3343
+ lines.push("derivation:");
3344
+ if (effort.derivation.parentSlug) {
3345
+ lines.push(` parentSlug: ${effort.derivation.parentSlug}`);
3346
+ }
3347
+ if (effort.derivation.coefficients && Object.keys(effort.derivation.coefficients).length > 0) {
3348
+ lines.push(" coefficients:");
3349
+ for (const [k, v] of Object.entries(effort.derivation.coefficients)) {
3350
+ lines.push(` ${k}: ${v}`);
3351
+ }
3352
+ }
3353
+ if (effort.derivation.hardOverrides && Object.keys(effort.derivation.hardOverrides).length > 0) {
3354
+ lines.push(" hardOverrides:");
3355
+ for (const [k, v] of Object.entries(effort.derivation.hardOverrides)) {
3356
+ lines.push(` ${k}: ${JSON.stringify(v)}`);
3357
+ }
3358
+ }
3359
+ }
3360
+ if (effort.createdAt) {
3361
+ lines.push(`createdAt: ${effort.createdAt}`);
3362
+ }
3363
+ if (effort.updatedAt) {
3364
+ lines.push(`updatedAt: ${effort.updatedAt}`);
3365
+ }
3366
+ if (effort.hints && Object.keys(effort.hints).length > 0) {
3367
+ lines.push("hints:");
3368
+ for (const [k, v] of Object.entries(effort.hints)) {
3369
+ lines.push(` ${k}: ${v !== null && typeof v === "object" ? JSON.stringify(v) : String(v)}`);
3370
+ }
3371
+ }
3372
+ lines.push("---");
3373
+ if (effort.body) {
3374
+ lines.push("");
3375
+ lines.push(effort.body);
3376
+ }
3377
+ return lines.join("\n");
3378
+ }
3379
+ function createDefaultEquipment() {
3380
+ return {
3381
+ id: `equipment-user-${v7()}`,
3382
+ slug: "",
3383
+ label: "",
3384
+ aliases: [],
3385
+ registrySource: "user"
3386
+ };
3387
+ }
3388
+ function documentToEquipment(doc, baseEquipment) {
3389
+ const errors = [];
3390
+ const blockMatch = doc.match(FRONTMATTER_RE);
3391
+ if (!blockMatch) {
3392
+ errors.push("Missing YAML frontmatter delimiters (---)");
3393
+ return { equipment: baseEquipment ?? createDefaultEquipment(), errors };
3394
+ }
3395
+ const { meta: meta2, body } = parseFrontmatter(doc);
3396
+ const bodyText = body.replace(/^\r?\n+/, "") || void 0;
3397
+ const type = getScalar(meta2, "type");
3398
+ const id = getScalar(meta2, "id");
3399
+ const slug = getScalar(meta2, "slug");
3400
+ const label = getScalar(meta2, "label");
3401
+ const registrySource = getScalar(meta2, "registrySource");
3402
+ const createdAt = getScalar(meta2, "createdAt");
3403
+ const updatedAt = getScalar(meta2, "updatedAt");
3404
+ const aliases = getListStrict(meta2, "aliases");
3405
+ if (type !== "equipment") errors.push("Missing or invalid discriminator: frontmatter must declare `type: equipment`");
3406
+ if (!slug || !SLUG_RE.test(String(slug))) errors.push("Invalid slug: must be a canonical slug (lowercase letters, numbers, hyphens)");
3407
+ if (!label) errors.push("Missing required field: label");
3408
+ if (aliases === void 0) {
3409
+ errors.push("Invalid aliases: must be a list (`aliases:` followed by `- item` lines)");
3410
+ } else {
3411
+ errors.push(...stringListProblems(aliases, "aliases"));
3412
+ }
3413
+ if (registrySource !== void 0 && registrySource !== "bundled" && registrySource !== "user") {
3414
+ errors.push("Invalid registrySource: must be 'bundled' or 'user'");
3415
+ }
3416
+ const equipment = {
3417
+ id: String(id ?? "") || baseEquipment?.id || `equipment-user-${v7()}`,
3418
+ slug: String(slug ?? ""),
3419
+ label: String(label ?? ""),
3420
+ aliases: aliases ?? [],
3421
+ registrySource: registrySource || baseEquipment?.registrySource || "user",
3422
+ ...createdAt ? { createdAt: String(createdAt) } : {},
3423
+ ...updatedAt ? { updatedAt: String(updatedAt) } : {},
3424
+ ...bodyText ? { body: bodyText } : {}
3425
+ };
3426
+ return { equipment, errors };
3427
+ }
3428
+ function equipmentToDocument(equipment) {
3429
+ const lines = ["---"];
3430
+ lines.push("type: equipment");
3431
+ lines.push(`id: ${equipment.id}`);
3432
+ lines.push(`slug: ${equipment.slug}`);
3433
+ lines.push(`label: ${quoteYaml(equipment.label)}`);
3434
+ if (equipment.aliases.length > 0) {
3435
+ lines.push("aliases:");
3436
+ for (const alias of equipment.aliases) {
3437
+ lines.push(` - ${quoteYaml(alias)}`);
3438
+ }
3439
+ } else {
3440
+ lines.push("aliases: []");
3441
+ }
3442
+ lines.push(`registrySource: ${equipment.registrySource}`);
3443
+ if (equipment.createdAt) {
3444
+ lines.push(`createdAt: ${equipment.createdAt}`);
3445
+ }
3446
+ if (equipment.updatedAt) {
3447
+ lines.push(`updatedAt: ${equipment.updatedAt}`);
3448
+ }
3449
+ lines.push("---");
3450
+ if (equipment.body) {
3451
+ lines.push("");
3452
+ lines.push(equipment.body);
3453
+ }
3454
+ return lines.join("\n");
3455
+ }
2841
3456
 
2842
3457
  // src/effort-registry/fuzzyMatch.ts
2843
3458
  function levenshteinDistance(a, b) {
@@ -3222,12 +3837,24 @@ var CompositeEffortRegistry = class {
3222
3837
  * Initialize the registry:
3223
3838
  * 1. Load bundled efforts into memory
3224
3839
  * 2. Load user efforts from storage adapter into memory
3840
+ *
3841
+ * An explicit `customBundled` REPLACES the bundled tier — passing an empty
3842
+ * set clears it — so a seed source removal never leaves stale bundled
3843
+ * efforts cached. The no-argument call keeps the constructor/default set.
3844
+ * Both tiers rebuild only AFTER a successful adapter load: a failed load
3845
+ * propagates (readiness must never report over stale data), and deleted
3846
+ * custom rows disappear because the user map is cleared too.
3225
3847
  */
3226
3848
  async loadBundled(customBundled) {
3227
- const seed = customBundled ?? this.initialBundled ?? bundledEfforts;
3228
- for (const effort of seed) {
3229
- this.bundledEfforts.set(effort.slug, effort);
3849
+ this.bundledEfforts.clear();
3850
+ const source = customBundled ?? this.initialBundled ?? bundledEfforts;
3851
+ for (const effort of source) {
3852
+ const prior = this.bundledEfforts.get(effort.slug);
3853
+ if (!prior || resolveEntityRank(effort) >= resolveEntityRank(prior)) {
3854
+ this.bundledEfforts.set(effort.slug, effort);
3855
+ }
3230
3856
  }
3857
+ this.userEfforts.clear();
3231
3858
  if (this.storage) {
3232
3859
  try {
3233
3860
  const userEfforts = await this.storage.load();
@@ -3257,8 +3884,8 @@ var CompositeEffortRegistry = class {
3257
3884
  if (normalizeForFuzzy(alias) === normalized) return effort;
3258
3885
  }
3259
3886
  }
3260
- for (const effort of this.bundledEfforts.values()) {
3261
- if (this.userEfforts.has(effort.slug)) continue;
3887
+ const bundled = Array.from(this.bundledEfforts.values()).filter((effort) => !this.userEfforts.has(effort.slug)).sort((a, b) => resolveEntityRank(b) - resolveEntityRank(a));
3888
+ for (const effort of bundled) {
3262
3889
  if (normalizeForFuzzy(effort.label) === normalized) return effort;
3263
3890
  for (const alias of effort.aliases) {
3264
3891
  if (normalizeForFuzzy(alias) === normalized) return effort;
@@ -3357,6 +3984,367 @@ async function addAliasToEffort(registry, targetSlug, newAlias) {
3357
3984
  await registry.upsert(updatedUserEffort);
3358
3985
  return updatedUserEffort;
3359
3986
  }
3987
+ function buildExactIndex(entities) {
3988
+ const deduped = dedupeEntitiesByPrecedence(entities);
3989
+ const byKey = /* @__PURE__ */ new Map();
3990
+ const add = (key, entity) => {
3991
+ if (!key) return;
3992
+ const existing = byKey.get(key);
3993
+ if (existing) {
3994
+ if (!existing.includes(entity)) existing.push(entity);
3995
+ } else {
3996
+ byKey.set(key, [entity]);
3997
+ }
3998
+ };
3999
+ for (const entity of deduped) {
4000
+ add(entity.slug.toLowerCase(), entity);
4001
+ add(normalizeForFuzzy(entity.label), entity);
4002
+ for (const alias of entity.aliases) add(normalizeForFuzzy(alias), entity);
4003
+ }
4004
+ return {
4005
+ byKey,
4006
+ lookup(key) {
4007
+ const direct = byKey.get(key) ?? byKey.get(normalizeForFuzzy(key));
4008
+ if (!direct || direct.length === 0) return { entity: null, ambiguous: [] };
4009
+ if (direct.length === 1) return { entity: direct[0], ambiguous: [] };
4010
+ let maxRank = -1;
4011
+ for (const candidate of direct) {
4012
+ const rank = resolveEntityRank(candidate);
4013
+ if (rank > maxRank) maxRank = rank;
4014
+ }
4015
+ const topTier = direct.filter((candidate) => resolveEntityRank(candidate) === maxRank);
4016
+ if (topTier.length === 1) return { entity: topTier[0], ambiguous: [] };
4017
+ return { entity: null, ambiguous: topTier };
4018
+ }
4019
+ };
4020
+ }
4021
+ function equipmentRef(entity) {
4022
+ return { kind: "equipment", slug: entity.slug, label: entity.label };
4023
+ }
4024
+ function dedupeRefs(refs) {
4025
+ const seen = /* @__PURE__ */ new Set();
4026
+ const result = [];
4027
+ for (const ref of refs) {
4028
+ const key = `${ref.kind}:${ref.slug}`;
4029
+ if (seen.has(key)) continue;
4030
+ seen.add(key);
4031
+ result.push(ref);
4032
+ }
4033
+ return result;
4034
+ }
4035
+ function createReferenceIndex(catalog, materialize) {
4036
+ const dedupedEquipment = dedupeEntitiesByPrecedence(catalog.equipment ?? []);
4037
+ const efforts = buildExactIndex(catalog.efforts ?? []);
4038
+ const equipment = buildExactIndex(dedupedEquipment);
4039
+ 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);
4040
+ const materializer = materialize ?? ((effort) => ({
4041
+ effort,
4042
+ definition: effort,
4043
+ slug: effort.slug,
4044
+ label: effort.label,
4045
+ met: effort.baseAttributes.met,
4046
+ baseAttributes: effort.baseAttributes,
4047
+ discipline: effort.baseAttributes.discipline,
4048
+ disciplineFactor: effort.baseAttributes.disciplineFactor ?? 1,
4049
+ intensityTier: effort.baseAttributes.intensityTier,
4050
+ modifiers: {},
4051
+ registrySource: effort.registrySource,
4052
+ resolvedFrom: effort.registrySource === "user" ? "user" : effort.registrySource === "bundled" ? "bundled" : "default",
4053
+ isEstimated: false
4054
+ }));
4055
+ const resolveEffortExact = (key) => {
4056
+ const found = efforts.lookup(key.trim());
4057
+ if (found.entity) return { entity: found.entity };
4058
+ if (found.ambiguous.length > 0) {
4059
+ return {
4060
+ entity: null,
4061
+ diagnostic: {
4062
+ reason: "ambiguous-effort",
4063
+ message: `"${key}" matches multiple efforts.`,
4064
+ token: key,
4065
+ candidates: found.ambiguous.map((e) => e.slug)
4066
+ }
4067
+ };
4068
+ }
4069
+ return {
4070
+ entity: null,
4071
+ diagnostic: { reason: "unknown-effort", message: `Unknown effort "${key}".`, token: key }
4072
+ };
4073
+ };
4074
+ const resolveEquipmentValues = (values) => {
4075
+ const refs = [];
4076
+ const diagnostics = [];
4077
+ for (const value of values) {
4078
+ if (typeof value !== "string" || !value.trim()) {
4079
+ diagnostics.push({
4080
+ reason: "invalid-explicit",
4081
+ message: `Equipment reference must be a slug string, got ${JSON.stringify(value) ?? "null"}.`,
4082
+ token: String(value)
4083
+ });
4084
+ continue;
4085
+ }
4086
+ const found = equipment.lookup(value.trim());
4087
+ if (found.entity) {
4088
+ refs.push(equipmentRef(found.entity));
4089
+ } else if (found.ambiguous.length > 0) {
4090
+ diagnostics.push({
4091
+ reason: "ambiguous-equipment",
4092
+ message: `"${value}" matches multiple equipment entries.`,
4093
+ token: value,
4094
+ candidates: found.ambiguous.map((e) => e.slug)
4095
+ });
4096
+ } else {
4097
+ diagnostics.push({
4098
+ reason: "invalid-explicit",
4099
+ message: `Unknown equipment "${value}".`,
4100
+ token: value
4101
+ });
4102
+ }
4103
+ }
4104
+ return { refs: dedupeRefs(refs), diagnostics };
4105
+ };
4106
+ const index = {
4107
+ resolveEffort(slugOrAlias) {
4108
+ return efforts.lookup(slugOrAlias).entity;
4109
+ },
4110
+ resolveEquipment(slugOrAlias) {
4111
+ return equipment.lookup(slugOrAlias).entity;
4112
+ },
4113
+ materialize(effort) {
4114
+ return materializer(effort);
4115
+ },
4116
+ resolve(label, defaults) {
4117
+ const source = (label ?? "").trim();
4118
+ const diagnostics = [];
4119
+ const push = (diagnostic) => {
4120
+ if (diagnostic) diagnostics.push(diagnostic);
4121
+ };
4122
+ let matched = "none";
4123
+ let effortEntity = null;
4124
+ let remaining = "";
4125
+ let equipmentRefs = [];
4126
+ let equipmentAuthored = false;
4127
+ const fillEquipment = () => {
4128
+ if (equipmentRefs.length > 0 || equipmentAuthored) return;
4129
+ if (effortEntity === null) return;
4130
+ if (defaults?.equipment?.length) {
4131
+ const resolvedDefaults = resolveEquipmentValues(defaults.equipment);
4132
+ equipmentRefs = resolvedDefaults.refs;
4133
+ for (const diagnostic of resolvedDefaults.diagnostics) {
4134
+ push({ ...diagnostic, reason: "unknown-equipment" });
4135
+ }
4136
+ if (equipmentRefs.length > 0) return;
4137
+ }
4138
+ if (effortEntity.equipment?.length) {
4139
+ const resolvedDefEquipment = resolveEquipmentValues(effortEntity.equipment);
4140
+ equipmentRefs = resolvedDefEquipment.refs;
4141
+ for (const diagnostic of resolvedDefEquipment.diagnostics) {
4142
+ push({ ...diagnostic, reason: "unknown-equipment" });
4143
+ }
4144
+ }
4145
+ };
4146
+ if (!source) {
4147
+ if (defaults?.effort) {
4148
+ const found = resolveEffortExact(defaults.effort);
4149
+ if (found.entity) {
4150
+ effortEntity = found.entity;
4151
+ matched = "default";
4152
+ } else {
4153
+ push({
4154
+ reason: found.diagnostic?.reason === "ambiguous-effort" ? "ambiguous-effort" : "unknown-effort",
4155
+ message: `Default effort "${defaults.effort}" is not in the catalog.`,
4156
+ token: defaults.effort,
4157
+ candidates: found.diagnostic?.candidates
4158
+ });
4159
+ }
4160
+ }
4161
+ fillEquipment();
4162
+ return {
4163
+ matched,
4164
+ label: source,
4165
+ effort: effortEntity ? materializer(effortEntity) : null,
4166
+ effortRef: effortEntity ? { kind: "effort", slug: effortEntity.slug, label: effortEntity.label } : null,
4167
+ equipment: equipmentRefs,
4168
+ remaining: "",
4169
+ diagnostics
4170
+ };
4171
+ }
4172
+ if (source.startsWith("{")) {
4173
+ let parsed;
4174
+ try {
4175
+ parsed = JSON.parse(source);
4176
+ } catch {
4177
+ return {
4178
+ matched: "none",
4179
+ label: source,
4180
+ effort: null,
4181
+ effortRef: null,
4182
+ equipment: [],
4183
+ remaining: "",
4184
+ diagnostics: [{
4185
+ reason: "malformed-json",
4186
+ message: "Reference object is not valid JSON.",
4187
+ token: source
4188
+ }]
4189
+ };
4190
+ }
4191
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed) && ("effort" in parsed || "equipment" in parsed)) {
4192
+ matched = "explicit";
4193
+ const obj = parsed;
4194
+ if (obj.effort !== void 0) {
4195
+ if (typeof obj.effort === "string" && obj.effort.trim()) {
4196
+ const found = resolveEffortExact(obj.effort);
4197
+ effortEntity = found.entity;
4198
+ if (found.diagnostic) {
4199
+ push(found.diagnostic.reason === "ambiguous-effort" ? found.diagnostic : { ...found.diagnostic, reason: "invalid-explicit", message: `Explicit effort "${obj.effort}" is not in the catalog.` });
4200
+ }
4201
+ } else {
4202
+ push({
4203
+ reason: "invalid-explicit",
4204
+ message: 'Explicit "effort" must be a slug string.',
4205
+ token: String(obj.effort)
4206
+ });
4207
+ }
4208
+ }
4209
+ if (obj.equipment !== void 0) {
4210
+ equipmentAuthored = true;
4211
+ const values = Array.isArray(obj.equipment) ? obj.equipment : [obj.equipment];
4212
+ const resolvedEquipment = resolveEquipmentValues(values);
4213
+ equipmentRefs = resolvedEquipment.refs;
4214
+ diagnostics.push(...resolvedEquipment.diagnostics);
4215
+ if (effortEntity?.equipment?.length) {
4216
+ const definitionSlugs = new Set(effortEntity.equipment);
4217
+ const authoredSlugs = equipmentRefs.map((ref) => ref.slug);
4218
+ if (authoredSlugs.length > 0 && authoredSlugs.some((slug) => !definitionSlugs.has(slug))) {
4219
+ push({
4220
+ reason: "conflict",
4221
+ message: `Explicit equipment [${authoredSlugs.join(", ")}] contradicts effort "${effortEntity.slug}" definition equipment [${effortEntity.equipment.join(", ")}].`,
4222
+ token: source
4223
+ });
4224
+ }
4225
+ }
4226
+ }
4227
+ fillEquipment();
4228
+ return {
4229
+ matched,
4230
+ label: source,
4231
+ effort: effortEntity ? materializer(effortEntity) : null,
4232
+ effortRef: effortEntity ? { kind: "effort", slug: effortEntity.slug, label: effortEntity.label } : null,
4233
+ equipment: equipmentRefs,
4234
+ remaining: "",
4235
+ diagnostics
4236
+ };
4237
+ }
4238
+ }
4239
+ const lowerSource = source.toLowerCase();
4240
+ for (const candidate of prefixCandidates) {
4241
+ if (!lowerSource.startsWith(candidate.lower)) continue;
4242
+ const after = source.slice(candidate.lower.length);
4243
+ if (after && !/^\s/.test(after)) continue;
4244
+ const candidateRemaining = after.trim();
4245
+ if (!candidateRemaining) continue;
4246
+ const equipmentFound = equipment.lookup(candidate.lower);
4247
+ if (equipmentFound.ambiguous.length > 0) {
4248
+ push({
4249
+ reason: "ambiguous-equipment",
4250
+ message: `"${candidate.lower}" matches multiple equipment entries.`,
4251
+ token: source,
4252
+ candidates: equipmentFound.ambiguous.map((e) => e.slug)
4253
+ });
4254
+ return {
4255
+ matched: "none",
4256
+ label: source,
4257
+ effort: null,
4258
+ effortRef: null,
4259
+ equipment: [],
4260
+ remaining: "",
4261
+ diagnostics
4262
+ };
4263
+ }
4264
+ if (!equipmentFound.entity) continue;
4265
+ const effortFound = resolveEffortExact(candidateRemaining);
4266
+ if (effortFound.entity) {
4267
+ matched = "split";
4268
+ effortEntity = effortFound.entity;
4269
+ equipmentRefs = [equipmentRef(equipmentFound.entity)];
4270
+ equipmentAuthored = true;
4271
+ remaining = candidateRemaining;
4272
+ break;
4273
+ }
4274
+ if (effortFound.diagnostic?.reason === "ambiguous-effort") {
4275
+ push(effortFound.diagnostic);
4276
+ return {
4277
+ matched: "none",
4278
+ label: source,
4279
+ effort: null,
4280
+ effortRef: null,
4281
+ equipment: [],
4282
+ remaining: "",
4283
+ diagnostics
4284
+ };
4285
+ }
4286
+ }
4287
+ if (matched === "none") {
4288
+ const found = resolveEffortExact(source);
4289
+ if (found.entity) {
4290
+ effortEntity = found.entity;
4291
+ matched = "whole";
4292
+ } else if (found.diagnostic?.reason === "ambiguous-effort") {
4293
+ push(found.diagnostic);
4294
+ return {
4295
+ matched: "none",
4296
+ label: source,
4297
+ effort: null,
4298
+ effortRef: null,
4299
+ equipment: [],
4300
+ remaining: "",
4301
+ diagnostics
4302
+ };
4303
+ } else if (found.diagnostic) {
4304
+ push(found.diagnostic);
4305
+ }
4306
+ }
4307
+ fillEquipment();
4308
+ return {
4309
+ matched,
4310
+ label: source,
4311
+ effort: effortEntity ? materializer(effortEntity) : null,
4312
+ effortRef: effortEntity ? { kind: "effort", slug: effortEntity.slug, label: effortEntity.label } : null,
4313
+ equipment: equipmentRefs,
4314
+ remaining,
4315
+ diagnostics
4316
+ };
4317
+ }
4318
+ };
4319
+ return index;
4320
+ }
4321
+ function extractStatementRefs(statements) {
4322
+ const efforts = [];
4323
+ const equipment = [];
4324
+ const seenEffort = /* @__PURE__ */ new Set();
4325
+ const seenEquipment = /* @__PURE__ */ new Set();
4326
+ const pushUnique = (list, seen, value) => {
4327
+ if (!value) return;
4328
+ if (seen.has(value)) return;
4329
+ seen.add(value);
4330
+ list.push(value);
4331
+ };
4332
+ for (const statement of statements ?? []) {
4333
+ const metrics = Array.isArray(statement.metrics) ? statement.metrics : statement.metrics?.toArray?.() ?? [];
4334
+ for (const metric of metrics) {
4335
+ const refs = getReferenceMetadata(metric.metadata)?.refs;
4336
+ if (!refs) continue;
4337
+ for (const ref of refs) {
4338
+ if (ref.kind === "effort") pushUnique(efforts, seenEffort, ref.slug);
4339
+ if (ref.kind === "equipment") pushUnique(equipment, seenEquipment, ref.slug);
4340
+ }
4341
+ }
4342
+ }
4343
+ return { efforts, equipment };
4344
+ }
4345
+ function slugifyRefLabel(label) {
4346
+ return normalizeForFuzzy(label).replace(/\s+/g, "-");
4347
+ }
3360
4348
 
3361
4349
  // src/runtime/contracts/IRuntimeOptions.ts
3362
4350
  var DEFAULT_RUNTIME_OPTIONS = {
@@ -6663,14 +7651,12 @@ function resolveDominantOrigin(origins) {
6663
7651
  if (origins.every((o) => o === "compiler")) return "compiler";
6664
7652
  return "analyzed";
6665
7653
  }
6666
-
6667
- // src/runtime/compiler/EffortEnrichmentPass.ts
6668
7654
  var logTrace = (message) => console.log(message);
6669
7655
  function applyEffortEnrichment(block, options) {
6670
- const { resolver, overwrite = false, debug = false } = options;
6671
- tryEnrichBlock(block, resolver, overwrite, debug);
7656
+ const { resolver, references, overwrite = false, debug = false } = options;
7657
+ tryEnrichBlock(block, resolver, references, overwrite, debug);
6672
7658
  }
6673
- function tryEnrichBlock(block, resolver, overwrite, debug) {
7659
+ function tryEnrichBlock(block, resolver, references, overwrite, debug) {
6674
7660
  const normalizedBlockType = block.blockType?.toLowerCase();
6675
7661
  if (normalizedBlockType !== "effort" && normalizedBlockType !== "exercise") {
6676
7662
  return;
@@ -6688,7 +7674,32 @@ function tryEnrichBlock(block, resolver, overwrite, debug) {
6688
7674
  return;
6689
7675
  }
6690
7676
  try {
6691
- const resolved = resolver.resolveEffort(label);
7677
+ let resolved;
7678
+ let matchedKind = "none";
7679
+ let equipmentRefs = [];
7680
+ let diagnostics = [];
7681
+ if (references) {
7682
+ const resolution = references.resolve(label);
7683
+ resolved = resolution.effort ?? void 0;
7684
+ matchedKind = resolution.matched;
7685
+ equipmentRefs = resolution.equipment;
7686
+ diagnostics = resolution.diagnostics;
7687
+ } else {
7688
+ resolved = resolver.resolveEffort(label);
7689
+ }
7690
+ if (!resolved) {
7691
+ if (diagnostics.length > 0) {
7692
+ block.pushMemory(new MemoryLocation("metric:tracked", [{
7693
+ type: MetricType.Hint,
7694
+ value: "reference.unresolved",
7695
+ image: label,
7696
+ origin: "compiler",
7697
+ metadata: { [REFERENCE_METADATA_KEY]: { matched: "none", source: label, diagnostics } }
7698
+ }]));
7699
+ }
7700
+ if (debug) logTrace(`[EffortEnrichment] Unresolved effort: ${label}`);
7701
+ return;
7702
+ }
6692
7703
  const effortHints = resolved.effort.hints;
6693
7704
  if (effortHints && Object.keys(effortHints).length > 0) {
6694
7705
  block.mergeHints(effortHints);
@@ -6701,7 +7712,8 @@ function tryEnrichBlock(block, resolver, overwrite, debug) {
6701
7712
  image: resolved.label,
6702
7713
  value: resolved,
6703
7714
  origin: "compiler",
6704
- timestamp: /* @__PURE__ */ new Date()
7715
+ timestamp: /* @__PURE__ */ new Date(),
7716
+ ...equipmentRefs.length > 0 || diagnostics.length > 0 ? { metadata: { [REFERENCE_METADATA_KEY]: { matched: matchedKind, source: label, refs: [{ kind: "effort", slug: resolved.slug, label: resolved.label }, ...equipmentRefs], ...diagnostics.length > 0 ? { diagnostics } : {} } } } : {}
6705
7717
  };
6706
7718
  const privateMetrics = block.getMetricMemoryByVisibility("private");
6707
7719
  const existingLoc = privateMetrics.find(
@@ -6716,10 +7728,31 @@ function tryEnrichBlock(block, resolver, overwrite, debug) {
6716
7728
  const location = new MemoryLocation("metric:tracked", [effortDataMetric]);
6717
7729
  block.pushMemory(location);
6718
7730
  }
7731
+ if (equipmentRefs.length > 0) {
7732
+ pushPrivateMetrics(block, MetricType.Equipment, [
7733
+ new EquipmentMetric(equipmentRefs.map((ref) => ref.slug), {
7734
+ refs: equipmentRefs,
7735
+ source: label,
7736
+ origin: "compiler"
7737
+ })
7738
+ ], MetricType.Equipment, overwrite);
7739
+ }
6719
7740
  } catch (err) {
6720
7741
  console.error(`[EffortEnrichment] Failed to resolve effort "${label}":`, err);
6721
7742
  }
6722
7743
  }
7744
+ function pushPrivateMetrics(block, markerType, metrics, replaceType, overwrite) {
7745
+ const privateMetrics = block.getMetricMemoryByVisibility("private");
7746
+ const existingLoc = privateMetrics.find(
7747
+ (loc) => loc.metrics.toArray().some((m) => m.type === markerType)
7748
+ );
7749
+ if (existingLoc && overwrite) {
7750
+ const filtered = existingLoc.metrics.toArray().filter((m) => m.type !== replaceType);
7751
+ existingLoc.update([...filtered, ...metrics]);
7752
+ } else if (!existingLoc) {
7753
+ block.pushMemory(new MemoryLocation("metric:tracked", metrics));
7754
+ }
7755
+ }
6723
7756
 
6724
7757
  // src/runtime/actions/stack/CompileAndPushBlockAction.ts
6725
7758
  var CompileAndPushBlockAction = class {
@@ -6745,7 +7778,8 @@ var CompileAndPushBlockAction = class {
6745
7778
  }
6746
7779
  if (runtime.analyticsContext?.effortResolver) {
6747
7780
  applyEffortEnrichment(compiledBlock, {
6748
- resolver: runtime.analyticsContext.effortResolver
7781
+ resolver: runtime.analyticsContext.effortResolver,
7782
+ references: runtime.analyticsContext.references
6749
7783
  });
6750
7784
  }
6751
7785
  return [new PushBlockAction(compiledBlock, this.options)];
@@ -10771,7 +11805,8 @@ function createAnalyticsEngineForBlock(block, options) {
10771
11805
  effortResolver = new EffortResolver(registry);
10772
11806
  }
10773
11807
  const analyticsContext = {
10774
- effortResolver
11808
+ effortResolver,
11809
+ ...options?.references ? { references: options.references } : {}
10775
11810
  };
10776
11811
  const context = {
10777
11812
  dialect,
@@ -13671,6 +14706,6 @@ function compareStatement(expectations, actual, mode) {
13671
14706
  return diffs;
13672
14707
  }
13673
14708
 
13674
- export { ABSENT, AGGREGATE_BUILTINS, AUTHORITATIVE_CASTS, AbortSessionAction, AmrapLogicStrategy, AnalyticsEngine, AnalyticsTransformer, BUILTIN_CALCS, BUNDLED_EFFORT_COUNT, BehaviorContext, BlockBuilder, BlockContext, ButtonBehavior, CONSUMED_HINTS, CONSUMED_HINT_KEYS, CONTEXT_ATOMS, CalcEngine, CalcEvalError, CalcParseError, CalcRegistrationError, CalcUnitError, CalculateBlockProcessor, CalculationRegistry, CardioDialect, ChildSelectionBehavior, ChildrenStrategy, ChoiceGroupMetric, ClearChildrenAction, ClimbDialect, ClimbMetricType, CompileAndPushBlockAction, CompletionTimestampBehavior, CompositeEffortRegistry, CountdownTimerBehavior, CountupTimerBehavior, CrossFitDialect, DAY, DEFAULT_RESOLVER_OPTIONS, DEFAULT_RUNTIME_OPTIONS, DEFAULT_UNRESOLVED_EFFORT_MET, DIM_COUNT, DIM_ENERGY, DIM_LENGTH, DIM_MASS, DIM_TIME, DIM_ZERO, DISCIPLINE_FACTORS, DialectStack, EFFORT_DATA_METRIC_TYPE, EFFORT_DISCIPLINES, EFFORT_REGISTRY_ORIGINS, EffortBlock, EffortFallbackStrategy, EffortResolver, EmitEventAction, EmitSystemOutputAction, ErrorAction, EventBus, ExecutionContext, ExitBehavior, FixtureSyntaxError, GenericGroupStrategy, GenericLoopStrategy, GenericTimerStrategy, HabitsDialect, INTENSITY_TIERS, InMemoryEffortRegistry, IntervalLogicStrategy, JitCompiler, LabelingBehavior, LocalEventProvider, LocalRuntimeSubscription, LookupRegistry, MdTimerRuntime, MemoryLocation, MemoryTypeEnum, MetricPromotionBehavior, NAMED_COMPOUNDS, NextAction, NextEvent, NextEventHandler, OutputEmitter, PREDEFINED_SOUNDS, PRODUCTION_STRATEGIES, PlaySoundAction, PopBlockAction, PushBlockAction, PushRestBlockAction, REST_EXCLUSION, RPE_LABELS, RegisterEventHandlerAction, Registry, ReportOutputBehavior, ReportOutputStrategy, RestBlock, RoundsMetric, RuntimeAdapter, RuntimeBlock, RuntimeBuilder, RuntimeClock, RuntimeFactory, RuntimeLogger, RuntimeMemory, RuntimeObservers, RuntimeStack, STANDARD_UNITS, STORE_CALCS, STREAM_ATOMS, ScriptRuntime, SessionRootBlock, SessionRootStrategy, SnapshotClock, SoundCueBehavior, SoundStrategy, SpanTrackingBehavior, StandardAnalyticsProfile, StartSessionAction, StartWorkoutAction, SubscriptionManager, ThrowError, ThrowErrorAction, TimeSpan, TwoPassEffortResolutionProcess, TypedMemoryReference, UNITS, UnitRegistry, UnitSet, UnitsDialect, UnregisterEventHandlerAction, UpdateNextPreviewAction, VISIBILITY_ICONS, VISIBILITY_LABELS, WaitingToStartBlock, WaitingToStartInjectorBehavior, WhiteboardScript, WodDialect, YogaDialect, addAliasToEffort, addDim, buildStaticLookups, buildTooltip, bundledEfforts, calculateElapsed, canonicalizeType, checkCalcLine, classifyStatements, closeCurrentSpan, collapseUnresolvedChoices, commonFixtureSet, compareStatement, compileLineForm, composeDivUnit, composeMulUnit, compoundName, computeColumnLabel, computeLabel, computeWorkloadRollups, convertScalar, createAnalyticsEngineForBlock, createCalcEngine, createCalcStack, createCompiler, createDialectStack, createDisciplinesTable, createEffortTable, createLineLocator, createPolicy as createMetricPresentationPolicy, createMockClock, createParser, createProfileTable, createRpeLabelsTable, dailySessionLoads, dayBucket, dialectRegistry, dialectStack, diffStatement, dimEquals, disciplineFactorFor, evaluate, evaluateCalculateDefinitions, extractEffortData, extractMetrics, extractStatements, extractStatementsRaw, extractSyntaxFacts, filterTagsByVisibility, findBestFuzzyMatch, findUnresolvedChoices, fixtureBackSquat, fixtureBurpee, fixturePlank, fixtureRowing, fixtureRunning, fixtureUserCustom, fixtureUserOverride, formatDim, formatDuration, metricsToLabel as fragmentsToLabel, fullFixtureSet, fuseUnits, fuseUnitsInMetrics, getAnalyticsFromLogs, getAnalyticsFromRuntime, getHints, getMetricVisibility, getUnit, hasHint, hintMetric, hintsToContainer, inferDim, isCastSubscription, isChoiceResolved, isDimensionless, isEffortDiscipline, isFragmentTag, isTimeLikeMetric, isWithinThreshold, levenshteinDistance, metricPresentation, mutateTimerSpans, normalizeForFuzzy, num, openSpan, outputNodeId, outputNodeIds, parseCalcLine, parseCalculateBlock, parseExpression, parseMetricLine, parseScript, printCalculation, readTimer, realtimeProcessorRegistry, renderMetric, resolveDominantOrigin, sharedParser, startSpan, str, strategyRegistry, subDim, summaryProcessorRegistry, toStoredOutputStatement, truthy, validateOutputUnit, parser as whiteboardParser, whiteboardScript, whiteboardScriptLanguage, writeChoiceSelection };
14709
+ export { ABSENT, AGGREGATE_BUILTINS, AUTHORITATIVE_CASTS, AbortSessionAction, AmrapLogicStrategy, AnalyticsEngine, AnalyticsTransformer, BUILTIN_CALCS, BUNDLED_EFFORT_COUNT, BehaviorContext, BlockBuilder, BlockContext, ButtonBehavior, CATALOG_PRECEDENCE_RANK, CONSUMED_HINTS, CONSUMED_HINT_KEYS, CONTEXT_ATOMS, CalcEngine, CalcEvalError, CalcParseError, CalcRegistrationError, CalcUnitError, CalculateBlockProcessor, CalculationRegistry, CardioDialect, ChildSelectionBehavior, ChildrenStrategy, ChoiceGroupMetric, ClearChildrenAction, ClimbDialect, ClimbMetricType, CompileAndPushBlockAction, CompletionTimestampBehavior, CompositeEffortRegistry, CountdownTimerBehavior, CountupTimerBehavior, CrossFitDialect, DAY, DEFAULT_RESOLVER_OPTIONS, DEFAULT_RUNTIME_OPTIONS, DEFAULT_UNRESOLVED_EFFORT_MET, DIM_COUNT, DIM_ENERGY, DIM_LENGTH, DIM_MASS, DIM_TIME, DIM_ZERO, DISCIPLINE_FACTORS, DialectStack, EFFORT_DATA_METRIC_TYPE, EFFORT_DISCIPLINES, EFFORT_REGISTRY_ORIGINS, EffortBlock, EffortFallbackStrategy, EffortResolver, EmitEventAction, EmitSystemOutputAction, ErrorAction, EventBus, ExecutionContext, ExitBehavior, FixtureSyntaxError, GenericGroupStrategy, GenericLoopStrategy, GenericTimerStrategy, HabitsDialect, INTENSITY_TIERS, InMemoryEffortRegistry, IntervalLogicStrategy, JitCompiler, LabelingBehavior, LocalEventProvider, LocalRuntimeSubscription, LookupRegistry, MdTimerRuntime, MemoryLocation, MemoryTypeEnum, MetricPromotionBehavior, NAMED_COMPOUNDS, NextAction, NextEvent, NextEventHandler, OutputEmitter, PREDEFINED_SOUNDS, PRODUCTION_STRATEGIES, PlaySoundAction, PopBlockAction, PushBlockAction, PushRestBlockAction, REST_EXCLUSION, RPE_LABELS, RegisterEventHandlerAction, Registry, ReportOutputBehavior, ReportOutputStrategy, RestBlock, RoundsMetric, RuntimeAdapter, RuntimeBlock, RuntimeBuilder, RuntimeClock, RuntimeFactory, RuntimeLogger, RuntimeMemory, RuntimeObservers, RuntimeStack, STANDARD_UNITS, STORE_CALCS, STREAM_ATOMS, ScriptRuntime, SessionRootBlock, SessionRootStrategy, SnapshotClock, SoundCueBehavior, SoundStrategy, SpanTrackingBehavior, StandardAnalyticsProfile, StartSessionAction, StartWorkoutAction, SubscriptionManager, ThrowError, ThrowErrorAction, TimeSpan, TwoPassEffortResolutionProcess, TypedMemoryReference, UNITS, UnitRegistry, UnitSet, UnitsDialect, UnregisterEventHandlerAction, UpdateNextPreviewAction, VISIBILITY_ICONS, VISIBILITY_LABELS, WaitingToStartBlock, WaitingToStartInjectorBehavior, WhiteboardScript, WodDialect, YogaDialect, addAliasToEffort, addDim, buildStaticLookups, buildTooltip, bundledEfforts, calculateElapsed, canonicalizeType, checkCalcLine, classifyStatements, closeCurrentSpan, collapseUnresolvedChoices, commonFixtureSet, compareStatement, compileLineForm, composeDivUnit, composeMulUnit, compoundName, computeColumnLabel, computeLabel, computeWorkloadRollups, convertScalar, createAnalyticsEngineForBlock, createCalcEngine, createCalcStack, createCompiler, createDialectStack, createDisciplinesTable, createEffortTable, createLineLocator, createPolicy as createMetricPresentationPolicy, createMockClock, createParser, createProfileTable, createReferenceIndex, createRpeLabelsTable, dailySessionLoads, dayBucket, dedupeEntitiesByPrecedence, dialectRegistry, dialectStack, diffStatement, dimEquals, disciplineFactorFor, documentToEffort, documentToEquipment, effortToDocument, equipmentToDocument, evaluate, evaluateCalculateDefinitions, extractEffortData, extractMetrics, extractStatementRefs, extractStatements, extractStatementsRaw, extractSyntaxFacts, filterTagsByVisibility, findBestFuzzyMatch, findUnresolvedChoices, fixtureBackSquat, fixtureBurpee, fixturePlank, fixtureRowing, fixtureRunning, fixtureUserCustom, fixtureUserOverride, formatDim, formatDuration, metricsToLabel as fragmentsToLabel, fullFixtureSet, fuseUnits, fuseUnitsInMetrics, getAnalyticsFromLogs, getAnalyticsFromRuntime, getHints, getMetricVisibility, getUnit, hasHint, hintMetric, hintsToContainer, inferDim, isCastSubscription, isChoiceResolved, isDimensionless, isEffortDiscipline, isFragmentTag, isTimeLikeMetric, isWithinThreshold, levenshteinDistance, metricPresentation, mutateTimerSpans, normalizeForFuzzy, num, openSpan, outputNodeId, outputNodeIds, parseCalcLine, parseCalculateBlock, parseEffortFile, parseEquipmentFile, parseExpression, parseMetricLine, parseScript, printCalculation, readNestedSection, readTimer, realtimeProcessorRegistry, renderMetric, resolveDominantOrigin, resolveEntityRank, resolveEntityTier, resolveSourceRank, resolveSourceTier, slugifyRefLabel, startSpan, str, strategyRegistry, subDim, summaryProcessorRegistry, toStoredOutputStatement, truthy, validateOutputUnit, parser as whiteboardParser, whiteboardScript, whiteboardScriptLanguage, writeChoiceSelection };
13675
14710
  //# sourceMappingURL=index.mjs.map
13676
14711
  //# sourceMappingURL=index.mjs.map