@bitcobblers/wod-wiki-tools 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.
Files changed (58) hide show
  1. package/dist/bin/wod.cjs +224 -231
  2. package/dist/bin/wod.cjs.map +1 -1
  3. package/dist/bin/wod.mjs +4 -4
  4. package/dist/{chunk-NATJMTAW.mjs → chunk-IEW74LUO.mjs} +109 -187
  5. package/dist/chunk-IEW74LUO.mjs.map +1 -0
  6. package/dist/{chunk-CYKNTEGY.mjs → chunk-IWT7U3WX.mjs} +8 -3
  7. package/dist/chunk-IWT7U3WX.mjs.map +1 -0
  8. package/dist/{chunk-MA6BBTWC.mjs → chunk-RWNL6APX.mjs} +3 -3
  9. package/dist/{chunk-MA6BBTWC.mjs.map → chunk-RWNL6APX.mjs.map} +1 -1
  10. package/dist/{chunk-GBBJBDLS.mjs → chunk-UNJ6OZVO.mjs} +116 -42
  11. package/dist/chunk-UNJ6OZVO.mjs.map +1 -0
  12. package/dist/{chunk-SQAI6B4P.mjs → chunk-YO256WSO.mjs} +3 -3
  13. package/dist/chunk-YO256WSO.mjs.map +1 -0
  14. package/dist/compiler-E6HYL477.mjs +9 -0
  15. package/dist/{compiler-B77PITYH.mjs.map → compiler-E6HYL477.mjs.map} +1 -1
  16. package/dist/index.cjs +224 -231
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.mjs +5 -5
  19. package/dist/{scaffoldCatalog-LQOUCJ4X.mjs → scaffoldCatalog-TLYSPOQK.mjs} +3 -3
  20. package/dist/{scaffoldCatalog-LQOUCJ4X.mjs.map → scaffoldCatalog-TLYSPOQK.mjs.map} +1 -1
  21. package/dist/{server-JXECWS4A.mjs → server-2DVBA35J.mjs} +4 -4
  22. package/dist/{server-JXECWS4A.mjs.map → server-2DVBA35J.mjs.map} +1 -1
  23. package/dist/web/playground/assets/main-BUcjhK2w.css +2 -0
  24. package/dist/web/playground/assets/main-CT3-Winw.js +194 -0
  25. package/dist/web/playground/assets/{statementBuilderFlow-BL8OKlcb.js → statementBuilderFlow-CtxxN_h0.js} +2 -2
  26. package/dist/web/playground/assets/{vendor-CTANVdPW.js → vendor-DaJxNU_W.js} +19 -19
  27. package/dist/web/playground/assets/{vendor-codemirror-BlXDdJID.js → vendor-codemirror-BtDM22HA.js} +8 -8
  28. package/dist/web/playground/index.html +4 -4
  29. package/dist/web/playground/seed/core/chunks/canvas.3c75c4cb.json +1 -0
  30. package/dist/web/playground/seed/core/chunks/efforts.1422c1a6.json +1 -0
  31. package/dist/web/playground/seed/core/chunks/equipment.c4fb1d64.json +1 -0
  32. package/dist/web/playground/seed/core/chunks/{template.e5e0e82d.json → template.0120276c.json} +1 -1
  33. package/dist/web/playground/seed/core/manifest.json +24 -10
  34. package/dist/web/playground/seed/core/report.html +15 -13
  35. package/dist/web/server/assets/main-BUcjhK2w.css +2 -0
  36. package/dist/web/server/assets/main-C5jPewkG.js +193 -0
  37. package/dist/web/server/assets/{statementBuilderFlow-xqsK_3T8.js → statementBuilderFlow-Df5Scxwh.js} +2 -2
  38. package/dist/web/server/assets/{vendor-CTANVdPW.js → vendor-DaJxNU_W.js} +19 -19
  39. package/dist/web/server/assets/{vendor-codemirror-BlXDdJID.js → vendor-codemirror-BtDM22HA.js} +8 -8
  40. package/dist/web/server/index.html +4 -4
  41. package/dist/web/server/seed/core/chunks/canvas.3c75c4cb.json +1 -0
  42. package/dist/web/server/seed/core/chunks/efforts.1422c1a6.json +1 -0
  43. package/dist/web/server/seed/core/chunks/equipment.c4fb1d64.json +1 -0
  44. package/dist/web/server/seed/core/chunks/{template.e5e0e82d.json → template.0120276c.json} +1 -1
  45. package/dist/web/server/seed/core/manifest.json +24 -10
  46. package/dist/web/server/seed/core/report.html +15 -13
  47. package/package.json +6 -6
  48. package/dist/chunk-CYKNTEGY.mjs.map +0 -1
  49. package/dist/chunk-GBBJBDLS.mjs.map +0 -1
  50. package/dist/chunk-NATJMTAW.mjs.map +0 -1
  51. package/dist/chunk-SQAI6B4P.mjs.map +0 -1
  52. package/dist/compiler-B77PITYH.mjs +0 -9
  53. package/dist/web/playground/assets/main-CFDrblxn.css +0 -2
  54. package/dist/web/playground/assets/main-OGIV1avF.js +0 -188
  55. package/dist/web/playground/seed/core/chunks/canvas.a2e32d8c.json +0 -1
  56. package/dist/web/server/assets/main-BdUrlkaF.js +0 -187
  57. package/dist/web/server/assets/main-CFDrblxn.css +0 -2
  58. package/dist/web/server/seed/core/chunks/canvas.a2e32d8c.json +0 -1
package/dist/bin/wod.cjs CHANGED
@@ -267,6 +267,11 @@ async function getEffortBySlug(db, slug) {
267
267
  const efforts = await allRows(sql, db, "efforts");
268
268
  return efforts.find((e) => e.slug === slug);
269
269
  }
