@bitcobblers/wod-wiki-tools 0.36.2136 → 0.37.2139

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 (60) hide show
  1. package/dist/bin/wod.cjs +522 -246
  2. package/dist/bin/wod.cjs.map +1 -1
  3. package/dist/bin/wod.mjs +4 -3
  4. package/dist/bin/wod.mjs.map +1 -1
  5. package/dist/{chunk-FZKXOYMJ.mjs → chunk-CVFMFD7D.mjs} +153 -188
  6. package/dist/chunk-CVFMFD7D.mjs.map +1 -0
  7. package/dist/{chunk-6QNX4FED.mjs → chunk-HRFOKCYI.mjs} +139 -47
  8. package/dist/chunk-HRFOKCYI.mjs.map +1 -0
  9. package/dist/{chunk-CYKNTEGY.mjs → chunk-IWT7U3WX.mjs} +8 -3
  10. package/dist/chunk-IWT7U3WX.mjs.map +1 -0
  11. package/dist/chunk-JUBKCRNV.mjs +12 -0
  12. package/dist/chunk-JUBKCRNV.mjs.map +1 -0
  13. package/dist/{chunk-MA6BBTWC.mjs → chunk-RWNL6APX.mjs} +3 -3
  14. package/dist/{chunk-MA6BBTWC.mjs.map → chunk-RWNL6APX.mjs.map} +1 -1
  15. package/dist/compiler-OXEXGIZG.mjs +9 -0
  16. package/dist/{compiler-CTPLIZJK.mjs.map → compiler-OXEXGIZG.mjs.map} +1 -1
  17. package/dist/index.cjs +522 -246
  18. package/dist/index.cjs.map +1 -1
  19. package/dist/index.d.cts +1 -1
  20. package/dist/index.d.ts +1 -1
  21. package/dist/index.mjs +5 -4
  22. package/dist/scaffoldCatalog-OIU4FEKJ.mjs +205 -0
  23. package/dist/scaffoldCatalog-OIU4FEKJ.mjs.map +1 -0
  24. package/dist/{server-JXECWS4A.mjs → server-2DVBA35J.mjs} +4 -4
  25. package/dist/{server-JXECWS4A.mjs.map → server-2DVBA35J.mjs.map} +1 -1
  26. package/dist/web/playground/assets/main-2qCz3_0z.js +194 -0
  27. package/dist/web/playground/assets/main-BUcjhK2w.css +2 -0
  28. package/dist/web/playground/assets/{statementBuilderFlow-ugwi0fu0.js → statementBuilderFlow-DKhL-YLz.js} +2 -2
  29. package/dist/web/playground/assets/{vendor-g-NJOjvC.js → vendor-DaJxNU_W.js} +3 -3
  30. package/dist/web/playground/assets/{vendor-codemirror-C41SFn-r.js → vendor-codemirror-BtDM22HA.js} +2 -2
  31. package/dist/web/playground/index.html +4 -4
  32. package/dist/web/playground/seed/core/chunks/canvas.a45c409e.json +1 -0
  33. package/dist/web/playground/seed/core/chunks/efforts.e68f2093.json +1 -0
  34. package/dist/web/playground/seed/core/chunks/equipment.c4fb1d64.json +1 -0
  35. package/dist/web/playground/seed/core/chunks/{template.e5e0e82d.json → template.0120276c.json} +1 -1
  36. package/dist/web/playground/seed/core/manifest.json +27 -10
  37. package/dist/web/playground/seed/core/report.html +15 -13
  38. package/dist/web/server/assets/main-BUcjhK2w.css +2 -0
  39. package/dist/web/server/assets/main-DxsZ-yOz.js +193 -0
  40. package/dist/web/server/assets/{statementBuilderFlow-40WaYjdq.js → statementBuilderFlow-CI5vrd60.js} +2 -2
  41. package/dist/web/server/assets/{vendor-g-NJOjvC.js → vendor-DaJxNU_W.js} +3 -3
  42. package/dist/web/server/assets/{vendor-codemirror-C41SFn-r.js → vendor-codemirror-BtDM22HA.js} +2 -2
  43. package/dist/web/server/index.html +4 -4
  44. package/dist/web/server/seed/core/chunks/canvas.a45c409e.json +1 -0
  45. package/dist/web/server/seed/core/chunks/efforts.e68f2093.json +1 -0
  46. package/dist/web/server/seed/core/chunks/equipment.c4fb1d64.json +1 -0
  47. package/dist/web/server/seed/core/chunks/{template.e5e0e82d.json → template.0120276c.json} +1 -1
  48. package/dist/web/server/seed/core/manifest.json +27 -10
  49. package/dist/web/server/seed/core/report.html +15 -13
  50. package/package.json +6 -6
  51. package/dist/chunk-6QNX4FED.mjs.map +0 -1
  52. package/dist/chunk-CYKNTEGY.mjs.map +0 -1
  53. package/dist/chunk-FZKXOYMJ.mjs.map +0 -1
  54. package/dist/compiler-CTPLIZJK.mjs +0 -9
  55. package/dist/web/playground/assets/main-B-bffxwE.css +0 -2
  56. package/dist/web/playground/assets/main-iLSeEl2E.js +0 -188
  57. package/dist/web/playground/seed/core/chunks/canvas.20048125.json +0 -1
  58. package/dist/web/server/assets/main-B-bffxwE.css +0 -2
  59. package/dist/web/server/assets/main-CRyLxlPN.js +0 -187
  60. package/dist/web/server/seed/core/chunks/canvas.20048125.json +0 -1
