@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/{browserRuntimeNow-CoR7dCNU.d.cts → browserRuntimeNow-M4hQ_DTl.d.cts} +205 -47
- package/dist/{browserRuntimeNow-CoR7dCNU.d.ts → browserRuntimeNow-M4hQ_DTl.d.ts} +205 -47
- package/dist/index.cjs +1083 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +120 -10
- package/dist/index.d.ts +120 -10
- package/dist/index.mjs +1069 -34
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.cts +2 -2
- package/dist/react.d.ts +2 -2
- package/package.json +2 -2
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1462
|
-
statement.
|
|
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,
|
|
1681
|
+
metrics: new PropertyMetric(pair.key, rawValue, {
|
|
1544
1682
|
type: metricType,
|
|
1545
|
-
image: `${pair.key}: ${
|
|
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
|
|
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
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
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
|
-
|
|
3261
|
-
|
|
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
|
-
|
|
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,
|
|
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
|