@bitcobblers/wod-wiki-tools 0.0.0-stage → 0.36.2134

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 (74) hide show
  1. package/dist/bin/wod.cjs +3543 -0
  2. package/dist/bin/wod.cjs.map +1 -0
  3. package/dist/bin/wod.d.cts +1 -0
  4. package/dist/bin/wod.d.ts +1 -0
  5. package/dist/bin/wod.mjs +27 -0
  6. package/dist/bin/wod.mjs.map +1 -0
  7. package/dist/chunk-32FASQP5.mjs +123 -0
  8. package/dist/chunk-32FASQP5.mjs.map +1 -0
  9. package/dist/chunk-6L5ZDZJ7.mjs +15 -0
  10. package/dist/chunk-6L5ZDZJ7.mjs.map +1 -0
  11. package/dist/chunk-B3QJYJVI.mjs +2061 -0
  12. package/dist/chunk-B3QJYJVI.mjs.map +1 -0
  13. package/dist/chunk-EUQWEX6Z.mjs +253 -0
  14. package/dist/chunk-EUQWEX6Z.mjs.map +1 -0
  15. package/dist/chunk-K3GZZFAC.mjs +300 -0
  16. package/dist/chunk-K3GZZFAC.mjs.map +1 -0
  17. package/dist/compiler-KT6P3AC6.mjs +730 -0
  18. package/dist/compiler-KT6P3AC6.mjs.map +1 -0
  19. package/dist/index.cjs +3548 -0
  20. package/dist/index.cjs.map +1 -0
  21. package/dist/index.d.cts +356 -0
  22. package/dist/index.d.ts +356 -0
  23. package/dist/index.mjs +12 -0
  24. package/dist/index.mjs.map +1 -0
  25. package/dist/server/app.cjs +299 -0
  26. package/dist/server/app.cjs.map +1 -0
  27. package/dist/server/app.d.cts +24 -0
  28. package/dist/server/app.d.ts +24 -0
  29. package/dist/server/app.mjs +9 -0
  30. package/dist/server/app.mjs.map +1 -0
  31. package/dist/server-3TJZDKQC.mjs +11 -0
  32. package/dist/server-3TJZDKQC.mjs.map +1 -0
  33. package/dist/web/playground/ai-skills/parse.md +656 -0
  34. package/dist/web/playground/assets/main-Bhn9NHgQ.css +2 -0
  35. package/dist/web/playground/assets/main-Bj_mxNMk.js +212 -0
  36. package/dist/web/playground/assets/rolldown-runtime-C0FnF6B9.js +1 -0
  37. package/dist/web/playground/assets/statementBuilderFlow-DXlHS86r.js +2 -0
  38. package/dist/web/playground/assets/vendor-BlJqgFME.js +201 -0
  39. package/dist/web/playground/assets/vendor-codemirror-BtJgoBjf.js +40 -0
  40. package/dist/web/playground/fonts/Inter-Regular.woff2 +0 -0
  41. package/dist/web/playground/fonts/Inter-SemiBold.woff2 +0 -0
  42. package/dist/web/playground/images/hero/editing.png +0 -0
  43. package/dist/web/playground/images/hero/journal.png +0 -0
  44. package/dist/web/playground/images/hero/results.png +0 -0
  45. package/dist/web/playground/images/hero/timer.png +0 -0
  46. package/dist/web/playground/images/wod-wiki-logo-dark.png +0 -0
  47. package/dist/web/playground/images/wod-wiki-logo-light.png +0 -0
  48. package/dist/web/playground/index.html +60 -0
  49. package/dist/web/playground/seed/core/chunks/canvas.054cd7db.json +1 -0
  50. package/dist/web/playground/seed/core/chunks/template.e5e0e82d.json +1 -0
  51. package/dist/web/playground/seed/core/manifest.json +24 -0
  52. package/dist/web/playground/seed/core/report.html +43 -0
  53. package/dist/web/server/ai-skills/parse.md +656 -0
  54. package/dist/web/server/assets/main-Bhn9NHgQ.css +2 -0
  55. package/dist/web/server/assets/main-CfAVkURA.js +211 -0
  56. package/dist/web/server/assets/rolldown-runtime-C0FnF6B9.js +1 -0
  57. package/dist/web/server/assets/statementBuilderFlow-CXugzTXB.js +2 -0
  58. package/dist/web/server/assets/vendor-BlJqgFME.js +201 -0
  59. package/dist/web/server/assets/vendor-codemirror-BtJgoBjf.js +40 -0
  60. package/dist/web/server/fonts/Inter-Regular.woff2 +0 -0
  61. package/dist/web/server/fonts/Inter-SemiBold.woff2 +0 -0
  62. package/dist/web/server/images/hero/editing.png +0 -0
  63. package/dist/web/server/images/hero/journal.png +0 -0
  64. package/dist/web/server/images/hero/results.png +0 -0
  65. package/dist/web/server/images/hero/timer.png +0 -0
  66. package/dist/web/server/images/wod-wiki-logo-dark.png +0 -0
  67. package/dist/web/server/images/wod-wiki-logo-light.png +0 -0
  68. package/dist/web/server/index.html +60 -0
  69. package/dist/web/server/seed/core/chunks/canvas.054cd7db.json +1 -0
  70. package/dist/web/server/seed/core/chunks/template.e5e0e82d.json +1 -0
  71. package/dist/web/server/seed/core/manifest.json +24 -0
  72. package/dist/web/server/seed/core/report.html +43 -0
  73. package/package.json +46 -4
  74. package/README.md +0 -3