package/dist/bin/wod.mjs CHANGED
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'module';
3
- import { cliMain } from '../chunk-6QNX4FED.mjs';
4
- import '../chunk-FZKXOYMJ.mjs';
5
- import '../chunk-CYKNTEGY.mjs';
3
+ import { cliMain } from '../chunk-HRFOKCYI.mjs';
4
+ import '../chunk-CVFMFD7D.mjs';
5
+ import '../chunk-JUBKCRNV.mjs';
6
+ import '../chunk-IWT7U3WX.mjs';
6
7
  import '../chunk-6L5ZDZJ7.mjs';
7
8
 
8
9
  if (typeof globalThis.require === 'undefined') {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/bin/wod.ts"],"names":[],"mappings":";;;;;;;;;;;;AASA,KAAA,CAAM,YAAY;AAChB,EAAA,MAAM,WAAW,MAAM,OAAA,CAAQ,QAAQ,IAAA,CAAK,KAAA,CAAM,CAAC,CAAC,CAAA;AACpD,EAAA,OAAA,CAAQ,QAAA,GAAW,QAAA;AACrB,CAAA,GAAG","file":"wod.mjs","sourcesContent":["#!/usr/bin/env node\n/**\n * wod CLI Entry Point\n *\n * Dispatches to cliMain(process.argv.slice(2)).\n * Runs under Node (v18+) or Bun across all subcommands, including server and playground.\n */\nimport { cliMain } from '../cli/runner';\n\nvoid (async () => {\n const exitCode = await cliMain(process.argv.slice(2));\n process.exitCode = exitCode;\n})();\n"]}
1
+ {"version":3,"sources":["../../src/bin/wod.ts"],"names":[],"mappings":";;;;;;;;;;;;;AASA,KAAA,CAAM,YAAY;AAChB,EAAA,MAAM,WAAW,MAAM,OAAA,CAAQ,QAAQ,IAAA,CAAK,KAAA,CAAM,CAAC,CAAC,CAAA;AACpD,EAAA,OAAA,CAAQ,QAAA,GAAW,QAAA;AACrB,CAAA,GAAG","file":"wod.mjs","sourcesContent":["#!/usr/bin/env node\n/**\n * wod CLI Entry Point\n *\n * Dispatches to cliMain(process.argv.slice(2)).\n * Runs under Node (v18+) or Bun across all subcommands, including server and playground.\n */\nimport { cliMain } from '../cli/runner';\n\nvoid (async () => {\n const exitCode = await cliMain(process.argv.slice(2));\n process.exitCode = exitCode;\n})();\n"]}
@@ -1,5 +1,5 @@
1
1
  import { createRequire } from 'module';
2
- import { parseScript, whiteboardParser, isEffortDiscipline } from '@bitcobblers/wod-wiki-lang';
2
+ import { parseScript, whiteboardParser, parseEffortFile, parseEquipmentFile, createReferenceIndex, slugifyRefLabel, extractStatementRefs } from '@bitcobblers/wod-wiki-lang';
3
3
  import { reconcileNoteBlocks, KNOWN_SECTION_WIDGETS, parseDocumentSections, blockContentId } from '@bitcobblers/wod-wiki-core';
4
4
  import { parseDashboardNote, buildDashboardDocument, isDashboardWidgetType, resolveWidgetType, parseQuery } from '@bitcobblers/wod-wiki-wql';
5
5
  import { execSync } from 'child_process';
@@ -10,9 +10,10 @@ import { createHash } from 'crypto';
10
10
  if (typeof globalThis.require === 'undefined') {
11
11
  try { globalThis.require = createRequire(import.meta.url); } catch {}
12
12
  }
13
- var SEED_SCHEMA = 8;
13
+ var SEED_SCHEMA = 9;
14
14
  var SEED_REGISTERED_WIDGETS = KNOWN_SECTION_WIDGETS;
15
15
  var EFFORTS_CHUNK_ID = "efforts";
16
+ var EQUIPMENT_CHUNK_ID = "equipment";
16
17
  var CANVAS_CHUNK_ID = "canvas";
17
18
  var DASHBOARDS_CHUNK_ID = "dashboards";
18
19
  var TEMPLATE_CHUNK_ID = "template";
@@ -32,6 +33,7 @@ function seedNoteSourceId(path, content, chunkId) {
32
33
  if (chunkId === CANVAS_CHUNK_ID) return `guides:${canvasRouteSlug(content) ?? under}`;
33
34
  if (chunkId === DASHBOARDS_CHUNK_ID) return "dashboards";
34
35
  if (chunkId === EFFORTS_CHUNK_ID) return "efforts";
36
+ if (chunkId === EQUIPMENT_CHUNK_ID) return "equipment";
35
37
  if (chunkId === TEMPLATE_CHUNK_ID) return "playground";
36
38
  if (chunkId.startsWith("collection.")) {
37
39
  const catalog = catalogForChunkId(chunkId);
@@ -41,24 +43,6 @@ function seedNoteSourceId(path, content, chunkId) {
41
43
  return void 0;
42
44
  }
43
45
 
44
- // src/seedbuild/effort-slugs.ts
45
- function extractEffortSlugs(statements) {
46
- const slugs = /* @__PURE__ */ new Set();
47
- for (const statement of statements) {
48
- if (statement.exerciseId) {
49
- slugs.add(statement.exerciseId);
50
- } else {
51
- const effortMetric = statement.metrics?.getMetric?.("effort");
52
- const effort = effortMetric && typeof effortMetric.value === "string" ? effortMetric.value : void 0;
53
- if (effort) {
54
- const slug = effort.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
55
- if (slug) slugs.add(slug);
56
- }
57
- }
58
- }
59
- return Array.from(slugs);
60
- }
61
-
62
46
  // src/seedbuild/filename.ts
63
47
  var FILE_TITLE_STOP_WORDS = {
64
48
  and: true,
@@ -99,141 +83,6 @@ function fileToDisplayName(filename) {
99
83
  }
100
84
  return titled.join(" ");
101
85
  }
102
-
103
- // src/seedbuild/frontmatter.ts
104
- var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
105
- function unquote(value) {
106
- return value.replace(
107
- /^(['"])(.*)\1$/,
108
- (_match, quote, inner) => quote === '"' ? inner.replace(/\\(["\\])/g, "$1") : inner
109
- );
110
- }
111
- function parseFrontmatter(raw) {
112
- const match = raw.match(FRONTMATTER_RE);
113
- if (!match) return { meta: {}, body: raw };
114
- return { meta: parseMetaLines(match[1].split(/\r?\n/)), body: raw.slice(match[0].length) };
115
- }
116
- function parseMetaLines(lines) {
117
- const meta = {};
118
- for (let i = 0; i < lines.length; i++) {
119
- const keyMatch = lines[i].match(/^([A-Za-z][\w.-]*)\s*:(.*)$/);
120
- if (!keyMatch) continue;
121
- const key = keyMatch[1];
122
- const rawVal = keyMatch[2].trim();
123
- if (rawVal === "") {
124
- const items = [];
125
- while (i + 1 < lines.length) {
126
- const itemMatch = lines[i + 1].match(/^\s+-\s+(.+)$/);
127
- if (!itemMatch) break;
128
- items.push(itemMatch[1].trim());
129
- i++;
130
- }
131
- meta[key] = items.length > 0 ? items : "";
132
- } else {
133
- const wasQuoted = /^(['"])(.*)\1$/.test(rawVal);
134
- const value = unquote(rawVal);
135
- const num = Number(value);
136
- meta[key] = !wasQuoted && value !== "" && !isNaN(num) ? num : value;
137
- }
138
- }
139
- return meta;
140
- }
141
- function getScalar(meta, key) {
142
- const value = meta[key];
143
- return typeof value === "string" || typeof value === "number" ? value : void 0;
144
- }
145
- function getList(meta, key) {
146
- const value = meta[key];
147
- return Array.isArray(value) ? value : [];
148
- }
149
-
150
- // src/seedbuild/effort-file.ts
151
- function parseDiscipline(val) {
152
- if (!val) return void 0;
153
- const normalized = unquoteYaml(val.trim()).toLowerCase();
154
- if (isEffortDiscipline(normalized)) return normalized;
155
- console.warn(`[effort-file] unknown discipline '${val}' \u2014 treating as unset`);
156
- return void 0;
157
- }
158
- function unquoteYaml(val) {
159
- if (val.startsWith('"') && val.endsWith('"')) {
160
- return val.slice(1, -1).replace(/\\(["\\])/g, "$1");
161
- }
162
- if (val.startsWith("'") && val.endsWith("'")) {
163
- return val.slice(1, -1).replace(/\\'/g, "'");
164
- }
165
- return val;
166
- }
167
- function coerceNestedScalar(raw) {
168
- if (raw === "true") return true;
169
- if (raw === "false") return false;
170
- if (raw !== "" && !Number.isNaN(Number(raw))) return Number(raw);
171
- return raw;
172
- }
173
- function readNestedSection(lines, key) {
174
- const start = lines.findIndex((l) => new RegExp(`^${key}:\\s*$`).test(l));
175
- if (start === -1) return void 0;
176
- const result = {};
177
- let subKey = null;
178
- for (let i = start + 1; i < lines.length; i++) {
179
- const line = lines[i];
180
- if (/^\S/.test(line)) break;
181
- const trimmed = line.trim();
182
- if (!trimmed || trimmed.startsWith("#")) continue;
183
- const indent = line.length - line.trimStart().length;
184
- const m = trimmed.match(/^([\w.-]+):\s*(.*)$/);
185
- if (!m) continue;
186
- const [, k, rawVal] = m;
187
- if (indent === 2) {
188
- if (rawVal === "") {
189
- subKey = k;
190
- result[k] = {};
191
- } else {
192
- subKey = null;
193
- result[k] = coerceNestedScalar(rawVal);
194
- }
195
- } else if (indent >= 4 && subKey !== null) {
196
- result[subKey][k] = coerceNestedScalar(rawVal);
197
- }
198
- }
199
- return Object.keys(result).length > 0 ? result : void 0;
200
- }
201
- function parseEffortFile(raw) {
202
- const blockMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
203
- if (!blockMatch) return null;
204
- const { meta, body } = parseFrontmatter(raw);
205
- const innerLines = blockMatch[1].split(/\r?\n/);
206
- const id = getScalar(meta, "id");
207
- const slug = getScalar(meta, "slug");
208
- const label = getScalar(meta, "label");
209
- const aliases = getList(meta, "aliases");
210
- const baseAttrs = readNestedSection(innerLines, "baseAttributes");
211
- const hints = readNestedSection(innerLines, "hints");
212
- const derivation = readNestedSection(innerLines, "derivation");
213
- const metRaw = baseAttrs?.met;
214
- const met = typeof metRaw === "number" ? metRaw : Number(metRaw);
215
- if (!id || !slug || !label || metRaw === void 0 || Number.isNaN(met)) return null;
216
- const discipline = parseDiscipline(
217
- typeof baseAttrs?.discipline === "string" ? baseAttrs.discipline : void 0
218
- );
219
- const intensityTier = baseAttrs?.intensityTier;
220
- const trimmedBody = body.trim();
221
- return {
222
- id: String(id),
223
- slug: String(slug),
224
- label: String(label),
225
- aliases,
226
- baseAttributes: {
227
- met,
228
- ...discipline ? { discipline } : {},
229
- ...intensityTier ? { intensityTier } : {}
230
- },
231
- registrySource: "bundled",
232
- ...trimmedBody ? { body: trimmedBody } : {},
233
- ...hints ? { hints } : {},
234
- ...derivation ? { derivation } : {}
235
- };
236
- }
237
86
  var UNKNOWN_GIT_VERSION = {
238
87
  version: 0,
239
88
  versionLabel: "unknown",
@@ -312,6 +161,69 @@ ${manifest.chunks.map(rowHtml).join("\n")}
312
161
  writeFileSync(file, html, "utf8");
313
162
  return file;
314
163
  }
164
+
165
+ // src/seedbuild/frontmatter.ts
166
+ var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
167
+ function unquote(value) {
168
+ return value.replace(
169
+ /^(['"])(.*)\1$/,
170
+ (_match, quote, inner) => quote === '"' ? inner.replace(/\\(["\\])/g, "$1") : inner
171
+ );
172
+ }
173
+ function parseFrontmatter(raw) {
174
+ const match = raw.match(FRONTMATTER_RE);
175
+ if (!match) return { meta: {}, body: raw };
176
+ return { meta: parseMetaLines(match[1].split(/\r?\n/)), body: raw.slice(match[0].length) };
177
+ }
178
+ function parseMetaLines(lines) {
179
+ const meta = {};
180
+ for (let i = 0; i < lines.length; i++) {
181
+ const keyMatch = lines[i].match(/^([A-Za-z][\w.-]*)\s*:(.*)$/);
182
+ if (!keyMatch) continue;
183
+ const key = keyMatch[1];
184
+ const rawVal = keyMatch[2].trim();
185
+ if (rawVal === "") {
186
+ const items = [];
187
+ while (i + 1 < lines.length) {
188
+ const itemMatch = lines[i + 1].match(/^\s+-\s+(.+)$/);
189
+ if (!itemMatch) break;
190
+ items.push(itemMatch[1].trim());
191
+ i++;
192
+ }
193
+ meta[key] = items.length > 0 ? items : "";
194
+ } else {
195
+ const wasQuoted = /^(['"])(.*)\1$/.test(rawVal);
196
+ const value = unquote(rawVal);
197
+ const num = Number(value);
198
+ meta[key] = !wasQuoted && value !== "" && !isNaN(num) ? num : value;
199
+ }
200
+ }
201
+ return meta;
202
+ }
203
+ function getScalar(meta, key) {
204
+ const value = meta[key];
205
+ return typeof value === "string" || typeof value === "number" ? value : void 0;
206
+ }
207
+ function object(value) {
208
+ return typeof value === "object" && value !== null && !Array.isArray(value);
209
+ }
210
+ function nonnegative(value) {
211
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
212
+ }
213
+ function assertSeedManifest(value) {
214
+ if (!object(value) || value.schema !== SEED_SCHEMA) throw new Error("Incompatible seed schema; rebuild seed artifacts");
215
+ if (!nonnegative(value.version) || typeof value.builtAt !== "string" || !Number.isFinite(Date.parse(value.builtAt)) || !Array.isArray(value.chunks)) throw new Error("seed manifest failed shape validation");
216
+ for (const key of ["versionLabel", "commit", "name", "description"]) if (value[key] !== void 0 && typeof value[key] !== "string") throw new Error("invalid seed manifest metadata");
217
+ if (value.repositoryUrl !== void 0 && (typeof value.repositoryUrl !== "string" || !/^https:\/\//.test(value.repositoryUrl) || new URL(value.repositoryUrl).username || new URL(value.repositoryUrl).password)) throw new Error("invalid seed repository URL");
218
+ if (value.dirty !== void 0 && typeof value.dirty !== "boolean") throw new Error("invalid seed manifest metadata");
219
+ const ids = /* @__PURE__ */ new Set(), paths = /* @__PURE__ */ new Set();
220
+ for (const chunk of value.chunks) {
221
+ if (!object(chunk) || typeof chunk.id !== "string" || !chunk.id || typeof chunk.path !== "string" || !/^chunks\/[a-zA-Z0-9._-]+\.json$/.test(chunk.path) || typeof chunk.sha256 !== "string" || !/^[0-9a-f]{64}$/.test(chunk.sha256) || !nonnegative(chunk.bytes) || !nonnegative(chunk.count) || chunk.kind !== void 0 && chunk.kind !== "notes" || ids.has(chunk.id) || paths.has(chunk.path)) throw new Error("seed manifest failed shape validation");
222
+ ids.add(chunk.id);
223
+ paths.add(chunk.path);
224
+ }
225
+ return value;
226
+ }
315
227
  function listMarkdownFiles(dir, prefix = "") {
316
228
  if (!existsSync(dir)) return [];
317
229
  const out = [];
@@ -332,6 +244,7 @@ function chunkIdFor(relPath) {
332
244
  case "canvas":
333
245
  case "dashboards":
334
246
  case "efforts":
247
+ case "equipment":
335
248
  return corpus;
336
249
  case "collections":
337
250
  return parts.length === 3 ? `collection._root` : `collection.${slug}`;
@@ -350,7 +263,7 @@ function partitionIntoChunks(paths) {
350
263
  const id = chunkIdFor(p);
351
264
  if (id === null) {
352
265
  throw new Error(
353
- `[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.`
266
+ `[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.`
354
267
  );
355
268
  }
356
269
  const list = byId.get(id);
@@ -406,6 +319,18 @@ function getFileCreatedAt(file, repoRoot) {
406
319
  return 0;
407
320
  }
408
321
  }
322
+ function frontmatterDateToCreatedAt(raw, path) {
323
+ const value = getScalar(parseFrontmatter(raw).meta, "date");
324
+ if (value === void 0 || value === "") return void 0;
325
+ const text = String(value);
326
+ const parts = text.match(/^(\d{4})-(\d{2})-(\d{2})$/);
327
+ const ts = parts ? Date.parse(`${text}T12:00:00Z`) : NaN;
328
+ const valid = !!parts && Number.isFinite(ts) && new Date(ts).getUTCMonth() === Number(parts[2]) - 1 && new Date(ts).getUTCDate() === Number(parts[3]);
329
+ if (!valid) {
330
+ throw new Error(`[wod build] invalid frontmatter date '${text}' in '${path}' \u2014 expected a real calendar day (YYYY-MM-DD)`);
331
+ }
332
+ return ts;
333
+ }
409
334
  function feedDateToCreatedAt(dateKey) {
410
335
  if (!/^\d{4}-\d{2}-\d{2}$/.test(dateKey)) return 0;
411
336
  const ts = Date.parse(`${dateKey}T00:00:00Z`);
@@ -420,7 +345,7 @@ function compileSeedRow(path, content, metadata = {}) {
420
345
  const section = sections.find((section2) => section2.from === block.from && section2.to === block.to);
421
346
  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: 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 };
422
347
  });
423
- return { path, content, blocks, blockIndex, blockEfforts: [] };
348
+ return { path, content, blocks, blockIndex, blockEfforts: [], blockEquipment: [] };
424
349
  }
425
350
  function buildBlockIndexRows(markdownDir, corpusRoot) {
426
351
  const rows = [];
@@ -433,7 +358,7 @@ function buildBlockIndexRows(markdownDir, corpusRoot) {
433
358
  const catalog = parts[2];
434
359
  const routeId = chunkId === "canvas" ? canvasRouteSlug(content) ?? void 0 : chunkId.startsWith("collection.") ? stem.toLowerCase() === "readme" ? catalog : `${catalog}/${stem}` : chunkId.startsWith("feed.") ? parts.slice(1).join("/").replace(/\.md$/, "") : void 0;
435
360
  const sourceId = seedNoteSourceId(path, content, chunkId);
436
- const createdAt = chunkId.startsWith("feed.") ? feedDateToCreatedAt(parts.at(-2) ?? "") : getFileCreatedAt(path, corpusRoot);
361
+ const createdAt = frontmatterDateToCreatedAt(content, path) ?? (chunkId.startsWith("feed.") ? feedDateToCreatedAt(parts.at(-2) ?? "") : getFileCreatedAt(path, corpusRoot));
437
362
  rows.push(...compileSeedRow(path, content, { routeId, sourceId, createdAt }).blockIndex);
438
363
  }
439
364
  return rows;
@@ -516,41 +441,37 @@ function formatDiscoveredLabel(raw) {
516
441
  const clean = raw.replace(/[-_]+/g, " ").trim();
517
442
  return clean.split(/\s+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(" ");
518
443
  }
519
- function loadExistingEffortMap(markdownDir) {
520
- const slugs = /* @__PURE__ */ new Set();
521
- const aliasToCanonical = /* @__PURE__ */ new Map();
444
+ function loadCorpusReferenceCatalog(markdownDir) {
445
+ const efforts = [];
446
+ const equipment = [];
522
447
  const effortsDir = join(markdownDir, "efforts");
523
448
  for (const file of listMarkdownFiles(effortsDir)) {
524
- const raw = readFileSync(join(effortsDir, file), "utf8");
525
- const parsed = parseEffortFile(raw);
526
- if (!parsed) continue;
527
- slugs.add(parsed.slug);
528
- aliasToCanonical.set(parsed.slug, parsed.slug);
529
- aliasToCanonical.set(parsed.label.toLowerCase(), parsed.slug);
530
- aliasToCanonical.set(parsed.slug.replace(/-/g, " "), parsed.slug);
531
- for (const alias of parsed.aliases) {
532
- aliasToCanonical.set(alias.toLowerCase(), parsed.slug);
533
- aliasToCanonical.set(alias.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, ""), parsed.slug);
534
- }
449
+ const parsed = parseEffortFile(readFileSync(join(effortsDir, file), "utf8"));
450
+ if (parsed) efforts.push(parsed);
451
+ }
452
+ const equipmentDir = join(markdownDir, "equipment");
453
+ for (const file of listMarkdownFiles(equipmentDir)) {
454
+ const parsed = parseEquipmentFile(readFileSync(join(equipmentDir, file), "utf8"));
455
+ if (parsed) equipment.push(parsed);
535
456
  }
536
- return { slugs, aliasToCanonical };
457
+ return { efforts, equipment };
537
458
  }
538
459
  function discoverUncatalogedEfforts(markdownDir, corpusRoot) {
539
- const { slugs, aliasToCanonical } = loadExistingEffortMap(markdownDir);
460
+ const catalog = loadCorpusReferenceCatalog(markdownDir);
461
+ const index = createReferenceIndex(catalog);
540
462
  const blocks = buildBlockIndexRows(markdownDir, corpusRoot);
541
463
  const discovered = /* @__PURE__ */ new Map();
542
464
  for (const block of blocks) {
543
465
  if (block.dataType !== "wod" || !block.rawContent) continue;
544
466
  const section = parseDocumentSections(block.rawContent, { registeredWidgets: SEED_REGISTERED_WIDGETS }).find((section2) => section2.scriptBlock);
545
- const script = parseScript(section?.scriptBlock?.content ?? block.rawContent);
467
+ const script = parseScript(section?.scriptBlock?.content ?? block.rawContent, { references: index });
546
468
  for (const statement of script.statements) {
547
469
  const effortMetric = statement.metrics?.getMetric?.("effort");
548
470
  const effortLabel = (effortMetric && typeof effortMetric.value === "string" ? effortMetric.value : void 0) ?? statement.exerciseId;
549
471
  if (!effortLabel) continue;
550
- const slugified = effortLabel.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
472
+ const slugified = slugifyRefLabel(effortLabel);
551
473
  if (!slugified || NON_EFFORT_SLUGS[slugified]) continue;
552
- const canonical = aliasToCanonical.get(slugified) ?? aliasToCanonical.get(effortLabel.toLowerCase());
553
- if (canonical && slugs.has(canonical)) continue;
474
+ if (index.resolveEffort(slugified) || index.resolveEffort(effortLabel)) continue;
554
475
  const existing = discovered.get(slugified);
555
476
  if (existing) {
556
477
  existing.rawLabels.add(effortLabel);
@@ -600,17 +521,16 @@ ${aliases.join("\n")}` : "aliases: []",
600
521
  }
601
522
  function buildBlockEffortRows(markdownDir, corpusRoot, blocks = buildBlockIndexRows(markdownDir, corpusRoot)) {
602
523
  const efforts = [];
603
- const { aliasToCanonical } = loadExistingEffortMap(markdownDir);
524
+ const index = createReferenceIndex(loadCorpusReferenceCatalog(markdownDir));
604
525
  for (const block of blocks) {
605
526
  if (block.dataType !== "wod" || !block.rawContent) continue;
606
527
  const section = parseDocumentSections(block.rawContent, { registeredWidgets: SEED_REGISTERED_WIDGETS }).find((section2) => section2.scriptBlock);
607
- const script = parseScript(section?.scriptBlock?.content ?? block.rawContent);
608
- const effortSlugs = extractEffortSlugs(script.statements);
528
+ const script = parseScript(section?.scriptBlock?.content ?? block.rawContent, { references: index });
609
529
  const linked = /* @__PURE__ */ new Set();
610
- for (const rawSlug of effortSlugs) {
611
- const norm = rawSlug.toLowerCase().trim();
612
- const canonicalSlug = aliasToCanonical.get(norm) ?? aliasToCanonical.get(norm.replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "")) ?? norm.replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
613
- if (!canonicalSlug || NON_EFFORT_SLUGS[canonicalSlug] || NON_EFFORT_SLUGS[norm]) {
530
+ for (const rawSlug of extractStatementRefs(script.statements).efforts) {
531
+ const canonical = index.resolveEffort(rawSlug);
532
+ const canonicalSlug = canonical?.slug;
533
+ if (!canonicalSlug || NON_EFFORT_SLUGS[canonicalSlug] || NON_EFFORT_SLUGS[rawSlug]) {
614
534
  continue;
615
535
  }
616
536
  if (linked.has(canonicalSlug)) continue;
@@ -629,6 +549,32 @@ function buildBlockEffortRows(markdownDir, corpusRoot, blocks = buildBlockIndexR
629
549
  }
630
550
  return efforts;
631
551
  }
552
+ function buildBlockEquipmentRows(markdownDir, corpusRoot, blocks = buildBlockIndexRows(markdownDir, corpusRoot)) {
553
+ const links = [];
554
+ const index = createReferenceIndex(loadCorpusReferenceCatalog(markdownDir));
555
+ for (const block of blocks) {
556
+ if (block.dataType !== "wod" || !block.rawContent) continue;
557
+ const section = parseDocumentSections(block.rawContent, { registeredWidgets: SEED_REGISTERED_WIDGETS }).find((s) => s.scriptBlock);
558
+ const script = parseScript(section?.scriptBlock?.content ?? block.rawContent, { references: index });
559
+ const linked = /* @__PURE__ */ new Set();
560
+ for (const rawSlug of extractStatementRefs(script.statements).equipment) {
561
+ const canonicalSlug = index.resolveEquipment(rawSlug)?.slug;
562
+ if (!canonicalSlug || linked.has(canonicalSlug)) continue;
563
+ linked.add(canonicalSlug);
564
+ links.push({
565
+ id: `${block.id}:${canonicalSlug}`,
566
+ noteId: block.noteId,
567
+ blockId: block.blockId,
568
+ blockContentId: block.blockContentId,
569
+ equipmentSlug: canonicalSlug,
570
+ isStatic: block.isStatic,
571
+ sourcePath: block.sourcePath,
572
+ createdAt: block.createdAt
573
+ });
574
+ }
575
+ }
576
+ return links;
577
+ }
632
578
  function sha256Hex(data) {
633
579
  return createHash("sha256").update(data).digest("hex");
634
580
  }
@@ -639,6 +585,13 @@ function generateSeed(options = {}) {
639
585
  const markdownDir = options.markdownDir ?? join(corpusRoot, "markdown");
640
586
  const outDir = options.outDir ?? join(corpusRoot, "dist");
641
587
  const gitVersion = options.version !== void 0 ? { ...resolveGitVersion(corpusRoot), version: options.version } : resolveGitVersion(corpusRoot);
588
+ const packageFile = join(corpusRoot, "package.json");
589
+ const packageMetadata = existsSync(packageFile) ? JSON.parse(readFileSync(packageFile, "utf8")) : {};
590
+ const metadata = packageMetadata && typeof packageMetadata === "object" ? packageMetadata : {};
591
+ const repository = "repository" in metadata ? metadata.repository : void 0;
592
+ const repositoryUrl = typeof repository === "string" ? repository : repository && typeof repository === "object" && "url" in repository && typeof repository.url === "string" ? repository.url : void 0;
593
+ const webRepositoryUrl = repositoryUrl?.replace(/^git\+/, "").replace(/\.git$/, "");
594
+ const repositoryLink = webRepositoryUrl && URL.canParse(webRepositoryUrl) ? new URL(webRepositoryUrl) : void 0;
642
595
  if (options.scaffoldDiscoveredEfforts) {
643
596
  const discovered = discoverUncatalogedEfforts(markdownDir, corpusRoot);
644
597
  if (discovered.length > 0) {
@@ -673,9 +626,11 @@ ${shown}${more}`);
673
626
  const manifestChunks = [];
674
627
  const tBlockEfforts = Date.now();
675
628
  const effortRows = buildBlockEffortRows(markdownDir, corpusRoot, blockRows);
629
+ const equipmentRows = buildBlockEquipmentRows(markdownDir, corpusRoot, blockRows);
676
630
  passMs.blockEfforts = Date.now() - tBlockEfforts;
677
631
  const blocksByPath = /* @__PURE__ */ new Map();
678
632
  const effortsByPath = /* @__PURE__ */ new Map();
633
+ const equipmentByPath = /* @__PURE__ */ new Map();
679
634
  for (const row of blockRows) {
680
635
  const rows = blocksByPath.get(row.sourcePath) ?? [];
681
636
  rows.push(row);
@@ -686,6 +641,11 @@ ${shown}${more}`);
686
641
  rows.push(row);
687
642
  effortsByPath.set(row.sourcePath, rows);
688
643
  }
644
+ for (const row of equipmentRows) {
645
+ const rows = equipmentByPath.get(row.sourcePath) ?? [];
646
+ rows.push(row);
647
+ equipmentByPath.set(row.sourcePath, rows);
648
+ }
689
649
  let totalFiles = 0;
690
650
  const tWrite = Date.now();
691
651
  for (const { id, files } of groups) {
@@ -696,6 +656,7 @@ ${shown}${more}`);
696
656
  const compiled = compileSeedRow(p, content);
697
657
  compiled.blockIndex = blocksByPath.get(p) ?? compiled.blockIndex;
698
658
  compiled.blockEfforts = effortsByPath.get(p) ?? [];
659
+ compiled.blockEquipment = equipmentByPath.get(p) ?? [];
699
660
  return compiled;
700
661
  });
701
662
  const bytes = Buffer.from(JSON.stringify(rows), "utf8");
@@ -707,6 +668,9 @@ ${shown}${more}`);
707
668
  }
708
669
  passMs.write = Date.now() - tWrite;
709
670
  const manifest = {
671
+ name: "name" in metadata && typeof metadata.name === "string" ? metadata.name : void 0,
672
+ description: "description" in metadata && typeof metadata.description === "string" ? metadata.description : void 0,
673
+ repositoryUrl: repositoryLink?.protocol === "https:" && !repositoryLink.username && !repositoryLink.password ? repositoryLink.href : void 0,
710
674
  schema: SEED_SCHEMA,
711
675
  version: gitVersion.version,
712
676
  versionLabel: gitVersion.versionLabel,
@@ -715,6 +679,7 @@ ${shown}${more}`);
715
679
  builtAt: (/* @__PURE__ */ new Date()).toISOString(),
716
680
  chunks: manifestChunks
717
681
  };
682
+ assertSeedManifest(manifest);
718
683
  writeFileSync(join(outDir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
719
684
  const reportPath = writeBuildReport(outDir, {
720
685
  manifest,
@@ -724,6 +689,6 @@ ${shown}${more}`);
724
689
  return { manifest, outDir, totalFiles, reportPath, passMs };
725
690
  }
726
691
 
727
- export { SEED_SCHEMA, buildBlockEffortRows, buildBlockIndexRows, chunkIdFor, collectCorpusMarkdown, compileSeedRow, discoverUncatalogedEfforts, feedDateToCreatedAt, generateSeed, loadExistingEffortMap, partitionIntoChunks, scaffoldDiscoveredEfforts, validateDashboardNote, validateWodBlockSyntax, writeBuildReport };
728
- //# sourceMappingURL=chunk-FZKXOYMJ.mjs.map
729
- //# sourceMappingURL=chunk-FZKXOYMJ.mjs.map
692
+ export { SEED_SCHEMA, buildBlockEffortRows, buildBlockEquipmentRows, buildBlockIndexRows, chunkIdFor, collectCorpusMarkdown, compileSeedRow, discoverUncatalogedEfforts, feedDateToCreatedAt, frontmatterDateToCreatedAt, generateSeed, loadCorpusReferenceCatalog, partitionIntoChunks, scaffoldDiscoveredEfforts, validateDashboardNote, validateWodBlockSyntax, writeBuildReport };
693
+ //# sourceMappingURL=chunk-CVFMFD7D.mjs.map
694
+ //# sourceMappingURL=chunk-CVFMFD7D.mjs.map