@bitcobblers/wod-wiki-engine 0.35.2129 → 0.36.2133

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,811 +1,11 @@
1
1
  'use strict';
2
2
 
3
- var wodWikiLang = require('@bitcobblers/wod-wiki-lang');
4
3
  var wodWikiCore = require('@bitcobblers/wod-wiki-core');
5
- var child_process = require('child_process');
6
- var fs = require('fs');
7
- var path = require('path');
8
- var wodWikiWql = require('@bitcobblers/wod-wiki-wql');
9
- var crypto$1 = require('crypto');
4
+ var wodWikiLang = require('@bitcobblers/wod-wiki-lang');
10
5
  var react = require('@bitcobblers/wod-wiki-lang/react');
11
- var wodWikiStorage = require('@bitcobblers/wod-wiki-storage');
12
-
13
- var __defProp = Object.defineProperty;
14
- var __getOwnPropNames = Object.getOwnPropertyNames;
15
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
16
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
17
- }) : x)(function(x) {
18
- if (typeof require !== "undefined") return require.apply(this, arguments);
19
- throw Error('Dynamic require of "' + x + '" is not supported');
20
- });
21
- var __esm = (fn, res) => function __init() {
22
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
23
- };
24
- var __export = (target, all) => {
25
- for (var name in all)
26
- __defProp(target, name, { get: all[name], enumerable: true });
27
- };
28
-
29
- // src/cli/seedbuild/frontmatter.ts
30
- function unquote(value) {
31
- return value.replace(
32
- /^(['"])(.*)\1$/,
33
- (_match, quote, inner) => quote === '"' ? inner.replace(/\\(["\\])/g, "$1") : inner
34
- );
35
- }
36
- function parseFrontmatter(raw) {
37
- const match = raw.match(FRONTMATTER_RE);
38
- if (!match) return { meta: {}, body: raw };
39
- return { meta: parseMetaLines(match[1].split(/\r?\n/)), body: raw.slice(match[0].length) };
40
- }
41
- function parseMetaLines(lines) {
42
- const meta = {};
43
- for (let i = 0; i < lines.length; i++) {
44
- const keyMatch = lines[i].match(/^([A-Za-z][\w.-]*)\s*:(.*)$/);
45
- if (!keyMatch) continue;
46
- const key = keyMatch[1];
47
- const rawVal = keyMatch[2].trim();
48
- if (rawVal === "") {
49
- const items = [];
50
- while (i + 1 < lines.length) {
51
- const itemMatch = lines[i + 1].match(/^\s+-\s+(.+)$/);
52
- if (!itemMatch) break;
53
- items.push(itemMatch[1].trim());
54
- i++;
55
- }
56
- meta[key] = items.length > 0 ? items : "";
57
- } else {
58
- const wasQuoted = /^(['"])(.*)\1$/.test(rawVal);
59
- const value = unquote(rawVal);
60
- const num2 = Number(value);
61
- meta[key] = !wasQuoted && value !== "" && !isNaN(num2) ? num2 : value;
62
- }
63
- }
64
- return meta;
65
- }
66
- function getScalar(meta, key) {
67
- const value = meta[key];
68
- return typeof value === "string" || typeof value === "number" ? value : void 0;
69
- }
70
- function getList(meta, key) {
71
- const value = meta[key];
72
- return Array.isArray(value) ? value : [];
73
- }
74
- var FRONTMATTER_RE;
75
- var init_frontmatter = __esm({
76
- "src/cli/seedbuild/frontmatter.ts"() {
77
- FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
78
- }
79
- });
80
- function parseDiscipline(val) {
81
- if (!val) return void 0;
82
- const normalized = unquoteYaml(val.trim()).toLowerCase();
83
- if (wodWikiLang.isEffortDiscipline(normalized)) return normalized;
84
- console.warn(`[effort-file] unknown discipline '${val}' \u2014 treating as unset`);
85
- return void 0;
86
- }
87
- function unquoteYaml(val) {
88
- if (val.startsWith('"') && val.endsWith('"')) {
89
- return val.slice(1, -1).replace(/\\(["\\])/g, "$1");
90
- }
91
- if (val.startsWith("'") && val.endsWith("'")) {
92
- return val.slice(1, -1).replace(/\\'/g, "'");
93
- }
94
- return val;
95
- }
96
- function coerceNestedScalar(raw) {
97
- if (raw === "true") return true;
98
- if (raw === "false") return false;
99
- if (raw !== "" && !Number.isNaN(Number(raw))) return Number(raw);
100
- return raw;
101
- }
102
- function readNestedSection(lines, key) {
103
- const start = lines.findIndex((l) => new RegExp(`^${key}:\\s*$`).test(l));
104
- if (start === -1) return void 0;
105
- const result = {};
106
- let subKey = null;
107
- for (let i = start + 1; i < lines.length; i++) {
108
- const line = lines[i];
109
- if (/^\S/.test(line)) break;
110
- const trimmed = line.trim();
111
- if (!trimmed || trimmed.startsWith("#")) continue;
112
- const indent = line.length - line.trimStart().length;
113
- const m = trimmed.match(/^([\w.-]+):\s*(.*)$/);
114
- if (!m) continue;
115
- const [, k, rawVal] = m;
116
- if (indent === 2) {
117
- if (rawVal === "") {
118
- subKey = k;
119
- result[k] = {};
120
- } else {
121
- subKey = null;
122
- result[k] = coerceNestedScalar(rawVal);
123
- }
124
- } else if (indent >= 4 && subKey !== null) {
125
- result[subKey][k] = coerceNestedScalar(rawVal);
126
- }
127
- }
128
- return Object.keys(result).length > 0 ? result : void 0;
129
- }
130
- function parseEffortFile(raw) {
131
- const blockMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
132
- if (!blockMatch) return null;
133
- const { meta, body } = parseFrontmatter(raw);
134
- const innerLines = blockMatch[1].split(/\r?\n/);
135
- const id = getScalar(meta, "id");
136
- const slug = getScalar(meta, "slug");
137
- const label = getScalar(meta, "label");
138
- const aliases = getList(meta, "aliases");
139
- const baseAttrs = readNestedSection(innerLines, "baseAttributes");
140
- const hints = readNestedSection(innerLines, "hints");
141
- const derivation = readNestedSection(innerLines, "derivation");
142
- const metRaw = baseAttrs?.met;
143
- const met = typeof metRaw === "number" ? metRaw : Number(metRaw);
144
- if (!id || !slug || !label || metRaw === void 0 || Number.isNaN(met)) return null;
145
- const discipline = parseDiscipline(
146
- typeof baseAttrs?.discipline === "string" ? baseAttrs.discipline : void 0
147
- );
148
- const intensityTier = baseAttrs?.intensityTier;
149
- const trimmedBody = body.trim();
150
- return {
151
- id: String(id),
152
- slug: String(slug),
153
- label: String(label),
154
- aliases,
155
- baseAttributes: {
156
- met,
157
- ...discipline ? { discipline } : {},
158
- ...intensityTier ? { intensityTier } : {}
159
- },
160
- registrySource: "bundled",
161
- ...trimmedBody ? { body: trimmedBody } : {},
162
- ...hints ? { hints } : {},
163
- ...derivation ? { derivation } : {}
164
- };
165
- }
166
- var init_effort_file = __esm({
167
- "src/cli/seedbuild/effort-file.ts"() {
168
- init_frontmatter();
169
- }
170
- });
171
- function seedMetaKey(sourceId) {
172
- return `seed:${sourceId}`;
173
- }
174
- function emptySeedMeta(key) {
175
- return { key, schema: exports.SEED_SCHEMA, seedVersion: 0, builtAt: "", importedAt: 0, chunks: {}, claim: null };
176
- }
177
- function catalogForChunkId(chunkId) {
178
- const value = /^(?:collection|feed)\.(.+)$/.exec(chunkId)?.[1];
179
- return value && value !== "_root" ? value : void 0;
180
- }
181
- function canvasRouteSlug(content) {
182
- const fm = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content);
183
- if (fm?.[1] && !/^template:\s*canvas\s*$/m.test(fm[1])) return null;
184
- return fm?.[1]?.match(/^route:\s*(\S+)\s*$/m)?.[1]?.replace(/^\//, "") || null;
185
- }
186
- function seedNoteSourceId(path, content, chunkId) {
187
- const parts = path.split("/");
188
- const stem = (parts.at(-1) ?? "").replace(/\.md$/i, "");
189
- const under = parts.slice(2).map((p) => p.replace(/\.md$/i, "")).join("/");
190
- if (chunkId === exports.CANVAS_CHUNK_ID) return `guides:${canvasRouteSlug(content) ?? under}`;
191
- if (chunkId === exports.DASHBOARDS_CHUNK_ID) return "dashboards";
192
- if (chunkId === exports.EFFORTS_CHUNK_ID) return "efforts";
193
- if (chunkId === exports.TEMPLATE_CHUNK_ID) return "playground";
194
- if (chunkId.startsWith("collection.")) {
195
- const catalog = catalogForChunkId(chunkId);
196
- return !catalog ? "collection:_root" : stem.toLowerCase() === "readme" ? `collection:${catalog}` : `collection:${catalog}/${stem}`;
197
- }
198
- if (chunkId.startsWith("feed.")) return `feed:feeds/${under}`;
199
- return void 0;
200
- }
201
- exports.SEED_SCHEMA = void 0; exports.SEED_REGISTERED_WIDGETS = void 0; exports.SEED_BROADCAST_CHANNEL = void 0; exports.EFFORTS_CHUNK_ID = void 0; exports.CANVAS_CHUNK_ID = void 0; exports.DASHBOARDS_CHUNK_ID = void 0; exports.TEMPLATE_CHUNK_ID = void 0;
202
- var init_contract = __esm({
203
- "src/cli/seedbuild/contract.ts"() {
204
- exports.SEED_SCHEMA = 8;
205
- exports.SEED_REGISTERED_WIDGETS = wodWikiCore.KNOWN_SECTION_WIDGETS;
206
- exports.SEED_BROADCAST_CHANNEL = "wodwiki.seed";
207
- exports.EFFORTS_CHUNK_ID = "efforts";
208
- exports.CANVAS_CHUNK_ID = "canvas";
209
- exports.DASHBOARDS_CHUNK_ID = "dashboards";
210
- exports.TEMPLATE_CHUNK_ID = "template";
211
- }
212
- });
213
-
214
- // src/cli/seedbuild/effort-slugs.ts
215
- function extractEffortSlugs2(statements) {
216
- const slugs = /* @__PURE__ */ new Set();
217
- for (const statement of statements) {
218
- if (statement.exerciseId) {
219
- slugs.add(statement.exerciseId);
220
- } else {
221
- const effortMetric = statement.metrics?.getMetric?.("effort");
222
- const effort = effortMetric && typeof effortMetric.value === "string" ? effortMetric.value : void 0;
223
- if (effort) {
224
- const slug = effort.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
225
- if (slug) slugs.add(slug);
226
- }
227
- }
228
- }
229
- return Array.from(slugs);
230
- }
231
- var init_effort_slugs = __esm({
232
- "src/cli/seedbuild/effort-slugs.ts"() {
233
- }
234
- });
235
-
236
- // src/cli/seedbuild/filename.ts
237
- function fileToDisplayName(filename) {
238
- const base = filename.replace(/\.md$/, "");
239
- if (base.toUpperCase() === "README") return "Overview";
240
- const cleaned = base.replace(/^day-\d+-/, "");
241
- const tokens = cleaned.split(/[-_]/);
242
- const titled = [];
243
- for (let i = 0; i < tokens.length; i++) {
244
- const token = tokens[i];
245
- const next = tokens[i + 1];
246
- const after = tokens[i + 2];
247
- const prev = tokens[i - 1];
248
- if (/^\d{1,2}$/.test(token) && /^\d{1,2}$/.test(next ?? "") && !/^\d+$/.test(prev ?? "") && !/^\d+$/.test(after ?? "")) {
249
- titled.push(`${token}.${next}`);
250
- i++;
251
- continue;
252
- }
253
- if (titled.length > 0 && FILE_TITLE_STOP_WORDS[token.toLowerCase()]) {
254
- titled.push(token.toLowerCase());
255
- continue;
256
- }
257
- titled.push(token.charAt(0).toUpperCase() + token.slice(1));
258
- }
259
- return titled.join(" ");
260
- }
261
- var FILE_TITLE_STOP_WORDS;
262
- var init_filename = __esm({
263
- "src/cli/seedbuild/filename.ts"() {
264
- FILE_TITLE_STOP_WORDS = {
265
- and: true,
266
- or: true,
267
- of: true,
268
- for: true,
269
- to: true,
270
- in: true,
271
- on: true,
272
- at: true,
273
- by: true,
274
- with: true,
275
- the: true,
276
- a: true,
277
- an: true
278
- };
279
- }
280
- });
281
- function resolveGitVersion(repoRoot) {
282
- try {
283
- const run = (args) => child_process.execSync(`git ${args}`, { cwd: repoRoot, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
284
- const count = Number(run("rev-list --count HEAD"));
285
- return {
286
- version: Number.isFinite(count) && count > 0 ? count : 0,
287
- versionLabel: run("describe --tags --long --always --dirty"),
288
- commit: run("rev-parse HEAD"),
289
- dirty: run("status --porcelain") !== ""
290
- };
291
- } catch {
292
- return UNKNOWN_GIT_VERSION;
293
- }
294
- }
295
- var UNKNOWN_GIT_VERSION;
296
- var init_git_version = __esm({
297
- "src/cli/seedbuild/git-version.ts"() {
298
- UNKNOWN_GIT_VERSION = {
299
- version: 0,
300
- versionLabel: "unknown",
301
- commit: "",
302
- dirty: false
303
- };
304
- }
305
- });
306
- function escapeHtml(s) {
307
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
308
- }
309
- function rowHtml(c) {
310
- const kind = c.kind ?? "notes";
311
- 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>`;
312
- }
313
- function writeBuildReport(outDir, data) {
314
- const { manifest, totalMs, passMs, fileCount } = data;
315
- const totalBytes = manifest.chunks.reduce((s, c) => s + c.bytes, 0);
316
- const dirtyBadge = manifest.dirty ? ' <strong style="color:#b45309">(dirty working tree \u2014 not reproducible from <code>' + escapeHtml(manifest.commit ?? "") + "</code>)</strong>" : "";
317
- const html = `<!doctype html>
318
- <html lang="en">
319
- <head>
320
- <meta charset="utf-8" />
321
- <title>Seed build report</title>
322
- <style>
323
- body { font-family: system-ui, sans-serif; max-width: 960px; margin: 2rem auto; padding: 0 1rem; color: #1f2937; }
324
- h1 { font-size: 1.4rem; } h2 { font-size: 1.1rem; margin-top: 2rem; }
325
- table { border-collapse: collapse; width: 100%; font-size: 0.9rem; }
326
- th, td { text-align: left; padding: 0.3rem 0.6rem; border-bottom: 1px solid #e5e7eb; }
327
- code { font-size: 0.85em; }
328
- dl { display: grid; grid-template-columns: max-content 1fr; gap: 0.2rem 1rem; font-size: 0.95rem; }
329
- dt { font-weight: 600; }
330
- </style>
331
- </head>
332
- <body>
333
- <h1>Seed build report</h1>
334
- <h2>Version</h2>
335
- <dl>
336
- <dt>version</dt><dd>${manifest.version} <code>${escapeHtml(manifest.versionLabel ?? "")}</code>${dirtyBadge}</dd>
337
- <dt>commit</dt><dd><code>${escapeHtml(manifest.commit ?? "unknown")}</code></dd>
338
- <dt>built at</dt><dd>${escapeHtml(manifest.builtAt)}</dd>
339
- <dt>schema</dt><dd>v${manifest.schema}</dd>
340
- </dl>
341
- <h2>Benchmarks</h2>
342
- <dl>
343
- <dt>total</dt><dd>${totalMs} ms</dd>
344
- <dt>corpus scan + partition</dt><dd>${passMs.scan} ms (${fileCount} files)</dd>
345
- <dt>block-index derivation</dt><dd>${passMs.blockIndex} ms</dd>
346
- <dt>block syntax gate</dt><dd>${passMs.parseGate} ms</dd>
347
- <dt>block-efforts derivation</dt><dd>${passMs.blockEfforts} ms</dd>
348
- <dt>chunk writes</dt><dd>${passMs.write} ms</dd>
349
- </dl>
350
- <h2>Chunks (${manifest.chunks.length}, ${(totalBytes / 1024).toFixed(0)} KB total)</h2>
351
- <table>
352
- <thead><tr><th>id</th><th>kind</th><th>rows</th><th>size</th><th>sha256</th></tr></thead>
353
- <tbody>
354
- ${manifest.chunks.map(rowHtml).join("\n")}
355
- </tbody>
356
- </table>
357
- </body>
358
- </html>
359
- `;
360
- const file = path.join(outDir, "report.html");
361
- fs.writeFileSync(file, html, "utf8");
362
- return file;
363
- }
364
- var init_report = __esm({
365
- "src/cli/seedbuild/report.ts"() {
366
- }
367
- });
6
+ var wodWikiWql = require('@bitcobblers/wod-wiki-wql');
368
7
 
369
- // src/cli/seedbuild/compiler.ts
370
- var compiler_exports = {};
371
- __export(compiler_exports, {
372
- buildBlockEffortRows: () => buildBlockEffortRows,
373
- buildBlockIndexRows: () => buildBlockIndexRows,
374
- chunkIdFor: () => chunkIdFor,
375
- collectCorpusMarkdown: () => collectCorpusMarkdown,
376
- compileSeedRow: () => compileSeedRow,
377
- discoverUncatalogedEfforts: () => discoverUncatalogedEfforts,
378
- feedDateToCreatedAt: () => feedDateToCreatedAt,
379
- generateSeed: () => generateSeed,
380
- loadExistingEffortMap: () => loadExistingEffortMap,
381
- partitionIntoChunks: () => partitionIntoChunks,
382
- scaffoldDiscoveredEfforts: () => scaffoldDiscoveredEfforts,
383
- validateDashboardNote: () => validateDashboardNote,
384
- validateWodBlockSyntax: () => validateWodBlockSyntax
385
- });
386
- function listMarkdownFiles(dir, prefix = "") {
387
- if (!fs.existsSync(dir)) return [];
388
- const out = [];
389
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
390
- const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
391
- if (entry.isDirectory()) out.push(...listMarkdownFiles(path.join(dir, entry.name), rel));
392
- else if (entry.isFile() && entry.name.endsWith(".md")) out.push(rel);
393
- }
394
- return out.sort();
395
- }
396
- function chunkIdFor(relPath) {
397
- const parts = relPath.split("/");
398
- if (parts[0] !== "markdown" || parts.length < 2) return null;
399
- const corpus = parts[1];
400
- if (parts.length === 2) return null;
401
- const slug = parts[2]?.replace(/\.md$/, "");
402
- switch (corpus) {
403
- case "canvas":
404
- case "dashboards":
405
- case "efforts":
406
- return corpus;
407
- case "collections":
408
- return parts.length === 3 ? `collection._root` : `collection.${slug}`;
409
- case "feeds":
410
- return parts.length === 3 ? `feed._root` : `feed.${slug}`;
411
- default:
412
- return null;
413
- }
414
- }
415
- function collectCorpusMarkdown(markdownDir) {
416
- return listMarkdownFiles(markdownDir).map((p) => `markdown/${p}`);
417
- }
418
- function partitionIntoChunks(paths) {
419
- const byId = /* @__PURE__ */ new Map();
420
- for (const p of paths) {
421
- const id = chunkIdFor(p);
422
- if (id === null) {
423
- throw new Error(
424
- `[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.`
425
- );
426
- }
427
- const list = byId.get(id);
428
- if (list) list.push(p);
429
- else byId.set(id, [p]);
430
- }
431
- return [...byId.entries()].sort(([a], [b]) => a < b ? -1 : 1).map(([id, files]) => ({ id, files }));
432
- }
433
- function validateDashboardNote(raw) {
434
- const problems = [];
435
- const { meta } = parseFrontmatter(raw);
436
- const { sections } = wodWikiWql.parseDashboardNote(raw);
437
- const doc = wodWikiWql.buildDashboardDocument(sections, meta);
438
- if (!doc.isDashboard) return ["file is not a dashboard note (missing dashboard frontmatter)"];
439
- if (doc.title === void 0) problems.push("dashboard has no title");
440
- if (doc.widgets.length < 3) problems.push(`dashboard has only ${doc.widgets.length} widgets (the locked format has \u2265 3)`);
441
- const declared = new Set(doc.tokens.map((t) => t.name));
442
- for (const widget of doc.widgets) {
443
- const where = `widget '${widget.title ?? widget.body.slice(0, 40)}'`;
444
- if (widget.title === void 0) problems.push(`${where}: missing title`);
445
- if (!wodWikiWql.isDashboardWidgetType(wodWikiWql.resolveWidgetType(widget.type))) {
446
- problems.push(`${where}: unknown widget type '${widget.type}'`);
447
- }
448
- const parsed = wodWikiWql.parseQuery(widget.body);
449
- if (parsed.error !== void 0) problems.push(`${where}: WQL parse error: ${parsed.error}`);
450
- for (const ref of [
451
- ...widget.body.matchAll(/\$([A-Za-z][\w-]*)/g),
452
- ...Object.values(widget.attributes).flatMap((v) => [...String(v).matchAll(/\$([A-Za-z][\w-]*)/g)])
453
- ]) {
454
- if (!declared.has(ref[1])) problems.push(`${where}: undeclared token $${ref[1]}`);
455
- }
456
- }
457
- return problems;
458
- }
459
- function gateDashboards(groups, corpusRoot) {
460
- for (const group of groups) {
461
- if (group.id !== exports.DASHBOARDS_CHUNK_ID) continue;
462
- for (const p of group.files) {
463
- const problems = validateDashboardNote(fs.readFileSync(path.join(corpusRoot, p), "utf8"));
464
- if (problems.length > 0) {
465
- throw new Error(`[generate-seed] Dashboard validation failed for '${p}':
466
- - ${problems.join("\n - ")}`);
467
- }
468
- }
469
- }
470
- }
471
- function getFileCreatedAt(file, repoRoot) {
472
- try {
473
- const out = child_process.execSync(`git log -1 --format=%ct -- '${file}'`, { cwd: repoRoot, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
474
- const ts = Number(out) * 1e3;
475
- return Number.isFinite(ts) && ts > 0 ? ts : 0;
476
- } catch {
477
- return 0;
478
- }
479
- }
480
- function feedDateToCreatedAt(dateKey) {
481
- if (!/^\d{4}-\d{2}-\d{2}$/.test(dateKey)) return 0;
482
- const ts = Date.parse(`${dateKey}T00:00:00Z`);
483
- return Number.isNaN(ts) ? 0 : ts;
484
- }
485
- function compileSeedRow(path, content, metadata = {}) {
486
- let ordinal = 0;
487
- const blocks = wodWikiCore.reconcileNoteBlocks(content, { sourceRevision: 1, registeredWidgets: exports.SEED_REGISTERED_WIDGETS, createId: () => `seed:${path}:${ordinal++}` });
488
- const noteId = metadata.routeId ?? path;
489
- const sections = wodWikiCore.parseDocumentSections(content);
490
- const blockIndex = Object.values(blocks).map((block, position) => {
491
- const section = sections.find((section2) => section2.from === block.from && section2.to === block.to);
492
- 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 };
493
- });
494
- return { path, content, blocks, blockIndex, blockEfforts: [] };
495
- }
496
- function buildBlockIndexRows(markdownDir, corpusRoot) {
497
- const rows = [];
498
- for (const path$1 of collectCorpusMarkdown(markdownDir)) {
499
- const chunkId = chunkIdFor(path$1);
500
- if (!chunkId) continue;
501
- const content = fs.readFileSync(path.join(corpusRoot, path$1), "utf8");
502
- const parts = path$1.split("/");
503
- const stem = (parts.at(-1) ?? "").replace(/\.md$/, "");
504
- const catalog = parts[2];
505
- 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;
506
- const sourceId = seedNoteSourceId(path$1, content, chunkId);
507
- const createdAt = chunkId.startsWith("feed.") ? feedDateToCreatedAt(parts.at(-2) ?? "") : getFileCreatedAt(path$1, corpusRoot);
508
- rows.push(...compileSeedRow(path$1, content, { routeId, sourceId, createdAt }).blockIndex);
509
- }
510
- return rows;
511
- }
512
- function validateWodBlockSyntax(blocks) {
513
- const problems = [];
514
- for (const block of blocks) {
515
- if (block.dataType !== "wod" || !block.rawContent?.trim()) continue;
516
- const section = wodWikiCore.parseDocumentSections(block.rawContent, { registeredWidgets: exports.SEED_REGISTERED_WIDGETS }).find((s) => s.scriptBlock);
517
- const source = section?.scriptBlock?.content ?? block.rawContent;
518
- if (!source.trim()) continue;
519
- const where = block.sourcePath ?? block.noteId;
520
- if (section?.sport) {
521
- const parsed = wodWikiLang.parseScript(source, { sport: section.sport });
522
- for (const error of parsed.errors ?? []) {
523
- problems.push(`${where}: ${error.message}`);
524
- }
525
- continue;
526
- }
527
- const doc = source.endsWith("\n") ? source : source + "\n";
528
- const tree = wodWikiLang.whiteboardParser.parse(doc);
529
- tree.iterate({
530
- enter: (node) => {
531
- if (!node.type.isError) return;
532
- const line = doc.slice(0, node.from).split("\n").length;
533
- const snippet = source.slice(node.from, Math.min(node.to, node.from + 60)).split("\n")[0];
534
- problems.push(`${where}:${line} \`${snippet}\``);
535
- }
536
- });
537
- }
538
- return problems;
539
- }
540
- function formatDiscoveredLabel(raw) {
541
- const clean = raw.replace(/[-_]+/g, " ").trim();
542
- return clean.split(/\s+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(" ");
543
- }
544
- function loadExistingEffortMap(markdownDir) {
545
- const slugs = /* @__PURE__ */ new Set();
546
- const aliasToCanonical = /* @__PURE__ */ new Map();
547
- const effortsDir = path.join(markdownDir, "efforts");
548
- for (const file of listMarkdownFiles(effortsDir)) {
549
- const raw = fs.readFileSync(path.join(effortsDir, file), "utf8");
550
- const parsed = parseEffortFile(raw);
551
- if (!parsed) continue;
552
- slugs.add(parsed.slug);
553
- aliasToCanonical.set(parsed.slug, parsed.slug);
554
- aliasToCanonical.set(parsed.label.toLowerCase(), parsed.slug);
555
- aliasToCanonical.set(parsed.slug.replace(/-/g, " "), parsed.slug);
556
- for (const alias of parsed.aliases) {
557
- aliasToCanonical.set(alias.toLowerCase(), parsed.slug);
558
- aliasToCanonical.set(alias.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, ""), parsed.slug);
559
- }
560
- }
561
- return { slugs, aliasToCanonical };
562
- }
563
- function discoverUncatalogedEfforts(markdownDir, corpusRoot) {
564
- const { slugs, aliasToCanonical } = loadExistingEffortMap(markdownDir);
565
- const blocks = buildBlockIndexRows(markdownDir, corpusRoot);
566
- const discovered = /* @__PURE__ */ new Map();
567
- for (const block of blocks) {
568
- if (block.dataType !== "wod" || !block.rawContent) continue;
569
- const section = wodWikiCore.parseDocumentSections(block.rawContent, { registeredWidgets: exports.SEED_REGISTERED_WIDGETS }).find((section2) => section2.scriptBlock);
570
- const script = wodWikiLang.parseScript(section?.scriptBlock?.content ?? block.rawContent);
571
- for (const statement of script.statements) {
572
- const effortMetric = statement.metrics?.getMetric?.("effort");
573
- const effortLabel = (effortMetric && typeof effortMetric.value === "string" ? effortMetric.value : void 0) ?? statement.exerciseId;
574
- if (!effortLabel) continue;
575
- const slugified = effortLabel.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
576
- if (!slugified || NON_EFFORT_SLUGS[slugified]) continue;
577
- const canonical = aliasToCanonical.get(slugified) ?? aliasToCanonical.get(effortLabel.toLowerCase());
578
- if (canonical && slugs.has(canonical)) continue;
579
- const existing = discovered.get(slugified);
580
- if (existing) {
581
- existing.rawLabels.add(effortLabel);
582
- } else {
583
- discovered.set(slugified, {
584
- label: formatDiscoveredLabel(effortLabel),
585
- rawLabels: /* @__PURE__ */ new Set([effortLabel])
586
- });
587
- }
588
- }
589
- }
590
- return Array.from(discovered.entries()).map(([slug, data]) => ({
591
- slug,
592
- label: data.label,
593
- rawLabels: Array.from(data.rawLabels)
594
- }));
595
- }
596
- function scaffoldDiscoveredEfforts(discovered, markdownDir) {
597
- const targetDir = path.join(markdownDir, "efforts", "discovered");
598
- if (!fs.existsSync(targetDir)) {
599
- fs.mkdirSync(targetDir, { recursive: true });
600
- }
601
- const createdPaths = [];
602
- for (const effort of discovered) {
603
- const filePath = path.join(targetDir, `${effort.slug}.md`);
604
- if (fs.existsSync(filePath)) continue;
605
- const aliases = effort.rawLabels.filter((l) => l.toLowerCase() !== effort.label.toLowerCase()).map((l) => ` - "${l.replace(/"/g, '\\"')}"`);
606
- const content = [
607
- "---",
608
- `id: effort-bundled-${effort.slug}`,
609
- `slug: ${effort.slug}`,
610
- `label: "${effort.label.replace(/"/g, '\\"')}"`,
611
- aliases.length > 0 ? `aliases:
612
- ${aliases.join("\n")}` : "aliases: []",
613
- "baseAttributes:",
614
- " met: 5.0",
615
- " discipline: general",
616
- " intensityTier: moderate",
617
- "registrySource: bundled",
618
- "---",
619
- ""
620
- ].join("\n");
621
- fs.writeFileSync(filePath, content, "utf8");
622
- createdPaths.push(filePath);
623
- }
624
- return createdPaths;
625
- }
626
- function buildBlockEffortRows(markdownDir, corpusRoot, blocks = buildBlockIndexRows(markdownDir, corpusRoot)) {
627
- const efforts = [];
628
- const { aliasToCanonical } = loadExistingEffortMap(markdownDir);
629
- for (const block of blocks) {
630
- if (block.dataType !== "wod" || !block.rawContent) continue;
631
- const section = wodWikiCore.parseDocumentSections(block.rawContent, { registeredWidgets: exports.SEED_REGISTERED_WIDGETS }).find((section2) => section2.scriptBlock);
632
- const script = wodWikiLang.parseScript(section?.scriptBlock?.content ?? block.rawContent);
633
- const effortSlugs = extractEffortSlugs2(script.statements);
634
- const linked = /* @__PURE__ */ new Set();
635
- for (const rawSlug of effortSlugs) {
636
- const norm = rawSlug.toLowerCase().trim();
637
- const canonicalSlug = aliasToCanonical.get(norm) ?? aliasToCanonical.get(norm.replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "")) ?? norm.replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
638
- if (!canonicalSlug || NON_EFFORT_SLUGS[canonicalSlug] || NON_EFFORT_SLUGS[norm]) {
639
- continue;
640
- }
641
- if (linked.has(canonicalSlug)) continue;
642
- linked.add(canonicalSlug);
643
- efforts.push({
644
- id: `${block.id}:${canonicalSlug}`,
645
- noteId: block.noteId,
646
- blockId: block.blockId,
647
- blockContentId: block.blockContentId,
648
- effortSlug: canonicalSlug,
649
- isStatic: block.isStatic,
650
- sourcePath: block.sourcePath,
651
- createdAt: block.createdAt
652
- });
653
- }
654
- }
655
- return efforts;
656
- }
657
- function sha256Hex(data) {
658
- return crypto$1.createHash("sha256").update(data).digest("hex");
659
- }
660
- function generateSeed(options = {}) {
661
- const t0 = Date.now();
662
- const passMs = { scan: 0, blockIndex: 0, parseGate: 0, blockEfforts: 0, write: 0 };
663
- const corpusRoot = options.corpusRoot ?? process.cwd();
664
- const markdownDir = options.markdownDir ?? path.join(corpusRoot, "markdown");
665
- const outDir = options.outDir ?? path.join(corpusRoot, "dist");
666
- const gitVersion = options.version !== void 0 ? { ...resolveGitVersion(corpusRoot), version: options.version } : resolveGitVersion(corpusRoot);
667
- if (options.scaffoldDiscoveredEfforts) {
668
- const discovered = discoverUncatalogedEfforts(markdownDir, corpusRoot);
669
- if (discovered.length > 0) {
670
- scaffoldDiscoveredEfforts(discovered, markdownDir);
671
- }
672
- }
673
- const tScan = Date.now();
674
- const paths = collectCorpusMarkdown(markdownDir);
675
- const groups = partitionIntoChunks(paths);
676
- gateDashboards(groups, corpusRoot);
677
- if (options.templateFile && fs.existsSync(options.templateFile)) {
678
- const templateRowPath = options.templateRowPath ?? path.relative(corpusRoot, options.templateFile).split("\\").join("/");
679
- groups.push({ id: exports.TEMPLATE_CHUNK_ID, files: [templateRowPath] });
680
- }
681
- passMs.scan = Date.now() - tScan;
682
- const tBlockIndex = Date.now();
683
- const blockRows = buildBlockIndexRows(markdownDir, corpusRoot);
684
- passMs.blockIndex = Date.now() - tBlockIndex;
685
- const tParseGate = Date.now();
686
- const syntaxProblems = validateWodBlockSyntax(blockRows);
687
- passMs.parseGate = Date.now() - tParseGate;
688
- if (syntaxProblems.length > 0) {
689
- const shown = syntaxProblems.slice(0, 20).map((p) => ` ${p}`).join("\n");
690
- const more = syntaxProblems.length > 20 ? `
691
- \u2026 and ${syntaxProblems.length - 20} more` : "";
692
- throw new Error(`[wod build] block syntax gate failed \u2014 ${syntaxProblems.length} block(s) do not parse:
693
- ${shown}${more}`);
694
- }
695
- const chunksDir = path.join(outDir, "chunks");
696
- fs.rmSync(outDir, { recursive: true, force: true });
697
- fs.mkdirSync(chunksDir, { recursive: true });
698
- const manifestChunks = [];
699
- const tBlockEfforts = Date.now();
700
- const effortRows = buildBlockEffortRows(markdownDir, corpusRoot, blockRows);
701
- passMs.blockEfforts = Date.now() - tBlockEfforts;
702
- const blocksByPath = /* @__PURE__ */ new Map();
703
- const effortsByPath = /* @__PURE__ */ new Map();
704
- for (const row of blockRows) {
705
- const rows = blocksByPath.get(row.sourcePath) ?? [];
706
- rows.push(row);
707
- blocksByPath.set(row.sourcePath, rows);
708
- }
709
- for (const row of effortRows) {
710
- const rows = effortsByPath.get(row.sourcePath) ?? [];
711
- rows.push(row);
712
- effortsByPath.set(row.sourcePath, rows);
713
- }
714
- let totalFiles = 0;
715
- const tWrite = Date.now();
716
- for (const { id, files } of groups) {
717
- const templateSource = id === exports.TEMPLATE_CHUNK_ID ? options.templateFile : void 0;
718
- const rows = files.map((p) => {
719
- const raw = fs.readFileSync(templateSource ?? path.join(corpusRoot, p), "utf8");
720
- const content = id === exports.TEMPLATE_CHUNK_ID ? raw.replace(/\$CURSOR/g, "") : raw;
721
- const compiled = compileSeedRow(p, content);
722
- compiled.blockIndex = blocksByPath.get(p) ?? compiled.blockIndex;
723
- compiled.blockEfforts = effortsByPath.get(p) ?? [];
724
- return compiled;
725
- });
726
- const bytes = Buffer.from(JSON.stringify(rows), "utf8");
727
- const sha = sha256Hex(bytes);
728
- const file = `chunks/${id}.${sha.slice(0, 8)}.json`;
729
- fs.writeFileSync(path.join(outDir, file), bytes);
730
- manifestChunks.push({ id, path: file, sha256: sha, bytes: bytes.length, count: rows.length });
731
- totalFiles += rows.length;
732
- }
733
- passMs.write = Date.now() - tWrite;
734
- const manifest = {
735
- schema: exports.SEED_SCHEMA,
736
- version: gitVersion.version,
737
- versionLabel: gitVersion.versionLabel,
738
- commit: gitVersion.commit,
739
- dirty: gitVersion.dirty,
740
- builtAt: (/* @__PURE__ */ new Date()).toISOString(),
741
- chunks: manifestChunks
742
- };
743
- fs.writeFileSync(path.join(outDir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
744
- const reportPath = writeBuildReport(outDir, {
745
- manifest,
746
- totalMs: Date.now() - t0,
747
- passMs,
748
- fileCount: paths.length});
749
- return { manifest, outDir, totalFiles, reportPath, passMs };
750
- }
751
- var NON_EFFORT_SLUGS;
752
- var init_compiler = __esm({
753
- "src/cli/seedbuild/compiler.ts"() {
754
- init_contract();
755
- init_effort_slugs();
756
- init_filename();
757
- init_effort_file();
758
- init_git_version();
759
- init_report();
760
- init_frontmatter();
761
- NON_EFFORT_SLUGS = {
762
- "amrap": true,
763
- "emom": true,
764
- "tabata": true,
765
- "for-time": true,
766
- "rounds": true,
767
- "sets": true,
768
- "repetitions": true,
769
- "count-down": true,
770
- "total-reps": true,
771
- "warmup": true,
772
- "cooldown": true,
773
- "cool-down": true,
774
- "transition": true,
775
- "rest": true,
776
- "break": true,
777
- "work": true,
778
- "i-suck-at-this": true,
779
- "isuckatthis": true,
780
- "movement": true,
781
- "movement-you": true,
782
- "selected-exercise": true,
783
- "training-setup": true,
784
- "grade-system": true,
785
- "hangs": true,
786
- "warm-light-wind": true,
787
- "basin": true,
788
- "rock-canyon": true,
789
- "b-onsight": true,
790
- "a-redpoint": true,
791
- "d-redpoint": true,
792
- "v0-flash": true,
793
- "v2-flash": true,
794
- "v7-redpoint": true,
795
- "avgrpe-mean": true,
796
- "calculate": true,
797
- "totalload-sum": true,
798
- "query": true,
799
- "by": true,
800
- "": true,
801
- "-": true,
802
- "...": true,
803
- "x": true,
804
- "s": true,
805
- "bw": true
806
- };
807
- }
808
- });
8
+ // src/index.ts
809
9
  var activePacks = /* @__PURE__ */ new Map();
810
10
  function defineLanguagePack(pack) {
811
11
  return pack;
@@ -866,7 +66,7 @@ function inMemoryEventStore(events) {
866
66
  getEventsByTimeRange: async (start, end) => rows.filter((r) => r.timestamp >= start && r.timestamp <= end),
867
67
  getEventsByResult: async (resultId) => rows.filter((r) => r.resultId === resultId),
868
68
  getEventsForNote: async (noteId) => rows.filter((r) => r.noteId === noteId),
869
- getEventsByContent: async (blockContentId5) => rows.filter((r) => r.blockContentId === blockContentId5),
69
+ getEventsByContent: async (blockContentId2) => rows.filter((r) => r.blockContentId === blockContentId2),
870
70
  scanAll: async () => rows,
871
71
  appendEvents: async (appended) => {
872
72
  rows.push(...appended);
@@ -880,914 +80,78 @@ function inMemoryEventStore(events) {
880
80
  },
881
81
  deleteEvents: async (ids) => {
882
82
  const doomed = new Set(ids);
883
- for (let i = rows.length - 1; i >= 0; i--) {
884
- if (doomed.has(rows[i].id)) rows.splice(i, 1);
885
- }
886
- }
887
- };
888
- }
889
- function createIRFile(kind, data, optionsOrSource, generatedAt) {
890
- let source = "wod:headless";
891
- let genAt = Date.now();
892
- if (typeof optionsOrSource === "string") {
893
- source = optionsOrSource;
894
- if (generatedAt !== void 0) genAt = generatedAt;
895
- } else if (optionsOrSource && typeof optionsOrSource === "object") {
896
- if (optionsOrSource.source) source = optionsOrSource.source;
897
- if (optionsOrSource.generatedAt !== void 0) genAt = optionsOrSource.generatedAt;
898
- }
899
- return {
900
- $schema: "https://wod-wiki.dev/ir/v1.json",
901
- kind,
902
- generatedAt: genAt,
903
- source,
904
- data
905
- };
906
- }
907
- function isIRFile(obj) {
908
- if (!obj || typeof obj !== "object") return false;
909
- const cand = obj;
910
- return cand.$schema === "https://wod-wiki.dev/ir/v1.json" && typeof cand.kind === "string" && typeof cand.generatedAt === "number" && typeof cand.source === "string" && "data" in cand;
911
- }
912
- function statementToNode(stmt, idMap) {
913
- const meta = stmt.meta;
914
- const metrics = stmt.metrics.toArray().map((m) => ({
915
- type: m.type,
916
- ...m.image !== void 0 ? { image: m.image } : {},
917
- ...m.value !== void 0 ? { value: m.value } : {},
918
- ...m.origin !== void 0 ? { origin: m.origin } : {}
919
- }));
920
- const raw = meta?.raw ?? "";
921
- const from = meta?.startOffset ?? 0;
922
- const to = meta?.endOffset ?? from + raw.length;
923
- const durationFrag = stmt.metrics.find((m) => m.type === wodWikiCore.MetricType.Duration || m.type === "duration");
924
- const roundsFrag = stmt.metrics.find((m) => m.type === wodWikiCore.MetricType.Rounds || m.type === "rounds");
925
- const type = durationFrag ? "duration" : roundsFrag ? "rounds" : "statement";
926
- const hints = wodWikiLang.getHints(stmt);
927
- const childIds = (stmt.children ?? []).flat();
928
- const children = childIds.map((cid) => idMap.get(cid)).filter((c) => c !== void 0).map((c) => statementToNode(c, idMap));
929
- return {
930
- id: stmt.id,
931
- type,
932
- raw,
933
- from,
934
- to,
935
- metrics,
936
- ...hints.length > 0 ? { hints } : {},
937
- ...children.length > 0 ? { children } : {}
938
- };
939
- }
940
- function buildStatementTree(script) {
941
- const idMap = new Map(script.statements.map((s) => [s.id, s]));
942
- const topLevel = script.statements.filter((s) => s.parent === void 0 || !idMap.has(s.parent));
943
- if (topLevel.length === 1 && topLevel[0].children.length > 0) {
944
- return statementToNode(topLevel[0], idMap);
945
- }
946
- return {
947
- id: 0,
948
- type: "root",
949
- raw: script.source,
950
- from: 0,
951
- to: script.source.length,
952
- metrics: [],
953
- children: topLevel.map((s) => statementToNode(s, idMap))
954
- };
955
- }
956
- var ParseSyntaxError = class extends Error {
957
- constructor(errors, message = "Parse error in Whiteboard Script") {
958
- const details = errors.map((e) => e.message).join("; ");
959
- super(`${message}: ${details}`);
960
- this.errors = errors;
961
- this.name = "ParseSyntaxError";
962
- }
963
- errors;
964
- };
965
- function runParse(source, options = {}) {
966
- const parser = wodWikiLang.createParser();
967
- const script = parser.read(source, options.sport);
968
- if (script.errors && script.errors.length > 0) {
969
- throw new ParseSyntaxError(script.errors);
970
- }
971
- const tree = buildStatementTree(script);
972
- return createIRFile("parse-tree", tree, { source: options.sourceLabel ?? "cli:wod parse" });
973
- }
974
- function buildWorkoutResults(outputs, options) {
975
- return {
976
- startTime: options.startTime ?? options.now.nowMs(),
977
- endTime: options.now.nowMs(),
978
- duration: options.elapsedTime,
979
- completed: options.completed,
980
- logs: outputs.map(wodWikiLang.toStoredOutputStatement)
981
- };
982
- }
983
- async function runExecution(source, options = {}) {
984
- const parser = wodWikiLang.createParser();
985
- const script = parser.read(source, options.sport);
986
- if (script.errors && script.errors.length > 0) {
987
- throw new ParseSyntaxError(script.errors);
988
- }
989
- const startTime = options.startTime ?? Date.now();
990
- const now = options.now ?? wodWikiLang.frozenNow(new Date(startTime));
991
- const compiler = wodWikiLang.createCompiler();
992
- const clock = wodWikiLang.createMockClock(new Date(startTime));
993
- const stack = new wodWikiLang.RuntimeStack();
994
- const eventBus = new wodWikiLang.EventBus();
995
- const runtime = new wodWikiLang.ScriptRuntime(
996
- script,
997
- compiler,
998
- { stack, clock, eventBus },
999
- {},
1000
- now
1001
- );
1002
- const { engine, analyticsContext } = wodWikiLang.createAnalyticsEngineForBlock({
1003
- id: options.blockId ?? "cli-block",
1004
- content: source,
1005
- statements: script.statements,
1006
- sport: options.sport,
1007
- version: options.blockVersion ?? 1,
1008
- startLine: 0,
1009
- endLine: 0,
1010
- dialect: options.dialect ?? "time",
1011
- state: "idle",
1012
- createdAt: Date.now()
1013
- });
1014
- runtime.analyticsContext = analyticsContext;
1015
- runtime.setAnalyticsEngine(engine);
1016
- const outputs = [];
1017
- runtime.subscribeToOutput((out) => {
1018
- outputs.push(out);
1019
- });
1020
- runtime.do(new wodWikiLang.StartSessionAction());
1021
- const maxSteps = options.maxIterations ?? 5e3;
1022
- let steps = 0;
1023
- while (stack.count > 0 && steps < maxSteps) {
1024
- runtime.handle(new wodWikiLang.NextEvent(void 0, runtime.nowProvider));
1025
- clock.advance(1e3);
1026
- steps++;
1027
- }
1028
- const workoutResults = buildWorkoutResults(outputs, {
1029
- startTime,
1030
- elapsedTime: steps * 1e3,
1031
- completed: stack.count === 0,
1032
- now
1033
- });
1034
- const logs = outputs.map(wodWikiLang.toStoredOutputStatement);
1035
- return createIRFile(
1036
- "execution-log",
1037
- {
1038
- results: workoutResults,
1039
- logs,
1040
- statements: logs,
1041
- sourceScript: source
1042
- },
1043
- { source: options.sourceLabel ?? "cli:wod run" }
1044
- );
1045
- }
1046
- function getFs() {
1047
- if (typeof globalThis.process?.versions?.node === "undefined" && typeof globalThis.Bun === "undefined") {
1048
- throw new Error("File system operations are only supported in Node / Bun environments");
1049
- }
1050
- return __require("fs");
1051
- }
1052
- var WqlSyntaxError = class extends Error {
1053
- constructor(query, syntaxError) {
1054
- super(`WQL Syntax Error in "${query}": ${syntaxError}`);
1055
- this.query = query;
1056
- this.syntaxError = syntaxError;
1057
- this.name = "WqlSyntaxError";
1058
- }
1059
- query;
1060
- syntaxError;
1061
- };
1062
- function extractPayload(rawJson) {
1063
- try {
1064
- const parsed = JSON.parse(rawJson);
1065
- if (isIRFile(parsed)) {
1066
- return parsed.data;
1067
- }
1068
- return parsed;
1069
- } catch (err) {
1070
- throw new Error(`Failed to parse JSON input: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
1071
- }
1072
- }
1073
- function buildStoresFromData(data) {
1074
- const events = [
1075
- ...data.eventRecords,
1076
- ...data.facts.map((f, i) => ({
1077
- id: `fact:${f.resultId ?? "stdin"}:${f.metricKey ?? f.type}:${i}`,
1078
- resultId: f.resultId ?? "stdin-result-1",
1079
- noteId: f.noteId ?? "stdin-note-1",
1080
- blockContentId: f.blockContentId,
1081
- pageId: f.pageId,
1082
- origin: f.origin,
1083
- timestamp: f.timestamp,
1084
- grain: f.grain === "event" ? "event" : "summary",
1085
- outputType: "analytics",
1086
- effortSlug: f.effortSlug,
1087
- metrics: [{
1088
- type: f.metricKey ?? f.type,
1089
- value: f.value,
1090
- ...f.unit ? { unit: f.unit } : {},
1091
- origin: "engine"
1092
- }],
1093
- blockId: f.blockId,
1094
- blockVersion: f.blockVersion
1095
- }))
1096
- ];
1097
- const eventStore = inMemoryEventStore(events);
1098
- const noteStore = {
1099
- getAllNotes: async () => [...data.notes],
1100
- getNotesByIds: async (ids) => {
1101
- const selected = new Set(ids);
1102
- return data.notes.filter((note) => selected.has(note.id));
1103
- },
1104
- getNoteIdsForTag: async (tag) => {
1105
- const matching = /* @__PURE__ */ new Set();
1106
- for (const [noteId, tags] of Object.entries(data.noteTags)) {
1107
- if (tags.includes(tag)) matching.add(noteId);
1108
- }
1109
- return matching;
1110
- },
1111
- getNoteTagLabels: async (noteId) => [...data.noteTags[noteId] ?? []]
1112
- };
1113
- const blockStore = {
1114
- getAllBlocks: async () => [...data.blocks]
1115
- };
1116
- const effortStore = {
1117
- getAllEfforts: async () => [...data.efforts]
1118
- };
1119
- return { eventStore, noteStore, blockStore, effortStore };
1120
- }
1121
- function loadQueryData(options) {
1122
- const data = {
1123
- eventRecords: [],
1124
- facts: [],
1125
- results: [],
1126
- notes: [],
1127
- blocks: [],
1128
- revisions: [],
1129
- efforts: [...wodWikiLang.bundledEfforts],
1130
- noteTags: {}
1131
- };
1132
- if (options.corpusPath) {
1133
- const fs = getFs();
1134
- if (!fs.existsSync(options.corpusPath)) {
1135
- throw new Error(`Corpus file not found at path: ${options.corpusPath}`);
1136
- }
1137
- const raw = fs.readFileSync(options.corpusPath, "utf-8");
1138
- const payload = extractPayload(raw);
1139
- if (Array.isArray(payload)) {
1140
- data.facts = payload;
1141
- } else if (payload && typeof payload === "object") {
1142
- const obj = payload;
1143
- if ("segments" in obj) throw new Error("Incompatible corpus segments; whole Notes and block revisions are required");
1144
- if (obj.kind === "event-journal" || Array.isArray(obj.records)) {
1145
- if (Array.isArray(obj.blockRevisions)) data.revisions = obj.blockRevisions;
1146
- if (Array.isArray(obj.sessions)) data.results = obj.sessions;
1147
- if (Array.isArray(obj.records)) {
1148
- data.eventRecords = obj.records;
1149
- }
1150
- if (Array.isArray(obj.notes)) {
1151
- data.notes = obj.notes;
1152
- for (const n of obj.notes) {
1153
- if (n && typeof n === "object" && n.id && Array.isArray(n.tags)) {
1154
- data.noteTags[n.id] = n.tags;
1155
- }
1156
- }
1157
- }
1158
- } else if (obj.facts || obj.results || obj.notes || obj.blocks || obj.efforts || obj.tags || obj.logs) {
1159
- const corpus = payload;
1160
- if (corpus.facts) data.facts = corpus.facts;
1161
- if (corpus.results) data.results = corpus.results;
1162
- if (corpus.notes) data.notes = corpus.notes;
1163
- if (corpus.blocks) data.blocks = corpus.blocks;
1164
- if (corpus.efforts) data.efforts = corpus.efforts;
1165
- if (corpus.revisions) data.revisions = corpus.revisions;
1166
- if (corpus.tags) data.noteTags = corpus.tags;
1167
- if (corpus.logs) {
1168
- const identity = {
1169
- noteId: "corpus-note-1",
1170
- resultId: "corpus-result-1",
1171
- origin: "journal",
1172
- workoutTimestamp: corpus.logs[0]?.timeSpan?.started ?? Date.now()
1173
- };
1174
- data.eventRecords = [...wodWikiWql.toEventRows(corpus.logs, identity), ...wodWikiWql.toSummaryEventRows(corpus.logs, identity)];
1175
- }
1176
- } else {
1177
- throw new Error(`Unrecognized corpus payload shape in "${options.corpusPath}" (expected event-journal, fact-set, or CorpusIRData)`);
1178
- }
1179
- }
1180
- } else if (options.stdinLog) {
1181
- const payload = extractPayload(options.stdinLog);
1182
- let executionLog;
1183
- const rawLogs = payload.logs ?? ("statements" in payload ? payload.statements : void 0) ?? [];
1184
- if ("results" in payload) {
1185
- executionLog = { results: payload.results, logs: rawLogs, statements: rawLogs };
1186
- } else {
1187
- executionLog = { results: payload, logs: rawLogs, statements: rawLogs };
1188
- }
1189
- const identity = {
1190
- noteId: "stdin-note-1",
1191
- resultId: "stdin-result-1",
1192
- origin: "journal",
1193
- workoutTimestamp: executionLog.results.startTime
1194
- };
1195
- data.eventRecords = [
1196
- ...wodWikiWql.toEventRows(executionLog.logs, identity),
1197
- ...wodWikiWql.toSummaryEventRows(executionLog.logs, identity)
1198
- ];
1199
- } else if (options.stdinFacts) {
1200
- const payload = extractPayload(options.stdinFacts);
1201
- if (Array.isArray(payload)) {
1202
- data.facts = payload;
1203
- } else if (payload && typeof payload === "object" && "facts" in payload) {
1204
- data.facts = payload.facts;
1205
- }
1206
- } else {
1207
- throw new Error("No input dataset provided. Specify --corpus <path>, --stdin-log, or --stdin-facts.");
1208
- }
1209
- for (const note of data.notes) {
1210
- if (typeof note.rawContent !== "string" || !Number.isSafeInteger(note.sourceRevision) || note.sourceRevision < 1) {
1211
- throw new Error("Incompatible corpus Note; exact rawContent and sourceRevision are required");
1212
- }
1213
- wodWikiCore.validateNoteBlocks(note.rawContent, note.blocks, note.sourceRevision);
1214
- if (!data.blocks.some((row) => row.noteId === note.id)) {
1215
- for (const [position, block] of Object.values(note.blocks).sort((a, b) => a.from - b.from).entries()) {
1216
- const rawContent = note.rawContent.slice(block.from, block.to);
1217
- const body = wodWikiCore.parseDocumentSections(rawContent)[0]?.displayContent ?? rawContent;
1218
- data.blocks.push({
1219
- id: `${note.id}:${block.id}`,
1220
- noteId: note.id,
1221
- blockId: block.id,
1222
- blockVersion: block.version,
1223
- sourceRevision: note.sourceRevision,
1224
- from: block.from,
1225
- to: block.to,
1226
- position,
1227
- dataType: block.kind,
1228
- rawContent,
1229
- noteTitle: note.title,
1230
- createdAt: note.createdAt,
1231
- ...block.kind === "wod" ? { blockContentId: wodWikiCore.blockContentId(body) } : {}
1232
- });
1233
- }
1234
- }
1235
- }
1236
- for (const session of data.results) {
1237
- if ("segmentId" in session || "segmentVersion" in session || "data" in session || "version" in session || "rawContent" in session || !session.blockId || !Number.isSafeInteger(session.blockVersion) || session.blockVersion < 1) {
1238
- throw new Error("Incompatible corpus Session; exact blockId/blockVersion pin required");
1239
- }
1240
- if (!data.revisions.some((row) => row.noteId === session.noteId && row.blockId === session.blockId && row.version === session.blockVersion && row.kind === "wod")) {
1241
- throw new Error("Corpus Session immutable revision is unavailable");
1242
- }
1243
- }
1244
- for (const row of [...data.eventRecords, ...data.facts, ...data.blocks]) {
1245
- if ("segmentId" in row || "segmentVersion" in row) throw new Error("Incompatible corpus segment pin");
1246
- }
1247
- const revisions = new Map(data.revisions.map((row) => [JSON.stringify([row.noteId, row.blockId, row.version]), row]));
1248
- for (const row of data.eventRecords) {
1249
- if (row.blockId !== void 0 || row.blockVersion !== void 0) {
1250
- if (!row.blockId || !Number.isSafeInteger(row.blockVersion) || !revisions.has(JSON.stringify([row.noteId, row.blockId, row.blockVersion]))) {
1251
- throw new Error("Corpus Event immutable revision is unavailable");
1252
- }
1253
- }
1254
- }
1255
- for (const row of data.blocks) {
1256
- const note = data.notes.find((candidate) => candidate.id === row.noteId);
1257
- const block = note?.blocks[row.blockId];
1258
- if (!note || !block || row.blockVersion !== block.version || row.sourceRevision !== note.sourceRevision || row.from !== block.from || row.to !== block.to || row.dataType !== block.kind) {
1259
- throw new Error("Corpus block projection does not match current Note source");
1260
- }
1261
- row.rawContent = note.rawContent.slice(block.from, block.to);
1262
- }
1263
- return data;
1264
- }
1265
- async function runQueryCli(wqlString, options = {}) {
1266
- const parsed = wodWikiWql.parseQuery(wqlString);
1267
- if (parsed.error) {
1268
- throw new WqlSyntaxError(wqlString, parsed.error);
1269
- }
1270
- const stores = options.stores ?? buildStoresFromData(loadQueryData(options));
1271
- const service = new wodWikiWql.QueryService(stores);
1272
- if (wodWikiWql.isPipelineQuery(parsed)) {
1273
- const result2 = await service.runPipeline(wqlString, {
1274
- preferredUnit: options.preferredUnit
1275
- });
1276
- return createIRFile("pipeline-result", result2, { source: options.sourceLabel ?? "cli:wod query" });
1277
- }
1278
- if (wodWikiWql.isFindQuery(parsed)) {
1279
- const result2 = await service.runFind(parsed);
1280
- const kind = parsed.target === "session" ? "rows-result" : "find-result";
1281
- return createIRFile(kind, result2, { source: options.sourceLabel ?? "cli:wod query" });
1282
- }
1283
- const result = await service.runQuery(wqlString, {
1284
- preferredUnit: options.preferredUnit
1285
- });
1286
- return createIRFile("query-result", result, { source: options.sourceLabel ?? "cli:wod query" });
1287
- }
1288
-
1289
- // src/cli/loader.ts
1290
- function getPathUtils() {
1291
- if (typeof globalThis.process?.versions?.node === "undefined" && !("Bun" in globalThis)) {
1292
- throw new Error("Path resolution is only supported in Node / Bun environments");
1293
- }
1294
- return {
1295
- // eslint-disable-next-line no-restricted-syntax -- lazy CJS require keeps node builtins out of browser bundles
1296
- resolve: __require("path").resolve,
1297
- // eslint-disable-next-line no-restricted-syntax -- lazy CJS require keeps node builtins out of browser bundles
1298
- pathToFileURL: __require("url").pathToFileURL
1299
- };
1300
- }
1301
- var PackLoadError = class extends Error {
1302
- constructor(spec, originalError) {
1303
- const msg = originalError instanceof Error ? originalError.message : String(originalError);
1304
- super(`Failed to load Language Pack "${spec}": ${msg}`);
1305
- this.spec = spec;
1306
- this.originalError = originalError;
1307
- this.name = "PackLoadError";
1308
- }
1309
- spec;
1310
- originalError;
1311
- };
1312
- async function loadLanguagePack(moduleSpec) {
1313
- try {
1314
- let importPath = moduleSpec;
1315
- if (moduleSpec.startsWith(".") || moduleSpec.startsWith("/") || moduleSpec.includes("\\") || moduleSpec.endsWith(".ts") || moduleSpec.endsWith(".js") || moduleSpec.endsWith(".mjs")) {
1316
- const { resolve, pathToFileURL } = getPathUtils();
1317
- const resolved = resolve(process.cwd(), moduleSpec);
1318
- importPath = pathToFileURL(resolved).href;
1319
- }
1320
- const imported = await import(
1321
- /* @vite-ignore */
1322
- importPath
1323
- );
1324
- const candidate = imported.default ?? imported.pack ?? imported;
1325
- if (!candidate || typeof candidate !== "object") {
1326
- throw new Error(`Module "${moduleSpec}" does not export a valid LanguagePack object`);
1327
- }
1328
- const pack = candidate;
1329
- registerLanguagePack(pack);
1330
- return pack;
1331
- } catch (error) {
1332
- if (error instanceof PackLoadError) throw error;
1333
- throw new PackLoadError(moduleSpec, error);
1334
- }
1335
- }
1336
- var nodePath = () => nodeRequire()("node:path");
1337
- var nodeFs = () => nodeRequire()("node:fs");
1338
- var nodeCrypto = () => nodeRequire()("node:crypto");
1339
- function nodeRequire() {
1340
- const g = globalThis;
1341
- if (typeof g.process?.versions?.node === "undefined" && typeof g.Bun === "undefined") {
1342
- throw new Error("SQLite operations are only supported in Node / Bun environments");
1343
- }
1344
- const req = g.require ?? (typeof __require === "function" ? __require : void 0);
1345
- if (!req) throw new Error("SQLite operations require a CJS require shim (missing tsup banner?)");
1346
- return req;
1347
- }
1348
- var sqlModulePromise;
1349
- function loadSql() {
1350
- sqlModulePromise ??= import(
1351
- /* @vite-ignore */
1352
- '@bitcobblers/wod-wiki-storage/sql'
1353
- );
1354
- return sqlModulePromise;
1355
- }
1356
- function resolveDbPath(dbPath) {
1357
- const chosen = dbPath ?? process.env.WOD_WIKI_DB ?? "./wodwiki.db";
1358
- return nodePath().resolve(process.cwd(), chosen);
1359
- }
1360
- async function openCliDb(dbPath) {
1361
- const [{ createClient }, sql] = await Promise.all([
1362
- import(
1363
- /* @vite-ignore */
1364
- '@libsql/client'
1365
- ),
1366
- loadSql()
1367
- ]);
1368
- const resolved = resolveDbPath(dbPath);
1369
- nodeFs().mkdirSync(nodePath().dirname(resolved), { recursive: true });
1370
- const db = sql.libsqlDatabase(createClient({ url: `file:${resolved}` }));
1371
- try {
1372
- await sql.initializeStore(db);
1373
- await sql.initializeDomain(db);
1374
- return { db, storage: new sql.SqlStorage(db), path: resolved, close: () => db.close() };
1375
- } catch (error) {
1376
- await db.close();
1377
- throw error;
1378
- }
1379
- }
1380
- function formatV5Digest(digest) {
1381
- const bytes = digest.slice(0, 16);
1382
- bytes[6] = bytes[6] & 15 | 80;
1383
- bytes[8] = bytes[8] & 63 | 128;
1384
- const hex = [...bytes].map((b) => b.toString(16).padStart(2, "0"));
1385
- return `${hex.slice(0, 4).join("")}-${hex.slice(4, 6).join("")}-${hex.slice(6, 8).join("")}-${hex.slice(8, 10).join("")}-${hex.slice(10, 16).join("")}`;
1386
- }
1387
- function uuid5(namespace, name) {
1388
- return formatV5Digest(nodeCrypto().createHash("sha1").update(namespace).update(name).digest());
1389
- }
1390
- var CLI_UUID_NAMESPACE = "c0ffee00-8b1a-4e2c-9d3f-5a6b7c8d9e0f";
1391
- function stableNoteId(sourceLabel, content) {
1392
- if (sourceLabel && sourceLabel !== "-") {
1393
- return uuid5(CLI_UUID_NAMESPACE, `wod-cli:${nodePath().resolve(process.cwd(), sourceLabel)}`);
1394
- }
1395
- return uuid5(CLI_UUID_NAMESPACE, `wod-cli:stdin:${content.length}:${content.slice(0, 256)}`);
1396
- }
1397
- function randomId() {
1398
- return nodeCrypto().randomUUID();
1399
- }
1400
- async function allRows(sql, db, store) {
1401
- return sql.listRows(db, sql.storeDefinition(store));
1402
- }
1403
- async function rowsByIndex(sql, db, store, index, only) {
1404
- return sql.listRows(db, sql.storeDefinition(store), { only }, void 0, index);
1405
- }
1406
- function sqliteEventStore(db) {
1407
- const all = async () => await allRows(await loadSql(), db, "events");
1408
- return {
1409
- getEventsByTimeRange: async (start, end) => (await all()).filter((r) => r.timestamp >= start && r.timestamp <= end),
1410
- getEventsByResult: async (resultId) => (await all()).filter((r) => r.resultId === resultId),
1411
- getEventsForNote: async (noteId) => (await all()).filter((r) => r.noteId === noteId),
1412
- getEventsByContent: async (blockContentId5) => (await all()).filter((r) => r.blockContentId === blockContentId5),
1413
- scanAll: all,
1414
- appendEvents: async (appended) => {
1415
- const sql = await loadSql();
1416
- await db.transaction(async (tx) => {
1417
- const eventsDef = sql.storeDefinition("events");
1418
- for (const row of appended) await sql.putRow(tx, eventsDef, row);
1419
- });
1420
- },
1421
- finalizeSummaries: async (resultId, finals) => {
1422
- const sql = await loadSql();
1423
- await db.transaction(async (tx) => {
1424
- const eventsDef = sql.storeDefinition("events");
1425
- const existing = (await sql.listRows(tx, eventsDef)).filter(
1426
- (r) => r.resultId === resultId && r.grain === "summary" && r.origin !== "user"
1427
- );
1428
- for (const row of existing) await sql.deleteRows(tx, eventsDef, row.id);
1429
- for (const row of finals) await sql.putRow(tx, eventsDef, row);
1430
- });
1431
- },
1432
- deleteEvents: async (ids) => {
1433
- const sql = await loadSql();
1434
- await db.transaction(async (tx) => {
1435
- const eventsDef = sql.storeDefinition("events");
1436
- for (const id of ids) await sql.deleteRows(tx, eventsDef, id);
1437
- });
1438
- }
1439
- };
1440
- }
1441
- function sqliteNoteStore(db) {
1442
- return {
1443
- getAllNotes: async () => await allRows(await loadSql(), db, "notes"),
1444
- getNotesByIds: async (noteIds) => {
1445
- const sql = await loadSql();
1446
- const rows = await sql.getRows(db, sql.storeDefinition("notes"), [...noteIds]);
1447
- return rows.filter((row) => row !== void 0);
1448
- },
1449
- getNoteIdsForTag: async (label) => {
1450
- const sql = await loadSql();
1451
- const tags = (await allRows(sql, db, "tags")).filter((t) => t.label === label);
1452
- const ids = {};
1453
- for (const tag of tags) {
1454
- for (const row of await rowsByIndex(sql, db, "note_tags", "by-tag", tag.id)) {
1455
- ids[row.noteId] = true;
1456
- }
1457
- }
1458
- return new Set(Object.keys(ids));
1459
- },
1460
- getNoteTagLabels: async (noteId) => {
1461
- const sql = await loadSql();
1462
- const tagRows = await allRows(sql, db, "tags");
1463
- const labelById = {};
1464
- for (const t of tagRows) labelById[t.id] = t.label;
1465
- const links = await rowsByIndex(sql, db, "note_tags", "by-note", noteId);
1466
- return links.map((l) => labelById[l.tagId]).filter((l) => typeof l === "string");
1467
- },
1468
- queryDomain: async (request) => {
1469
- const result = await (await loadSql()).handleDomainQuery(db, request);
1470
- return result;
1471
- }
1472
- };
1473
- }
1474
- function sqliteBlockStore(db) {
1475
- return {
1476
- getAllBlocks: async () => await allRows(await loadSql(), db, "block_index"),
1477
- queryDomain: async (request) => {
1478
- const result = await (await loadSql()).handleDomainQuery(db, { ...request, plan: "blocks" });
1479
- return result;
1480
- }
1481
- };
1482
- }
1483
- function sqliteEffortStore(db) {
1484
- return {
1485
- getAllEfforts: async () => {
1486
- const stored = await allRows(await loadSql(), db, "efforts");
1487
- const bySlug = {};
1488
- for (const effort of stored) bySlug[effort.slug] = effort;
1489
- for (const effort of wodWikiLang.bundledEfforts) {
1490
- bySlug[effort.slug] ??= effort;
83
+ for (let i = rows.length - 1; i >= 0; i--) {
84
+ if (doomed.has(rows[i].id)) rows.splice(i, 1);
1491
85
  }
1492
- return Object.values(bySlug);
1493
86
  }
1494
87
  };
1495
88
  }
1496
- function sqliteIndexStore(db, store) {
89
+ function createIRFile(kind, data, optionsOrSource, generatedAt) {
90
+ let source = "wod:headless";
91
+ let genAt = Date.now();
92
+ if (typeof optionsOrSource === "string") {
93
+ source = optionsOrSource;
94
+ if (generatedAt !== void 0) genAt = generatedAt;
95
+ } else if (optionsOrSource && typeof optionsOrSource === "object") {
96
+ if (optionsOrSource.source) source = optionsOrSource.source;
97
+ if (optionsOrSource.generatedAt !== void 0) genAt = optionsOrSource.generatedAt;
98
+ }
1497
99
  return {
1498
- getAllFromIndex: async (index, key) => rowsByIndex(await loadSql(), db, store, index, key)
100
+ $schema: "https://wod-wiki.dev/ir/v1.json",
101
+ kind,
102
+ generatedAt: genAt,
103
+ source,
104
+ data
1499
105
  };
1500
106
  }
1501
- async function getNoteBundle(db, noteId) {
1502
- const sql = await loadSql();
1503
- const note = await sql.getRow(db, sql.storeDefinition("notes"), noteId);
1504
- return { note };
1505
- }
1506
- async function getSessionBundle(db, resultId) {
1507
- const sql = await loadSql();
1508
- const session = await sql.getRow(db, sql.storeDefinition("sessions"), resultId);
1509
- if (!session) return void 0;
1510
- const events = await sql.listRows(db, sql.storeDefinition("events"), { only: resultId }, void 0, "by-result-grain");
1511
- return { session, events };
1512
- }
1513
- async function getEffortBySlug(db, slug) {
1514
- const sql = await loadSql();
1515
- const efforts = await allRows(sql, db, "efforts");
1516
- return efforts.find((e) => e.slug === slug);
1517
- }
1518
- async function listTagsByType(db, type) {
1519
- const sql = await loadSql();
1520
- const tags = await allRows(sql, db, "tags");
1521
- return tags.filter((t) => t.type === type);
1522
- }
1523
- var registeredWidgets = wodWikiCore.KNOWN_SECTION_WIDGETS;
1524
- var saveStores = ["notes", "block_revisions", "block_index", "block_efforts", "tags", "note_tags", "sessions"];
1525
- function extractEffortSlugs(statements) {
1526
- const slugs = /* @__PURE__ */ new Set();
1527
- for (const statement of statements) {
1528
- if (statement.exerciseId) {
1529
- slugs.add(statement.exerciseId);
1530
- } else {
1531
- const effortMetric = statement.metrics?.getMetric?.("effort");
1532
- const effort = effortMetric && "effort" in effortMetric ? effortMetric.effort : void 0;
1533
- if (typeof effort === "string") {
1534
- const slug = effort.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
1535
- if (slug) slugs.add(slug);
1536
- }
1537
- }
1538
- }
1539
- return [...slugs];
1540
- }
1541
- function titleFromLabel(sourceLabel) {
1542
- if (!sourceLabel || sourceLabel === "-") return "stdin";
1543
- const stem = sourceLabel.split("/").pop() ?? sourceLabel;
1544
- return stem.replace(/\.[^.]+$/, "") || sourceLabel;
1545
- }
1546
- async function saveSource(db, options, startTime) {
1547
- const noteId = options.noteId ?? stableNoteId(options.sourceLabel, options.source);
1548
- const observed = await db.storage.readonly("notes").get(noteId);
1549
- const expected = options.expectedSourceRevision ?? observed?.sourceRevision ?? 0;
1550
- const now = options.now ?? Date.now();
1551
- return db.storage.transaction([...saveStores], "readwrite", async (tx) => {
1552
- const existing = await tx.readonly("notes").get(noteId);
1553
- if ((existing?.sourceRevision ?? 0) !== expected) {
1554
- throw new Error(`Stale Note source revision for ${noteId}; expected ${expected}`);
1555
- }
1556
- let sourceRevision = existing?.rawContent === options.source ? existing.sourceRevision : (existing?.sourceRevision ?? 0) + 1;
1557
- const blocks = wodWikiCore.reconcileNoteBlocks(options.source, {
1558
- sourceRevision,
1559
- blocks: options.blocks ?? (existing?.rawContent === options.source ? existing.blocks : void 0),
1560
- registeredWidgets,
1561
- createId: randomId
1562
- });
1563
- wodWikiCore.validateNoteBlocks(options.source, blocks, sourceRevision, { registeredWidgets });
1564
- const history = await tx.readonly("block_revisions").getAllFromIndex("by-note", noteId);
1565
- const latest = /* @__PURE__ */ new Map();
1566
- for (const row of history) {
1567
- if ((latest.get(row.blockId)?.version ?? 0) < row.version) latest.set(row.blockId, row);
1568
- }
1569
- const revisions = [];
1570
- for (const block of Object.values(blocks)) {
1571
- const saved = latest.get(block.id);
1572
- const rawContent = options.source.slice(block.from, block.to);
1573
- const unchanged = saved && saved.rawContent === rawContent && saved.kind === block.kind && saved.dialect === block.dialect && saved.sport === block.sport;
1574
- block.version = unchanged ? saved.version : (saved?.version ?? 0) + 1;
1575
- if (!unchanged) revisions.push({
1576
- noteId,
1577
- blockId: block.id,
1578
- version: block.version,
1579
- kind: block.kind,
1580
- rawContent,
1581
- savedAt: now,
1582
- ...block.dialect ? { dialect: block.dialect } : {},
1583
- ...block.sport ? { sport: block.sport } : {}
1584
- });
1585
- }
1586
- if (existing?.rawContent === options.source && !wodWikiStorage.rowsEqual(existing.blocks, blocks)) {
1587
- sourceRevision++;
1588
- for (const block of Object.values(blocks)) block.sourceRevision = sourceRevision;
1589
- }
1590
- const note = {
1591
- ...existing,
1592
- id: noteId,
1593
- title: options.title ?? existing?.title ?? titleFromLabel(options.sourceLabel),
1594
- type: existing?.type ?? "note",
1595
- date: existing?.date ?? now,
1596
- createdAt: existing?.createdAt ?? now,
1597
- rawContent: options.source,
1598
- sourceRevision,
1599
- blocks
1600
- };
1601
- const priorRows = await tx.readonly("block_index").getAllFromIndex("by-note", noteId);
1602
- const priorEfforts = await tx.readonly("block_efforts").getAllFromIndex("by-note", noteId);
1603
- const priorById = new Map(priorRows.map((row) => [row.id, row]));
1604
- const nextRows = [];
1605
- const nextEfforts = [];
1606
- for (const [position, block] of Object.values(blocks).sort((a, b) => a.from - b.from).entries()) {
1607
- const id = `${noteId}:${block.id}`;
1608
- const prior = priorById.get(id);
1609
- const rawContent = options.source.slice(block.from, block.to);
1610
- const body = wodWikiCore.parseDocumentSections(rawContent, { registeredWidgets })[0]?.displayContent ?? rawContent;
1611
- const fingerprint = block.kind === "wod" ? wodWikiCore.blockContentId(body) : void 0;
1612
- nextRows.push({
1613
- ...prior,
1614
- id,
1615
- noteId,
1616
- blockId: block.id,
1617
- blockVersion: block.version,
1618
- dataType: block.kind,
1619
- sourceRevision,
1620
- from: block.from,
1621
- to: block.to,
1622
- position,
1623
- rawContent,
1624
- noteTitle: note.title,
1625
- createdAt: prior?.createdAt ?? now,
1626
- blockContentId: fingerprint,
1627
- ...note.sourceId ? { sourceId: note.sourceId } : {}
1628
- });
1629
- if (block.kind === "wod") {
1630
- for (const effortSlug of extractEffortSlugs(wodWikiLang.parseScript(body, { sport: block.sport, dialect: block.dialect }).statements)) {
1631
- const effortId = `${noteId}:${block.id}:${effortSlug}`;
1632
- const previous = priorEfforts.find((row) => row.id === effortId);
1633
- nextEfforts.push({
1634
- ...previous,
1635
- id: effortId,
1636
- noteId,
1637
- blockId: block.id,
1638
- blockContentId: fingerprint,
1639
- effortSlug,
1640
- createdAt: previous?.createdAt ?? now
1641
- });
1642
- }
1643
- }
1644
- }
1645
- if (!wodWikiStorage.rowsEqual(existing, note)) await tx.readwrite("notes").put(note);
1646
- if (revisions.length) await tx.readwrite("block_revisions").putMany(revisions);
1647
- await diffProjection(tx, "block_index", priorRows, nextRows);
1648
- await diffProjection(tx, "block_efforts", priorEfforts, nextEfforts);
1649
- const tags = await saveTags(tx, noteId, options.tags, now);
1650
- let session;
1651
- let revision;
1652
- if (startTime !== void 0) {
1653
- const block = Object.values(blocks).filter((row) => row.kind === "wod").sort((a, b) => a.from - b.from)[0];
1654
- if (!block) throw new Error("[wod run] Note has no actionable workout block");
1655
- revision = revisions.find((row) => row.blockId === block.id) ?? latest.get(block.id);
1656
- if (!revision) throw new Error("[wod run] Saved workout revision is unavailable");
1657
- session = {
1658
- id: randomId(),
1659
- noteId,
1660
- blockId: block.id,
1661
- blockVersion: block.version,
1662
- blockContentId: wodWikiCore.blockContentId(wodWikiCore.parseDocumentSections(revision.rawContent)[0]?.displayContent ?? revision.rawContent),
1663
- origin: "journal",
1664
- status: "in-progress",
1665
- startTime,
1666
- endTime: startTime,
1667
- duration: 0,
1668
- completed: false,
1669
- createdAt: startTime
1670
- };
1671
- await tx.readwrite("sessions").put(session);
1672
- }
1673
- return { noteId, note, blocks: nextRows.length, efforts: nextEfforts.length, tags, created: !existing, session, revision };
1674
- });
1675
- }
1676
- async function diffProjection(tx, store, previous, next) {
1677
- const nextIds = new Set(next.map((row) => row.id));
1678
- for (const row of previous) if (!nextIds.has(row.id)) await tx.readwrite(store).delete(row.id);
1679
- const old = new Map(previous.map((row) => [row.id, row]));
1680
- for (const row of next) {
1681
- if (!wodWikiStorage.rowsEqual(old.get(row.id), row)) {
1682
- if (store === "block_index" && "blockVersion" in row) await tx.readwrite("block_index").put(row);
1683
- if (store === "block_efforts" && "effortSlug" in row) await tx.readwrite("block_efforts").put(row);
1684
- }
1685
- }
1686
- }
1687
- async function saveTags(tx, noteId, items, now) {
1688
- const previous = await tx.readonly("note_tags").getAllFromIndex("by-note", noteId);
1689
- if (!items) return previous.length;
1690
- const next = /* @__PURE__ */ new Map();
1691
- for (const item of items) {
1692
- const label = item.label.trim();
1693
- if (!label) continue;
1694
- const found = (await tx.readonly("tags").getAllFromIndex("by-label", label))[0];
1695
- const tag = found ? { ...found, ...!found.type && item.type ? { type: item.type } : {} } : { id: uuid5(CLI_UUID_NAMESPACE, `tag:${label}`), label, type: item.type, createdAt: now };
1696
- if (!wodWikiStorage.rowsEqual(found, tag)) await tx.readwrite("tags").put(tag);
1697
- const id = previous.find((link) => link.tagId === tag.id)?.id ?? uuid5(CLI_UUID_NAMESPACE, `note-tag:${noteId}:${tag.id}`);
1698
- next.set(id, { ...previous.find((link) => link.id === id), id, noteId, tagId: tag.id });
1699
- }
1700
- for (const link of previous) if (!next.has(link.id)) await tx.readwrite("note_tags").delete(link.id);
1701
- const old = new Map(previous.map((row) => [row.id, row]));
1702
- for (const link of next.values()) if (!wodWikiStorage.rowsEqual(old.get(link.id), link)) await tx.readwrite("note_tags").put(link);
1703
- return next.size;
1704
- }
1705
- function persistNote(db, options) {
1706
- return saveSource(db, options);
1707
- }
1708
- async function startRun(db, options, startTime = Date.now()) {
1709
- const saved = await saveSource(db, options, startTime);
1710
- if (!saved.session || !saved.revision) throw new Error("[wod run] Atomic run capture failed");
1711
- return { session: saved.session, revision: saved.revision, noteCreated: saved.created };
107
+ function isIRFile(obj) {
108
+ if (!obj || typeof obj !== "object") return false;
109
+ const cand = obj;
110
+ return cand.$schema === "https://wod-wiki.dev/ir/v1.json" && typeof cand.kind === "string" && typeof cand.generatedAt === "number" && typeof cand.source === "string" && "data" in cand;
1712
111
  }
1713
- async function persistRun(db, started, executionLog) {
1714
- const results = executionLog.results;
1715
- const session = {
1716
- ...started.session,
1717
- status: results.completed ? "completed" : "in-progress",
1718
- endTime: results.endTime,
1719
- duration: results.duration ?? 0,
1720
- completed: results.completed
1721
- };
1722
- const identity = {
1723
- noteId: session.noteId,
1724
- resultId: session.id,
1725
- blockId: session.blockId,
1726
- blockVersion: session.blockVersion,
1727
- blockContentId: session.blockContentId,
1728
- origin: session.origin,
1729
- pageId: session.pageId,
1730
- workoutTimestamp: session.createdAt
112
+ function statementToNode(stmt, idMap) {
113
+ const meta = stmt.meta;
114
+ const metrics = stmt.metrics.toArray().map((m) => ({
115
+ type: m.type,
116
+ ...m.image !== void 0 ? { image: m.image } : {},
117
+ ...m.value !== void 0 ? { value: m.value } : {},
118
+ ...m.origin !== void 0 ? { origin: m.origin } : {}
119
+ }));
120
+ const raw = meta?.raw ?? "";
121
+ const from = meta?.startOffset ?? 0;
122
+ const to = meta?.endOffset ?? from + raw.length;
123
+ const durationFrag = stmt.metrics.find((m) => m.type === wodWikiCore.MetricType.Duration || m.type === "duration");
124
+ const roundsFrag = stmt.metrics.find((m) => m.type === wodWikiCore.MetricType.Rounds || m.type === "rounds");
125
+ const type = durationFrag ? "duration" : roundsFrag ? "rounds" : "statement";
126
+ const hints = wodWikiLang.getHints(stmt);
127
+ const childIds = (stmt.children ?? []).flat();
128
+ const children = childIds.map((cid) => idMap.get(cid)).filter((c) => c !== void 0).map((c) => statementToNode(c, idMap));
129
+ return {
130
+ id: stmt.id,
131
+ type,
132
+ raw,
133
+ from,
134
+ to,
135
+ metrics,
136
+ ...hints.length > 0 ? { hints } : {},
137
+ ...children.length > 0 ? { children } : {}
1731
138
  };
1732
- const events = wodWikiWql.toEventRows(executionLog.logs, identity);
1733
- const summaries = wodWikiWql.toSummaryEventRows(executionLog.logs, identity);
1734
- await db.storage.transaction(["sessions", "events", "block_revisions"], "readwrite", async (tx) => {
1735
- const saved = await tx.readonly("sessions").get(session.id);
1736
- if (!saved || saved.noteId !== session.noteId || saved.blockId !== session.blockId || saved.blockVersion !== session.blockVersion) {
1737
- throw new Error("[wod run] Captured Session source pin changed");
1738
- }
1739
- const revision = await tx.readonly("block_revisions").get([session.noteId, session.blockId, session.blockVersion]);
1740
- if (!revision || !wodWikiStorage.rowsEqual(revision, started.revision)) throw new Error("[wod run] Captured revision is unavailable");
1741
- await tx.readwrite("sessions").put(session);
1742
- await tx.readwrite("events").putMany([...events, ...summaries]);
1743
- });
1744
- return { noteId: session.noteId, resultId: session.id, session, events: events.length, summaries: summaries.length, noteCreated: started.noteCreated };
1745
139
  }
1746
- async function persistSessionBundle(db, session, events) {
1747
- if ("segmentId" in session || "segmentVersion" in session || "version" in session || "rawContent" in session || "data" in session || !session.noteId || !session.blockId || !Number.isSafeInteger(session.blockVersion) || session.blockVersion < 1) {
1748
- throw new Error("[wod import] Session requires an exact block revision pin");
140
+ function buildStatementTree(script) {
141
+ const idMap = new Map(script.statements.map((s) => [s.id, s]));
142
+ const topLevel = script.statements.filter((s) => s.parent === void 0 || !idMap.has(s.parent));
143
+ if (topLevel.length === 1 && topLevel[0].children.length > 0) {
144
+ return statementToNode(topLevel[0], idMap);
1749
145
  }
1750
- await db.storage.transaction(["sessions", "events", "block_revisions"], "readwrite", async (tx) => {
1751
- const revision = await tx.readonly("block_revisions").get([session.noteId, session.blockId, session.blockVersion]);
1752
- if (!revision || revision.kind !== "wod") throw new Error("[wod import] Exact immutable workout revision is unavailable");
1753
- for (const event of events) {
1754
- if (event.resultId !== session.id || event.noteId !== session.noteId || event.blockId !== session.blockId || event.blockVersion !== session.blockVersion) {
1755
- throw new Error("[wod import] Event source pin differs from Session source pin");
1756
- }
1757
- }
1758
- const existing = await tx.readonly("sessions").get(session.id);
1759
- if (existing && !wodWikiStorage.rowsEqual(existing, session)) throw new Error("[wod import] Session identity conflicts with existing row");
1760
- if (!existing) await tx.readwrite("sessions").put(session);
1761
- for (const event of events) {
1762
- const old = await tx.readonly("events").get(event.id);
1763
- if (old && !wodWikiStorage.rowsEqual(old, event)) throw new Error("[wod import] Event identity conflicts with existing row");
1764
- if (!old) await tx.readwrite("events").put(event);
1765
- }
1766
- });
1767
- return { events: events.length };
1768
- }
1769
- async function persistEffort(db, effort) {
1770
- const sql = await loadSql();
1771
- await db.db.transaction(async (tx) => {
1772
- await sql.putRow(tx, sql.storeDefinition("efforts"), effort);
1773
- });
1774
- }
1775
- async function persistTypedTags(db, labels, type) {
1776
- return db.storage.transaction(["tags"], "readwrite", async (tx) => {
1777
- let added = 0;
1778
- for (const raw of labels) {
1779
- const label = raw.trim();
1780
- if (!label) continue;
1781
- const tag = (await tx.readonly("tags").getAllFromIndex("by-label", label))[0];
1782
- if (!tag) {
1783
- await tx.readwrite("tags").put({ id: uuid5(CLI_UUID_NAMESPACE, `tag:${label}`), label, type, createdAt: Date.now() });
1784
- added++;
1785
- } else if (!tag.type) {
1786
- await tx.readwrite("tags").put({ ...tag, type });
1787
- }
1788
- }
1789
- return added;
1790
- });
146
+ return {
147
+ id: 0,
148
+ type: "root",
149
+ raw: script.source,
150
+ from: 0,
151
+ to: script.source.length,
152
+ metrics: [],
153
+ children: topLevel.map((s) => statementToNode(s, idMap))
154
+ };
1791
155
  }
1792
156
  var NOTE_ID_FRONTMATTER_KEY = "wod-note-id";
1793
157
  var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
@@ -2281,13 +645,188 @@ function csvToSessionRows(csv) {
2281
645
  if (!session) throw new Error("[wod import] Session CSV did not contain session columns");
2282
646
  return { session, events: [...eventRows.values()] };
2283
647
  }
2284
-
2285
- // src/cli/seedbuild/runtime.ts
2286
- init_effort_file();
2287
- init_contract();
2288
-
2289
- // src/cli/seedbuild/validation.ts
2290
- init_contract();
648
+
649
+ // src/version.ts
650
+ var VERSION = "0.36.2133";
651
+ var GIT_SHA = "bb2644d";
652
+ var BUILD_TIME = "2026-10-15T01:00:35.539Z";
653
+ var SEMVER = {
654
+ version: "0.36.2133",
655
+ gitSha: "bb2644d",
656
+ buildTime: "2026-10-15T01:00:35.539Z"
657
+ };
658
+ var SEED_SCHEMA = 8;
659
+ var SEED_REGISTERED_WIDGETS = wodWikiCore.KNOWN_SECTION_WIDGETS;
660
+ var SEED_BROADCAST_CHANNEL = "wodwiki.seed";
661
+ var EFFORTS_CHUNK_ID = "efforts";
662
+ var CANVAS_CHUNK_ID = "canvas";
663
+ var DASHBOARDS_CHUNK_ID = "dashboards";
664
+ var TEMPLATE_CHUNK_ID = "template";
665
+ function seedMetaKey(sourceId) {
666
+ return `seed:${sourceId}`;
667
+ }
668
+ function emptySeedMeta(key) {
669
+ return { key, schema: SEED_SCHEMA, seedVersion: 0, builtAt: "", importedAt: 0, chunks: {}, claim: null };
670
+ }
671
+ function catalogForChunkId(chunkId) {
672
+ const value = /^(?:collection|feed)\.(.+)$/.exec(chunkId)?.[1];
673
+ return value && value !== "_root" ? value : void 0;
674
+ }
675
+ function canvasRouteSlug(content) {
676
+ const fm = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content);
677
+ if (fm?.[1] && !/^template:\s*canvas\s*$/m.test(fm[1])) return null;
678
+ return fm?.[1]?.match(/^route:\s*(\S+)\s*$/m)?.[1]?.replace(/^\//, "") || null;
679
+ }
680
+ function seedNoteSourceId(path, content, chunkId) {
681
+ const parts = path.split("/");
682
+ const stem = (parts.at(-1) ?? "").replace(/\.md$/i, "");
683
+ const under = parts.slice(2).map((p) => p.replace(/\.md$/i, "")).join("/");
684
+ if (chunkId === CANVAS_CHUNK_ID) return `guides:${canvasRouteSlug(content) ?? under}`;
685
+ if (chunkId === DASHBOARDS_CHUNK_ID) return "dashboards";
686
+ if (chunkId === EFFORTS_CHUNK_ID) return "efforts";
687
+ if (chunkId === TEMPLATE_CHUNK_ID) return "playground";
688
+ if (chunkId.startsWith("collection.")) {
689
+ const catalog = catalogForChunkId(chunkId);
690
+ return !catalog ? "collection:_root" : stem.toLowerCase() === "readme" ? `collection:${catalog}` : `collection:${catalog}/${stem}`;
691
+ }
692
+ if (chunkId.startsWith("feed.")) return `feed:feeds/${under}`;
693
+ return void 0;
694
+ }
695
+
696
+ // src/seed/frontmatter.ts
697
+ var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
698
+ function unquote(value) {
699
+ return value.replace(
700
+ /^(['"])(.*)\1$/,
701
+ (_match, quote, inner) => quote === '"' ? inner.replace(/\\(["\\])/g, "$1") : inner
702
+ );
703
+ }
704
+ function parseFrontmatter(raw) {
705
+ const match = raw.match(FRONTMATTER_RE);
706
+ if (!match) return { meta: {}, body: raw };
707
+ return { meta: parseMetaLines(match[1].split(/\r?\n/)), body: raw.slice(match[0].length) };
708
+ }
709
+ function parseMetaLines(lines) {
710
+ const meta = {};
711
+ for (let i = 0; i < lines.length; i++) {
712
+ const keyMatch = lines[i].match(/^([A-Za-z][\w.-]*)\s*:(.*)$/);
713
+ if (!keyMatch) continue;
714
+ const key = keyMatch[1];
715
+ const rawVal = keyMatch[2].trim();
716
+ if (rawVal === "") {
717
+ const items = [];
718
+ while (i + 1 < lines.length) {
719
+ const itemMatch = lines[i + 1].match(/^\s+-\s+(.+)$/);
720
+ if (!itemMatch) break;
721
+ items.push(itemMatch[1].trim());
722
+ i++;
723
+ }
724
+ meta[key] = items.length > 0 ? items : "";
725
+ } else {
726
+ const wasQuoted = /^(['"])(.*)\1$/.test(rawVal);
727
+ const value = unquote(rawVal);
728
+ const num2 = Number(value);
729
+ meta[key] = !wasQuoted && value !== "" && !isNaN(num2) ? num2 : value;
730
+ }
731
+ }
732
+ return meta;
733
+ }
734
+ function getScalar(meta, key) {
735
+ const value = meta[key];
736
+ return typeof value === "string" || typeof value === "number" ? value : void 0;
737
+ }
738
+ function getList(meta, key) {
739
+ const value = meta[key];
740
+ return Array.isArray(value) ? value : [];
741
+ }
742
+
743
+ // src/seed/effort-file.ts
744
+ function parseDiscipline(val) {
745
+ if (!val) return void 0;
746
+ const normalized = unquoteYaml(val.trim()).toLowerCase();
747
+ if (wodWikiLang.isEffortDiscipline(normalized)) return normalized;
748
+ console.warn(`[effort-file] unknown discipline '${val}' \u2014 treating as unset`);
749
+ return void 0;
750
+ }
751
+ function unquoteYaml(val) {
752
+ if (val.startsWith('"') && val.endsWith('"')) {
753
+ return val.slice(1, -1).replace(/\\(["\\])/g, "$1");
754
+ }
755
+ if (val.startsWith("'") && val.endsWith("'")) {
756
+ return val.slice(1, -1).replace(/\\'/g, "'");
757
+ }
758
+ return val;
759
+ }
760
+ function coerceNestedScalar(raw) {
761
+ if (raw === "true") return true;
762
+ if (raw === "false") return false;
763
+ if (raw !== "" && !Number.isNaN(Number(raw))) return Number(raw);
764
+ return raw;
765
+ }
766
+ function readNestedSection(lines, key) {
767
+ const start = lines.findIndex((l) => new RegExp(`^${key}:\\s*$`).test(l));
768
+ if (start === -1) return void 0;
769
+ const result = {};
770
+ let subKey = null;
771
+ for (let i = start + 1; i < lines.length; i++) {
772
+ const line = lines[i];
773
+ if (/^\S/.test(line)) break;
774
+ const trimmed = line.trim();
775
+ if (!trimmed || trimmed.startsWith("#")) continue;
776
+ const indent = line.length - line.trimStart().length;
777
+ const m = trimmed.match(/^([\w.-]+):\s*(.*)$/);
778
+ if (!m) continue;
779
+ const [, k, rawVal] = m;
780
+ if (indent === 2) {
781
+ if (rawVal === "") {
782
+ subKey = k;
783
+ result[k] = {};
784
+ } else {
785
+ subKey = null;
786
+ result[k] = coerceNestedScalar(rawVal);
787
+ }
788
+ } else if (indent >= 4 && subKey !== null) {
789
+ result[subKey][k] = coerceNestedScalar(rawVal);
790
+ }
791
+ }
792
+ return Object.keys(result).length > 0 ? result : void 0;
793
+ }
794
+ function parseEffortFile(raw) {
795
+ const blockMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
796
+ if (!blockMatch) return null;
797
+ const { meta, body } = parseFrontmatter(raw);
798
+ const innerLines = blockMatch[1].split(/\r?\n/);
799
+ const id = getScalar(meta, "id");
800
+ const slug = getScalar(meta, "slug");
801
+ const label = getScalar(meta, "label");
802
+ const aliases = getList(meta, "aliases");
803
+ const baseAttrs = readNestedSection(innerLines, "baseAttributes");
804
+ const hints = readNestedSection(innerLines, "hints");
805
+ const derivation = readNestedSection(innerLines, "derivation");
806
+ const metRaw = baseAttrs?.met;
807
+ const met = typeof metRaw === "number" ? metRaw : Number(metRaw);
808
+ if (!id || !slug || !label || metRaw === void 0 || Number.isNaN(met)) return null;
809
+ const discipline = parseDiscipline(
810
+ typeof baseAttrs?.discipline === "string" ? baseAttrs.discipline : void 0
811
+ );
812
+ const intensityTier = baseAttrs?.intensityTier;
813
+ const trimmedBody = body.trim();
814
+ return {
815
+ id: String(id),
816
+ slug: String(slug),
817
+ label: String(label),
818
+ aliases,
819
+ baseAttributes: {
820
+ met,
821
+ ...discipline ? { discipline } : {},
822
+ ...intensityTier ? { intensityTier } : {}
823
+ },
824
+ registrySource: "bundled",
825
+ ...trimmedBody ? { body: trimmedBody } : {},
826
+ ...hints ? { hints } : {},
827
+ ...derivation ? { derivation } : {}
828
+ };
829
+ }
2291
830
  function object(value) {
2292
831
  return typeof value === "object" && value !== null && !Array.isArray(value);
2293
832
  }
@@ -2298,7 +837,7 @@ function nonnegative(value) {
2298
837
  return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
2299
838
  }
2300
839
  function assertSeedManifest(value) {
2301
- if (!object(value) || value.schema !== exports.SEED_SCHEMA) throw new Error("Incompatible seed schema; rebuild seed artifacts");
840
+ if (!object(value) || value.schema !== SEED_SCHEMA) throw new Error("Incompatible seed schema; rebuild seed artifacts");
2302
841
  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");
2303
842
  for (const key of ["versionLabel", "commit"]) if (value[key] !== void 0 && typeof value[key] !== "string") throw new Error("invalid seed manifest metadata");
2304
843
  if (value.dirty !== void 0 && typeof value.dirty !== "boolean") throw new Error("invalid seed manifest metadata");
@@ -2319,9 +858,9 @@ function assertSeedRows(value) {
2319
858
  for (const [id, block] of Object.entries(row.blocks)) {
2320
859
  if (!object(block) || block.id !== id || !id || block.version !== 1 || block.sourceRevision !== 1 || !nonnegative(block.from) || !positive(block.to) || block.from >= block.to || block.to > row.content.length || !["wod", "query", "widget", "embed"].includes(String(block.kind)) || (block.kind === "wod" ? !["time", "log"].includes(String(block.dialect)) : block.dialect !== void 0) || block.sport !== void 0 && typeof block.sport !== "string") throw new Error("invalid seed block range or revision");
2321
860
  }
2322
- wodWikiCore.validateNoteBlocks(row.content, row.blocks, 1, { registeredWidgets: exports.SEED_REGISTERED_WIDGETS });
861
+ wodWikiCore.validateNoteBlocks(row.content, row.blocks, 1, { registeredWidgets: SEED_REGISTERED_WIDGETS });
2323
862
  const indexed = /* @__PURE__ */ new Set();
2324
- const sections = wodWikiCore.parseDocumentSections(row.content, { registeredWidgets: exports.SEED_REGISTERED_WIDGETS });
863
+ const sections = wodWikiCore.parseDocumentSections(row.content, { registeredWidgets: SEED_REGISTERED_WIDGETS });
2325
864
  for (const projection of row.blockIndex) {
2326
865
  if (!object(projection) || typeof projection.blockId !== "string") throw new Error("invalid seed projection");
2327
866
  const block = row.blocks[projection.blockId];
@@ -2349,7 +888,7 @@ function assertSeedRows(value) {
2349
888
  return value;
2350
889
  }
2351
890
 
2352
- // src/cli/seedbuild/runtime.ts
891
+ // src/seed/runtime.ts
2353
892
  var SEED_NAMESPACE = "1b671a64-40d5-491e-99b0-da01ff1f3341";
2354
893
  async function seedNoteId(path) {
2355
894
  const digest = await crypto.subtle.digest("SHA-1", new TextEncoder().encode(`${SEED_NAMESPACE}:${path}`));
@@ -2380,7 +919,7 @@ function refreshedBlocks(existing, rawContent, sourceRevision) {
2380
919
  const trusted = intersect.length <= 1 ? existing.blocks : Object.fromEntries(Object.entries(existing.blocks).filter(([, b]) => !intersect.includes(b)));
2381
920
  const mapped = wodWikiCore.mapNoteBlockChanges(trusted, [{ from, to: endOld, insert: rawContent.slice(from, endNew) }]);
2382
921
  let ordinal = 0;
2383
- return wodWikiCore.reconcileNoteBlocks(rawContent, { sourceRevision, blocks: mapped, registeredWidgets: exports.SEED_REGISTERED_WIDGETS, createId: () => `seed:${existing.id}:r${sourceRevision}:${ordinal++}` });
922
+ return wodWikiCore.reconcileNoteBlocks(rawContent, { sourceRevision, blocks: mapped, registeredWidgets: SEED_REGISTERED_WIDGETS, createId: () => `seed:${existing.id}:r${sourceRevision}:${ordinal++}` });
2384
923
  }
2385
924
  async function putChanged(store, values, keys) {
2386
925
  if (!values.length) return;
@@ -2410,7 +949,7 @@ async function applySeedChunk(storage, write) {
2410
949
  const metaStore = tx.readwrite("meta");
2411
950
  const actualMeta = await metaStore.get(write.meta.key);
2412
951
  if (!equal(actualMeta, write.expectedMeta)) throw new Error("Seed checkpoint changed during import");
2413
- if (write.meta.schema !== exports.SEED_SCHEMA || write.manifest.schema !== exports.SEED_SCHEMA) throw new Error("Incompatible seed schema; recreate artifacts");
952
+ if (write.meta.schema !== SEED_SCHEMA || write.manifest.schema !== SEED_SCHEMA) throw new Error("Incompatible seed schema; recreate artifacts");
2414
953
  const noteStore = tx.readwrite("notes");
2415
954
  const priorIds = write.chunkId ? write.expectedMeta?.chunks[write.chunkId]?.noteIds ?? [] : [];
2416
955
  const declared = new Set(ids);
@@ -2448,1154 +987,113 @@ async function applySeedChunk(storage, write) {
2448
987
  }
2449
988
  if (!existing) result.addedNotes++;
2450
989
  else if (existing.rawContent !== row.content) result.updatedNotes++;
2451
- wodWikiCore.validateNoteBlocks(row.content, row.blocks, 1, { registeredWidgets: exports.SEED_REGISTERED_WIDGETS });
990
+ wodWikiCore.validateNoteBlocks(row.content, row.blocks, 1, { registeredWidgets: SEED_REGISTERED_WIDGETS });
2452
991
  const sourceRevision = existing ? existing.sourceRevision + (existing.rawContent === row.content ? 0 : 1) : 1;
2453
992
  let ordinal = 0;
2454
993
  const retainedHistory = history.get(id) ?? [];
2455
- const map = !existing ? retainedHistory.length ? wodWikiCore.reconcileNoteBlocks(row.content, { sourceRevision, registeredWidgets: exports.SEED_REGISTERED_WIDGETS, createId: () => `seed:${id}:new${retainedHistory.length}:${ordinal++}` }) : structuredClone(row.blocks) : existing.rawContent === row.content ? structuredClone(existing.blocks) : refreshedBlocks(existing, row.content, sourceRevision);
994
+ const map = !existing ? retainedHistory.length ? wodWikiCore.reconcileNoteBlocks(row.content, { sourceRevision, registeredWidgets: SEED_REGISTERED_WIDGETS, createId: () => `seed:${id}:new${retainedHistory.length}:${ordinal++}` }) : structuredClone(row.blocks) : existing.rawContent === row.content ? structuredClone(existing.blocks) : refreshedBlocks(existing, row.content, sourceRevision);
2456
995
  for (const block of Object.values(map)) {
2457
996
  const saved = (history.get(id) ?? []).filter((r) => r.blockId === block.id).sort((a, b) => b.version - a.version)[0];
2458
997
  const source = row.content.slice(block.from, block.to);
2459
- if (existing?.rawContent === row.content) continue;
2460
- const same = saved && saved.kind === block.kind && saved.rawContent === source && saved.dialect === block.dialect && saved.sport === block.sport;
2461
- block.version = !existing ? 1 : same ? saved.version : (saved?.version ?? 0) + 1;
2462
- if (!existing || !same) revisions.push({ noteId: id, blockId: block.id, version: block.version, kind: block.kind, rawContent: source, savedAt: write.meta.importedAt, ...block.dialect ? { dialect: block.dialect } : {}, ...block.sport ? { sport: block.sport } : {} });
2463
- }
2464
- wodWikiCore.validateNoteBlocks(row.content, map, sourceRevision, { registeredWidgets: exports.SEED_REGISTERED_WIDGETS });
2465
- const fm = readFrontmatterMeta(row.content);
2466
- const title = typeof fm["title"] === "string" && fm["title"].trim() ? fm["title"].trim() : titleFromPath(row.path);
2467
- const sourceId = write.chunkId ? seedNoteSourceId(row.path, row.content, write.chunkId) : void 0;
2468
- if (!sourceId) throw new Error(`Unknown seed source family ${write.chunkId}`);
2469
- const date = typeof fm["date"] === "string" && /^\d{4}-\d{2}-\d{2}$/.test(fm["date"]) ? Date.parse(`${fm["date"]}T12:00:00Z`) : void 0;
2470
- const note = { ...existing, id, title, rawContent: row.content, sourceRevision, blocks: map, createdAt: write.manifest.version, ...date === void 0 ? {} : { date }, type: write.chunkId === exports.DASHBOARDS_CHUNK_ID ? "dashboard" : "note", sourceId, catalog: catalogForChunkId(write.chunkId ?? ""), seedOrigin: "seed", seedVersion: write.manifest.version, seedChunkId: write.chunkId ?? void 0, sourcePath: row.path };
2471
- if (date === void 0) delete note.date;
2472
- if (!equal(note, existing)) changedNotes.push(note);
2473
- const byRange = new Map(Object.values(map).map((b) => [`${b.from}:${b.to}`, b]));
2474
- const compiledById = new Map(row.blockIndex.map((b) => [b.blockId, b]));
2475
- const remap = /* @__PURE__ */ new Map();
2476
- for (const compiled of row.blockIndex) {
2477
- const block = byRange.get(`${compiled.from}:${compiled.to}`);
2478
- if (!block || block.kind !== compiled.dataType) throw new Error("Orphan seed projection");
2479
- remap.set(compiled.blockId, block.id);
2480
- const projection = { ...compiled, id: `${id}:${block.id}`, noteId: id, blockId: block.id, blockVersion: block.version, sourceRevision, noteTitle: title, sourceId, sourcePath: row.path, rawContent: row.content.slice(block.from, block.to) };
2481
- const prior = currentIndexes[slot.get(id)].find((b) => b.id === projection.id);
2482
- if (!prior || prior.isStatic === true) blocks.push(projection);
2483
- }
2484
- for (const compiled of row.blockEfforts) {
2485
- const blockId = remap.get(compiled.blockId);
2486
- if (!blockId || compiledById.get(compiled.blockId)?.dataType !== "wod") throw new Error("Orphan seed effort link");
2487
- const link = { ...compiled, id: `${id}:${blockId}:${compiled.effortSlug}`, noteId: id, blockId, sourcePath: row.path };
2488
- const prior = currentEfforts[slot.get(id)].find((b) => b.id === link.id);
2489
- if (!prior || prior.isStatic === true) blockEfforts.push(prior ? { ...link, createdAt: prior.createdAt } : link);
2490
- }
2491
- const n = slot.get(id);
2492
- const wantedTags = /* @__PURE__ */ new Set();
2493
- for (const item of extractTypedTags(row.content)) {
2494
- let tag = tagsByLabel.get(item.label);
2495
- if (!tag) {
2496
- tag = { id: tagIds.get(item.label), label: item.label, type: item.type, createdAt: write.meta.importedAt };
2497
- changedTags.push(tag);
2498
- tagsByLabel.set(item.label, tag);
2499
- } else if (item.type && !tag.type) {
2500
- tag = { ...tag, type: item.type };
2501
- changedTags.push(tag);
2502
- tagsByLabel.set(item.label, tag);
2503
- }
2504
- if (wantedTags.has(tag.id)) continue;
2505
- wantedTags.add(tag.id);
2506
- const link = currentTags[n].find((l) => l.tagId === tag.id);
2507
- if (!link) noteTags.push({ id: `${id}:${tag.id}`, noteId: id, tagId: tag.id });
2508
- }
2509
- for (const link of currentTags[n]) if (!wantedTags.has(link.tagId)) await tagLinks.delete(link.id);
2510
- const slug = fm["slug"];
2511
- if (typeof slug === "string" && pagesBySlug.has(slug)) {
2512
- const pageId = pagesBySlug.get(slug);
2513
- pages.push({ id: pageId, slug, title: titleFromPath(row.path), createdAt: write.manifest.version });
2514
- pageNotes.push({ id: linkIds.get(id), pageId, noteId: id, createdAt: write.manifest.version });
2515
- }
2516
- result.totalNotes++;
2517
- result.totalBlocks += Object.keys(map).length;
2518
- }
2519
- const wantedBlocks = new Set(blocks.map((b) => b.id)), wantedEfforts = new Set(blockEfforts.map((b) => b.id));
2520
- for (const rows of currentIndexes) for (const row of rows) if (row.isStatic === true && !wantedBlocks.has(row.id)) await indexStore.delete(row.id);
2521
- for (const rows of currentEfforts) for (const row of rows) if (row.isStatic === true && !wantedEfforts.has(row.id)) await effortLinks.delete(row.id);
2522
- for (const id of goneIds) {
2523
- if (!owned.has(id) || !existingById.get(id)) continue;
2524
- const n = slot.get(id);
2525
- await noteStore.delete(id);
2526
- for (const link of currentTags[n]) await tagLinks.delete(link.id);
2527
- for (const link of currentPages[n]) await pageLinks.delete(link.id);
2528
- result.deleted++;
2529
- }
2530
- const effortStore = tx.readwrite("efforts");
2531
- if (write.chunkId === exports.EFFORTS_CHUNK_ID) {
2532
- const efforts = write.rows.filter((_row, i) => owned.has(ids[i])).map((r) => parseEffortFile(r.content)).filter((e) => e !== null).map((e) => ({ ...e, registrySource: "bundled" }));
2533
- const prior = await effortStore.getMany(efforts.map((e) => e.slug));
2534
- const changed = efforts.filter((e, i) => (!prior[i] || prior[i]?.registrySource === "bundled") && !equal(e, prior[i]));
2535
- if (changed.length) await effortStore.putMany(changed);
2536
- result.totalEfforts = changed.length;
2537
- const wantedSlugs = new Set(efforts.map((effort) => effort.slug));
2538
- const removed = allIds.map((id) => {
2539
- const note = existingById.get(id);
2540
- return owned.has(id) && note ? parseEffortFile(note.rawContent)?.slug : void 0;
2541
- }).filter((slug) => !!slug && !wantedSlugs.has(slug));
2542
- const old = await effortStore.getMany(removed);
2543
- for (const [i, effort] of old.entries()) if (effort?.registrySource === "bundled") await effortStore.delete(removed[i]);
2544
- }
2545
- if (changedNotes.length) await noteStore.putMany(changedNotes);
2546
- if (revisions.length) await revisionStore.putMany(revisions);
2547
- await putChanged(indexStore, blocks, blocks.map((b) => b.id));
2548
- await putChanged(effortLinks, blockEfforts, blockEfforts.map((b) => b.id));
2549
- if (changedTags.length) await tagsStore.putMany([...new Map(changedTags.map((t) => [t.id, t])).values()]);
2550
- if (noteTags.length) await tagLinks.putMany(noteTags);
2551
- await putChanged(tx.readwrite("page"), pages, pages.map((p) => p.id));
2552
- await putChanged(pageLinks, pageNotes, pageNotes.map((p) => p.id));
2553
- await metaStore.put(write.meta);
2554
- return result;
2555
- });
2556
- }
2557
-
2558
- // src/cli/seed.ts
2559
- init_contract();
2560
- async function importSeedDir(db, dir, sourceId) {
2561
- const fs = nodeFs(), path = nodePath();
2562
- const source = sourceId || path.basename(dir);
2563
- const manifest = assertSeedManifest(JSON.parse(fs.readFileSync(path.resolve(dir, "manifest.json"), "utf-8")));
2564
- const key = seedMetaKey(source);
2565
- let expected = await db.storage.readonly("meta").get(key);
2566
- if (expected && expected.schema !== exports.SEED_SCHEMA) throw new Error("Incompatible seed checkpoint; recreate storage");
2567
- let meta = expected ?? emptySeedMeta(key);
2568
- if (manifest.version < meta.seedVersion) throw new Error("Seed manifest is older than stored checkpoint");
2569
- const summary = { sourceId: source, version: manifest.version, notes: 0, efforts: 0, blocks: 0, blockEfforts: 0, deleted: 0, skippedUserOwned: 0 };
2570
- const metadataChanged = manifest.version !== meta.seedVersion || manifest.builtAt !== meta.builtAt;
2571
- const plan = manifest.chunks.filter((c) => metadataChanged || meta.chunks[c.id]?.sha256 !== c.sha256);
2572
- plan.sort((a, b) => Number(b.id === exports.CANVAS_CHUNK_ID) - Number(a.id === exports.CANVAS_CHUNK_ID));
2573
- const accumulate = (result) => {
2574
- summary.notes += result.totalNotes;
2575
- summary.efforts += result.totalEfforts;
2576
- summary.blocks += result.totalBlocks;
2577
- summary.deleted += result.deleted;
2578
- summary.skippedUserOwned += result.skippedUserOwned;
2579
- };
2580
- for (const chunk of plan) {
2581
- const rows = assertSeedRows(JSON.parse(fs.readFileSync(path.resolve(dir, chunk.path), "utf-8")));
2582
- if (rows.length !== chunk.count) throw new Error("Seed chunk count mismatch");
2583
- const next = { ...meta, chunks: { ...meta.chunks, [chunk.id]: { sha256: chunk.sha256, noteIds: await Promise.all(rows.map((r) => seedNoteId(r.path))) } }, importedAt: Date.now() };
2584
- accumulate(await applySeedChunk(db.storage, { chunkId: chunk.id, rows, manifest, expectedMeta: expected, meta: next }));
2585
- summary.blockEfforts += rows.reduce((count, row) => count + row.blockEfforts.length, 0);
2586
- meta = next;
2587
- expected = next;
2588
- }
2589
- for (const chunkId of Object.keys(meta.chunks)) {
2590
- if (manifest.chunks.some((c) => c.id === chunkId)) continue;
2591
- const chunks = { ...meta.chunks };
2592
- delete chunks[chunkId];
2593
- const next = { ...meta, chunks, importedAt: Date.now() };
2594
- accumulate(await applySeedChunk(db.storage, { chunkId, rows: [], manifest, expectedMeta: expected, meta: next }));
2595
- meta = next;
2596
- expected = next;
2597
- }
2598
- if (plan.length || metadataChanged || Object.keys(meta.chunks).length !== Object.keys(expected?.chunks ?? {}).length) {
2599
- const next = { ...meta, schema: exports.SEED_SCHEMA, seedVersion: manifest.version, builtAt: manifest.builtAt, importedAt: Date.now() };
2600
- await applySeedChunk(db.storage, { chunkId: null, rows: [], manifest, expectedMeta: expected, meta: next });
2601
- }
2602
- return summary;
2603
- }
2604
- function defaultSeedDirs(cwd) {
2605
- const base = nodePath().resolve(cwd, "apps", "playground", "public", "seed");
2606
- return ["core", "community"].map((id) => nodePath().resolve(base, id)).filter((dir) => nodeFs().existsSync(nodePath().resolve(dir, "manifest.json")));
2607
- }
2608
-
2609
- // src/cli/formatters.ts
2610
- function escapeCsvField2(field) {
2611
- if (field === void 0 || field === null) return "";
2612
- const str = String(field);
2613
- if (str.includes(",") || str.includes('"') || str.includes("\n") || str.includes("\r")) {
2614
- return `"${str.replace(/"/g, '""')}"`;
2615
- }
2616
- return str;
2617
- }
2618
- function formatDate(ts) {
2619
- if (!ts) return "-";
2620
- try {
2621
- return new Date(ts).toISOString();
2622
- } catch {
2623
- return String(ts);
2624
- }
2625
- }
2626
- function formatMetricValue(m) {
2627
- if (m.image) return m.image;
2628
- if (m.value === void 0 || m.value === null) return "";
2629
- if (typeof m.value === "object") return JSON.stringify(m.value);
2630
- return String(m.value);
2631
- }
2632
- function formatMetric(m) {
2633
- const t = m.type || "metric";
2634
- return `${t}:${formatMetricValue(m)}`;
2635
- }
2636
- function formatParseOutput(ir, format, pretty = true) {
2637
- if (format === "json") {
2638
- return JSON.stringify(ir, null, pretty ? 2 : void 0);
2639
- }
2640
- const flatNodes = [];
2641
- const walk = (node) => {
2642
- flatNodes.push(node);
2643
- for (const child of node.children ?? []) {
2644
- walk(child);
2645
- }
2646
- };
2647
- walk(ir.data);
2648
- if (format === "csv") {
2649
- const header = "id,type,from,to,raw,metrics,hints";
2650
- const rows = flatNodes.map(
2651
- (n) => [
2652
- n.id,
2653
- n.type,
2654
- n.from,
2655
- n.to,
2656
- escapeCsvField2(n.raw),
2657
- escapeCsvField2(n.metrics.map(formatMetric).join("; ")),
2658
- escapeCsvField2(n.hints?.join("; ") ?? "")
2659
- ].join(",")
2660
- );
2661
- return [header, ...rows].join("\n");
2662
- }
2663
- const lines = [];
2664
- lines.push(`Parse Tree (${flatNodes.length} statements):`);
2665
- lines.push("\u2500".repeat(80));
2666
- lines.push(
2667
- ["ID".padEnd(6), "Type".padEnd(12), "Span".padEnd(12), "Hints".padEnd(20), "Raw Text"].join(" | ")
2668
- );
2669
- lines.push("\u2500".repeat(80));
2670
- for (const n of flatNodes) {
2671
- const span = `${n.from}-${n.to}`;
2672
- const hints = (n.hints ?? []).join(", ");
2673
- lines.push(
2674
- [
2675
- String(n.id).padEnd(6),
2676
- n.type.padEnd(12),
2677
- span.padEnd(12),
2678
- (hints.length > 18 ? hints.slice(0, 15) + "..." : hints).padEnd(20),
2679
- n.raw.replace(/\n/g, "\\n")
2680
- ].join(" | ")
2681
- );
2682
- }
2683
- return lines.join("\n");
2684
- }
2685
- function formatExecutionOutput(ir, format, pretty = true) {
2686
- if (format === "json") {
2687
- return JSON.stringify(ir, null, pretty ? 2 : void 0);
2688
- }
2689
- const { results, logs } = ir.data;
2690
- const statements = logs;
2691
- if (format === "csv") {
2692
- const header = "id,outputType,started,ended,sourceBlockKey,stackLevel,completionReason,metrics,hints";
2693
- const rows = statements.map(
2694
- (s) => [
2695
- s.id,
2696
- s.outputType,
2697
- s.timeSpan?.started ?? s.timestamp ?? 0,
2698
- s.timeSpan?.ended ?? "",
2699
- escapeCsvField2(s.sourceBlockKey),
2700
- s.stackLevel,
2701
- escapeCsvField2(s.completionReason ?? ""),
2702
- escapeCsvField2(s.metrics.map(formatMetric).join("; ")),
2703
- escapeCsvField2(s.hints?.join("; ") ?? "")
2704
- ].join(",")
2705
- );
2706
- return [header, ...rows].join("\n");
2707
- }
2708
- const lines = [];
2709
- lines.push(`Workout Execution Results:`);
2710
- lines.push(` Duration: ${results.duration}ms`);
2711
- lines.push(` Completed: ${results.completed ? "yes" : "no"}`);
2712
- lines.push(` Start: ${formatDate(results.startTime)}`);
2713
- lines.push(` End: ${formatDate(results.endTime)}`);
2714
- lines.push(` Logs: ${statements.length} statements emitted`);
2715
- lines.push("\u2500".repeat(80));
2716
- lines.push(
2717
- ["ID".padEnd(8), "Type".padEnd(12), "Block".padEnd(18), "Reason".padEnd(15), "Metrics"].join(" | ")
2718
- );
2719
- lines.push("\u2500".repeat(80));
2720
- for (const s of statements) {
2721
- const metricsStr = s.metrics.map(formatMetric).join(", ");
2722
- lines.push(
2723
- [
2724
- String(s.id).padEnd(8),
2725
- (s.outputType ?? "segment").padEnd(12),
2726
- (s.sourceBlockKey ?? "-").padEnd(18),
2727
- (s.completionReason ?? "-").padEnd(15),
2728
- metricsStr
2729
- ].join(" | ")
2730
- );
2731
- }
2732
- return lines.join("\n");
2733
- }
2734
- function formatQueryOutput(ir, format, pretty = true) {
2735
- if (format === "json") {
2736
- return JSON.stringify(ir, null, pretty ? 2 : void 0);
2737
- }
2738
- const kind = ir.kind;
2739
- if (kind === "rows-result") {
2740
- const data2 = ir.data;
2741
- if (format === "csv") {
2742
- const header = "resultId,timestamp,outputType,eventId,metrics";
2743
- const rows = [];
2744
- for (const run of data2.runs) {
2745
- for (const event of run.events) {
2746
- rows.push(
2747
- [
2748
- run.resultId,
2749
- run.timestamp,
2750
- event.outputType,
2751
- event.id,
2752
- escapeCsvField2(event.metrics.map(formatMetric).join("; "))
2753
- ].join(",")
2754
- );
2755
- }
2756
- }
2757
- return [header, ...rows].join("\n");
2758
- }
2759
- const lines2 = [];
2760
- lines2.push(`Rows Query Result (${data2.runs.length} runs):`);
2761
- if (data2.error) lines2.push(`Error: ${data2.error}`);
2762
- lines2.push("\u2500".repeat(80));
2763
- lines2.push(
2764
- ["Result ID".padEnd(20), "Date".padEnd(24), "Events"].join(" | ")
2765
- );
2766
- lines2.push("\u2500".repeat(80));
2767
- for (const run of data2.runs) {
2768
- lines2.push(
2769
- [
2770
- run.resultId.padEnd(20),
2771
- formatDate(run.timestamp).padEnd(24),
2772
- String(run.events.length)
2773
- ].join(" | ")
2774
- );
2775
- }
2776
- return lines2.join("\n");
2777
- }
2778
- if (kind === "find-result") {
2779
- const data2 = ir.data;
2780
- if (format === "csv") {
2781
- const header = "target,id,title_or_label,sourceId";
2782
- const rows = [];
2783
- for (const n of data2.notes) {
2784
- rows.push(["note", n.id, escapeCsvField2(n.title), escapeCsvField2(n.sourceId ?? "")].join(","));
2785
- }
2786
- for (const b of data2.blocks) {
2787
- rows.push(["block", b.id, escapeCsvField2(b.noteTitle), escapeCsvField2(b.sourceId ?? "")].join(","));
2788
- }
2789
- for (const e of data2.efforts ?? []) {
2790
- rows.push(["effort", e.id, escapeCsvField2(e.label), escapeCsvField2(e.slug)].join(","));
2791
- }
2792
- return [header, ...rows].join("\n");
2793
- }
2794
- const lines2 = [];
2795
- const matchedCount2 = data2.stages?.matched ?? 0;
2796
- const selectedCount = data2.stages?.selected ?? 0;
2797
- lines2.push(`Find Result (matched: ${matchedCount2} / selected: ${selectedCount}):`);
2798
- lines2.push("\u2500".repeat(80));
2799
- lines2.push(["Type".padEnd(8), "ID".padEnd(24), "Title / Label".padEnd(30), "Source ID"].join(" | "));
2800
- lines2.push("\u2500".repeat(80));
2801
- for (const n of data2.notes) {
2802
- lines2.push(["note".padEnd(8), n.id.padEnd(24), n.title.padEnd(30), n.sourceId ?? "-"].join(" | "));
2803
- }
2804
- for (const b of data2.blocks) {
2805
- lines2.push(["block".padEnd(8), b.id.padEnd(24), b.noteTitle.padEnd(30), b.sourceId ?? "-"].join(" | "));
2806
- }
2807
- for (const e of data2.efforts ?? []) {
2808
- lines2.push(["effort".padEnd(8), e.id.padEnd(24), e.label.padEnd(30), e.slug].join(" | "));
2809
- }
2810
- return lines2.join("\n");
2811
- }
2812
- if (kind === "pipeline-result") {
2813
- const data2 = ir.data;
2814
- const sink = data2.chart ? `:${data2.chart.head}` : "data";
2815
- if (format === "csv") {
2816
- const header = "series,timestamp,date,value,unit";
2817
- const rows = [];
2818
- for (const s of data2.series ?? []) {
2819
- for (const pt of s.points) {
2820
- rows.push(
2821
- [
2822
- escapeCsvField2(s.label || s.key),
2823
- pt.ts,
2824
- formatDate(pt.ts),
2825
- pt.value,
2826
- escapeCsvField2(s.unit ?? data2.unit ?? "")
2827
- ].join(",")
2828
- );
2829
- }
2830
- }
2831
- return [header, ...rows].join("\n");
2832
- }
2833
- const lines2 = [];
2834
- if (data2.error) lines2.push(`Pipeline Error: ${data2.error}`);
2835
- lines2.push(`Pipeline Result (\u2192 ${sink}):`);
2836
- lines2.push("\u2500".repeat(80));
2837
- if (data2.table) {
2838
- lines2.push(`Table: ${data2.table.rows.length} of ${data2.table.totalCount} rows`);
2839
- }
2840
- for (const s of data2.series ?? []) {
2841
- lines2.push(`${s.label || s.key}: ${s.points.length} points${s.unit ? ` (${s.unit})` : ""}`);
2842
- }
2843
- if (data2.notes?.length) lines2.push(`notes: ${data2.notes.length}`);
2844
- if (data2.blocks?.length) lines2.push(`blocks: ${data2.blocks.length}`);
2845
- if (data2.efforts?.length) lines2.push(`efforts: ${data2.efforts.length}`);
2846
- if (data2.runs?.length) lines2.push(`runs: ${data2.runs.length}`);
2847
- return lines2.join("\n");
2848
- }
2849
- const data = ir.data;
2850
- if (data.scalar !== void 0 && data.series.length === 0) {
2851
- if (format === "csv") {
2852
- return `metric,scalar,unit
2853
- ${escapeCsvField2(data.parsed.metric)},${data.scalar},${escapeCsvField2(data.unit ?? "")}`;
2854
- }
2855
- return `Scalar Result: ${data.scalar}${data.unit ? " " + data.unit : ""}`;
2856
- }
2857
- if (format === "csv") {
2858
- const header = "series,timestamp,date,value,unit";
2859
- const rows = [];
2860
- for (const s of data.series) {
2861
- for (const pt of s.points) {
2862
- rows.push(
2863
- [
2864
- escapeCsvField2(s.label || s.key),
2865
- pt.ts,
2866
- formatDate(pt.ts),
2867
- pt.value,
2868
- escapeCsvField2(s.unit ?? data.unit ?? "")
2869
- ].join(",")
2870
- );
2871
- }
2872
- }
2873
- return [header, ...rows].join("\n");
2874
- }
2875
- const lines = [];
2876
- const matchedCount = data.stages?.matched ?? data.stages?.selected ?? data.matched?.length ?? 0;
2877
- lines.push(`Query Result (${data.series.length} series, ${matchedCount} matched facts):`);
2878
- lines.push(
2879
- ["Series / Group".padEnd(20), "Timestamp".padEnd(16), "Date".padEnd(24), "Value".padEnd(12), "Unit"].join(" | ")
2880
- );
2881
- lines.push("\u2500".repeat(80));
2882
- for (const s of data.series) {
2883
- for (const pt of s.points) {
2884
- lines.push(
2885
- [
2886
- (s.label || s.key).padEnd(20),
2887
- String(pt.ts).padEnd(16),
2888
- formatDate(pt.ts).padEnd(24),
2889
- String(pt.value).padEnd(12),
2890
- s.unit ?? data.unit ?? "-"
2891
- ].join(" | ")
2892
- );
2893
- }
2894
- }
2895
- return lines.join("\n");
2896
- }
2897
- function getFs2() {
2898
- if (typeof globalThis.process?.versions?.node === "undefined" && typeof globalThis.Bun === "undefined") {
2899
- throw new Error("File system operations are only supported in Node / Bun environments");
2900
- }
2901
- return __require("fs");
2902
- }
2903
- var VERSION = "0.6.0";
2904
- function cliNoteSource(source, label, sport) {
2905
- if (/\.(?:md|markdown)$/i.test(label) || wodWikiCore.parseDocumentSections(source).some((section) => section.scriptBlock)) return source;
2906
- let length = 3;
2907
- for (const match of source.matchAll(/`+/g)) length = Math.max(length, match[0].length + 1);
2908
- const fence = "`".repeat(length);
2909
- return `${fence}time${sport ? `:${sport}` : ""}
2910
- ${source}
2911
- ${fence}
2912
- `;
2913
- }
2914
- function parseCliArgs(args) {
2915
- const result = {
2916
- format: "json",
2917
- packs: [],
2918
- sources: []
2919
- };
2920
- let i = 0;
2921
- while (i < args.length) {
2922
- const arg = args[i];
2923
- if (arg === "--at" || arg.startsWith("--at=")) throw new Error("Unknown option --at; whole Note exports have no as-of version");
2924
- if (arg === "--help" || arg === "-h") {
2925
- result.help = true;
2926
- i++;
2927
- } else if (arg === "--version" || arg === "-v") {
2928
- result.version = true;
2929
- i++;
2930
- } else if (arg === "--corpus" || arg === "-c") {
2931
- result.corpus = args[++i];
2932
- i++;
2933
- } else if (arg === "--stdin-log") {
2934
- result.stdinLog = true;
2935
- i++;
2936
- } else if (arg === "--stdin-facts") {
2937
- result.stdinFacts = true;
2938
- i++;
2939
- } else if (arg === "--format") {
2940
- const fmt = args[++i];
2941
- if (fmt === "json" || fmt === "table" || fmt === "csv") {
2942
- result.format = fmt;
2943
- }
2944
- i++;
2945
- } else if (arg.startsWith("--format=")) {
2946
- const fmt = arg.slice("--format=".length);
2947
- if (fmt === "json" || fmt === "table" || fmt === "csv") {
2948
- result.format = fmt;
2949
- }
2950
- i++;
2951
- } else if (arg === "-o" || arg === "--output") {
2952
- result.output = args[++i];
2953
- i++;
2954
- } else if (arg.startsWith("--output=")) {
2955
- result.output = arg.slice("--output=".length);
2956
- i++;
2957
- } else if (arg === "--pack") {
2958
- const packSpec = args[++i];
2959
- if (packSpec) result.packs.push(packSpec);
2960
- i++;
2961
- } else if (arg.startsWith("--pack=")) {
2962
- result.packs.push(arg.slice("--pack=".length));
2963
- i++;
2964
- } else if (arg === "--sport") {
2965
- result.sport = args[++i];
2966
- i++;
2967
- } else if (arg === "--unit") {
2968
- result.unit = args[++i];
2969
- i++;
2970
- } else if (arg === "--db") {
2971
- result.db = args[++i];
2972
- i++;
2973
- } else if (arg.startsWith("--db=")) {
2974
- result.db = arg.slice("--db=".length);
2975
- i++;
2976
- } else if (arg === "--debug") {
2977
- result.debug = "";
2978
- i++;
2979
- } else if (arg.startsWith("--debug=")) {
2980
- result.debug = arg.slice("--debug=".length);
2981
- i++;
2982
- } else if (arg === "--source") {
2983
- const source = args[++i];
2984
- if (source) result.sources.push(source);
2985
- i++;
2986
- } else if (arg.startsWith("--source=")) {
2987
- result.sources.push(arg.slice("--source=".length));
2988
- i++;
2989
- } else if (arg === "--scaffold") {
2990
- result.scaffold = true;
2991
- i++;
2992
- } else if (arg === "--template") {
2993
- result.template = args[++i];
2994
- i++;
2995
- } else if (arg.startsWith("--template=")) {
2996
- result.template = arg.slice("--template=".length);
2997
- i++;
2998
- } else if (arg === "--out") {
2999
- result.output = args[++i];
3000
- i++;
3001
- } else if (arg.startsWith("--out=")) {
3002
- result.output = arg.slice("--out=".length);
3003
- i++;
3004
- } else if (arg === "--dir") {
3005
- result.dir = args[++i];
3006
- i++;
3007
- } else if (arg.startsWith("--dir=")) {
3008
- result.dir = arg.slice("--dir=".length);
3009
- i++;
3010
- } else if (!result.subcommand) {
3011
- if (arg === "parse" || arg === "run" || arg === "query" || arg === "download" || arg === "import" || arg === "build" || arg === "seed" || arg === "reset" || arg === "help" || arg === "version") {
3012
- result.subcommand = arg;
3013
- } else {
3014
- result.target = arg;
3015
- }
3016
- i++;
3017
- } else if (!result.target) {
3018
- result.target = arg;
3019
- i++;
3020
- } else if (!result.target2) {
3021
- result.target2 = arg;
3022
- i++;
3023
- } else {
3024
- i++;
3025
- }
3026
- }
3027
- return result;
3028
- }
3029
- function readBunStdinText() {
3030
- const g = globalThis;
3031
- if (g && typeof g === "object" && "Bun" in g) {
3032
- const bun = g.Bun;
3033
- if (bun && typeof bun === "object" && "stdin" in bun) {
3034
- const stdin = bun.stdin;
3035
- if (stdin && typeof stdin === "object" && "text" in stdin) {
3036
- const text = stdin.text;
3037
- if (typeof text === "function") return text;
3038
- }
3039
- }
3040
- }
3041
- return void 0;
3042
- }
3043
- async function readStdin() {
3044
- const bunText = readBunStdinText();
3045
- if (bunText) return bunText();
3046
- return new Promise((resolve, reject) => {
3047
- const chunks = [];
3048
- process.stdin.on("data", (chunk) => {
3049
- chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
3050
- });
3051
- process.stdin.on("end", () => {
3052
- resolve(Buffer.concat(chunks).toString("utf-8"));
3053
- });
3054
- process.stdin.on("error", (err) => {
3055
- reject(err);
3056
- });
3057
- });
3058
- }
3059
- function getUsageText() {
3060
- return `
3061
- wod CLI \u2014 Headless Whiteboard Language & WQL Engine runner (v${VERSION})
3062
-
3063
- USAGE:
3064
- wod parse <file | -> [options]
3065
- wod run <file | -> [options]
3066
- wod query '<wql-string>' [options]
3067
- wod download <note|session|effort|equipment> <id> [options]
3068
- wod import <note|session|effort|equipment> <file> [options]
3069
- wod build [dir] [options]
3070
- wod seed [options]
3071
- wod reset [options]
3072
-
3073
- SUBCOMMANDS:
3074
- parse <file | -> Parse Whiteboard Script, create a note in the database
3075
- (whole source + saved revisions + sparse block index), optionally emit parse-tree IR.
3076
- Markdown carrying a 'wod-note-id' frontmatter stamp
3077
- (see download) imports back into that same note.
3078
- run <file | -> Execute the script state machine, record a session +
3079
- event/summary rows in the database, optionally emit
3080
- execution-log IR
3081
- query '<wql>' Evaluate WQL against the database (or an explicit
3082
- dataset) and emit QueryResult / RowsQueryResult IR
3083
- download note <id> Export the note's authored markdown to <id>.md.
3084
- The frontmatter identity stamp allows re-import.
3085
- Frontmatter tags (general + typed equipment/quality/\u2026)
3086
- are materialized into the tag graph on parse/import.
3087
- download session <id>
3088
- Write the session's generated metrics (event + summary
3089
- rows, one CSV row per metric) to <id>.csv. Import back
3090
- with 'wod import session'.
3091
- download effort <slug>
3092
- Write the effort registry row as corpus-style markdown
3093
- (frontmatter id/slug/label/aliases/baseAttributes + body)
3094
- to <slug>.md. Import back with 'wod import effort'.
3095
- download equipment
3096
- Write every equipment tag as a one-column CSV to
3097
- equipment.csv. Import back with 'wod import equipment'.
3098
- import note <file> Re-import exported note markdown (same as parse with
3099
- the stamped identity).
3100
- import session <file>
3101
- Import a session + its events from a downloaded CSV.
3102
- The exact saved block revision must already exist.
3103
- import effort <file>
3104
- Upsert an effort registry row from corpus-style markdown.
3105
- import equipment <file>
3106
- Create/upgrade equipment tags (CSV, one label per row).
3107
- build [dir] Compile a seed corpus: validate every .md under
3108
- <dir>/markdown (parity gate + dashboard format gate) and
3109
- write manifest.json + content-hashed chunks/ +
3110
- report.html to <dir>/dist with source/maps/projections bundled
3111
- (override with -o/--output). Version stamps derive from
3112
- <dir>'s git history (full checkout required).
3113
-
3114
- BUILD OPTIONS (for 'build'):
3115
- --scaffold Also scaffold markdown/efforts/discovered/*.md stubs for
3116
- workout effort labels missing from the registry.
3117
- --template <file> Compile an extra markdown file as the 'template' chunk
3118
- (the host's playground intake template).
3119
-
3120
- INPUT SOURCING (for 'query'; without these, the database is queried):
3121
- -c, --corpus <path> Load historical corpus dataset from a versioned JSON file
3122
- --stdin-log Read kind: 'execution-log' JSON payload from stdin
3123
- --stdin-facts Read raw kind: 'fact-set' JSON payload from stdin
3124
-
3125
- DATABASE (parse / run / query / download / import / seed / reset):
3126
- --db <path> SQLite database file (default: $WOD_WIKI_DB or
3127
- ./wodwiki.db). Created on demand by parse/run/seed.
3128
-
3129
- SEED SOURCING (for 'seed'):
3130
- --source <dir> Extra compiled seed directory (manifest.json + chunks);
3131
- repeatable. Defaults to the bundled core + community
3132
- artifacts under apps/playground/public/seed.
3133
-
3134
- OPTIONS:
3135
- --format <fmt> Output format: json (default), table, csv
3136
- -o, --output <path> Write the IR payload (or download) to a path instead of
3137
- the default; '-' writes to stdout
3138
- --debug[=name] Also write the IR payload to <name> (default
3139
- wod-<subcommand>-debug.json); without it, parse/run
3140
- print a one-line summary of what was persisted
3141
- --pack <module> Dynamically load and register a Language Pack (repeatable)
3142
- --sport <sport> Target sport fence dialect (e.g. climbing, crossfit)
3143
- --unit <unit> Preferred display unit for queries (e.g. lb, kg)
3144
- -h, --help Show help message
3145
- -v, --version Show version number
3146
-
3147
- EXIT CODES:
3148
- 0 - OK / Success
3149
- 1 - General / Runtime error
3150
- 2 - Parse / Syntax error
3151
- 3 - Language Pack load error
3152
- `;
3153
- }
3154
- function printUsage(writer) {
3155
- const text = getUsageText();
3156
- if (writer) {
3157
- writer(text);
3158
- } else {
3159
- process.stdout.write(text.endsWith("\n") ? text : text + "\n");
3160
- }
3161
- }
3162
- async function cliMain(argv = process.argv.slice(2), io = {}) {
3163
- const writeOut = io.stdout ?? ((t) => process.stdout.write(t + "\n"));
3164
- const writeErr = io.stderr ?? ((t) => process.stderr.write(t + "\n"));
3165
- const getStdin = io.readStdinFn ?? readStdin;
3166
- let parsed;
3167
- try {
3168
- parsed = parseCliArgs(argv);
3169
- } catch (error) {
3170
- writeErr(error instanceof Error ? error.message : String(error));
3171
- return 1;
3172
- }
3173
- if (parsed.version || parsed.subcommand === "version") {
3174
- writeOut(`wod v${VERSION}`);
3175
- return 0;
3176
- }
3177
- if (parsed.help || parsed.subcommand === "help") {
3178
- printUsage(writeOut);
3179
- return 0;
3180
- }
3181
- if (!parsed.subcommand) {
3182
- printUsage(writeErr);
3183
- return 1;
3184
- }
3185
- for (const packSpec of parsed.packs) {
3186
- try {
3187
- await loadLanguagePack(packSpec);
3188
- } catch (err) {
3189
- if (err instanceof PackLoadError) {
3190
- writeErr(err.message);
3191
- } else {
3192
- writeErr(`[wod] Language pack load error: ${err instanceof Error ? err.message : String(err)}`);
3193
- }
3194
- return 3;
3195
- }
3196
- }
3197
- const deliverOutput = (formattedText) => {
3198
- if (parsed.output && parsed.output !== "-") {
3199
- getFs2().writeFileSync(parsed.output, formattedText, "utf-8");
3200
- } else {
3201
- writeOut(formattedText);
3202
- }
3203
- };
3204
- const debugTarget = (subcommand) => parsed.debug === void 0 ? void 0 : parsed.debug || `wod-${subcommand}-debug.json`;
3205
- const writeDebugFile = (subcommand, ir) => {
3206
- const target = debugTarget(subcommand);
3207
- if (target !== void 0) {
3208
- getFs2().writeFileSync(target, JSON.stringify(ir, null, 2) + "\n", "utf-8");
3209
- }
3210
- };
3211
- try {
3212
- if (parsed.subcommand === "parse") {
3213
- if (!parsed.target) {
3214
- writeErr('[wod parse] Error: Missing input file or "-" for stdin');
3215
- return 1;
3216
- }
3217
- let source;
3218
- if (parsed.target === "-") {
3219
- source = await getStdin();
3220
- } else {
3221
- const fs = getFs2();
3222
- if (!fs.existsSync(parsed.target)) {
3223
- writeErr(`[wod parse] File not found: ${parsed.target}`);
3224
- return 1;
3225
- }
3226
- source = fs.readFileSync(parsed.target, "utf-8");
3227
- }
3228
- const noteSource = cliNoteSource(source, parsed.target, parsed.sport);
3229
- const ir = runParse(source, { sport: parsed.sport, sourceLabel: `cli:wod parse ${parsed.target}` });
3230
- const identity = extractNoteIdentity(source);
3231
- const tags = extractTypedTags(source);
3232
- const db = await openCliDb(parsed.db);
3233
- try {
3234
- const persisted = await persistNote(db, {
3235
- source: noteSource,
3236
- sourceLabel: parsed.target,
3237
- noteId: identity?.noteId,
3238
- title: identity?.title,
3239
- tags
3240
- });
3241
- writeOut(
3242
- `[wod parse] note ${persisted.noteId}${persisted.created ? "" : " (updated)"} \u2014 ${persisted.blocks} blocks, ${persisted.efforts} efforts${persisted.tags > 0 ? `, ${persisted.tags} tags` : ""} \u2192 ${db.path}`
3243
- );
3244
- } finally {
3245
- await db.close();
3246
- }
3247
- if (parsed.output) {
3248
- const pretty = Boolean(process.stdout.isTTY);
3249
- deliverOutput(formatParseOutput(ir, parsed.format, pretty));
3250
- }
3251
- writeDebugFile("parse", ir);
3252
- return 0;
3253
- }
3254
- if (parsed.subcommand === "run") {
3255
- if (!parsed.target) {
3256
- writeErr('[wod run] Error: Missing input file or "-" for stdin');
3257
- return 1;
3258
- }
3259
- let source;
3260
- if (parsed.target === "-") {
3261
- source = await getStdin();
3262
- } else {
3263
- const fs = getFs2();
3264
- if (!fs.existsSync(parsed.target)) {
3265
- writeErr(`[wod run] File not found: ${parsed.target}`);
3266
- return 1;
3267
- }
3268
- source = fs.readFileSync(parsed.target, "utf-8");
3269
- }
3270
- const identity = extractNoteIdentity(source);
3271
- const db = await openCliDb(parsed.db);
3272
- let ir;
3273
- try {
3274
- const started = await startRun(db, {
3275
- source: cliNoteSource(source, parsed.target, parsed.sport),
3276
- sourceLabel: parsed.target,
3277
- noteId: identity?.noteId,
3278
- title: identity?.title,
3279
- tags: extractTypedTags(source)
3280
- });
3281
- const section = wodWikiCore.parseDocumentSections(started.revision.rawContent).find((row) => row.scriptBlock);
3282
- if (!section?.scriptBlock) throw new Error("[wod run] Captured workout source cannot be parsed");
3283
- const bare = !/\.(?:md|markdown)$/i.test(parsed.target) && !wodWikiCore.parseDocumentSections(source).some((row) => row.scriptBlock);
3284
- const frozenBody = bare ? section.scriptBlock.content.slice(0, -1) : section.scriptBlock.content;
3285
- ir = await runExecution(frozenBody, {
3286
- sport: started.revision.sport,
3287
- dialect: started.revision.dialect,
3288
- blockId: started.session.blockId,
3289
- blockVersion: started.session.blockVersion,
3290
- startTime: started.session.startTime,
3291
- sourceLabel: `cli:wod run ${parsed.target}`
3292
- });
3293
- const persisted = await persistRun(db, started, ir.data);
3294
- writeOut(
3295
- `[wod run] session ${persisted.resultId} \u2014 ${persisted.events} events, ${persisted.summaries} summaries on note ${persisted.noteId} \u2192 ${db.path}`
3296
- );
3297
- } finally {
3298
- await db.close();
3299
- }
3300
- if (parsed.output) {
3301
- const pretty = Boolean(process.stdout.isTTY);
3302
- deliverOutput(formatExecutionOutput(ir, parsed.format, pretty));
3303
- }
3304
- writeDebugFile("run", ir);
3305
- return 0;
3306
- }
3307
- if (parsed.subcommand === "query") {
3308
- if (!parsed.target) {
3309
- writeErr("[wod query] Error: Missing WQL query argument");
3310
- return 1;
3311
- }
3312
- const useDb = !parsed.corpus && !parsed.stdinLog && !parsed.stdinFacts;
3313
- let stdinLogContent;
3314
- let stdinFactsContent;
3315
- if (parsed.stdinLog) {
3316
- stdinLogContent = await getStdin();
3317
- } else if (parsed.stdinFacts) {
3318
- stdinFactsContent = await getStdin();
3319
- }
3320
- let ir;
3321
- if (useDb) {
3322
- const dbPath = resolveDbPath(parsed.db);
3323
- if (!nodeFs().existsSync(dbPath)) {
3324
- writeErr(
3325
- `[wod query] No input dataset and no database at ${dbPath}. Specify --corpus <file>, --stdin-log, --stdin-facts, or run \`wod seed\` first.`
3326
- );
3327
- return 1;
3328
- }
3329
- const db = await openCliDb(parsed.db);
3330
- try {
3331
- ir = await runQueryCli(parsed.target, {
3332
- preferredUnit: parsed.unit,
3333
- sourceLabel: `cli:wod query "${parsed.target}"`,
3334
- stores: {
3335
- eventStore: sqliteEventStore(db.db),
3336
- noteStore: sqliteNoteStore(db.db),
3337
- blockStore: sqliteBlockStore(db.db),
3338
- effortStore: sqliteEffortStore(db.db),
3339
- blockEffortsStore: sqliteIndexStore(db.db, "block_efforts"),
3340
- tagsStore: sqliteIndexStore(db.db, "tags"),
3341
- noteTagsStore: sqliteIndexStore(db.db, "note_tags")
3342
- }
3343
- });
3344
- } finally {
3345
- await db.close();
3346
- }
3347
- } else {
3348
- if (!parsed.corpus && !stdinLogContent && !stdinFactsContent) {
3349
- writeErr("[wod query] Error: Query requires input data. Specify --corpus <file>, --stdin-log, or --stdin-facts.");
3350
- return 1;
3351
- }
3352
- ir = await runQueryCli(parsed.target, {
3353
- corpusPath: parsed.corpus,
3354
- stdinLog: stdinLogContent,
3355
- stdinFacts: stdinFactsContent,
3356
- preferredUnit: parsed.unit,
3357
- sourceLabel: `cli:wod query "${parsed.target}"`
3358
- });
3359
- }
3360
- const pretty = !parsed.output && Boolean(process.stdout.isTTY);
3361
- const formatted = formatQueryOutput(ir, parsed.format, pretty);
3362
- deliverOutput(formatted);
3363
- writeDebugFile("query", ir);
3364
- return 0;
3365
- }
3366
- if (parsed.subcommand === "download") {
3367
- const kind = parsed.target;
3368
- const id = parsed.target2;
3369
- const DOWNLOAD_KINDS = ["note", "session", "effort", "equipment"];
3370
- if (!id && kind !== "equipment" || !DOWNLOAD_KINDS.includes(kind ?? "")) {
3371
- writeErr("[wod download] Usage: wod download <note|session|effort|equipment> <id> [-o <path>]");
3372
- return 1;
3373
- }
3374
- const targetId = id;
3375
- const db = await openCliDb(parsed.db);
3376
- try {
3377
- if (kind === "note") {
3378
- const bundle = await getNoteBundle(db.db, targetId);
3379
- if (!bundle.note) {
3380
- writeErr(`[wod download] Note not found in ${db.path}: ${id}`);
3381
- return 1;
3382
- }
3383
- const markdown = composeNoteMarkdown(bundle.note);
3384
- const target = parsed.output && parsed.output !== "-" ? parsed.output : `${id}.md`;
3385
- if (parsed.output === "-") {
3386
- writeOut(markdown);
3387
- } else {
3388
- nodeFs().writeFileSync(target, markdown, "utf-8");
3389
- }
3390
- if (parsed.output !== "-") writeOut(`[wod download] note ${id} \u2192 ${target}`);
3391
- } else if (kind === "session") {
3392
- const bundle = await getSessionBundle(db.db, targetId);
3393
- if (!bundle) {
3394
- writeErr(`[wod download] Session not found in ${db.path}: ${id}`);
3395
- return 1;
3396
- }
3397
- const csv = sessionToCsv(bundle.session, bundle.events);
3398
- const target = parsed.output && parsed.output !== "-" ? parsed.output : `${id}.csv`;
3399
- if (parsed.output === "-") {
3400
- writeOut(csv);
3401
- } else {
3402
- nodeFs().writeFileSync(target, csv, "utf-8");
3403
- }
3404
- if (parsed.output !== "-") writeOut(`[wod download] session ${id} \u2192 ${target} (${bundle.events.length} events)`);
3405
- } else if (kind === "effort") {
3406
- const effort = await getEffortBySlug(db.db, targetId);
3407
- if (!effort) {
3408
- writeErr(`[wod download] Effort not found in ${db.path}: ${id}`);
3409
- return 1;
3410
- }
3411
- const markdown = effortToMarkdown(effort);
3412
- const target = parsed.output && parsed.output !== "-" ? parsed.output : `${id}.md`;
3413
- if (parsed.output === "-") {
3414
- writeOut(markdown);
3415
- } else {
3416
- nodeFs().writeFileSync(target, markdown, "utf-8");
3417
- }
3418
- writeOut(`[wod download] effort ${id} \u2192 ${target}`);
3419
- } else {
3420
- const labels = (await listTagsByType(db.db, "equipment")).map((t) => t.label).sort();
3421
- const csv = equipmentToCsv(labels);
3422
- const target = parsed.output && parsed.output !== "-" ? parsed.output : "equipment.csv";
3423
- if (parsed.output === "-") {
3424
- writeOut(csv);
3425
- } else {
3426
- nodeFs().writeFileSync(target, csv, "utf-8");
3427
- }
3428
- writeOut(`[wod download] equipment \u2192 ${target} (${labels.length} labels)`);
3429
- }
3430
- } finally {
3431
- await db.close();
3432
- }
3433
- return 0;
3434
- }
3435
- if (parsed.subcommand === "import") {
3436
- const kind = parsed.target;
3437
- const file = parsed.target2;
3438
- const IMPORT_KINDS = ["note", "session", "effort", "equipment"];
3439
- if (!file || !IMPORT_KINDS.includes(kind ?? "")) {
3440
- writeErr("[wod import] Usage: wod import <note|session|effort|equipment> <file>");
3441
- return 1;
3442
- }
3443
- const fs = nodeFs();
3444
- if (!fs.existsSync(file)) {
3445
- writeErr(`[wod import] File not found: ${file}`);
3446
- return 1;
3447
- }
3448
- if (kind === "note") {
3449
- const source = fs.readFileSync(file, "utf-8");
3450
- const identity = extractNoteIdentity(source);
3451
- const tags = extractTypedTags(source);
3452
- const db2 = await openCliDb(parsed.db);
3453
- try {
3454
- const persisted = await persistNote(db2, {
3455
- source,
3456
- sourceLabel: file,
3457
- noteId: identity?.noteId,
3458
- title: identity?.title,
3459
- tags
3460
- });
3461
- writeOut(
3462
- `[wod import] note ${persisted.noteId}${persisted.created ? "" : " (updated)"} \u2014 ${persisted.blocks} blocks${persisted.tags > 0 ? `, ${persisted.tags} tags` : ""} \u2192 ${db2.path}`
3463
- );
3464
- } finally {
3465
- await db2.close();
3466
- }
3467
- return 0;
3468
- }
3469
- if (kind === "effort") {
3470
- const effort = markdownToEffort(fs.readFileSync(file, "utf-8"));
3471
- if (!effort) {
3472
- writeErr(`[wod import] Unparseable effort file (need frontmatter id/slug/label + numeric baseAttributes.met): ${file}`);
3473
- return 1;
3474
- }
3475
- const db2 = await openCliDb(parsed.db);
3476
- try {
3477
- await persistEffort(db2, { ...effort, registrySource: "user" });
3478
- writeOut(`[wod import] effort ${effort.slug} \u2014 ${effort.label} \u2192 ${db2.path}`);
3479
- } finally {
3480
- await db2.close();
3481
- }
3482
- return 0;
3483
- }
3484
- if (kind === "equipment") {
3485
- const labels = csvToEquipmentLabels(fs.readFileSync(file, "utf-8"));
3486
- if (labels.length === 0) {
3487
- writeErr(`[wod import] No equipment labels found in ${file}`);
3488
- return 1;
3489
- }
3490
- const db2 = await openCliDb(parsed.db);
3491
- try {
3492
- const added = await persistTypedTags(db2, labels, "equipment");
3493
- writeOut(`[wod import] equipment \u2014 ${labels.length} labels (${added} new) \u2192 ${db2.path}`);
3494
- } finally {
3495
- await db2.close();
3496
- }
3497
- return 0;
3498
- }
3499
- const { session, events } = csvToSessionRows(fs.readFileSync(file, "utf-8"));
3500
- const db = await openCliDb(parsed.db);
3501
- try {
3502
- const result = await persistSessionBundle(db, session, events);
3503
- writeOut(
3504
- `[wod import] session ${session.id} \u2014 ${result.events} events \u2192 ${db.path}`
3505
- );
3506
- } finally {
3507
- await db.close();
3508
- }
3509
- return 0;
3510
- }
3511
- if (parsed.subcommand === "build") {
3512
- const cwd = process.cwd();
3513
- const root = nodePath().resolve(cwd, parsed.dir ?? parsed.target ?? ".");
3514
- if (!nodeFs().existsSync(nodePath().resolve(root, "markdown"))) {
3515
- writeErr(`[wod build] No markdown/ corpus under ${root} \u2014 run from the content repo root (or pass its directory).`);
3516
- return 1;
998
+ if (existing?.rawContent === row.content) continue;
999
+ const same = saved && saved.kind === block.kind && saved.rawContent === source && saved.dialect === block.dialect && saved.sport === block.sport;
1000
+ block.version = !existing ? 1 : same ? saved.version : (saved?.version ?? 0) + 1;
1001
+ if (!existing || !same) revisions.push({ noteId: id, blockId: block.id, version: block.version, kind: block.kind, rawContent: source, savedAt: write.meta.importedAt, ...block.dialect ? { dialect: block.dialect } : {}, ...block.sport ? { sport: block.sport } : {} });
3517
1002
  }
3518
- const { generateSeed: generateSeed2 } = await Promise.resolve().then(() => (init_compiler(), compiler_exports));
3519
- try {
3520
- const result = generateSeed2({
3521
- corpusRoot: root,
3522
- ...parsed.output ? { outDir: nodePath().resolve(cwd, parsed.output) } : {},
3523
- ...parsed.template ? { templateFile: nodePath().resolve(cwd, parsed.template) } : {},
3524
- scaffoldDiscoveredEfforts: parsed.scaffold === true
3525
- });
3526
- const kb = (n) => `${(n / 1024).toFixed(0)} KB`;
3527
- const totalBytes = result.manifest.chunks.reduce((s, c) => s + c.bytes, 0);
3528
- writeOut(`[wod build] schema v${result.manifest.schema} version ${result.manifest.version} (${result.manifest.versionLabel}) \u2192 ${result.outDir}`);
3529
- for (const c of result.manifest.chunks) {
3530
- writeOut(` ${c.id.padEnd(48)} ${String(c.count).padStart(4)} files ${kb(c.bytes).padStart(8)}`);
3531
- }
3532
- writeOut(`[wod build] ${result.manifest.chunks.length} chunks, ${result.totalFiles} files, ${kb(totalBytes)} total \u2014 scan ${result.passMs.scan}ms, block-index ${result.passMs.blockIndex}ms, block-efforts ${result.passMs.blockEfforts}ms, write ${result.passMs.write}ms`);
3533
- return 0;
3534
- } catch (error) {
3535
- writeErr(`[wod build] ${error.message}`);
3536
- return 1;
1003
+ wodWikiCore.validateNoteBlocks(row.content, map, sourceRevision, { registeredWidgets: SEED_REGISTERED_WIDGETS });
1004
+ const fm = readFrontmatterMeta(row.content);
1005
+ const title = typeof fm["title"] === "string" && fm["title"].trim() ? fm["title"].trim() : titleFromPath(row.path);
1006
+ const sourceId = write.chunkId ? seedNoteSourceId(row.path, row.content, write.chunkId) : void 0;
1007
+ if (!sourceId) throw new Error(`Unknown seed source family ${write.chunkId}`);
1008
+ const date = typeof fm["date"] === "string" && /^\d{4}-\d{2}-\d{2}$/.test(fm["date"]) ? Date.parse(`${fm["date"]}T12:00:00Z`) : void 0;
1009
+ const note = { ...existing, id, title, rawContent: row.content, sourceRevision, blocks: map, createdAt: write.manifest.version, ...date === void 0 ? {} : { date }, type: write.chunkId === DASHBOARDS_CHUNK_ID ? "dashboard" : "note", sourceId, catalog: catalogForChunkId(write.chunkId ?? ""), seedOrigin: "seed", seedVersion: write.manifest.version, seedChunkId: write.chunkId ?? void 0, sourcePath: row.path };
1010
+ if (date === void 0) delete note.date;
1011
+ if (!equal(note, existing)) changedNotes.push(note);
1012
+ const byRange = new Map(Object.values(map).map((b) => [`${b.from}:${b.to}`, b]));
1013
+ const compiledById = new Map(row.blockIndex.map((b) => [b.blockId, b]));
1014
+ const remap = /* @__PURE__ */ new Map();
1015
+ for (const compiled of row.blockIndex) {
1016
+ const block = byRange.get(`${compiled.from}:${compiled.to}`);
1017
+ if (!block || block.kind !== compiled.dataType) throw new Error("Orphan seed projection");
1018
+ remap.set(compiled.blockId, block.id);
1019
+ const projection = { ...compiled, id: `${id}:${block.id}`, noteId: id, blockId: block.id, blockVersion: block.version, sourceRevision, noteTitle: title, sourceId, sourcePath: row.path, rawContent: row.content.slice(block.from, block.to) };
1020
+ const prior = currentIndexes[slot.get(id)].find((b) => b.id === projection.id);
1021
+ if (!prior || prior.isStatic === true) blocks.push(projection);
3537
1022
  }
3538
- }
3539
- if (parsed.subcommand === "seed") {
3540
- const dirs = parsed.sources.length > 0 ? parsed.sources.map((s) => nodePath().resolve(process.cwd(), s)) : defaultSeedDirs(process.cwd());
3541
- if (dirs.length === 0) {
3542
- writeErr(
3543
- "[wod seed] No seed sources found. Run `bun scripts/sync-seed.ts` to build the bundled artifacts, or pass --source <dir> pointing at a compiled seed directory (manifest.json + chunks)."
3544
- );
3545
- return 1;
1023
+ for (const compiled of row.blockEfforts) {
1024
+ const blockId = remap.get(compiled.blockId);
1025
+ if (!blockId || compiledById.get(compiled.blockId)?.dataType !== "wod") throw new Error("Orphan seed effort link");
1026
+ const link = { ...compiled, id: `${id}:${blockId}:${compiled.effortSlug}`, noteId: id, blockId, sourcePath: row.path };
1027
+ const prior = currentEfforts[slot.get(id)].find((b) => b.id === link.id);
1028
+ if (!prior || prior.isStatic === true) blockEfforts.push(prior ? { ...link, createdAt: prior.createdAt } : link);
3546
1029
  }
3547
- const db = await openCliDb(parsed.db);
3548
- try {
3549
- for (const dir of dirs) {
3550
- const summary = await importSeedDir(db, dir, "");
3551
- writeOut(
3552
- `[wod seed] ${summary.sourceId}: v${summary.version} \u2014 ${summary.notes} notes, ${summary.efforts} efforts, ${summary.blocks} blocks, ${summary.blockEfforts} block-efforts (${summary.deleted} deleted, ${summary.skippedUserOwned} user-owned skipped) \u2192 ${db.path}`
3553
- );
1030
+ const n = slot.get(id);
1031
+ const wantedTags = /* @__PURE__ */ new Set();
1032
+ for (const item of extractTypedTags(row.content)) {
1033
+ let tag = tagsByLabel.get(item.label);
1034
+ if (!tag) {
1035
+ tag = { id: tagIds.get(item.label), label: item.label, type: item.type, createdAt: write.meta.importedAt };
1036
+ changedTags.push(tag);
1037
+ tagsByLabel.set(item.label, tag);
1038
+ } else if (item.type && !tag.type) {
1039
+ tag = { ...tag, type: item.type };
1040
+ changedTags.push(tag);
1041
+ tagsByLabel.set(item.label, tag);
3554
1042
  }
3555
- } finally {
3556
- await db.close();
1043
+ if (wantedTags.has(tag.id)) continue;
1044
+ wantedTags.add(tag.id);
1045
+ const link = currentTags[n].find((l) => l.tagId === tag.id);
1046
+ if (!link) noteTags.push({ id: `${id}:${tag.id}`, noteId: id, tagId: tag.id });
3557
1047
  }
3558
- return 0;
3559
- }
3560
- if (parsed.subcommand === "reset") {
3561
- const target = resolveDbPath(parsed.db);
3562
- if (nodeFs().existsSync(target)) {
3563
- nodeFs().unlinkSync(target);
3564
- writeOut(`[wod reset] removed ${target}`);
3565
- } else {
3566
- writeOut(`[wod reset] nothing to reset \u2014 ${target} does not exist`);
1048
+ for (const link of currentTags[n]) if (!wantedTags.has(link.tagId)) await tagLinks.delete(link.id);
1049
+ const slug = fm["slug"];
1050
+ if (typeof slug === "string" && pagesBySlug.has(slug)) {
1051
+ const pageId = pagesBySlug.get(slug);
1052
+ pages.push({ id: pageId, slug, title: titleFromPath(row.path), createdAt: write.manifest.version });
1053
+ pageNotes.push({ id: linkIds.get(id), pageId, noteId: id, createdAt: write.manifest.version });
3567
1054
  }
3568
- return 0;
1055
+ result.totalNotes++;
1056
+ result.totalBlocks += Object.keys(map).length;
3569
1057
  }
3570
- writeErr(`[wod] Unknown subcommand "${parsed.subcommand}"`);
3571
- return 1;
3572
- } catch (error) {
3573
- if (error instanceof ParseSyntaxError || error instanceof WqlSyntaxError) {
3574
- writeErr(error.message);
3575
- return 2;
1058
+ const wantedBlocks = new Set(blocks.map((b) => b.id)), wantedEfforts = new Set(blockEfforts.map((b) => b.id));
1059
+ for (const rows of currentIndexes) for (const row of rows) if (row.isStatic === true && !wantedBlocks.has(row.id)) await indexStore.delete(row.id);
1060
+ for (const rows of currentEfforts) for (const row of rows) if (row.isStatic === true && !wantedEfforts.has(row.id)) await effortLinks.delete(row.id);
1061
+ for (const id of goneIds) {
1062
+ if (!owned.has(id) || !existingById.get(id)) continue;
1063
+ const n = slot.get(id);
1064
+ await noteStore.delete(id);
1065
+ for (const link of currentTags[n]) await tagLinks.delete(link.id);
1066
+ for (const link of currentPages[n]) await pageLinks.delete(link.id);
1067
+ result.deleted++;
3576
1068
  }
3577
- if (error instanceof PackLoadError) {
3578
- writeErr(error.message);
3579
- return 3;
1069
+ const effortStore = tx.readwrite("efforts");
1070
+ if (write.chunkId === EFFORTS_CHUNK_ID) {
1071
+ const efforts = write.rows.filter((_row, i) => owned.has(ids[i])).map((r) => parseEffortFile(r.content)).filter((e) => e !== null).map((e) => ({ ...e, registrySource: "bundled" }));
1072
+ const prior = await effortStore.getMany(efforts.map((e) => e.slug));
1073
+ const changed = efforts.filter((e, i) => (!prior[i] || prior[i]?.registrySource === "bundled") && !equal(e, prior[i]));
1074
+ if (changed.length) await effortStore.putMany(changed);
1075
+ result.totalEfforts = changed.length;
1076
+ const wantedSlugs = new Set(efforts.map((effort) => effort.slug));
1077
+ const removed = allIds.map((id) => {
1078
+ const note = existingById.get(id);
1079
+ return owned.has(id) && note ? parseEffortFile(note.rawContent)?.slug : void 0;
1080
+ }).filter((slug) => !!slug && !wantedSlugs.has(slug));
1081
+ const old = await effortStore.getMany(removed);
1082
+ for (const [i, effort] of old.entries()) if (effort?.registrySource === "bundled") await effortStore.delete(removed[i]);
3580
1083
  }
3581
- writeErr(`[wod] Error: ${error instanceof Error ? error.message : String(error)}`);
3582
- return 1;
3583
- }
1084
+ if (changedNotes.length) await noteStore.putMany(changedNotes);
1085
+ if (revisions.length) await revisionStore.putMany(revisions);
1086
+ await putChanged(indexStore, blocks, blocks.map((b) => b.id));
1087
+ await putChanged(effortLinks, blockEfforts, blockEfforts.map((b) => b.id));
1088
+ if (changedTags.length) await tagsStore.putMany([...new Map(changedTags.map((t) => [t.id, t])).values()]);
1089
+ if (noteTags.length) await tagLinks.putMany(noteTags);
1090
+ await putChanged(tx.readwrite("page"), pages, pages.map((p) => p.id));
1091
+ await putChanged(pageLinks, pageNotes, pageNotes.map((p) => p.id));
1092
+ await metaStore.put(write.meta);
1093
+ return result;
1094
+ });
3584
1095
  }
3585
1096
 
3586
- // src/version.ts
3587
- var VERSION2 = "0.35.2129";
3588
- var GIT_SHA = "a49fd2c";
3589
- var BUILD_TIME = "2026-10-14T20:59:51.571Z";
3590
- var SEMVER = {
3591
- version: "0.35.2129",
3592
- gitSha: "a49fd2c",
3593
- buildTime: "2026-10-14T20:59:51.571Z"
3594
- };
3595
-
3596
- // src/index.ts
3597
- init_contract();
3598
-
3599
1097
  Object.defineProperty(exports, "ABSENT", {
3600
1098
  enumerable: true,
3601
1099
  get: function () { return wodWikiLang.ABSENT; }
@@ -4204,6 +1702,86 @@ Object.defineProperty(exports, "writeChoiceSelection", {
4204
1702
  enumerable: true,
4205
1703
  get: function () { return wodWikiLang.writeChoiceSelection; }
4206
1704
  });
1705
+ Object.defineProperty(exports, "BlockTimerDisplay", {
1706
+ enumerable: true,
1707
+ get: function () { return react.BlockTimerDisplay; }
1708
+ });
1709
+ Object.defineProperty(exports, "ScriptRuntimeProvider", {
1710
+ enumerable: true,
1711
+ get: function () { return react.ScriptRuntimeProvider; }
1712
+ });
1713
+ Object.defineProperty(exports, "useActiveControls", {
1714
+ enumerable: true,
1715
+ get: function () { return react.useActiveControls; }
1716
+ });
1717
+ Object.defineProperty(exports, "useBlockMemory", {
1718
+ enumerable: true,
1719
+ get: function () { return react.useBlockMemory; }
1720
+ });
1721
+ Object.defineProperty(exports, "useLiveAnalytics", {
1722
+ enumerable: true,
1723
+ get: function () { return react.useLiveAnalytics; }
1724
+ });
1725
+ Object.defineProperty(exports, "useMemorySubscription", {
1726
+ enumerable: true,
1727
+ get: function () { return react.useMemorySubscription; }
1728
+ });
1729
+ Object.defineProperty(exports, "useNextPreview", {
1730
+ enumerable: true,
1731
+ get: function () { return react.useNextPreview; }
1732
+ });
1733
+ Object.defineProperty(exports, "useOutputStatements", {
1734
+ enumerable: true,
1735
+ get: function () { return react.useOutputStatements; }
1736
+ });
1737
+ Object.defineProperty(exports, "usePrimaryTimer", {
1738
+ enumerable: true,
1739
+ get: function () { return react.usePrimaryTimer; }
1740
+ });
1741
+ Object.defineProperty(exports, "useRoundDisplay", {
1742
+ enumerable: true,
1743
+ get: function () { return react.useRoundDisplay; }
1744
+ });
1745
+ Object.defineProperty(exports, "useRuntimeExecution", {
1746
+ enumerable: true,
1747
+ get: function () { return react.useRuntimeExecution; }
1748
+ });
1749
+ Object.defineProperty(exports, "useScriptRuntime", {
1750
+ enumerable: true,
1751
+ get: function () { return react.useScriptRuntime; }
1752
+ });
1753
+ Object.defineProperty(exports, "useSecondaryTimers", {
1754
+ enumerable: true,
1755
+ get: function () { return react.useSecondaryTimers; }
1756
+ });
1757
+ Object.defineProperty(exports, "useSnapshotBlocks", {
1758
+ enumerable: true,
1759
+ get: function () { return react.useSnapshotBlocks; }
1760
+ });
1761
+ Object.defineProperty(exports, "useStackDisplayRows", {
1762
+ enumerable: true,
1763
+ get: function () { return react.useStackDisplayRows; }
1764
+ });
1765
+ Object.defineProperty(exports, "useStackFragmentSources", {
1766
+ enumerable: true,
1767
+ get: function () { return react.useStackFragmentSources; }
1768
+ });
1769
+ Object.defineProperty(exports, "useStackSnapshot", {
1770
+ enumerable: true,
1771
+ get: function () { return react.useStackSnapshot; }
1772
+ });
1773
+ Object.defineProperty(exports, "useStackTimers", {
1774
+ enumerable: true,
1775
+ get: function () { return react.useStackTimers; }
1776
+ });
1777
+ Object.defineProperty(exports, "useTimerDisplay", {
1778
+ enumerable: true,
1779
+ get: function () { return react.useTimerDisplay; }
1780
+ });
1781
+ Object.defineProperty(exports, "useTimerElapsed", {
1782
+ enumerable: true,
1783
+ get: function () { return react.useTimerElapsed; }
1784
+ });
4207
1785
  Object.defineProperty(exports, "DASHBOARD_GRID_MAX_COLS", {
4208
1786
  enumerable: true,
4209
1787
  get: function () { return wodWikiWql.DASHBOARD_GRID_MAX_COLS; }
@@ -4404,103 +1982,25 @@ Object.defineProperty(exports, "wqlTerms", {
4404
1982
  enumerable: true,
4405
1983
  get: function () { return wodWikiWql.wqlTerms; }
4406
1984
  });
4407
- Object.defineProperty(exports, "BlockTimerDisplay", {
4408
- enumerable: true,
4409
- get: function () { return react.BlockTimerDisplay; }
4410
- });
4411
- Object.defineProperty(exports, "ScriptRuntimeProvider", {
4412
- enumerable: true,
4413
- get: function () { return react.ScriptRuntimeProvider; }
4414
- });
4415
- Object.defineProperty(exports, "useActiveControls", {
4416
- enumerable: true,
4417
- get: function () { return react.useActiveControls; }
4418
- });
4419
- Object.defineProperty(exports, "useBlockMemory", {
4420
- enumerable: true,
4421
- get: function () { return react.useBlockMemory; }
4422
- });
4423
- Object.defineProperty(exports, "useLiveAnalytics", {
4424
- enumerable: true,
4425
- get: function () { return react.useLiveAnalytics; }
4426
- });
4427
- Object.defineProperty(exports, "useMemorySubscription", {
4428
- enumerable: true,
4429
- get: function () { return react.useMemorySubscription; }
4430
- });
4431
- Object.defineProperty(exports, "useNextPreview", {
4432
- enumerable: true,
4433
- get: function () { return react.useNextPreview; }
4434
- });
4435
- Object.defineProperty(exports, "useOutputStatements", {
4436
- enumerable: true,
4437
- get: function () { return react.useOutputStatements; }
4438
- });
4439
- Object.defineProperty(exports, "usePrimaryTimer", {
4440
- enumerable: true,
4441
- get: function () { return react.usePrimaryTimer; }
4442
- });
4443
- Object.defineProperty(exports, "useRoundDisplay", {
4444
- enumerable: true,
4445
- get: function () { return react.useRoundDisplay; }
4446
- });
4447
- Object.defineProperty(exports, "useRuntimeExecution", {
4448
- enumerable: true,
4449
- get: function () { return react.useRuntimeExecution; }
4450
- });
4451
- Object.defineProperty(exports, "useScriptRuntime", {
4452
- enumerable: true,
4453
- get: function () { return react.useScriptRuntime; }
4454
- });
4455
- Object.defineProperty(exports, "useSecondaryTimers", {
4456
- enumerable: true,
4457
- get: function () { return react.useSecondaryTimers; }
4458
- });
4459
- Object.defineProperty(exports, "useSnapshotBlocks", {
4460
- enumerable: true,
4461
- get: function () { return react.useSnapshotBlocks; }
4462
- });
4463
- Object.defineProperty(exports, "useStackDisplayRows", {
4464
- enumerable: true,
4465
- get: function () { return react.useStackDisplayRows; }
4466
- });
4467
- Object.defineProperty(exports, "useStackFragmentSources", {
4468
- enumerable: true,
4469
- get: function () { return react.useStackFragmentSources; }
4470
- });
4471
- Object.defineProperty(exports, "useStackSnapshot", {
4472
- enumerable: true,
4473
- get: function () { return react.useStackSnapshot; }
4474
- });
4475
- Object.defineProperty(exports, "useStackTimers", {
4476
- enumerable: true,
4477
- get: function () { return react.useStackTimers; }
4478
- });
4479
- Object.defineProperty(exports, "useTimerDisplay", {
4480
- enumerable: true,
4481
- get: function () { return react.useTimerDisplay; }
4482
- });
4483
- Object.defineProperty(exports, "useTimerElapsed", {
4484
- enumerable: true,
4485
- get: function () { return react.useTimerElapsed; }
4486
- });
4487
1985
  exports.BUILD_TIME = BUILD_TIME;
1986
+ exports.CANVAS_CHUNK_ID = CANVAS_CHUNK_ID;
1987
+ exports.DASHBOARDS_CHUNK_ID = DASHBOARDS_CHUNK_ID;
1988
+ exports.EFFORTS_CHUNK_ID = EFFORTS_CHUNK_ID;
4488
1989
  exports.GIT_SHA = GIT_SHA;
4489
1990
  exports.NOTE_ID_FRONTMATTER_KEY = NOTE_ID_FRONTMATTER_KEY;
4490
- exports.PackLoadError = PackLoadError;
4491
- exports.ParseSyntaxError = ParseSyntaxError;
1991
+ exports.SEED_BROADCAST_CHANNEL = SEED_BROADCAST_CHANNEL;
1992
+ exports.SEED_REGISTERED_WIDGETS = SEED_REGISTERED_WIDGETS;
1993
+ exports.SEED_SCHEMA = SEED_SCHEMA;
4492
1994
  exports.SEMVER = SEMVER;
4493
1995
  exports.SESSION_CSV_HEADER = SESSION_CSV_HEADER;
4494
- exports.VERSION = VERSION2;
4495
- exports.WqlSyntaxError = WqlSyntaxError;
1996
+ exports.TEMPLATE_CHUNK_ID = TEMPLATE_CHUNK_ID;
1997
+ exports.VERSION = VERSION;
4496
1998
  exports.applySeedChunk = applySeedChunk;
4497
1999
  exports.assertSeedManifest = assertSeedManifest;
4498
2000
  exports.assertSeedRows = assertSeedRows;
4499
2001
  exports.buildStatementTree = buildStatementTree;
4500
- exports.buildWorkoutResults = buildWorkoutResults;
4501
2002
  exports.canvasRouteSlug = canvasRouteSlug;
4502
2003
  exports.catalogForChunkId = catalogForChunkId;
4503
- exports.cliMain = cliMain;
4504
2004
  exports.composeNoteMarkdown = composeNoteMarkdown;
4505
2005
  exports.createIRFile = createIRFile;
4506
2006
  exports.csvToEquipmentLabels = csvToEquipmentLabels;
@@ -4511,25 +2011,15 @@ exports.emptySeedMeta = emptySeedMeta;
4511
2011
  exports.equipmentToCsv = equipmentToCsv;
4512
2012
  exports.extractNoteIdentity = extractNoteIdentity;
4513
2013
  exports.extractTypedTags = extractTypedTags;
4514
- exports.formatExecutionOutput = formatExecutionOutput;
4515
- exports.formatParseOutput = formatParseOutput;
4516
- exports.formatQueryOutput = formatQueryOutput;
4517
2014
  exports.getRegisteredLanguagePacks = getRegisteredLanguagePacks;
4518
2015
  exports.inMemoryEventStore = inMemoryEventStore;
4519
2016
  exports.injectIdentityFrontmatter = injectIdentityFrontmatter;
4520
2017
  exports.isIRFile = isIRFile;
4521
2018
  exports.listLanguagePacks = listLanguagePacks;
4522
- exports.loadLanguagePack = loadLanguagePack;
4523
- exports.loadQueryData = loadQueryData;
4524
2019
  exports.markdownToEffort = markdownToEffort;
4525
- exports.parseCliArgs = parseCliArgs;
4526
2020
  exports.parseCsvLine = parseCsvLine;
4527
2021
  exports.readFrontmatterMeta = readFrontmatterMeta;
4528
- exports.readStdin = readStdin;
4529
2022
  exports.registerLanguagePack = registerLanguagePack;
4530
- exports.runExecution = runExecution;
4531
- exports.runParse = runParse;
4532
- exports.runQueryCli = runQueryCli;
4533
2023
  exports.seedMetaKey = seedMetaKey;
4534
2024
  exports.seedNoteId = seedNoteId;
4535
2025
  exports.seedNoteSourceId = seedNoteSourceId;