@@ -0,0 +1,730 @@
1
+ import { createRequire } from 'module';
2
+ import './chunk-6L5ZDZJ7.mjs';
3
+ import { parseScript, whiteboardParser, isEffortDiscipline } from '@bitcobblers/wod-wiki-lang';
4
+ import { reconcileNoteBlocks, KNOWN_SECTION_WIDGETS, parseDocumentSections, blockContentId } from '@bitcobblers/wod-wiki-core';
5
+ import { parseDashboardNote, buildDashboardDocument, isDashboardWidgetType, resolveWidgetType, parseQuery } from '@bitcobblers/wod-wiki-wql';
6
+ import { execSync } from 'child_process';
7
+ import { readFileSync, existsSync, mkdirSync, writeFileSync, rmSync, readdirSync } from 'fs';
8
+ import { join, relative } from 'path';
9
+ import { createHash } from 'crypto';
10
+
11
+ if (typeof globalThis.require === 'undefined') {
12
+ try { globalThis.require = createRequire(import.meta.url); } catch {}
13
+ }
14
+ var SEED_SCHEMA = 8;
15
+ var SEED_REGISTERED_WIDGETS = KNOWN_SECTION_WIDGETS;
16
+ var EFFORTS_CHUNK_ID = "efforts";
17
+ var CANVAS_CHUNK_ID = "canvas";
18
+ var DASHBOARDS_CHUNK_ID = "dashboards";
19
+ var TEMPLATE_CHUNK_ID = "template";
20
+ function catalogForChunkId(chunkId) {
21
+ const value = /^(?:collection|feed)\.(.+)$/.exec(chunkId)?.[1];
22
+ return value && value !== "_root" ? value : void 0;
23
+ }
24
+ function canvasRouteSlug(content) {
25
+ const fm = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content);
26
+ if (fm?.[1] && !/^template:\s*canvas\s*$/m.test(fm[1])) return null;
27
+ return fm?.[1]?.match(/^route:\s*(\S+)\s*$/m)?.[1]?.replace(/^\//, "") || null;
28
+ }
29
+ function seedNoteSourceId(path, content, chunkId) {
30
+ const parts = path.split("/");
31
+ const stem = (parts.at(-1) ?? "").replace(/\.md$/i, "");
32
+ const under = parts.slice(2).map((p) => p.replace(/\.md$/i, "")).join("/");
33
+ if (chunkId === CANVAS_CHUNK_ID) return `guides:${canvasRouteSlug(content) ?? under}`;
34
+ if (chunkId === DASHBOARDS_CHUNK_ID) return "dashboards";
35
+ if (chunkId === EFFORTS_CHUNK_ID) return "efforts";
36
+ if (chunkId === TEMPLATE_CHUNK_ID) return "playground";
37
+ if (chunkId.startsWith("collection.")) {
38
+ const catalog = catalogForChunkId(chunkId);
39
+ return !catalog ? "collection:_root" : stem.toLowerCase() === "readme" ? `collection:${catalog}` : `collection:${catalog}/${stem}`;
40
+ }
41
+ if (chunkId.startsWith("feed.")) return `feed:feeds/${under}`;
42
+ return void 0;
43
+ }
44
+
45
+ // src/seedbuild/effort-slugs.ts
46
+ function extractEffortSlugs(statements) {
47
+ const slugs = /* @__PURE__ */ new Set();
48
+ for (const statement of statements) {
49
+ if (statement.exerciseId) {
50
+ slugs.add(statement.exerciseId);
51
+ } else {
52
+ const effortMetric = statement.metrics?.getMetric?.("effort");
53
+ const effort = effortMetric && typeof effortMetric.value === "string" ? effortMetric.value : void 0;
54
+ if (effort) {
55
+ const slug = effort.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
56
+ if (slug) slugs.add(slug);
57
+ }
58
+ }
59
+ }
60
+ return Array.from(slugs);
61
+ }
62
+
63
+ // src/seedbuild/filename.ts
64
+ var FILE_TITLE_STOP_WORDS = {
65
+ and: true,
66
+ or: true,
67
+ of: true,
68
+ for: true,
69
+ to: true,
70
+ in: true,
71
+ on: true,
72
+ at: true,
73
+ by: true,
74
+ with: true,
75
+ the: true,
76
+ a: true,
77
+ an: true
78
+ };
79
+ function fileToDisplayName(filename) {
80
+ const base = filename.replace(/\.md$/, "");
81
+ if (base.toUpperCase() === "README") return "Overview";
82
+ const cleaned = base.replace(/^day-\d+-/, "");
83
+ const tokens = cleaned.split(/[-_]/);
84
+ const titled = [];
85
+ for (let i = 0; i < tokens.length; i++) {
86
+ const token = tokens[i];
87
+ const next = tokens[i + 1];
88
+ const after = tokens[i + 2];
89
+ const prev = tokens[i - 1];
90
+ if (/^\d{1,2}$/.test(token) && /^\d{1,2}$/.test(next ?? "") && !/^\d+$/.test(prev ?? "") && !/^\d+$/.test(after ?? "")) {
91
+ titled.push(`${token}.${next}`);
92
+ i++;
93
+ continue;
94
+ }
95
+ if (titled.length > 0 && FILE_TITLE_STOP_WORDS[token.toLowerCase()]) {
96
+ titled.push(token.toLowerCase());
97
+ continue;
98
+ }
99
+ titled.push(token.charAt(0).toUpperCase() + token.slice(1));
100
+ }
101
+ return titled.join(" ");
102
+ }
103
+
104
+ // src/seedbuild/frontmatter.ts
105
+ var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
106
+ function unquote(value) {
107
+ return value.replace(
108
+ /^(['"])(.*)\1$/,
109
+ (_match, quote, inner) => quote === '"' ? inner.replace(/\\(["\\])/g, "$1") : inner
110
+ );
111
+ }
112
+ function parseFrontmatter(raw) {
113
+ const match = raw.match(FRONTMATTER_RE);
114
+ if (!match) return { meta: {}, body: raw };
115
+ return { meta: parseMetaLines(match[1].split(/\r?\n/)), body: raw.slice(match[0].length) };
116
+ }
117
+ function parseMetaLines(lines) {
118
+ const meta = {};
119
+ for (let i = 0; i < lines.length; i++) {
120
+ const keyMatch = lines[i].match(/^([A-Za-z][\w.-]*)\s*:(.*)$/);
121
+ if (!keyMatch) continue;
122
+ const key = keyMatch[1];
123
+ const rawVal = keyMatch[2].trim();
124
+ if (rawVal === "") {
125
+ const items = [];
126
+ while (i + 1 < lines.length) {
127
+ const itemMatch = lines[i + 1].match(/^\s+-\s+(.+)$/);
128
+ if (!itemMatch) break;
129
+ items.push(itemMatch[1].trim());
130
+ i++;
131
+ }
132
+ meta[key] = items.length > 0 ? items : "";
133
+ } else {
134
+ const wasQuoted = /^(['"])(.*)\1$/.test(rawVal);
135
+ const value = unquote(rawVal);
136
+ const num = Number(value);
137
+ meta[key] = !wasQuoted && value !== "" && !isNaN(num) ? num : value;
138
+ }
139
+ }
140
+ return meta;
141
+ }
142
+ function getScalar(meta, key) {
143
+ const value = meta[key];
144
+ return typeof value === "string" || typeof value === "number" ? value : void 0;
145
+ }
146
+ function getList(meta, key) {
147
+ const value = meta[key];
148
+ return Array.isArray(value) ? value : [];
149
+ }
150
+
151
+ // src/seedbuild/effort-file.ts
152
+ function parseDiscipline(val) {
153
+ if (!val) return void 0;
154
+ const normalized = unquoteYaml(val.trim()).toLowerCase();
155
+ if (isEffortDiscipline(normalized)) return normalized;
156
+ console.warn(`[effort-file] unknown discipline '${val}' \u2014 treating as unset`);
157
+ return void 0;
158
+ }
159
+ function unquoteYaml(val) {
160
+ if (val.startsWith('"') && val.endsWith('"')) {
161
+ return val.slice(1, -1).replace(/\\(["\\])/g, "$1");
162
+ }
163
+ if (val.startsWith("'") && val.endsWith("'")) {
164
+ return val.slice(1, -1).replace(/\\'/g, "'");
165
+ }
166
+ return val;
167
+ }
168
+ function coerceNestedScalar(raw) {
169
+ if (raw === "true") return true;
170
+ if (raw === "false") return false;
171
+ if (raw !== "" && !Number.isNaN(Number(raw))) return Number(raw);
172
+ return raw;
173
+ }
174
+ function readNestedSection(lines, key) {
175
+ const start = lines.findIndex((l) => new RegExp(`^${key}:\\s*$`).test(l));
176
+ if (start === -1) return void 0;
177
+ const result = {};
178
+ let subKey = null;
179
+ for (let i = start + 1; i < lines.length; i++) {
180
+ const line = lines[i];
181
+ if (/^\S/.test(line)) break;
182
+ const trimmed = line.trim();
183
+ if (!trimmed || trimmed.startsWith("#")) continue;
184
+ const indent = line.length - line.trimStart().length;
185
+ const m = trimmed.match(/^([\w.-]+):\s*(.*)$/);
186
+ if (!m) continue;
187
+ const [, k, rawVal] = m;
188
+ if (indent === 2) {
189
+ if (rawVal === "") {
190
+ subKey = k;
191
+ result[k] = {};
192
+ } else {
193
+ subKey = null;
194
+ result[k] = coerceNestedScalar(rawVal);
195
+ }
196
+ } else if (indent >= 4 && subKey !== null) {
197
+ result[subKey][k] = coerceNestedScalar(rawVal);
198
+ }
199
+ }
200
+ return Object.keys(result).length > 0 ? result : void 0;
201
+ }
202
+ function parseEffortFile(raw) {
203
+ const blockMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
204
+ if (!blockMatch) return null;
205
+ const { meta, body } = parseFrontmatter(raw);
206
+ const innerLines = blockMatch[1].split(/\r?\n/);
207
+ const id = getScalar(meta, "id");
208
+ const slug = getScalar(meta, "slug");
209
+ const label = getScalar(meta, "label");
210
+ const aliases = getList(meta, "aliases");
211
+ const baseAttrs = readNestedSection(innerLines, "baseAttributes");
212
+ const hints = readNestedSection(innerLines, "hints");
213
+ const derivation = readNestedSection(innerLines, "derivation");
214
+ const metRaw = baseAttrs?.met;
215
+ const met = typeof metRaw === "number" ? metRaw : Number(metRaw);
216
+ if (!id || !slug || !label || metRaw === void 0 || Number.isNaN(met)) return null;
217
+ const discipline = parseDiscipline(
218
+ typeof baseAttrs?.discipline === "string" ? baseAttrs.discipline : void 0
219
+ );
220
+ const intensityTier = baseAttrs?.intensityTier;
221
+ const trimmedBody = body.trim();
222
+ return {
223
+ id: String(id),
224
+ slug: String(slug),
225
+ label: String(label),
226
+ aliases,
227
+ baseAttributes: {
228
+ met,
229
+ ...discipline ? { discipline } : {},
230
+ ...intensityTier ? { intensityTier } : {}
231
+ },
232
+ registrySource: "bundled",
233
+ ...trimmedBody ? { body: trimmedBody } : {},
234
+ ...hints ? { hints } : {},
235
+ ...derivation ? { derivation } : {}
236
+ };
237
+ }
238
+ var UNKNOWN_GIT_VERSION = {
239
+ version: 0,
240
+ versionLabel: "unknown",
241
+ commit: "",
242
+ dirty: false
243
+ };
244
+ function resolveGitVersion(repoRoot) {
245
+ try {
246
+ const run = (args) => execSync(`git ${args}`, { cwd: repoRoot, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
247
+ const count = Number(run("rev-list --count HEAD"));
248
+ return {
249
+ version: Number.isFinite(count) && count > 0 ? count : 0,
250
+ versionLabel: run("describe --tags --long --always --dirty"),
251
+ commit: run("rev-parse HEAD"),
252
+ dirty: run("status --porcelain") !== ""
253
+ };
254
+ } catch {
255
+ return UNKNOWN_GIT_VERSION;
256
+ }
257
+ }
258
+ function escapeHtml(s) {
259
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
260
+ }
261
+ function rowHtml(c) {
262
+ const kind = c.kind ?? "notes";
263
+ return `<tr><td>${escapeHtml(c.id)}</td><td>${kind}</td><td>${c.count}</td><td>${(c.bytes / 1024).toFixed(1)} KB</td><td><code>${c.sha256.slice(0, 8)}</code></td></tr>`;
264
+ }
265
+ function writeBuildReport(outDir, data) {
266
+ const { manifest, totalMs, passMs, fileCount } = data;
267
+ const totalBytes = manifest.chunks.reduce((s, c) => s + c.bytes, 0);
268
+ const dirtyBadge = manifest.dirty ? ' <strong style="color:#b45309">(dirty working tree \u2014 not reproducible from <code>' + escapeHtml(manifest.commit ?? "") + "</code>)</strong>" : "";
269
+ const html = `<!doctype html>
270
+ <html lang="en">
271
+ <head>
272
+ <meta charset="utf-8" />
273
+ <title>Seed build report</title>
274
+ <style>
275
+ body { font-family: system-ui, sans-serif; max-width: 960px; margin: 2rem auto; padding: 0 1rem; color: #1f2937; }
276
+ h1 { font-size: 1.4rem; } h2 { font-size: 1.1rem; margin-top: 2rem; }
277
+ table { border-collapse: collapse; width: 100%; font-size: 0.9rem; }
278
+ th, td { text-align: left; padding: 0.3rem 0.6rem; border-bottom: 1px solid #e5e7eb; }
279
+ code { font-size: 0.85em; }
280
+ dl { display: grid; grid-template-columns: max-content 1fr; gap: 0.2rem 1rem; font-size: 0.95rem; }
281
+ dt { font-weight: 600; }
282
+ </style>
283
+ </head>
284
+ <body>
285
+ <h1>Seed build report</h1>
286
+ <h2>Version</h2>
287
+ <dl>
288
+ <dt>version</dt><dd>${manifest.version} <code>${escapeHtml(manifest.versionLabel ?? "")}</code>${dirtyBadge}</dd>
289
+ <dt>commit</dt><dd><code>${escapeHtml(manifest.commit ?? "unknown")}</code></dd>
290
+ <dt>built at</dt><dd>${escapeHtml(manifest.builtAt)}</dd>
291
+ <dt>schema</dt><dd>v${manifest.schema}</dd>
292
+ </dl>
293
+ <h2>Benchmarks</h2>
294
+ <dl>
295
+ <dt>total</dt><dd>${totalMs} ms</dd>
296
+ <dt>corpus scan + partition</dt><dd>${passMs.scan} ms (${fileCount} files)</dd>
297
+ <dt>block-index derivation</dt><dd>${passMs.blockIndex} ms</dd>
298
+ <dt>block syntax gate</dt><dd>${passMs.parseGate} ms</dd>
299
+ <dt>block-efforts derivation</dt><dd>${passMs.blockEfforts} ms</dd>
300
+ <dt>chunk writes</dt><dd>${passMs.write} ms</dd>
301
+ </dl>
302
+ <h2>Chunks (${manifest.chunks.length}, ${(totalBytes / 1024).toFixed(0)} KB total)</h2>
303
+ <table>
304
+ <thead><tr><th>id</th><th>kind</th><th>rows</th><th>size</th><th>sha256</th></tr></thead>
305
+ <tbody>
306
+ ${manifest.chunks.map(rowHtml).join("\n")}
307
+ </tbody>
308
+ </table>
309
+ </body>
310
+ </html>
311
+ `;
312
+ const file = join(outDir, "report.html");
313
+ writeFileSync(file, html, "utf8");
314
+ return file;
315
+ }
316
+ function listMarkdownFiles(dir, prefix = "") {
317
+ if (!existsSync(dir)) return [];
318
+ const out = [];
319
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
320
+ const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
321
+ if (entry.isDirectory()) out.push(...listMarkdownFiles(join(dir, entry.name), rel));
322
+ else if (entry.isFile() && entry.name.endsWith(".md")) out.push(rel);
323
+ }
324
+ return out.sort();
325
+ }
326
+ function chunkIdFor(relPath) {
327
+ const parts = relPath.split("/");
328
+ if (parts[0] !== "markdown" || parts.length < 2) return null;
329
+ const corpus = parts[1];
330
+ if (parts.length === 2) return null;
331
+ const slug = parts[2]?.replace(/\.md$/, "");
332
+ switch (corpus) {
333
+ case "canvas":
334
+ case "dashboards":
335
+ case "efforts":
336
+ return corpus;
337
+ case "collections":
338
+ return parts.length === 3 ? `collection._root` : `collection.${slug}`;
339
+ case "feeds":
340
+ return parts.length === 3 ? `feed._root` : `feed.${slug}`;
341
+ default:
342
+ return null;
343
+ }
344
+ }
345
+ function collectCorpusMarkdown(markdownDir) {
346
+ return listMarkdownFiles(markdownDir).map((p) => `markdown/${p}`);
347
+ }
348
+ function partitionIntoChunks(paths) {
349
+ const byId = /* @__PURE__ */ new Map();
350
+ for (const p of paths) {
351
+ const id = chunkIdFor(p);
352
+ if (id === null) {
353
+ throw new Error(
354
+ `[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.`
355
+ );
356
+ }
357
+ const list = byId.get(id);
358
+ if (list) list.push(p);
359
+ else byId.set(id, [p]);
360
+ }
361
+ return [...byId.entries()].sort(([a], [b]) => a < b ? -1 : 1).map(([id, files]) => ({ id, files }));
362
+ }
363
+ function validateDashboardNote(raw) {
364
+ const problems = [];
365
+ const { meta } = parseFrontmatter(raw);
366
+ const { sections } = parseDashboardNote(raw);
367
+ const doc = buildDashboardDocument(sections, meta);
368
+ if (!doc.isDashboard) return ["file is not a dashboard note (missing dashboard frontmatter)"];
369
+ if (doc.title === void 0) problems.push("dashboard has no title");
370
+ if (doc.widgets.length < 3) problems.push(`dashboard has only ${doc.widgets.length} widgets (the locked format has \u2265 3)`);
371
+ const declared = new Set(doc.tokens.map((t) => t.name));
372
+ for (const widget of doc.widgets) {
373
+ const where = `widget '${widget.title ?? widget.body.slice(0, 40)}'`;
374
+ if (widget.title === void 0) problems.push(`${where}: missing title`);
375
+ if (!isDashboardWidgetType(resolveWidgetType(widget.type))) {
376
+ problems.push(`${where}: unknown widget type '${widget.type}'`);
377
+ }
378
+ const parsed = parseQuery(widget.body);
379
+ if (parsed.error !== void 0) problems.push(`${where}: WQL parse error: ${parsed.error}`);
380
+ for (const ref of [
381
+ ...widget.body.matchAll(/\$([A-Za-z][\w-]*)/g),
382
+ ...Object.values(widget.attributes).flatMap((v) => [...String(v).matchAll(/\$([A-Za-z][\w-]*)/g)])
383
+ ]) {
384
+ if (!declared.has(ref[1])) problems.push(`${where}: undeclared token $${ref[1]}`);
385
+ }
386
+ }
387
+ return problems;
388
+ }
389
+ function gateDashboards(groups, corpusRoot) {
390
+ for (const group of groups) {
391
+ if (group.id !== DASHBOARDS_CHUNK_ID) continue;
392
+ for (const p of group.files) {
393
+ const problems = validateDashboardNote(readFileSync(join(corpusRoot, p), "utf8"));
394
+ if (problems.length > 0) {
395
+ throw new Error(`[generate-seed] Dashboard validation failed for '${p}':
396
+ - ${problems.join("\n - ")}`);
397
+ }
398
+ }
399
+ }
400
+ }
401
+ function getFileCreatedAt(file, repoRoot) {
402
+ try {
403
+ const out = execSync(`git log -1 --format=%ct -- '${file}'`, { cwd: repoRoot, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
404
+ const ts = Number(out) * 1e3;
405
+ return Number.isFinite(ts) && ts > 0 ? ts : 0;
406
+ } catch {
407
+ return 0;
408
+ }
409
+ }
410
+ function feedDateToCreatedAt(dateKey) {
411
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(dateKey)) return 0;
412
+ const ts = Date.parse(`${dateKey}T00:00:00Z`);
413
+ return Number.isNaN(ts) ? 0 : ts;
414
+ }
415
+ function compileSeedRow(path, content, metadata = {}) {
416
+ let ordinal = 0;
417
+ const blocks = reconcileNoteBlocks(content, { sourceRevision: 1, registeredWidgets: SEED_REGISTERED_WIDGETS, createId: () => `seed:${path}:${ordinal++}` });
418
+ const noteId = metadata.routeId ?? path;
419
+ const sections = parseDocumentSections(content);
420
+ const blockIndex = Object.values(blocks).map((block, position) => {
421
+ const section = sections.find((section2) => section2.from === block.from && section2.to === block.to);
422
+ 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 };
423
+ });
424
+ return { path, content, blocks, blockIndex, blockEfforts: [] };
425
+ }
426
+ function buildBlockIndexRows(markdownDir, corpusRoot) {
427
+ const rows = [];
428
+ for (const path of collectCorpusMarkdown(markdownDir)) {
429
+ const chunkId = chunkIdFor(path);
430
+ if (!chunkId) continue;
431
+ const content = readFileSync(join(corpusRoot, path), "utf8");
432
+ const parts = path.split("/");
433
+ const stem = (parts.at(-1) ?? "").replace(/\.md$/, "");
434
+ const catalog = parts[2];
435
+ 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;
436
+ const sourceId = seedNoteSourceId(path, content, chunkId);
437
+ const createdAt = chunkId.startsWith("feed.") ? feedDateToCreatedAt(parts.at(-2) ?? "") : getFileCreatedAt(path, corpusRoot);
438
+ rows.push(...compileSeedRow(path, content, { routeId, sourceId, createdAt }).blockIndex);
439
+ }
440
+ return rows;
441
+ }
442
+ function validateWodBlockSyntax(blocks) {
443
+ const problems = [];
444
+ for (const block of blocks) {
445
+ if (block.dataType !== "wod" || !block.rawContent?.trim()) continue;
446
+ const section = parseDocumentSections(block.rawContent, { registeredWidgets: SEED_REGISTERED_WIDGETS }).find((s) => s.scriptBlock);
447
+ const source = section?.scriptBlock?.content ?? block.rawContent;
448
+ if (!source.trim()) continue;
449
+ const where = block.sourcePath ?? block.noteId;
450
+ if (section?.sport) {
451
+ const parsed = parseScript(source, { sport: section.sport });
452
+ for (const error of parsed.errors ?? []) {
453
+ problems.push(`${where}: ${error.message}`);
454
+ }
455
+ continue;
456
+ }
457
+ const doc = source.endsWith("\n") ? source : source + "\n";
458
+ const tree = whiteboardParser.parse(doc);
459
+ tree.iterate({
460
+ enter: (node) => {
461
+ if (!node.type.isError) return;
462
+ const line = doc.slice(0, node.from).split("\n").length;
463
+ const snippet = source.slice(node.from, Math.min(node.to, node.from + 60)).split("\n")[0];
464
+ problems.push(`${where}:${line} \`${snippet}\``);
465
+ }
466
+ });
467
+ }
468
+ return problems;
469
+ }
470
+ var NON_EFFORT_SLUGS = {
471
+ "amrap": true,
472
+ "emom": true,
473
+ "tabata": true,
474
+ "for-time": true,
475
+ "rounds": true,
476
+ "sets": true,
477
+ "repetitions": true,
478
+ "count-down": true,
479
+ "total-reps": true,
480
+ "warmup": true,
481
+ "cooldown": true,
482
+ "cool-down": true,
483
+ "transition": true,
484
+ "rest": true,
485
+ "break": true,
486
+ "work": true,
487
+ "i-suck-at-this": true,
488
+ "isuckatthis": true,
489
+ "movement": true,
490
+ "movement-you": true,
491
+ "selected-exercise": true,
492
+ "training-setup": true,
493
+ "grade-system": true,
494
+ "hangs": true,
495
+ "warm-light-wind": true,
496
+ "basin": true,
497
+ "rock-canyon": true,
498
+ "b-onsight": true,
499
+ "a-redpoint": true,
500
+ "d-redpoint": true,
501
+ "v0-flash": true,
502
+ "v2-flash": true,
503
+ "v7-redpoint": true,
504
+ "avgrpe-mean": true,
505
+ "calculate": true,
506
+ "totalload-sum": true,
507
+ "query": true,
508
+ "by": true,
509
+ "": true,
510
+ "-": true,
511
+ "...": true,
512
+ "x": true,
513
+ "s": true,
514
+ "bw": true
515
+ };
516
+ function formatDiscoveredLabel(raw) {
517
+ const clean = raw.replace(/[-_]+/g, " ").trim();
518
+ return clean.split(/\s+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(" ");
519
+ }
520
+ function loadExistingEffortMap(markdownDir) {
521
+ const slugs = /* @__PURE__ */ new Set();
522
+ const aliasToCanonical = /* @__PURE__ */ new Map();
523
+ const effortsDir = join(markdownDir, "efforts");
524
+ for (const file of listMarkdownFiles(effortsDir)) {
525
+ const raw = readFileSync(join(effortsDir, file), "utf8");
526
+ const parsed = parseEffortFile(raw);
527
+ if (!parsed) continue;
528
+ slugs.add(parsed.slug);
529
+ aliasToCanonical.set(parsed.slug, parsed.slug);
530
+ aliasToCanonical.set(parsed.label.toLowerCase(), parsed.slug);
531
+ aliasToCanonical.set(parsed.slug.replace(/-/g, " "), parsed.slug);
532
+ for (const alias of parsed.aliases) {
533
+ aliasToCanonical.set(alias.toLowerCase(), parsed.slug);
534
+ aliasToCanonical.set(alias.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, ""), parsed.slug);
535
+ }
536
+ }
537
+ return { slugs, aliasToCanonical };
538
+ }
539
+ function discoverUncatalogedEfforts(markdownDir, corpusRoot) {
540
+ const { slugs, aliasToCanonical } = loadExistingEffortMap(markdownDir);
541
+ const blocks = buildBlockIndexRows(markdownDir, corpusRoot);
542
+ const discovered = /* @__PURE__ */ new Map();
543
+ for (const block of blocks) {
544
+ if (block.dataType !== "wod" || !block.rawContent) continue;
545
+ const section = parseDocumentSections(block.rawContent, { registeredWidgets: SEED_REGISTERED_WIDGETS }).find((section2) => section2.scriptBlock);
546
+ const script = parseScript(section?.scriptBlock?.content ?? block.rawContent);
547
+ for (const statement of script.statements) {
548
+ const effortMetric = statement.metrics?.getMetric?.("effort");
549
+ const effortLabel = (effortMetric && typeof effortMetric.value === "string" ? effortMetric.value : void 0) ?? statement.exerciseId;
550
+ if (!effortLabel) continue;
551
+ const slugified = effortLabel.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
552
+ if (!slugified || NON_EFFORT_SLUGS[slugified]) continue;
553
+ const canonical = aliasToCanonical.get(slugified) ?? aliasToCanonical.get(effortLabel.toLowerCase());
554
+ if (canonical && slugs.has(canonical)) continue;
555
+ const existing = discovered.get(slugified);
556
+ if (existing) {
557
+ existing.rawLabels.add(effortLabel);
558
+ } else {
559
+ discovered.set(slugified, {
560
+ label: formatDiscoveredLabel(effortLabel),
561
+ rawLabels: /* @__PURE__ */ new Set([effortLabel])
562
+ });
563
+ }
564
+ }
565
+ }
566
+ return Array.from(discovered.entries()).map(([slug, data]) => ({
567
+ slug,
568
+ label: data.label,
569
+ rawLabels: Array.from(data.rawLabels)
570
+ }));
571
+ }
572
+ function scaffoldDiscoveredEfforts(discovered, markdownDir) {
573
+ const targetDir = join(markdownDir, "efforts", "discovered");
574
+ if (!existsSync(targetDir)) {
575
+ mkdirSync(targetDir, { recursive: true });
576
+ }
577
+ const createdPaths = [];
578
+ for (const effort of discovered) {
579
+ const filePath = join(targetDir, `${effort.slug}.md`);
580
+ if (existsSync(filePath)) continue;
581
+ const aliases = effort.rawLabels.filter((l) => l.toLowerCase() !== effort.label.toLowerCase()).map((l) => ` - "${l.replace(/"/g, '\\"')}"`);
582
+ const content = [
583
+ "---",
584
+ `id: effort-bundled-${effort.slug}`,
585
+ `slug: ${effort.slug}`,
586
+ `label: "${effort.label.replace(/"/g, '\\"')}"`,
587
+ aliases.length > 0 ? `aliases:
588
+ ${aliases.join("\n")}` : "aliases: []",
589
+ "baseAttributes:",
590
+ " met: 5.0",
591
+ " discipline: general",
592
+ " intensityTier: moderate",
593
+ "registrySource: bundled",
594
+ "---",
595
+ ""
596
+ ].join("\n");
597
+ writeFileSync(filePath, content, "utf8");
598
+ createdPaths.push(filePath);
599
+ }
600
+ return createdPaths;
601
+ }
602
+ function buildBlockEffortRows(markdownDir, corpusRoot, blocks = buildBlockIndexRows(markdownDir, corpusRoot)) {
603
+ const efforts = [];
604
+ const { aliasToCanonical } = loadExistingEffortMap(markdownDir);
605
+ for (const block of blocks) {
606
+ if (block.dataType !== "wod" || !block.rawContent) continue;
607
+ const section = parseDocumentSections(block.rawContent, { registeredWidgets: SEED_REGISTERED_WIDGETS }).find((section2) => section2.scriptBlock);
608
+ const script = parseScript(section?.scriptBlock?.content ?? block.rawContent);
609
+ const effortSlugs = extractEffortSlugs(script.statements);
610
+ const linked = /* @__PURE__ */ new Set();
611
+ for (const rawSlug of effortSlugs) {
612
+ const norm = rawSlug.toLowerCase().trim();
613
+ const canonicalSlug = aliasToCanonical.get(norm) ?? aliasToCanonical.get(norm.replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "")) ?? norm.replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
614
+ if (!canonicalSlug || NON_EFFORT_SLUGS[canonicalSlug] || NON_EFFORT_SLUGS[norm]) {
615
+ continue;
616
+ }
617
+ if (linked.has(canonicalSlug)) continue;
618
+ linked.add(canonicalSlug);
619
+ efforts.push({
620
+ id: `${block.id}:${canonicalSlug}`,
621
+ noteId: block.noteId,
622
+ blockId: block.blockId,
623
+ blockContentId: block.blockContentId,
624
+ effortSlug: canonicalSlug,
625
+ isStatic: block.isStatic,
626
+ sourcePath: block.sourcePath,
627
+ createdAt: block.createdAt
628
+ });
629
+ }
630
+ }
631
+ return efforts;
632
+ }
633
+ function sha256Hex(data) {
634
+ return createHash("sha256").update(data).digest("hex");
635
+ }
636
+ function generateSeed(options = {}) {
637
+ const t0 = Date.now();
638
+ const passMs = { scan: 0, blockIndex: 0, parseGate: 0, blockEfforts: 0, write: 0 };
639
+ const corpusRoot = options.corpusRoot ?? process.cwd();
640
+ const markdownDir = options.markdownDir ?? join(corpusRoot, "markdown");
641
+ const outDir = options.outDir ?? join(corpusRoot, "dist");
642
+ const gitVersion = options.version !== void 0 ? { ...resolveGitVersion(corpusRoot), version: options.version } : resolveGitVersion(corpusRoot);
643
+ if (options.scaffoldDiscoveredEfforts) {
644
+ const discovered = discoverUncatalogedEfforts(markdownDir, corpusRoot);
645
+ if (discovered.length > 0) {
646
+ scaffoldDiscoveredEfforts(discovered, markdownDir);
647
+ }
648
+ }
649
+ const tScan = Date.now();
650
+ const paths = collectCorpusMarkdown(markdownDir);
651
+ const groups = partitionIntoChunks(paths);
652
+ gateDashboards(groups, corpusRoot);
653
+ if (options.templateFile && existsSync(options.templateFile)) {
654
+ const templateRowPath = options.templateRowPath ?? relative(corpusRoot, options.templateFile).split("\\").join("/");
655
+ groups.push({ id: TEMPLATE_CHUNK_ID, files: [templateRowPath] });
656
+ }
657
+ passMs.scan = Date.now() - tScan;
658
+ const tBlockIndex = Date.now();
659
+ const blockRows = buildBlockIndexRows(markdownDir, corpusRoot);
660
+ passMs.blockIndex = Date.now() - tBlockIndex;
661
+ const tParseGate = Date.now();
662
+ const syntaxProblems = validateWodBlockSyntax(blockRows);
663
+ passMs.parseGate = Date.now() - tParseGate;
664
+ if (syntaxProblems.length > 0) {
665
+ const shown = syntaxProblems.slice(0, 20).map((p) => ` ${p}`).join("\n");
666
+ const more = syntaxProblems.length > 20 ? `
667
+ \u2026 and ${syntaxProblems.length - 20} more` : "";
668
+ throw new Error(`[wod build] block syntax gate failed \u2014 ${syntaxProblems.length} block(s) do not parse:
669
+ ${shown}${more}`);
670
+ }
671
+ const chunksDir = join(outDir, "chunks");
672
+ rmSync(outDir, { recursive: true, force: true });
673
+ mkdirSync(chunksDir, { recursive: true });
674
+ const manifestChunks = [];
675
+ const tBlockEfforts = Date.now();
676
+ const effortRows = buildBlockEffortRows(markdownDir, corpusRoot, blockRows);
677
+ passMs.blockEfforts = Date.now() - tBlockEfforts;
678
+ const blocksByPath = /* @__PURE__ */ new Map();
679
+ const effortsByPath = /* @__PURE__ */ new Map();
680
+ for (const row of blockRows) {
681
+ const rows = blocksByPath.get(row.sourcePath) ?? [];
682
+ rows.push(row);
683
+ blocksByPath.set(row.sourcePath, rows);
684
+ }
685
+ for (const row of effortRows) {
686
+ const rows = effortsByPath.get(row.sourcePath) ?? [];
687
+ rows.push(row);
688
+ effortsByPath.set(row.sourcePath, rows);
689
+ }
690
+ let totalFiles = 0;
691
+ const tWrite = Date.now();
692
+ for (const { id, files } of groups) {
693
+ const templateSource = id === TEMPLATE_CHUNK_ID ? options.templateFile : void 0;
694
+ const rows = files.map((p) => {
695
+ const raw = readFileSync(templateSource ?? join(corpusRoot, p), "utf8");
696
+ const content = id === TEMPLATE_CHUNK_ID ? raw.replace(/\$CURSOR/g, "") : raw;
697
+ const compiled = compileSeedRow(p, content);
698
+ compiled.blockIndex = blocksByPath.get(p) ?? compiled.blockIndex;
699
+ compiled.blockEfforts = effortsByPath.get(p) ?? [];
700
+ return compiled;
701
+ });
702
+ const bytes = Buffer.from(JSON.stringify(rows), "utf8");
703
+ const sha = sha256Hex(bytes);
704
+ const file = `chunks/${id}.${sha.slice(0, 8)}.json`;
705
+ writeFileSync(join(outDir, file), bytes);
706
+ manifestChunks.push({ id, path: file, sha256: sha, bytes: bytes.length, count: rows.length });
707
+ totalFiles += rows.length;
708
+ }
709
+ passMs.write = Date.now() - tWrite;
710
+ const manifest = {
711
+ schema: SEED_SCHEMA,
712
+ version: gitVersion.version,
713
+ versionLabel: gitVersion.versionLabel,
714
+ commit: gitVersion.commit,
715
+ dirty: gitVersion.dirty,
716
+ builtAt: (/* @__PURE__ */ new Date()).toISOString(),
717
+ chunks: manifestChunks
718
+ };
719
+ writeFileSync(join(outDir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
720
+ const reportPath = writeBuildReport(outDir, {
721
+ manifest,
722
+ totalMs: Date.now() - t0,
723
+ passMs,
724
+ fileCount: paths.length});
725
+ return { manifest, outDir, totalFiles, reportPath, passMs };
726
+ }
727
+
728
+ export { buildBlockEffortRows, buildBlockIndexRows, chunkIdFor, collectCorpusMarkdown, compileSeedRow, discoverUncatalogedEfforts, feedDateToCreatedAt, generateSeed, loadExistingEffortMap, partitionIntoChunks, scaffoldDiscoveredEfforts, validateDashboardNote, validateWodBlockSyntax };
729
+ //# sourceMappingURL=compiler-KT6P3AC6.mjs.map
730
+ //# sourceMappingURL=compiler-KT6P3AC6.mjs.map