270
+ async function getEquipmentBySlug(db, slug) {
271
+ const sql = await loadSql();
272
+ const equipment = await allRows(sql, db, "equipment");
273
+ return equipment.find((e) => e.slug === slug);
274
+ }
270
275
  async function listTagsByType(db, type) {
271
276
  const sql = await loadSql();
272
277
  const tags = await allRows(sql, db, "tags");
@@ -297,6 +302,7 @@ function seedNoteSourceId(path, content, chunkId) {
297
302
  if (chunkId === CANVAS_CHUNK_ID2) return `guides:${canvasRouteSlug(content) ?? under}`;
298
303
  if (chunkId === DASHBOARDS_CHUNK_ID) return "dashboards";
299
304
  if (chunkId === EFFORTS_CHUNK_ID) return "efforts";
305
+ if (chunkId === EQUIPMENT_CHUNK_ID) return "equipment";
300
306
  if (chunkId === TEMPLATE_CHUNK_ID) return "playground";
301
307
  if (chunkId.startsWith("collection.")) {
302
308
  const catalog = catalogForChunkId(chunkId);
@@ -305,40 +311,19 @@ function seedNoteSourceId(path, content, chunkId) {
305
311
  if (chunkId.startsWith("feed.")) return `feed:feeds/${under}`;
306
312
  return void 0;
307
313
  }
308
- var SEED_SCHEMA2, SEED_REGISTERED_WIDGETS, EFFORTS_CHUNK_ID, CANVAS_CHUNK_ID2, DASHBOARDS_CHUNK_ID, TEMPLATE_CHUNK_ID;
314
+ var SEED_SCHEMA2, SEED_REGISTERED_WIDGETS, EFFORTS_CHUNK_ID, EQUIPMENT_CHUNK_ID, CANVAS_CHUNK_ID2, DASHBOARDS_CHUNK_ID, TEMPLATE_CHUNK_ID;
309
315
  var init_contract = __esm({
310
316
  "src/seedbuild/contract.ts"() {
311
- SEED_SCHEMA2 = 8;
317
+ SEED_SCHEMA2 = 9;
312
318
  SEED_REGISTERED_WIDGETS = wodWikiCore.KNOWN_SECTION_WIDGETS;
313
319
  EFFORTS_CHUNK_ID = "efforts";
320
+ EQUIPMENT_CHUNK_ID = "equipment";
314
321
  CANVAS_CHUNK_ID2 = "canvas";
315
322
  DASHBOARDS_CHUNK_ID = "dashboards";
316
323
  TEMPLATE_CHUNK_ID = "template";
317
324
  }
318
325
  });
319
326
 
320
- // src/seedbuild/effort-slugs.ts
321
- function extractEffortSlugs2(statements) {
322
- const slugs = /* @__PURE__ */ new Set();
323
- for (const statement of statements) {
324
- if (statement.exerciseId) {
325
- slugs.add(statement.exerciseId);
326
- } else {
327
- const effortMetric = statement.metrics?.getMetric?.("effort");
328
- const effort = effortMetric && typeof effortMetric.value === "string" ? effortMetric.value : void 0;
329
- if (effort) {
330
- const slug = effort.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
331
- if (slug) slugs.add(slug);
332
- }
333
- }
334
- }
335
- return Array.from(slugs);
336
- }
337
- var init_effort_slugs = __esm({
338
- "src/seedbuild/effort-slugs.ts"() {
339
- }
340
- });
341
-
342
327
  // src/seedbuild/filename.ts
343
328
  function fileToDisplayName(filename) {
344
329
  const base = filename.replace(/\.md$/, "");
@@ -384,149 +369,6 @@ var init_filename = __esm({
384
369
  };
385
370
  }
386
371
  });
387
-
388
- // src/seedbuild/frontmatter.ts
389
- function unquote(value) {
390
- return value.replace(
391
- /^(['"])(.*)\1$/,
392
- (_match, quote, inner) => quote === '"' ? inner.replace(/\\(["\\])/g, "$1") : inner
393
- );
394
- }
395
- function parseFrontmatter(raw) {
396
- const match = raw.match(FRONTMATTER_RE);
397
- if (!match) return { meta: {}, body: raw };
398
- return { meta: parseMetaLines(match[1].split(/\r?\n/)), body: raw.slice(match[0].length) };
399
- }
400
- function parseMetaLines(lines) {
401
- const meta = {};
402
- for (let i = 0; i < lines.length; i++) {
403
- const keyMatch = lines[i].match(/^([A-Za-z][\w.-]*)\s*:(.*)$/);
404
- if (!keyMatch) continue;
405
- const key = keyMatch[1];
406
- const rawVal = keyMatch[2].trim();
407
- if (rawVal === "") {
408
- const items = [];
409
- while (i + 1 < lines.length) {
410
- const itemMatch = lines[i + 1].match(/^\s+-\s+(.+)$/);
411
- if (!itemMatch) break;
412
- items.push(itemMatch[1].trim());
413
- i++;
414
- }
415
- meta[key] = items.length > 0 ? items : "";
416
- } else {
417
- const wasQuoted = /^(['"])(.*)\1$/.test(rawVal);
418
- const value = unquote(rawVal);
419
- const num = Number(value);
420
- meta[key] = !wasQuoted && value !== "" && !isNaN(num) ? num : value;
421
- }
422
- }
423
- return meta;
424
- }
425
- function getScalar(meta, key) {
426
- const value = meta[key];
427
- return typeof value === "string" || typeof value === "number" ? value : void 0;
428
- }
429
- function getList(meta, key) {
430
- const value = meta[key];
431
- return Array.isArray(value) ? value : [];
432
- }
433
- var FRONTMATTER_RE;
434
- var init_frontmatter = __esm({
435
- "src/seedbuild/frontmatter.ts"() {
436
- FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
437
- }
438
- });
439
- function parseDiscipline(val) {
440
- if (!val) return void 0;
441
- const normalized = unquoteYaml(val.trim()).toLowerCase();
442
- if (wodWikiLang.isEffortDiscipline(normalized)) return normalized;
443
- console.warn(`[effort-file] unknown discipline '${val}' \u2014 treating as unset`);
444
- return void 0;
445
- }
446
- function unquoteYaml(val) {
447
- if (val.startsWith('"') && val.endsWith('"')) {
448
- return val.slice(1, -1).replace(/\\(["\\])/g, "$1");
449
- }
450
- if (val.startsWith("'") && val.endsWith("'")) {
451
- return val.slice(1, -1).replace(/\\'/g, "'");
452
- }
453
- return val;
454
- }
455
- function coerceNestedScalar(raw) {
456
- if (raw === "true") return true;
457
- if (raw === "false") return false;
458
- if (raw !== "" && !Number.isNaN(Number(raw))) return Number(raw);
459
- return raw;
460
- }
461
- function readNestedSection(lines, key) {
462
- const start = lines.findIndex((l) => new RegExp(`^${key}:\\s*$`).test(l));
463
- if (start === -1) return void 0;
464
- const result = {};
465
- let subKey = null;
466
- for (let i = start + 1; i < lines.length; i++) {
467
- const line = lines[i];
468
- if (/^\S/.test(line)) break;
469
- const trimmed = line.trim();
470
- if (!trimmed || trimmed.startsWith("#")) continue;
471
- const indent = line.length - line.trimStart().length;
472
- const m = trimmed.match(/^([\w.-]+):\s*(.*)$/);
473
- if (!m) continue;
474
- const [, k, rawVal] = m;
475
- if (indent === 2) {
476
- if (rawVal === "") {
477
- subKey = k;
478
- result[k] = {};
479
- } else {
480
- subKey = null;
481
- result[k] = coerceNestedScalar(rawVal);
482
- }
483
- } else if (indent >= 4 && subKey !== null) {
484
- result[subKey][k] = coerceNestedScalar(rawVal);
485
- }
486
- }
487
- return Object.keys(result).length > 0 ? result : void 0;
488
- }
489
- function parseEffortFile(raw) {
490
- const blockMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
491
- if (!blockMatch) return null;
492
- const { meta, body } = parseFrontmatter(raw);
493
- const innerLines = blockMatch[1].split(/\r?\n/);
494
- const id = getScalar(meta, "id");
495
- const slug = getScalar(meta, "slug");
496
- const label = getScalar(meta, "label");
497
- const aliases = getList(meta, "aliases");
498
- const baseAttrs = readNestedSection(innerLines, "baseAttributes");
499
- const hints = readNestedSection(innerLines, "hints");
500
- const derivation = readNestedSection(innerLines, "derivation");
501
- const metRaw = baseAttrs?.met;
502
- const met = typeof metRaw === "number" ? metRaw : Number(metRaw);
503
- if (!id || !slug || !label || metRaw === void 0 || Number.isNaN(met)) return null;
504
- const discipline = parseDiscipline(
505
- typeof baseAttrs?.discipline === "string" ? baseAttrs.discipline : void 0
506
- );
507
- const intensityTier = baseAttrs?.intensityTier;
508
- const trimmedBody = body.trim();
509
- return {
510
- id: String(id),
511
- slug: String(slug),
512
- label: String(label),
513
- aliases,
514
- baseAttributes: {
515
- met,
516
- ...discipline ? { discipline } : {},
517
- ...intensityTier ? { intensityTier } : {}
518
- },
519
- registrySource: "bundled",
520
- ...trimmedBody ? { body: trimmedBody } : {},
521
- ...hints ? { hints } : {},
522
- ...derivation ? { derivation } : {}
523
- };
524
- }
525
- var init_effort_file = __esm({
526
- "src/seedbuild/effort-file.ts"() {
527
- init_frontmatter();
528
- }
529
- });
530
372
  function resolveGitVersion(repoRoot) {
531
373
  try {
532
374
  const run = (args) => child_process.execSync(`git ${args}`, { cwd: repoRoot, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
@@ -614,6 +456,54 @@ var init_report = __esm({
614
456
  "src/seedbuild/report.ts"() {
615
457
  }
616
458
  });
459
+
460
+ // src/seedbuild/frontmatter.ts
461
+ function unquote(value) {
462
+ return value.replace(
463
+ /^(['"])(.*)\1$/,
464
+ (_match, quote, inner) => quote === '"' ? inner.replace(/\\(["\\])/g, "$1") : inner
465
+ );
466
+ }
467
+ function parseFrontmatter(raw) {
468
+ const match = raw.match(FRONTMATTER_RE);
469
+ if (!match) return { meta: {}, body: raw };
470
+ return { meta: parseMetaLines(match[1].split(/\r?\n/)), body: raw.slice(match[0].length) };
471
+ }
472
+ function parseMetaLines(lines) {
473
+ const meta = {};
474
+ for (let i = 0; i < lines.length; i++) {
475
+ const keyMatch = lines[i].match(/^([A-Za-z][\w.-]*)\s*:(.*)$/);
476
+ if (!keyMatch) continue;
477
+ const key = keyMatch[1];
478
+ const rawVal = keyMatch[2].trim();
479
+ if (rawVal === "") {
480
+ const items = [];
481
+ while (i + 1 < lines.length) {
482
+ const itemMatch = lines[i + 1].match(/^\s+-\s+(.+)$/);
483
+ if (!itemMatch) break;
484
+ items.push(itemMatch[1].trim());
485
+ i++;
486
+ }
487
+ meta[key] = items.length > 0 ? items : "";
488
+ } else {
489
+ const wasQuoted = /^(['"])(.*)\1$/.test(rawVal);
490
+ const value = unquote(rawVal);
491
+ const num = Number(value);
492
+ meta[key] = !wasQuoted && value !== "" && !isNaN(num) ? num : value;
493
+ }
494
+ }
495
+ return meta;
496
+ }
497
+ function getScalar(meta, key) {
498
+ const value = meta[key];
499
+ return typeof value === "string" || typeof value === "number" ? value : void 0;
500
+ }
501
+ var FRONTMATTER_RE;
502
+ var init_frontmatter = __esm({
503
+ "src/seedbuild/frontmatter.ts"() {
504
+ FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
505
+ }
506
+ });
617
507
  function object(value) {
618
508
  return typeof value === "object" && value !== null && !Array.isArray(value);
619
509
  }
@@ -644,6 +534,7 @@ var init_validation = __esm({
644
534
  var compiler_exports = {};
645
535
  __export(compiler_exports, {
646
536
  buildBlockEffortRows: () => buildBlockEffortRows,
537
+ buildBlockEquipmentRows: () => buildBlockEquipmentRows,
647
538
  buildBlockIndexRows: () => buildBlockIndexRows,
648
539
  chunkIdFor: () => chunkIdFor,
649
540
  collectCorpusMarkdown: () => collectCorpusMarkdown,
@@ -652,7 +543,7 @@ __export(compiler_exports, {
652
543
  feedDateToCreatedAt: () => feedDateToCreatedAt,
653
544
  frontmatterDateToCreatedAt: () => frontmatterDateToCreatedAt,
654
545
  generateSeed: () => generateSeed,
655
- loadExistingEffortMap: () => loadExistingEffortMap,
546
+ loadCorpusReferenceCatalog: () => loadCorpusReferenceCatalog,
656
547
  partitionIntoChunks: () => partitionIntoChunks,
657
548
  scaffoldDiscoveredEfforts: () => scaffoldDiscoveredEfforts,
658
549
  validateDashboardNote: () => validateDashboardNote,
@@ -678,6 +569,7 @@ function chunkIdFor(relPath) {
678
569
  case "canvas":
679
570
  case "dashboards":
680
571
  case "efforts":
572
+ case "equipment":
681
573
  return corpus;
682
574
  case "collections":
683
575
  return parts.length === 3 ? `collection._root` : `collection.${slug}`;
@@ -696,7 +588,7 @@ function partitionIntoChunks(paths) {
696
588
  const id = chunkIdFor(p);
697
589
  if (id === null) {
698
590
  throw new Error(
699
- `[generate-seed] Parity failure: '${p}' is not part of any known corpus (canvas | dashboards | efforts | collections | feeds). Extend chunkIdFor() if this is a new corpus.`
591
+ `[generate-seed] Parity failure: '${p}' is not part of any known corpus (canvas | dashboards | efforts | equipment | collections | feeds). Extend chunkIdFor() if this is a new corpus.`
700
592
  );
701
593
  }
702
594
  const list = byId.get(id);
@@ -778,7 +670,7 @@ function compileSeedRow(path, content, metadata = {}) {
778
670
  const section = sections.find((section2) => section2.from === block.from && section2.to === block.to);
779
671
  return { id: `${noteId}:${block.id}`, noteId, blockId: block.id, blockVersion: 1, sourceRevision: 1, from: block.from, to: block.to, position, dataType: block.kind, ...block.kind === "wod" ? { blockContentId: wodWikiCore.blockContentId(section?.scriptBlock?.content ?? content.slice(block.from, block.to)) } : {}, rawContent: content.slice(block.from, block.to), noteTitle: metadata.noteTitle ?? fileToDisplayName(path.split("/").at(-1) ?? path), createdAt: metadata.createdAt ?? 0, isStatic: true, sourceId: metadata.sourceId, sourcePath: path, routeId: metadata.routeId };
780
672
  });
781
- return { path, content, blocks, blockIndex, blockEfforts: [] };
673
+ return { path, content, blocks, blockIndex, blockEfforts: [], blockEquipment: [] };
782
674
  }
783
675
  function buildBlockIndexRows(markdownDir, corpusRoot) {
784
676
  const rows = [];
@@ -828,41 +720,37 @@ function formatDiscoveredLabel(raw) {
828
720
  const clean = raw.replace(/[-_]+/g, " ").trim();
829
721
  return clean.split(/\s+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(" ");
830
722
  }
831
- function loadExistingEffortMap(markdownDir) {
832
- const slugs = /* @__PURE__ */ new Set();
833
- const aliasToCanonical = /* @__PURE__ */ new Map();
723
+ function loadCorpusReferenceCatalog(markdownDir) {
724
+ const efforts = [];
725
+ const equipment = [];
834
726
  const effortsDir = path.join(markdownDir, "efforts");
835
727
  for (const file of listMarkdownFiles(effortsDir)) {
836
- const raw = fs.readFileSync(path.join(effortsDir, file), "utf8");
837
- const parsed = parseEffortFile(raw);
838
- if (!parsed) continue;
839
- slugs.add(parsed.slug);
840
- aliasToCanonical.set(parsed.slug, parsed.slug);
841
- aliasToCanonical.set(parsed.label.toLowerCase(), parsed.slug);
842
- aliasToCanonical.set(parsed.slug.replace(/-/g, " "), parsed.slug);
843
- for (const alias of parsed.aliases) {
844
- aliasToCanonical.set(alias.toLowerCase(), parsed.slug);
845
- aliasToCanonical.set(alias.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, ""), parsed.slug);
846
- }
728
+ const parsed = wodWikiLang.parseEffortFile(fs.readFileSync(path.join(effortsDir, file), "utf8"));
729
+ if (parsed) efforts.push(parsed);
730
+ }
731
+ const equipmentDir = path.join(markdownDir, "equipment");
732
+ for (const file of listMarkdownFiles(equipmentDir)) {
733
+ const parsed = wodWikiLang.parseEquipmentFile(fs.readFileSync(path.join(equipmentDir, file), "utf8"));
734
+ if (parsed) equipment.push(parsed);
847
735
  }
848
- return { slugs, aliasToCanonical };
736
+ return { efforts, equipment };
849
737
  }
850
738
  function discoverUncatalogedEfforts(markdownDir, corpusRoot) {
851
- const { slugs, aliasToCanonical } = loadExistingEffortMap(markdownDir);
739
+ const catalog = loadCorpusReferenceCatalog(markdownDir);
740
+ const index = wodWikiLang.createReferenceIndex(catalog);
852
741
  const blocks = buildBlockIndexRows(markdownDir, corpusRoot);
853
742
  const discovered = /* @__PURE__ */ new Map();
854
743
  for (const block of blocks) {
855
744
  if (block.dataType !== "wod" || !block.rawContent) continue;
856
745
  const section = wodWikiCore.parseDocumentSections(block.rawContent, { registeredWidgets: SEED_REGISTERED_WIDGETS }).find((section2) => section2.scriptBlock);
857
- const script = wodWikiLang.parseScript(section?.scriptBlock?.content ?? block.rawContent);
746
+ const script = wodWikiLang.parseScript(section?.scriptBlock?.content ?? block.rawContent, { references: index });
858
747
  for (const statement of script.statements) {
859
748
  const effortMetric = statement.metrics?.getMetric?.("effort");
860
749
  const effortLabel = (effortMetric && typeof effortMetric.value === "string" ? effortMetric.value : void 0) ?? statement.exerciseId;
861
750
  if (!effortLabel) continue;
862
- const slugified = effortLabel.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
751
+ const slugified = wodWikiLang.slugifyRefLabel(effortLabel);
863
752
  if (!slugified || NON_EFFORT_SLUGS[slugified]) continue;
864
- const canonical = aliasToCanonical.get(slugified) ?? aliasToCanonical.get(effortLabel.toLowerCase());
865
- if (canonical && slugs.has(canonical)) continue;
753
+ if (index.resolveEffort(slugified) || index.resolveEffort(effortLabel)) continue;
866
754
  const existing = discovered.get(slugified);
867
755
  if (existing) {
868
756
  existing.rawLabels.add(effortLabel);
@@ -912,17 +800,16 @@ ${aliases.join("\n")}` : "aliases: []",
912
800
  }
913
801
  function buildBlockEffortRows(markdownDir, corpusRoot, blocks = buildBlockIndexRows(markdownDir, corpusRoot)) {
914
802
  const efforts = [];
915
- const { aliasToCanonical } = loadExistingEffortMap(markdownDir);
803
+ const index = wodWikiLang.createReferenceIndex(loadCorpusReferenceCatalog(markdownDir));
916
804
  for (const block of blocks) {
917
805
  if (block.dataType !== "wod" || !block.rawContent) continue;
918
806
  const section = wodWikiCore.parseDocumentSections(block.rawContent, { registeredWidgets: SEED_REGISTERED_WIDGETS }).find((section2) => section2.scriptBlock);
919
- const script = wodWikiLang.parseScript(section?.scriptBlock?.content ?? block.rawContent);
920
- const effortSlugs = extractEffortSlugs2(script.statements);
807
+ const script = wodWikiLang.parseScript(section?.scriptBlock?.content ?? block.rawContent, { references: index });
921
808
  const linked = /* @__PURE__ */ new Set();
922
- for (const rawSlug of effortSlugs) {
923
- const norm = rawSlug.toLowerCase().trim();
924
- const canonicalSlug = aliasToCanonical.get(norm) ?? aliasToCanonical.get(norm.replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "")) ?? norm.replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
925
- if (!canonicalSlug || NON_EFFORT_SLUGS[canonicalSlug] || NON_EFFORT_SLUGS[norm]) {
809
+ for (const rawSlug of wodWikiLang.extractStatementRefs(script.statements).efforts) {
810
+ const canonical = index.resolveEffort(rawSlug);
811
+ const canonicalSlug = canonical?.slug;
812
+ if (!canonicalSlug || NON_EFFORT_SLUGS[canonicalSlug] || NON_EFFORT_SLUGS[rawSlug]) {
926
813
  continue;
927
814
  }
928
815
  if (linked.has(canonicalSlug)) continue;
@@ -941,6 +828,32 @@ function buildBlockEffortRows(markdownDir, corpusRoot, blocks = buildBlockIndexR
941
828
  }
942
829
  return efforts;
943
830
  }
831
+ function buildBlockEquipmentRows(markdownDir, corpusRoot, blocks = buildBlockIndexRows(markdownDir, corpusRoot)) {
832
+ const links = [];
833
+ const index = wodWikiLang.createReferenceIndex(loadCorpusReferenceCatalog(markdownDir));
834
+ for (const block of blocks) {
835
+ if (block.dataType !== "wod" || !block.rawContent) continue;
836
+ const section = wodWikiCore.parseDocumentSections(block.rawContent, { registeredWidgets: SEED_REGISTERED_WIDGETS }).find((s) => s.scriptBlock);
837
+ const script = wodWikiLang.parseScript(section?.scriptBlock?.content ?? block.rawContent, { references: index });
838
+ const linked = /* @__PURE__ */ new Set();
839
+ for (const rawSlug of wodWikiLang.extractStatementRefs(script.statements).equipment) {
840
+ const canonicalSlug = index.resolveEquipment(rawSlug)?.slug;
841
+ if (!canonicalSlug || linked.has(canonicalSlug)) continue;
842
+ linked.add(canonicalSlug);
843
+ links.push({
844
+ id: `${block.id}:${canonicalSlug}`,
845
+ noteId: block.noteId,
846
+ blockId: block.blockId,
847
+ blockContentId: block.blockContentId,
848
+ equipmentSlug: canonicalSlug,
849
+ isStatic: block.isStatic,
850
+ sourcePath: block.sourcePath,
851
+ createdAt: block.createdAt
852
+ });
853
+ }
854
+ }
855
+ return links;
856
+ }
944
857
  function sha256Hex(data) {
945
858
  return crypto.createHash("sha256").update(data).digest("hex");
946
859
  }
@@ -992,9 +905,11 @@ ${shown}${more}`);
992
905
  const manifestChunks = [];
993
906
  const tBlockEfforts = Date.now();
994
907
  const effortRows = buildBlockEffortRows(markdownDir, corpusRoot, blockRows);
908
+ const equipmentRows = buildBlockEquipmentRows(markdownDir, corpusRoot, blockRows);
995
909
  passMs.blockEfforts = Date.now() - tBlockEfforts;
996
910
  const blocksByPath = /* @__PURE__ */ new Map();
997
911
  const effortsByPath = /* @__PURE__ */ new Map();
912
+ const equipmentByPath = /* @__PURE__ */ new Map();
998
913
  for (const row of blockRows) {
999
914
  const rows = blocksByPath.get(row.sourcePath) ?? [];
1000
915
  rows.push(row);
@@ -1005,6 +920,11 @@ ${shown}${more}`);
1005
920
  rows.push(row);
1006
921
  effortsByPath.set(row.sourcePath, rows);
1007
922
  }
923
+ for (const row of equipmentRows) {
924
+ const rows = equipmentByPath.get(row.sourcePath) ?? [];
925
+ rows.push(row);
926
+ equipmentByPath.set(row.sourcePath, rows);
927
+ }
1008
928
  let totalFiles = 0;
1009
929
  const tWrite = Date.now();
1010
930
  for (const { id, files } of groups) {
@@ -1015,6 +935,7 @@ ${shown}${more}`);
1015
935
  const compiled = compileSeedRow(p, content);
1016
936
  compiled.blockIndex = blocksByPath.get(p) ?? compiled.blockIndex;
1017
937
  compiled.blockEfforts = effortsByPath.get(p) ?? [];
938
+ compiled.blockEquipment = equipmentByPath.get(p) ?? [];
1018
939
  return compiled;
1019
940
  });
1020
941
  const bytes = Buffer.from(JSON.stringify(rows), "utf8");
@@ -1050,9 +971,7 @@ var NON_EFFORT_SLUGS;
1050
971
  var init_compiler = __esm({
1051
972
  "src/seedbuild/compiler.ts"() {
1052
973
  init_contract();
1053
- init_effort_slugs();
1054
974
  init_filename();
1055
- init_effort_file();
1056
975
  init_git_version();
1057
976
  init_report();
1058
977
  init_frontmatter();
@@ -1110,7 +1029,7 @@ var init_compiler = __esm({
1110
1029
  var VERSION;
1111
1030
  var init_version = __esm({
1112
1031
  "src/version.ts"() {
1113
- VERSION = "0.37.2137";
1032
+ VERSION = "0.37.2140";
1114
1033
  }
1115
1034
  });
1116
1035
 
@@ -2408,23 +2327,7 @@ function resolveDatabaseConfig(options = {}, env) {
2408
2327
  // src/cli/persist.ts
2409
2328
  init_db();
2410
2329
  var registeredWidgets = wodWikiCore.KNOWN_SECTION_WIDGETS;
2411
- var saveStores = ["notes", "block_revisions", "block_index", "block_efforts", "tags", "note_tags", "sessions"];
2412
- function extractEffortSlugs(statements) {
2413
- const slugs = /* @__PURE__ */ new Set();
2414
- for (const statement of statements) {
2415
- if (statement.exerciseId) {
2416
- slugs.add(statement.exerciseId);
2417
- } else {
2418
- const effortMetric = statement.metrics?.getMetric?.("effort");
2419
- const effort = effortMetric && "effort" in effortMetric ? effortMetric.effort : void 0;
2420
- if (typeof effort === "string") {
2421
- const slug = effort.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
2422
- if (slug) slugs.add(slug);
2423
- }
2424
- }
2425
- }
2426
- return [...slugs];
2427
- }
2330
+ var saveStores = ["notes", "block_revisions", "block_index", "block_efforts", "block_equipment", "efforts", "equipment", "tags", "note_tags", "sessions"];
2428
2331
  function titleFromLabel(sourceLabel) {
2429
2332
  if (!sourceLabel || sourceLabel === "-") return "stdin";
2430
2333
  const stem = sourceLabel.split("/").pop() ?? sourceLabel;
@@ -2496,9 +2399,19 @@ async function saveSource(db, options, startTime) {
2496
2399
  };
2497
2400
  const priorRows = await tx.readonly("block_index").getAllFromIndex("by-note", noteId);
2498
2401
  const priorEfforts = await tx.readonly("block_efforts").getAllFromIndex("by-note", noteId);
2402
+ const priorEquipment = await tx.readonly("block_equipment").getAllFromIndex("by-note", noteId);
2403
+ const storedEfforts = await tx.readonly("efforts").getAll();
2404
+ const effortsBySlug = /* @__PURE__ */ new Map();
2405
+ for (const effort of wodWikiLang.bundledEfforts) effortsBySlug.set(effort.slug, effort);
2406
+ for (const effort of storedEfforts) effortsBySlug.set(effort.slug, effort);
2407
+ const referenceIndex = wodWikiLang.createReferenceIndex({
2408
+ efforts: [...effortsBySlug.values()],
2409
+ equipment: await tx.readonly("equipment").getAll()
2410
+ });
2499
2411
  const priorById = new Map(priorRows.map((row) => [row.id, row]));
2500
2412
  const nextRows = [];
2501
2413
  const nextEfforts = [];
2414
+ const nextEquipment = [];
2502
2415
  for (const [position, block] of Object.values(blocks).sort((a, b) => a.from - b.from).entries()) {
2503
2416
  const id = `${noteId}:${block.id}`;
2504
2417
  const prior = priorById.get(id);
@@ -2524,7 +2437,20 @@ async function saveSource(db, options, startTime) {
2524
2437
  ...note.sourceId ? { sourceId: note.sourceId } : {}
2525
2438
  });
2526
2439
  if (block.kind === "wod") {
2527
- for (const effortSlug of extractEffortSlugs(wodWikiLang.parseScript(body, { sport: block.sport, dialect: block.dialect }).statements)) {
2440
+ const statements = wodWikiLang.parseScript(body, { sport: block.sport, dialect: block.dialect, references: referenceIndex }).statements;
2441
+ const refs = wodWikiLang.extractStatementRefs(statements);
2442
+ const effortSlugs = new Set(refs.efforts);
2443
+ if (effortSlugs.size === 0) {
2444
+ for (const s of statements) {
2445
+ const m = s.metrics?.getMetric?.(wodWikiCore.MetricType.Effort);
2446
+ const val = typeof m?.value === "string" ? m.value : m && "effort" in m ? String(m.effort) : "";
2447
+ const slug = val.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
2448
+ if (slug && !["rest", "amrap", "emom", "tabata", "for-time"].includes(slug)) {
2449
+ effortSlugs.add(slug);
2450
+ }
2451
+ }
2452
+ }
2453
+ for (const effortSlug of effortSlugs) {
2528
2454
  const effortId = `${noteId}:${block.id}:${effortSlug}`;
2529
2455
  const previous = priorEfforts.find((row) => row.id === effortId);
2530
2456
  nextEfforts.push({
@@ -2537,12 +2463,26 @@ async function saveSource(db, options, startTime) {
2537
2463
  createdAt: previous?.createdAt ?? now
2538
2464
  });
2539
2465
  }
2466
+ for (const equipmentSlug of refs.equipment) {
2467
+ const equipmentId = `${noteId}:${block.id}:${equipmentSlug}`;
2468
+ const previous = priorEquipment.find((row) => row.id === equipmentId);
2469
+ nextEquipment.push({
2470
+ ...previous,
2471
+ id: equipmentId,
2472
+ noteId,
2473
+ blockId: block.id,
2474
+ blockContentId: fingerprint,
2475
+ equipmentSlug,
2476
+ createdAt: previous?.createdAt ?? now
2477
+ });
2478
+ }
2540
2479
  }
2541
2480
  }
2542
2481
  if (!wodWikiStorage.rowsEqual(existing, note)) await tx.readwrite("notes").put(note);
2543
2482
  if (revisions.length) await tx.readwrite("block_revisions").putMany(revisions);
2544
2483
  await diffProjection(tx, "block_index", priorRows, nextRows);
2545
2484
  await diffProjection(tx, "block_efforts", priorEfforts, nextEfforts);
2485
+ await diffProjection(tx, "block_equipment", priorEquipment, nextEquipment);
2546
2486
  const tags = await saveTags(tx, noteId, options.tags, now);
2547
2487
  let session;
2548
2488
  let revision;
@@ -2578,6 +2518,7 @@ async function diffProjection(tx, store, previous, next) {
2578
2518
  if (!wodWikiStorage.rowsEqual(old.get(row.id), row)) {
2579
2519
  if (store === "block_index" && "blockVersion" in row) await tx.readwrite("block_index").put(row);
2580
2520
  if (store === "block_efforts" && "effortSlug" in row) await tx.readwrite("block_efforts").put(row);
2521
+ if (store === "block_equipment" && "equipmentSlug" in row) await tx.readwrite("block_equipment").put(row);
2581
2522
  }
2582
2523
  }
2583
2524
  }
@@ -2669,6 +2610,12 @@ async function persistEffort(db, effort) {
2669
2610
  await sql.putRow(tx, sql.storeDefinition("efforts"), effort);
2670
2611
  });
2671
2612
  }
2613
+ async function persistEquipment(db, equipment) {
2614
+ const sql = await loadSql();
2615
+ await db.db.transaction(async (tx) => {
2616
+ await sql.putRow(tx, sql.storeDefinition("equipment"), equipment);
2617
+ });
2618
+ }
2672
2619
  async function persistTypedTags(db, labels, type) {
2673
2620
  return db.storage.transaction(["tags"], "readwrite", async (tx) => {
2674
2621
  let added = 0;
@@ -2694,17 +2641,18 @@ async function importSeedDir(db, dir, sourceId) {
2694
2641
  const source = sourceId || path.basename(dir);
2695
2642
  const manifest = wodWikiEngine.assertSeedManifest(JSON.parse(fs.readFileSync(path.resolve(dir, "manifest.json"), "utf-8")));
2696
2643
  const key = wodWikiEngine.seedMetaKey(source);
2697
- let expected = await db.storage.readonly("meta").get(key);
2698
- if (expected && expected.schema !== wodWikiEngine.SEED_SCHEMA) throw new Error("Incompatible seed checkpoint; recreate storage");
2644
+ const stored = await db.storage.readonly("meta").get(key);
2645
+ let expected = stored?.schema === wodWikiEngine.SEED_SCHEMA ? stored : void 0;
2699
2646
  let meta = expected ?? wodWikiEngine.emptySeedMeta(key);
2700
2647
  if (manifest.version < meta.seedVersion) throw new Error("Seed manifest is older than stored checkpoint");
2701
- const summary = { sourceId: source, version: manifest.version, notes: 0, efforts: 0, blocks: 0, blockEfforts: 0, deleted: 0, skippedUserOwned: 0 };
2648
+ const summary = { sourceId: source, version: manifest.version, notes: 0, efforts: 0, equipment: 0, blocks: 0, blockEfforts: 0, deleted: 0, skippedUserOwned: 0 };
2702
2649
  const metadataChanged = manifest.version !== meta.seedVersion || manifest.builtAt !== meta.builtAt;
2703
2650
  const plan = manifest.chunks.filter((c) => metadataChanged || meta.chunks[c.id]?.sha256 !== c.sha256);
2704
2651
  plan.sort((a, b) => Number(b.id === wodWikiEngine.CANVAS_CHUNK_ID) - Number(a.id === wodWikiEngine.CANVAS_CHUNK_ID));
2705
2652
  const accumulate = (result) => {
2706
2653
  summary.notes += result.totalNotes;
2707
2654
  summary.efforts += result.totalEfforts;
2655
+ summary.equipment += result.totalEquipment;
2708
2656
  summary.blocks += result.totalBlocks;
2709
2657
  summary.deleted += result.deleted;
2710
2658
  summary.skippedUserOwned += result.skippedUserOwned;
@@ -2780,6 +2728,7 @@ async function publishCatalogSeed(db, options) {
2780
2728
  }
2781
2729
  const allIndexRows = await db.storage.readonly("block_index").getAll();
2782
2730
  const allEffortRows = await db.storage.readonly("block_efforts").getAll();
2731
+ const allEquipmentRows = await db.storage.readonly("block_equipment").getAll();
2783
2732
  const indexByNote = /* @__PURE__ */ new Map();
2784
2733
  for (const row of allIndexRows) {
2785
2734
  const list = indexByNote.get(row.noteId) ?? [];
@@ -2792,6 +2741,12 @@ async function publishCatalogSeed(db, options) {
2792
2741
  list.push(row);
2793
2742
  effortsByNote.set(row.noteId, list);
2794
2743
  }
2744
+ const equipmentByNote = /* @__PURE__ */ new Map();
2745
+ for (const row of allEquipmentRows) {
2746
+ const list = equipmentByNote.get(row.noteId) ?? [];
2747
+ list.push(row);
2748
+ equipmentByNote.set(row.noteId, list);
2749
+ }
2795
2750
  const outDir = path.resolve(process.cwd(), options.outDir);
2796
2751
  const chunksDir = path.join(outDir, "chunks");
2797
2752
  fs.mkdirSync(chunksDir, { recursive: true });
@@ -2806,6 +2761,7 @@ async function publishCatalogSeed(db, options) {
2806
2761
  const notePath = note.sourcePath ?? `markdown/collections/${catalogName}/${stem}.md`;
2807
2762
  const noteBlockIndex = indexByNote.get(note.id) ?? [];
2808
2763
  const noteBlockEfforts = effortsByNote.get(note.id) ?? [];
2764
+ const noteBlockEquipment = equipmentByNote.get(note.id) ?? [];
2809
2765
  const blockIndex = noteBlockIndex.length > 0 ? noteBlockIndex.map((b) => ({
2810
2766
  ...b,
2811
2767
  isStatic: true,
@@ -2825,12 +2781,18 @@ async function publishCatalogSeed(db, options) {
2825
2781
  isStatic: true,
2826
2782
  sourcePath: e.sourcePath ?? notePath
2827
2783
  }));
2784
+ const blockEquipment = noteBlockEquipment.map((e) => ({
2785
+ ...e,
2786
+ isStatic: true,
2787
+ sourcePath: e.sourcePath ?? notePath
2788
+ }));
2828
2789
  chunkRows.push({
2829
2790
  path: notePath,
2830
2791
  content: note.rawContent,
2831
2792
  blocks: note.blocks,
2832
2793
  blockIndex,
2833
- blockEfforts
2794
+ blockEfforts,
2795
+ blockEquipment
2834
2796
  });
2835
2797
  }
2836
2798
  const chunkId = `collection.${catalogName}`;
@@ -3800,7 +3762,9 @@ async function cliMain(argv = process.argv.slice(2), io = {}) {
3800
3762
  writeErr(`[wod ${verb}] Effort not found in ${db.path}: ${id}`);
3801
3763
  return 1;
3802
3764
  }
3803
- const markdown = wodWikiEngine.effortToMarkdown(effort);
3765
+ const ownerNoteId = typeof effort.ownerNoteId === "string" ? effort.ownerNoteId : `effort/${effort.slug}`;
3766
+ const owner = await db.storage.readonly("notes").get(ownerNoteId);
3767
+ const markdown = owner?.rawContent ?? wodWikiEngine.effortToMarkdown(effort);
3804
3768
  const target = parsed.output && parsed.output !== "-" ? parsed.output : `${id}.md`;
3805
3769
  if (parsed.output === "-") {
3806
3770
  writeOut(markdown);
@@ -3809,15 +3773,29 @@ async function cliMain(argv = process.argv.slice(2), io = {}) {
3809
3773
  }
3810
3774
  writeOut(`[wod ${verb}] effort ${id} \u2192 ${target}`);
3811
3775
  } else {
3812
- const labels = (await listTagsByType(db.db, "equipment")).map((t) => t.label).sort();
3813
- const csv = wodWikiEngine.equipmentToCsv(labels);
3814
- const target = parsed.output && parsed.output !== "-" ? parsed.output : "equipment.csv";
3815
- if (parsed.output === "-") {
3816
- writeOut(csv);
3776
+ const equipment = id ? await getEquipmentBySlug(db.db, targetId) : void 0;
3777
+ if (equipment) {
3778
+ const ownerNoteId = typeof equipment.ownerNoteId === "string" ? equipment.ownerNoteId : `equipment/${equipment.slug}`;
3779
+ const owner = await db.storage.readonly("notes").get(ownerNoteId);
3780
+ const markdown = owner?.rawContent ?? wodWikiEngine.equipmentToMarkdown(equipment);
3781
+ const target = parsed.output && parsed.output !== "-" ? parsed.output : `${id}.md`;
3782
+ if (parsed.output === "-") {
3783
+ writeOut(markdown);
3784
+ } else {
3785
+ nodeFs().writeFileSync(target, markdown, "utf-8");
3786
+ }
3787
+ writeOut(`[wod ${verb}] equipment ${id} \u2192 ${target}`);
3817
3788
  } else {
3818
- nodeFs().writeFileSync(target, csv, "utf-8");
3789
+ const labels = (await listTagsByType(db.db, "equipment")).map((t) => t.label).sort();
3790
+ const csv = wodWikiEngine.equipmentToCsv(labels);
3791
+ const target = parsed.output && parsed.output !== "-" ? parsed.output : "equipment.csv";
3792
+ if (parsed.output === "-") {
3793
+ writeOut(csv);
3794
+ } else {
3795
+ nodeFs().writeFileSync(target, csv, "utf-8");
3796
+ }
3797
+ writeOut(`[wod ${verb}] equipment \u2192 ${target} (${labels.length} labels)`);
3819
3798
  }
3820
- writeOut(`[wod ${verb}] equipment \u2192 ${target} (${labels.length} labels)`);
3821
3799
  }
3822
3800
  } finally {
3823
3801
  await db.close();
@@ -3859,7 +3837,7 @@ async function cliMain(argv = process.argv.slice(2), io = {}) {
3859
3837
  return 0;
3860
3838
  }
3861
3839
  if (kind === "effort") {
3862
- const effort = wodWikiEngine.markdownToEffort(fs.readFileSync(file, "utf-8"));
3840
+ const effort = wodWikiLang.parseEffortFile(fs.readFileSync(file, "utf-8"));
3863
3841
  if (!effort) {
3864
3842
  writeErr(`[wod import] Unparseable effort file (need frontmatter id/slug/label + numeric baseAttributes.met): ${file}`);
3865
3843
  return 1;
@@ -3873,6 +3851,21 @@ async function cliMain(argv = process.argv.slice(2), io = {}) {
3873
3851
  }
3874
3852
  return 0;
3875
3853
  }
3854
+ if (kind === "equipment" && /\.md$/i.test(file)) {
3855
+ const equipment = wodWikiLang.parseEquipmentFile(fs.readFileSync(file, "utf-8"));
3856
+ if (!equipment) {
3857
+ writeErr(`[wod import] Unparseable equipment file (need frontmatter type: equipment + id/slug/label): ${file}`);
3858
+ return 1;
3859
+ }
3860
+ const db2 = await openCliDb(cliDb());
3861
+ try {
3862
+ await persistEquipment(db2, { ...equipment, registrySource: "user" });
3863
+ writeOut(`[wod import] equipment ${equipment.slug} \u2014 ${equipment.label} \u2192 ${db2.path}`);
3864
+ } finally {
3865
+ await db2.close();
3866
+ }
3867
+ return 0;
3868
+ }
3876
3869
  if (kind === "equipment") {
3877
3870
  const labels = wodWikiEngine.csvToEquipmentLabels(fs.readFileSync(file, "utf-8"));
3878
3871
  if (labels.length === 0) {