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