@bitcobblers/wod-wiki-engine 0.11.43 → 0.35.2115

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.
@@ -0,0 +1,2722 @@
1
+ import { createRequire } from 'module';
2
+ import { __require } from './chunk-6L5ZDZJ7.mjs';
3
+ import { dialectRegistry, realtimeProcessorRegistry, summaryProcessorRegistry, getHints, createParser, toStoredOutputStatement, frozenNow, createCompiler, createMockClock, RuntimeStack, EventBus, ScriptRuntime, createAnalyticsEngineForBlock, StartSessionAction, NextEvent, bundledEfforts, parseScript } from '@bitcobblers/wod-wiki-lang';
4
+ import { MetricType, parseDocumentSections } from '@bitcobblers/wod-wiki-core';
5
+ import { WQL_TYPED_TAG_KEYS, toEventRows, parseQuery, QueryService, isPipelineQuery, isFindQuery, toSummaryEventRows } from '@bitcobblers/wod-wiki-wql';
6
+
7
+ if (typeof globalThis.require === 'undefined') {
8
+ try { globalThis.require = createRequire(import.meta.url); } catch {}
9
+ }
10
+ var activePacks = /* @__PURE__ */ new Map();
11
+ function defineLanguagePack(pack) {
12
+ return pack;
13
+ }
14
+ function registerLanguagePack(pack) {
15
+ const packId = pack.identity?.id ?? pack.identity?.name ?? "anonymous-pack";
16
+ activePacks.set(packId, pack);
17
+ if (pack.lang?.analyzer) {
18
+ const AnalyzerCtor = pack.lang.analyzer;
19
+ const instance = new AnalyzerCtor();
20
+ dialectRegistry.register(instance);
21
+ }
22
+ if (pack.lang?.analytics) {
23
+ for (const ProcessorCtor of pack.lang.analytics) {
24
+ const instance = new ProcessorCtor();
25
+ if ("processOutput" in instance) {
26
+ realtimeProcessorRegistry.register(instance);
27
+ }
28
+ if ("processSummary" in instance) {
29
+ summaryProcessorRegistry.register(instance);
30
+ }
31
+ }
32
+ }
33
+ }
34
+ function unregisterLanguagePack(packOrId) {
35
+ const packId = typeof packOrId === "string" ? packOrId : packOrId.identity?.id ?? packOrId.identity?.name ?? "anonymous-pack";
36
+ const pack = activePacks.get(packId);
37
+ activePacks.delete(packId);
38
+ if (pack?.lang?.analyzer) {
39
+ const instance = new pack.lang.analyzer();
40
+ dialectRegistry.unregister(instance.id);
41
+ } else if (typeof packOrId === "string") {
42
+ dialectRegistry.unregister(packOrId);
43
+ }
44
+ if (pack?.lang?.analytics) {
45
+ for (const ProcessorCtor of pack.lang.analytics) {
46
+ const instance = new ProcessorCtor();
47
+ if ("processOutput" in instance) {
48
+ realtimeProcessorRegistry.unregister(instance.id);
49
+ }
50
+ if ("processSummary" in instance) {
51
+ summaryProcessorRegistry.unregister(instance.id);
52
+ }
53
+ }
54
+ }
55
+ }
56
+ function listLanguagePacks() {
57
+ return Array.from(activePacks.values());
58
+ }
59
+ function getRegisteredLanguagePacks() {
60
+ return Array.from(activePacks.values());
61
+ }
62
+
63
+ // src/store.ts
64
+ function inMemoryEventStore(events) {
65
+ const rows = [...events];
66
+ return {
67
+ getEventsByTimeRange: async (start, end) => rows.filter((r) => r.timestamp >= start && r.timestamp <= end),
68
+ getEventsByResult: async (resultId) => rows.filter((r) => r.resultId === resultId),
69
+ getEventsForNote: async (noteId) => rows.filter((r) => r.noteId === noteId),
70
+ getEventsByContent: async (blockContentId) => rows.filter((r) => r.blockContentId === blockContentId),
71
+ scanAll: async () => rows,
72
+ appendEvents: async (appended) => {
73
+ rows.push(...appended);
74
+ },
75
+ finalizeSummaries: async (resultId, finals) => {
76
+ for (let i = rows.length - 1; i >= 0; i--) {
77
+ const row = rows[i];
78
+ if (row.resultId === resultId && row.grain === "summary" && row.origin !== "user") rows.splice(i, 1);
79
+ }
80
+ rows.push(...finals);
81
+ },
82
+ deleteEvents: async (ids) => {
83
+ const doomed = new Set(ids);
84
+ for (let i = rows.length - 1; i >= 0; i--) {
85
+ if (doomed.has(rows[i].id)) rows.splice(i, 1);
86
+ }
87
+ }
88
+ };
89
+ }
90
+ function createIRFile(kind, data, optionsOrSource, generatedAt) {
91
+ let source = "wod:headless";
92
+ let genAt = Date.now();
93
+ if (typeof optionsOrSource === "string") {
94
+ source = optionsOrSource;
95
+ if (generatedAt !== void 0) genAt = generatedAt;
96
+ } else if (optionsOrSource && typeof optionsOrSource === "object") {
97
+ if (optionsOrSource.source) source = optionsOrSource.source;
98
+ if (optionsOrSource.generatedAt !== void 0) genAt = optionsOrSource.generatedAt;
99
+ }
100
+ return {
101
+ $schema: "https://wod-wiki.dev/ir/v1.json",
102
+ kind,
103
+ generatedAt: genAt,
104
+ source,
105
+ data
106
+ };
107
+ }
108
+ function isIRFile(obj) {
109
+ if (!obj || typeof obj !== "object") return false;
110
+ const cand = obj;
111
+ 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;
112
+ }
113
+ function statementToNode(stmt, idMap) {
114
+ const meta = stmt.meta;
115
+ const metrics = stmt.metrics.toArray().map((m) => ({
116
+ type: m.type,
117
+ ...m.image !== void 0 ? { image: m.image } : {},
118
+ ...m.value !== void 0 ? { value: m.value } : {},
119
+ ...m.origin !== void 0 ? { origin: m.origin } : {}
120
+ }));
121
+ const raw = meta?.raw ?? "";
122
+ const from = meta?.startOffset ?? 0;
123
+ const to = meta?.endOffset ?? from + raw.length;
124
+ const durationFrag = stmt.metrics.find((m) => m.type === MetricType.Duration || m.type === "duration");
125
+ const roundsFrag = stmt.metrics.find((m) => m.type === MetricType.Rounds || m.type === "rounds");
126
+ const type = durationFrag ? "duration" : roundsFrag ? "rounds" : "statement";
127
+ const hints = getHints(stmt);
128
+ const childIds = (stmt.children ?? []).flat();
129
+ const children = childIds.map((cid) => idMap.get(cid)).filter((c) => c !== void 0).map((c) => statementToNode(c, idMap));
130
+ return {
131
+ id: stmt.id,
132
+ type,
133
+ raw,
134
+ from,
135
+ to,
136
+ metrics,
137
+ ...hints.length > 0 ? { hints } : {},
138
+ ...children.length > 0 ? { children } : {}
139
+ };
140
+ }
141
+ function buildStatementTree(script) {
142
+ const idMap = new Map(script.statements.map((s) => [s.id, s]));
143
+ const topLevel = script.statements.filter((s) => s.parent === void 0 || !idMap.has(s.parent));
144
+ if (topLevel.length === 1 && topLevel[0].children.length > 0) {
145
+ return statementToNode(topLevel[0], idMap);
146
+ }
147
+ return {
148
+ id: 0,
149
+ type: "root",
150
+ raw: script.source,
151
+ from: 0,
152
+ to: script.source.length,
153
+ metrics: [],
154
+ children: topLevel.map((s) => statementToNode(s, idMap))
155
+ };
156
+ }
157
+ var ParseSyntaxError = class extends Error {
158
+ constructor(errors, message = "Parse error in Whiteboard Script") {
159
+ const details = errors.map((e) => e.message).join("; ");
160
+ super(`${message}: ${details}`);
161
+ this.errors = errors;
162
+ this.name = "ParseSyntaxError";
163
+ }
164
+ errors;
165
+ };
166
+ function runParse(source, options = {}) {
167
+ const parser = createParser();
168
+ const script = parser.read(source, options.sport);
169
+ if (script.errors && script.errors.length > 0) {
170
+ throw new ParseSyntaxError(script.errors);
171
+ }
172
+ const tree = buildStatementTree(script);
173
+ return createIRFile("parse-tree", tree, { source: options.sourceLabel ?? "cli:wod parse" });
174
+ }
175
+ function buildWorkoutResults(outputs, options) {
176
+ return {
177
+ startTime: options.startTime ?? options.now.nowMs(),
178
+ endTime: options.now.nowMs(),
179
+ duration: options.elapsedTime,
180
+ completed: options.completed,
181
+ logs: outputs.map(toStoredOutputStatement)
182
+ };
183
+ }
184
+ async function runExecution(source, options = {}) {
185
+ const parser = createParser();
186
+ const script = parser.read(source, options.sport);
187
+ if (script.errors && script.errors.length > 0) {
188
+ throw new ParseSyntaxError(script.errors);
189
+ }
190
+ const startTime = options.startTime ?? Date.now();
191
+ const now = options.now ?? frozenNow(new Date(startTime));
192
+ const compiler = createCompiler();
193
+ const clock = createMockClock(new Date(startTime));
194
+ const stack = new RuntimeStack();
195
+ const eventBus = new EventBus();
196
+ const runtime = new ScriptRuntime(
197
+ script,
198
+ compiler,
199
+ { stack, clock, eventBus },
200
+ {},
201
+ now
202
+ );
203
+ const { engine, analyticsContext } = createAnalyticsEngineForBlock({
204
+ id: "cli-block",
205
+ content: source,
206
+ statements: script.statements,
207
+ sport: options.sport,
208
+ version: 1,
209
+ startLine: 0,
210
+ endLine: 0,
211
+ dialect: "time",
212
+ state: "idle",
213
+ createdAt: Date.now()
214
+ });
215
+ runtime.analyticsContext = analyticsContext;
216
+ runtime.setAnalyticsEngine(engine);
217
+ const outputs = [];
218
+ runtime.subscribeToOutput((out) => {
219
+ outputs.push(out);
220
+ });
221
+ runtime.do(new StartSessionAction());
222
+ const maxSteps = options.maxIterations ?? 5e3;
223
+ let steps = 0;
224
+ while (stack.count > 0 && steps < maxSteps) {
225
+ runtime.handle(new NextEvent(void 0, runtime.nowProvider));
226
+ clock.advance(1e3);
227
+ steps++;
228
+ }
229
+ const workoutResults = buildWorkoutResults(outputs, {
230
+ startTime,
231
+ elapsedTime: steps * 1e3,
232
+ completed: stack.count === 0,
233
+ now
234
+ });
235
+ const logs = outputs.map(toStoredOutputStatement);
236
+ return createIRFile(
237
+ "execution-log",
238
+ {
239
+ results: workoutResults,
240
+ logs,
241
+ statements: logs
242
+ },
243
+ { source: options.sourceLabel ?? "cli:wod run" }
244
+ );
245
+ }
246
+ function getFs() {
247
+ if (typeof globalThis.process?.versions?.node === "undefined" && typeof globalThis.Bun === "undefined") {
248
+ throw new Error("File system operations are only supported in Node / Bun environments");
249
+ }
250
+ return __require("fs");
251
+ }
252
+ var WqlSyntaxError = class extends Error {
253
+ constructor(query, syntaxError) {
254
+ super(`WQL Syntax Error in "${query}": ${syntaxError}`);
255
+ this.query = query;
256
+ this.syntaxError = syntaxError;
257
+ this.name = "WqlSyntaxError";
258
+ }
259
+ query;
260
+ syntaxError;
261
+ };
262
+ function legacyLogsOf(result) {
263
+ const legacy = result;
264
+ return legacy.data?.logs ?? [];
265
+ }
266
+ function extractPayload(rawJson) {
267
+ try {
268
+ const parsed = JSON.parse(rawJson);
269
+ if (isIRFile(parsed)) {
270
+ return parsed.data;
271
+ }
272
+ return parsed;
273
+ } catch (err) {
274
+ throw new Error(`Failed to parse JSON input: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
275
+ }
276
+ }
277
+ function buildStoresFromData(data) {
278
+ const identity = (r) => ({
279
+ noteId: r.noteId,
280
+ resultId: r.id,
281
+ segmentId: r.segmentId,
282
+ segmentVersion: r.segmentVersion,
283
+ blockContentId: r.blockContentId,
284
+ origin: r.origin,
285
+ pageId: r.pageId,
286
+ workoutTimestamp: r.createdAt
287
+ });
288
+ const events = [
289
+ ...data.eventRecords,
290
+ ...data.facts.map((f, i) => ({
291
+ id: `fact:${f.resultId ?? "stdin"}:${f.metricKey ?? f.type}:${i}`,
292
+ resultId: f.resultId ?? "stdin-result-1",
293
+ noteId: f.noteId ?? "stdin-note-1",
294
+ blockContentId: f.blockContentId,
295
+ pageId: f.pageId,
296
+ origin: f.origin,
297
+ timestamp: f.timestamp,
298
+ grain: f.grain === "event" ? "event" : "summary",
299
+ outputType: "analytics",
300
+ effortSlug: f.effortSlug,
301
+ metrics: [{
302
+ type: f.metricKey ?? f.type,
303
+ value: f.value,
304
+ ...f.unit ? { unit: f.unit } : {},
305
+ origin: "engine"
306
+ }],
307
+ segmentId: f.segmentId,
308
+ segmentVersion: f.segmentVersion
309
+ })),
310
+ ...data.results.flatMap((r) => {
311
+ const logs = legacyLogsOf(r);
312
+ return [...toEventRows(logs, identity(r)), ...toSummaryEventRows(logs, identity(r))];
313
+ })
314
+ ];
315
+ const eventStore = inMemoryEventStore(events);
316
+ const noteStore = {
317
+ getAllNotes: async () => [...data.notes],
318
+ getNoteIdsForTag: async (tag) => {
319
+ const matching = /* @__PURE__ */ new Set();
320
+ for (const [noteId, tags] of Object.entries(data.noteTags)) {
321
+ if (tags.includes(tag)) matching.add(noteId);
322
+ }
323
+ return matching;
324
+ },
325
+ getNoteTagLabels: async (noteId) => [...data.noteTags[noteId] ?? []]
326
+ };
327
+ const blockStore = {
328
+ getAllBlocks: async () => [...data.blocks]
329
+ };
330
+ const effortStore = {
331
+ getAllEfforts: async () => [...data.efforts]
332
+ };
333
+ return { eventStore, noteStore, blockStore, effortStore };
334
+ }
335
+ function loadQueryData(options) {
336
+ const data = {
337
+ eventRecords: [],
338
+ facts: [],
339
+ results: [],
340
+ notes: [],
341
+ blocks: [],
342
+ efforts: [...bundledEfforts],
343
+ noteTags: {}
344
+ };
345
+ if (options.corpusPath) {
346
+ const fs = getFs();
347
+ if (!fs.existsSync(options.corpusPath)) {
348
+ throw new Error(`Corpus file not found at path: ${options.corpusPath}`);
349
+ }
350
+ const raw = fs.readFileSync(options.corpusPath, "utf-8");
351
+ const payload = extractPayload(raw);
352
+ if (Array.isArray(payload)) {
353
+ data.facts = payload;
354
+ } else if (payload && typeof payload === "object") {
355
+ const obj = payload;
356
+ if (obj.kind === "event-journal" || Array.isArray(obj.records)) {
357
+ if (Array.isArray(obj.records)) {
358
+ data.eventRecords = obj.records;
359
+ }
360
+ if (Array.isArray(obj.notes)) {
361
+ data.notes = obj.notes;
362
+ for (const n of obj.notes) {
363
+ if (n && typeof n === "object" && n.id && Array.isArray(n.tags)) {
364
+ data.noteTags[n.id] = n.tags;
365
+ }
366
+ }
367
+ }
368
+ } else if (obj.facts || obj.results || obj.notes || obj.blocks || obj.efforts || obj.tags || obj.logs) {
369
+ const corpus = payload;
370
+ if (corpus.facts) data.facts = corpus.facts;
371
+ if (corpus.results) data.results = corpus.results;
372
+ if (corpus.notes) data.notes = corpus.notes;
373
+ if (corpus.blocks) data.blocks = corpus.blocks;
374
+ if (corpus.efforts) data.efforts = corpus.efforts;
375
+ if (corpus.tags) data.noteTags = corpus.tags;
376
+ if (corpus.logs && (!corpus.results || corpus.results.length === 0)) {
377
+ const syntheticResult = {
378
+ id: "corpus-result-1",
379
+ noteId: "corpus-note-1",
380
+ blockContentId: "corpus-block-1",
381
+ origin: "journal",
382
+ createdAt: corpus.logs[0]?.timeSpan?.started ?? Date.now(),
383
+ startTime: corpus.logs[0]?.timeSpan?.started ?? Date.now(),
384
+ endTime: corpus.logs[corpus.logs.length - 1]?.timeSpan?.ended ?? Date.now(),
385
+ duration: 0,
386
+ completed: true
387
+ };
388
+ data.results = [syntheticResult];
389
+ }
390
+ } else {
391
+ throw new Error(`Unrecognized corpus payload shape in "${options.corpusPath}" (expected event-journal, fact-set, or CorpusIRData)`);
392
+ }
393
+ }
394
+ } else if (options.stdinLog) {
395
+ const payload = extractPayload(options.stdinLog);
396
+ let executionLog;
397
+ const rawLogs = payload.logs ?? ("statements" in payload ? payload.statements : void 0) ?? [];
398
+ if ("results" in payload) {
399
+ executionLog = { results: payload.results, logs: rawLogs, statements: rawLogs };
400
+ } else {
401
+ executionLog = { results: payload, logs: rawLogs, statements: rawLogs };
402
+ }
403
+ const resultId = "stdin-result-1";
404
+ const workoutResult = {
405
+ id: resultId,
406
+ noteId: "stdin-note-1",
407
+ blockContentId: "stdin-block-1",
408
+ origin: "journal",
409
+ createdAt: executionLog.results.startTime,
410
+ startTime: executionLog.results.startTime,
411
+ endTime: executionLog.results.endTime,
412
+ duration: executionLog.results.duration ?? 0,
413
+ completed: executionLog.results.completed
414
+ };
415
+ data.results = [workoutResult];
416
+ data.eventRecords = toEventRows(executionLog.logs, {
417
+ noteId: workoutResult.noteId,
418
+ resultId: workoutResult.id,
419
+ segmentId: workoutResult.segmentId,
420
+ segmentVersion: workoutResult.segmentVersion,
421
+ blockContentId: workoutResult.blockContentId,
422
+ origin: workoutResult.origin,
423
+ pageId: workoutResult.pageId,
424
+ workoutTimestamp: workoutResult.createdAt
425
+ });
426
+ } else if (options.stdinFacts) {
427
+ const payload = extractPayload(options.stdinFacts);
428
+ if (Array.isArray(payload)) {
429
+ data.facts = payload;
430
+ } else if (payload && typeof payload === "object" && "facts" in payload) {
431
+ data.facts = payload.facts;
432
+ }
433
+ } else {
434
+ throw new Error("No input dataset provided. Specify --corpus <path>, --stdin-log, or --stdin-facts.");
435
+ }
436
+ return data;
437
+ }
438
+ async function runQueryCli(wqlString, options = {}) {
439
+ const parsed = parseQuery(wqlString);
440
+ if (parsed.error) {
441
+ throw new WqlSyntaxError(wqlString, parsed.error);
442
+ }
443
+ const stores = options.stores ?? buildStoresFromData(loadQueryData(options));
444
+ const service = new QueryService(stores);
445
+ if (isPipelineQuery(parsed)) {
446
+ const result2 = await service.runPipeline(wqlString, {
447
+ preferredUnit: options.preferredUnit
448
+ });
449
+ return createIRFile("pipeline-result", result2, { source: options.sourceLabel ?? "cli:wod query" });
450
+ }
451
+ if (isFindQuery(parsed)) {
452
+ const result2 = await service.runFind(parsed);
453
+ const kind = parsed.target === "session" ? "rows-result" : "find-result";
454
+ return createIRFile(kind, result2, { source: options.sourceLabel ?? "cli:wod query" });
455
+ }
456
+ const result = await service.runQuery(wqlString, {
457
+ preferredUnit: options.preferredUnit
458
+ });
459
+ return createIRFile("query-result", result, { source: options.sourceLabel ?? "cli:wod query" });
460
+ }
461
+
462
+ // src/cli/loader.ts
463
+ function getPathUtils() {
464
+ if (typeof globalThis.process?.versions?.node === "undefined" && !("Bun" in globalThis)) {
465
+ throw new Error("Path resolution is only supported in Node / Bun environments");
466
+ }
467
+ return {
468
+ // eslint-disable-next-line no-restricted-syntax -- lazy CJS require keeps node builtins out of browser bundles
469
+ resolve: __require("path").resolve,
470
+ // eslint-disable-next-line no-restricted-syntax -- lazy CJS require keeps node builtins out of browser bundles
471
+ pathToFileURL: __require("url").pathToFileURL
472
+ };
473
+ }
474
+ var PackLoadError = class extends Error {
475
+ constructor(spec, originalError) {
476
+ const msg = originalError instanceof Error ? originalError.message : String(originalError);
477
+ super(`Failed to load Language Pack "${spec}": ${msg}`);
478
+ this.spec = spec;
479
+ this.originalError = originalError;
480
+ this.name = "PackLoadError";
481
+ }
482
+ spec;
483
+ originalError;
484
+ };
485
+ async function loadLanguagePack(moduleSpec) {
486
+ try {
487
+ let importPath = moduleSpec;
488
+ if (moduleSpec.startsWith(".") || moduleSpec.startsWith("/") || moduleSpec.includes("\\") || moduleSpec.endsWith(".ts") || moduleSpec.endsWith(".js") || moduleSpec.endsWith(".mjs")) {
489
+ const { resolve, pathToFileURL } = getPathUtils();
490
+ const resolved = resolve(process.cwd(), moduleSpec);
491
+ importPath = pathToFileURL(resolved).href;
492
+ }
493
+ const imported = await import(
494
+ /* @vite-ignore */
495
+ importPath
496
+ );
497
+ const candidate = imported.default ?? imported.pack ?? imported;
498
+ if (!candidate || typeof candidate !== "object") {
499
+ throw new Error(`Module "${moduleSpec}" does not export a valid LanguagePack object`);
500
+ }
501
+ const pack = candidate;
502
+ registerLanguagePack(pack);
503
+ return pack;
504
+ } catch (error) {
505
+ if (error instanceof PackLoadError) throw error;
506
+ throw new PackLoadError(moduleSpec, error);
507
+ }
508
+ }
509
+ var NOTE_ID_FRONTMATTER_KEY = "wod-note-id";
510
+ 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;
511
+ function extractNoteIdentity(markdown) {
512
+ const block = /^---\r?\n([\s\S]*?)\r?\n---/.exec(markdown);
513
+ if (!block) return void 0;
514
+ let noteId;
515
+ let title;
516
+ for (const line of block[1].split(/\r?\n/)) {
517
+ const kv = line.match(/^([^:]+):\s*(.*)$/);
518
+ if (!kv) continue;
519
+ const key = kv[1].trim();
520
+ const value = kv[2].trim();
521
+ if (key === NOTE_ID_FRONTMATTER_KEY && UUID_RE.test(value)) noteId = value;
522
+ if (key === "title" && value) title = value;
523
+ }
524
+ return noteId ? { noteId, title } : void 0;
525
+ }
526
+ var FRONTMATTER_SAFE = /^[^:#\[\]{}'"*&,!?|>%@`\s][^:#\[\]{}"']*$/;
527
+ function injectIdentityFrontmatter(markdown, identity) {
528
+ const stamp = (lines2) => {
529
+ const replaceOrInsert = (key, value) => {
530
+ if (value === void 0 || !FRONTMATTER_SAFE.test(value)) return;
531
+ const idx = lines2.findIndex((l) => l.startsWith(`${key}:`));
532
+ if (idx >= 0) lines2[idx] = `${key}: ${value}`;
533
+ else lines2.push(`${key}: ${value}`);
534
+ };
535
+ replaceOrInsert("title", identity.title);
536
+ replaceOrInsert(NOTE_ID_FRONTMATTER_KEY, identity.noteId);
537
+ return lines2;
538
+ };
539
+ const block = /^---\r?\n([\s\S]*?)\r?\n---/.exec(markdown);
540
+ if (block) {
541
+ const lines2 = stamp(block[1].split(/\r?\n/));
542
+ return `---
543
+ ${lines2.join("\n")}
544
+ ---${markdown.slice(block[0].length)}`;
545
+ }
546
+ const lines = stamp([]);
547
+ return `---
548
+ ${lines.join("\n")}
549
+ ---
550
+
551
+ ${markdown}`;
552
+ }
553
+ function segmentToFragment(segment) {
554
+ if (segment.sourceContent !== void 0) return segment.sourceContent;
555
+ if (segment.dataType === "wod") {
556
+ const block = segment.data;
557
+ const dialect = block?.dialect ?? "time";
558
+ const fenceTag = block?.sport ? `${dialect}:${block.sport}` : dialect;
559
+ return `\`\`\`${fenceTag}
560
+ ${segment.rawContent}
561
+ \`\`\``;
562
+ }
563
+ if (segment.dataType === "frontmatter") {
564
+ return `---
565
+ ${segment.rawContent}
566
+ ---`;
567
+ }
568
+ return segment.rawContent;
569
+ }
570
+ function composeNoteMarkdown(note, segments, version) {
571
+ const pool = version === void 0 ? segments.filter((s) => s.isHistory !== true) : segments;
572
+ const cap = version ?? Number.MAX_SAFE_INTEGER;
573
+ const bySegmentId = {};
574
+ for (const segment of pool) {
575
+ if (segment.version > cap) continue;
576
+ const current = bySegmentId[segment.id];
577
+ if (!current || segment.version > current.version) bySegmentId[segment.id] = segment;
578
+ }
579
+ const picked = Object.values(bySegmentId).sort((a, b) => (a.position ?? 0) - (b.position ?? 0));
580
+ const markdown = picked.map(segmentToFragment).join("");
581
+ return injectIdentityFrontmatter(markdown, { noteId: note.id, title: note.title });
582
+ }
583
+ var FRONTMATTER_BLOCK_RE = /^---\r?\n([\s\S]*?)\r?\n---/;
584
+ function readFrontmatterMeta(markdown) {
585
+ const match = FRONTMATTER_BLOCK_RE.exec(markdown);
586
+ if (!match) return {};
587
+ const meta = {};
588
+ const lines = match[1].split(/\r?\n/);
589
+ let currentListKey;
590
+ for (const rawLine of lines) {
591
+ const line = rawLine.trimEnd();
592
+ const item = line.match(/^\s+-\s+(.*)$/);
593
+ if (item && currentListKey) {
594
+ meta[currentListKey].push(item[1].trim());
595
+ continue;
596
+ }
597
+ const kv = line.match(/^([^:]+):\s*(.*)$/);
598
+ if (!kv) continue;
599
+ const key = kv[1].trim().toLowerCase();
600
+ const value = kv[2].trim();
601
+ if (value === "") {
602
+ meta[key] = [];
603
+ currentListKey = key;
604
+ } else if (value.startsWith("[") && value.endsWith("]")) {
605
+ meta[key] = value.slice(1, -1).split(",").map((v) => v.trim().replace(/^['"]|['"]$/g, "")).filter((v) => v.length > 0);
606
+ currentListKey = void 0;
607
+ } else {
608
+ meta[key] = value;
609
+ currentListKey = void 0;
610
+ }
611
+ }
612
+ return meta;
613
+ }
614
+ var asList = (value) => Array.isArray(value) ? value : typeof value === "string" ? value.split(",").map((v) => v.trim()).filter((v) => v.length > 0) : [];
615
+ function extractTypedTags(markdown, typedKeys = WQL_TYPED_TAG_KEYS) {
616
+ const meta = readFrontmatterMeta(markdown);
617
+ const results = asList(meta["tags"]).map((label) => ({ label }));
618
+ const metaKeys = Object.keys(meta);
619
+ for (const typeName of typedKeys) {
620
+ if (typeName === "tags") continue;
621
+ const matchedKey = metaKeys.find((k) => k === typeName.toLowerCase());
622
+ if (!matchedKey) continue;
623
+ for (const label of asList(meta[matchedKey])) {
624
+ results.push({ label, type: typeName });
625
+ }
626
+ }
627
+ return results.filter((t) => t.label.length > 0);
628
+ }
629
+ function markdownToEffort(raw) {
630
+ const match = FRONTMATTER_BLOCK_RE.exec(raw);
631
+ if (!match) return null;
632
+ const meta = readFrontmatterMeta(raw);
633
+ const body = raw.slice(match[0].length).trim();
634
+ const id = scalarOf(meta["id"]);
635
+ const slug = scalarOf(meta["slug"]);
636
+ const label = scalarOf(meta["label"]);
637
+ if (!id || !slug || !label) return null;
638
+ const baseAttributes = {};
639
+ const blockRe = /^\s{2,}([^:]+):\s*(.+)$/;
640
+ let inAttrs = false;
641
+ for (const line of match[1].split(/\r?\n/)) {
642
+ if (/^baseAttributes:/.test(line)) {
643
+ inAttrs = true;
644
+ continue;
645
+ }
646
+ if (inAttrs) {
647
+ const attr = blockRe.exec(line);
648
+ if (attr && /^ {2,}/.test(line)) {
649
+ const v = attr[2].trim();
650
+ baseAttributes[attr[1].trim()] = /^\d+(\.\d+)?$/.test(v) ? Number(v) : v;
651
+ } else if (/^\S/.test(line)) {
652
+ inAttrs = false;
653
+ }
654
+ }
655
+ }
656
+ if (typeof baseAttributes["met"] !== "number") return null;
657
+ return {
658
+ id,
659
+ slug,
660
+ label,
661
+ aliases: asList(meta["aliases"]),
662
+ baseAttributes,
663
+ ...body ? { body } : {}
664
+ };
665
+ }
666
+ function effortToMarkdown(effort) {
667
+ const lines = ["---", `id: ${effort.id}`, `slug: ${effort.slug}`, `label: ${effort.label}`];
668
+ if (effort.aliases && effort.aliases.length > 0) {
669
+ lines.push("aliases:");
670
+ for (const alias of effort.aliases) lines.push(` - ${alias}`);
671
+ }
672
+ const attrs = Object.entries(effort.baseAttributes).filter(([, v]) => typeof v === "number" || typeof v === "string");
673
+ if (attrs.length > 0) {
674
+ lines.push("baseAttributes:");
675
+ for (const [k, v] of attrs) lines.push(` ${k}: ${String(v)}`);
676
+ }
677
+ lines.push("---", "");
678
+ return `${lines.join("\n")}
679
+ ${(effort.body ?? "").trim()}`.trimEnd() + "\n";
680
+ }
681
+ function scalarOf(value) {
682
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
683
+ }
684
+ function equipmentToCsv(labels) {
685
+ return ["label", ...labels].map((l) => escapeCsvField(l)).join("\n") + "\n";
686
+ }
687
+ function csvToEquipmentLabels(csv) {
688
+ const lines = splitCsvRecords(csv);
689
+ if (lines.length === 0) return [];
690
+ const header = parseCsvLine(lines[0]);
691
+ const labelIdx = header[0] === "label" ? 0 : -1;
692
+ const start = labelIdx >= 0 ? 1 : 0;
693
+ const labels = [];
694
+ for (const line of lines.slice(start)) {
695
+ const cells = parseCsvLine(line);
696
+ const label = (labelIdx >= 0 ? cells[0] : cells.join(",")).trim();
697
+ if (label) labels.push(label);
698
+ }
699
+ return [...new Set(labels)];
700
+ }
701
+ var SESSION_HEADER = [
702
+ "sessionId",
703
+ "noteId",
704
+ "segmentId",
705
+ "segmentVersion",
706
+ "blockId",
707
+ "blockContentId",
708
+ "origin",
709
+ "status",
710
+ "startTime",
711
+ "endTime",
712
+ "duration",
713
+ "completed",
714
+ "sessionCreatedAt"
715
+ ];
716
+ var EVENT_HEADER = [
717
+ "eventId",
718
+ "grain",
719
+ "outputType",
720
+ "timestamp",
721
+ "pageId",
722
+ "effortSlug",
723
+ "eventSegmentId",
724
+ "eventSegmentVersion",
725
+ "eventBlockContentId",
726
+ "eventOrigin",
727
+ "timeSpan",
728
+ "sourceBlockKey",
729
+ "sourceStatementId",
730
+ "line",
731
+ "text",
732
+ "dialect",
733
+ "stackLevel",
734
+ "completionReason",
735
+ "representationKind",
736
+ "metricType",
737
+ "metricValue",
738
+ "metricUnit",
739
+ "metricImage",
740
+ "metricOrigin",
741
+ "metricMetadata",
742
+ "metricTemporal",
743
+ // Verbatim JSON backstops: make the machine round-trip lossless across
744
+ // schema fields that have no dedicated spreadsheet column. Explicit cells
745
+ // above still win on import, so hand-edited values are honored.
746
+ "metricJson",
747
+ "eventExtraJson"
748
+ ];
749
+ var SESSION_CSV_HEADER = [...SESSION_HEADER, ...EVENT_HEADER];
750
+ function escapeCsvField(field) {
751
+ if (field === void 0 || field === null) return "";
752
+ const str = typeof field === "object" ? JSON.stringify(field) : String(field);
753
+ if (str.includes(",") || str.includes('"') || str.includes("\n") || str.includes("\r")) {
754
+ return `"${str.replace(/"/g, '""')}"`;
755
+ }
756
+ return str;
757
+ }
758
+ var metricValueToCell = (value) => {
759
+ if (value === void 0 || value === null) return "";
760
+ if (typeof value === "object") return JSON.stringify(value);
761
+ return String(value);
762
+ };
763
+ var metricValueFromCell = (cell) => {
764
+ if (cell === "") return void 0;
765
+ if (/^-?\d+(\.\d+)?$/.test(cell)) return Number(cell);
766
+ if (cell === "true" || cell === "false") return cell === "true";
767
+ if (cell.startsWith("{") || cell.startsWith("[")) {
768
+ try {
769
+ return JSON.parse(cell);
770
+ } catch {
771
+ return cell;
772
+ }
773
+ }
774
+ return cell;
775
+ };
776
+ function sessionCells(session) {
777
+ return [
778
+ session.id,
779
+ session.noteId,
780
+ session.segmentId,
781
+ session.segmentVersion,
782
+ session.blockId,
783
+ session.blockContentId,
784
+ session.origin,
785
+ session.status,
786
+ session.startTime,
787
+ session.endTime,
788
+ session.duration,
789
+ session.completed,
790
+ session.createdAt
791
+ ].map(escapeCsvField);
792
+ }
793
+ function metricCells(event, metric, index) {
794
+ return [
795
+ event.id,
796
+ event.grain,
797
+ event.outputType,
798
+ event.timestamp,
799
+ event.pageId,
800
+ event.effortSlug,
801
+ event.segmentId,
802
+ event.segmentVersion,
803
+ event.blockContentId,
804
+ event.origin,
805
+ event.timeSpan,
806
+ event.sourceBlockKey,
807
+ event.sourceStatementId,
808
+ event.line,
809
+ event.text,
810
+ event.dialect,
811
+ event.stackLevel,
812
+ event.completionReason,
813
+ event.representationKind,
814
+ metric.type,
815
+ metricValueToCell(metric.value),
816
+ metric.unit,
817
+ metric.image,
818
+ metric.origin,
819
+ metric.metadata === void 0 ? "" : JSON.stringify(metric.metadata),
820
+ event.metricTemporal?.[index] === void 0 ? "" : JSON.stringify(event.metricTemporal[index]),
821
+ JSON.stringify(metric)
822
+ ].map(escapeCsvField);
823
+ }
824
+ function eventExtraCells(event) {
825
+ const { metrics: _metrics, ...rest } = event;
826
+ return escapeCsvField(JSON.stringify(rest));
827
+ }
828
+ function sessionToCsv(session, events) {
829
+ const lines = [SESSION_CSV_HEADER.join(",")];
830
+ lines.push([...sessionCells(session), ...EVENT_HEADER.map(() => "")].join(","));
831
+ const ordered = [...events].sort((a, b) => a.id.localeCompare(b.id));
832
+ for (const event of ordered) {
833
+ const extra = eventExtraCells(event);
834
+ const metrics = event.metrics ?? [];
835
+ if (metrics.length === 0) {
836
+ lines.push([...sessionCells(session), ...metricCells(event, { type: "" }, 0), extra].join(","));
837
+ continue;
838
+ }
839
+ for (let i = 0; i < metrics.length; i++) {
840
+ lines.push([...sessionCells(session), ...metricCells(event, metrics[i], i), extra].join(","));
841
+ }
842
+ }
843
+ return lines.join("\n") + "\n";
844
+ }
845
+ function parseCsvLine(line) {
846
+ const cells = [];
847
+ let current = "";
848
+ let quoted = false;
849
+ for (let i = 0; i < line.length; i++) {
850
+ const ch = line[i];
851
+ if (quoted) {
852
+ if (ch === '"') {
853
+ if (line[i + 1] === '"') {
854
+ current += '"';
855
+ i += 1;
856
+ } else {
857
+ quoted = false;
858
+ }
859
+ } else {
860
+ current += ch;
861
+ }
862
+ } else if (ch === '"' && current === "") {
863
+ quoted = true;
864
+ } else if (ch === ",") {
865
+ cells.push(current);
866
+ current = "";
867
+ } else {
868
+ current += ch;
869
+ }
870
+ }
871
+ cells.push(current);
872
+ return cells;
873
+ }
874
+ function splitCsvRecords(csv) {
875
+ const records = [];
876
+ let current = "";
877
+ let quoted = false;
878
+ for (let i = 0; i < csv.length; i++) {
879
+ const ch = csv[i];
880
+ if (ch === '"') {
881
+ if (quoted && csv[i + 1] === '"') {
882
+ current += '""';
883
+ i += 1;
884
+ continue;
885
+ }
886
+ quoted = !quoted;
887
+ current += ch;
888
+ } else if (!quoted && (ch === "\n" || ch === "\r")) {
889
+ if (ch === "\r" && csv[i + 1] === "\n") i += 1;
890
+ if (current.length > 0) records.push(current);
891
+ current = "";
892
+ } else {
893
+ current += ch;
894
+ }
895
+ }
896
+ if (current.length > 0) records.push(current);
897
+ return records;
898
+ }
899
+ var num = (cell) => cell === "" ? void 0 : Number(cell);
900
+ var nonempty = (cell) => cell === "" ? void 0 : cell;
901
+ function jsonCell(cell) {
902
+ if (!cell) return void 0;
903
+ try {
904
+ return JSON.parse(cell);
905
+ } catch {
906
+ return void 0;
907
+ }
908
+ }
909
+ function csvToSessionRows(csv) {
910
+ const lines = splitCsvRecords(csv);
911
+ if (lines.length < 2) throw new Error("[wod import] Session CSV has no data rows");
912
+ const header = parseCsvLine(lines[0]);
913
+ const col = (name) => {
914
+ const idx = header.indexOf(name);
915
+ if (idx < 0) throw new Error(`[wod import] Session CSV missing column "${name}"`);
916
+ return idx;
917
+ };
918
+ const at = (cells, name) => cells[col(name)] ?? "";
919
+ const eventRows = /* @__PURE__ */ new Map();
920
+ let session;
921
+ const ensureSession = (cells) => {
922
+ if (session) return session;
923
+ session = {
924
+ id: at(cells, "sessionId"),
925
+ noteId: at(cells, "noteId"),
926
+ segmentId: at(cells, "segmentId") || void 0,
927
+ segmentVersion: num(at(cells, "segmentVersion")),
928
+ blockId: at(cells, "blockId") || void 0,
929
+ blockContentId: at(cells, "blockContentId") || void 0,
930
+ origin: at(cells, "origin") || void 0,
931
+ status: at(cells, "status") || void 0,
932
+ startTime: Number(at(cells, "startTime") || 0),
933
+ endTime: Number(at(cells, "endTime") || 0),
934
+ duration: Number(at(cells, "duration") || 0),
935
+ completed: at(cells, "completed") === "true",
936
+ createdAt: Number(at(cells, "sessionCreatedAt") || 0)
937
+ };
938
+ return session;
939
+ };
940
+ for (const line of lines.slice(1)) {
941
+ const cells = parseCsvLine(line);
942
+ if (cells.length === 1 && cells[0] === "") continue;
943
+ const currentSession = ensureSession(cells);
944
+ const eventId = at(cells, "eventId");
945
+ if (!eventId) continue;
946
+ let event = eventRows.get(eventId);
947
+ if (!event) {
948
+ const extra = jsonCell(at(cells, "eventExtraJson")) ?? {};
949
+ event = {
950
+ ...extra,
951
+ id: eventId,
952
+ resultId: currentSession.id,
953
+ noteId: nonempty(at(cells, "noteId")) ?? extra.noteId ?? currentSession.noteId,
954
+ blockContentId: nonempty(at(cells, "eventBlockContentId")) ?? extra.blockContentId,
955
+ pageId: nonempty(at(cells, "pageId")) ?? extra.pageId,
956
+ origin: nonempty(at(cells, "eventOrigin")) ?? extra.origin,
957
+ timestamp: Number(at(cells, "timestamp") || extra.timestamp || 0),
958
+ grain: nonempty(at(cells, "grain")) ?? extra.grain ?? "event",
959
+ effortSlug: nonempty(at(cells, "effortSlug")) ?? extra.effortSlug,
960
+ outputType: nonempty(at(cells, "outputType")) ?? extra.outputType ?? "event",
961
+ segmentId: nonempty(at(cells, "eventSegmentId")) ?? extra.segmentId,
962
+ segmentVersion: num(at(cells, "eventSegmentVersion")) ?? extra.segmentVersion,
963
+ timeSpan: jsonCell(at(cells, "timeSpan")) ?? extra.timeSpan,
964
+ sourceBlockKey: nonempty(at(cells, "sourceBlockKey")) ?? extra.sourceBlockKey,
965
+ sourceStatementId: num(at(cells, "sourceStatementId")) ?? extra.sourceStatementId,
966
+ line: num(at(cells, "line")) ?? extra.line,
967
+ text: nonempty(at(cells, "text")) ?? extra.text,
968
+ dialect: nonempty(at(cells, "dialect")) ?? extra.dialect,
969
+ stackLevel: num(at(cells, "stackLevel")) ?? extra.stackLevel,
970
+ completionReason: nonempty(at(cells, "completionReason")) ?? extra.completionReason,
971
+ representationKind: nonempty(at(cells, "representationKind")) ?? extra.representationKind,
972
+ metrics: [],
973
+ // Rebuilt per-row from the metricTemporal column below — never
974
+ // inherited from the backstop, or rows would double-collect.
975
+ metricTemporal: void 0
976
+ };
977
+ eventRows.set(eventId, event);
978
+ }
979
+ const metricType = at(cells, "metricType");
980
+ if (!metricType) continue;
981
+ const baseMetric = jsonCell(at(cells, "metricJson")) ?? {};
982
+ const metric = {
983
+ ...baseMetric,
984
+ type: metricType,
985
+ ...at(cells, "metricValue") !== "" ? { value: metricValueFromCell(at(cells, "metricValue")) } : {},
986
+ ...nonempty(at(cells, "metricUnit")) ? { unit: at(cells, "metricUnit") } : {},
987
+ ...nonempty(at(cells, "metricImage")) ? { image: at(cells, "metricImage") } : {},
988
+ ...nonempty(at(cells, "metricOrigin")) ? { origin: at(cells, "metricOrigin") } : {},
989
+ ...at(cells, "metricMetadata") !== "" ? { metadata: jsonCell(at(cells, "metricMetadata")) } : {}
990
+ };
991
+ event.metrics.push(metric);
992
+ const temporal = jsonCell(at(cells, "metricTemporal"));
993
+ if (temporal !== void 0) {
994
+ (event.metricTemporal ??= []).push(temporal);
995
+ }
996
+ }
997
+ if (!session) throw new Error("[wod import] Session CSV did not contain session columns");
998
+ return { session, events: [...eventRows.values()] };
999
+ }
1000
+
1001
+ // src/cli/formatters.ts
1002
+ function escapeCsvField2(field) {
1003
+ if (field === void 0 || field === null) return "";
1004
+ const str = String(field);
1005
+ if (str.includes(",") || str.includes('"') || str.includes("\n") || str.includes("\r")) {
1006
+ return `"${str.replace(/"/g, '""')}"`;
1007
+ }
1008
+ return str;
1009
+ }
1010
+ function formatDate(ts) {
1011
+ if (!ts) return "-";
1012
+ try {
1013
+ return new Date(ts).toISOString();
1014
+ } catch {
1015
+ return String(ts);
1016
+ }
1017
+ }
1018
+ function formatMetricValue(m) {
1019
+ if (m.image) return m.image;
1020
+ if (m.value === void 0 || m.value === null) return "";
1021
+ if (typeof m.value === "object") return JSON.stringify(m.value);
1022
+ return String(m.value);
1023
+ }
1024
+ function formatMetric(m) {
1025
+ const t = m.type || "metric";
1026
+ return `${t}:${formatMetricValue(m)}`;
1027
+ }
1028
+ function formatParseOutput(ir, format, pretty = true) {
1029
+ if (format === "json") {
1030
+ return JSON.stringify(ir, null, pretty ? 2 : void 0);
1031
+ }
1032
+ const flatNodes = [];
1033
+ const walk = (node) => {
1034
+ flatNodes.push(node);
1035
+ for (const child of node.children ?? []) {
1036
+ walk(child);
1037
+ }
1038
+ };
1039
+ walk(ir.data);
1040
+ if (format === "csv") {
1041
+ const header = "id,type,from,to,raw,metrics,hints";
1042
+ const rows = flatNodes.map(
1043
+ (n) => [
1044
+ n.id,
1045
+ n.type,
1046
+ n.from,
1047
+ n.to,
1048
+ escapeCsvField2(n.raw),
1049
+ escapeCsvField2(n.metrics.map(formatMetric).join("; ")),
1050
+ escapeCsvField2(n.hints?.join("; ") ?? "")
1051
+ ].join(",")
1052
+ );
1053
+ return [header, ...rows].join("\n");
1054
+ }
1055
+ const lines = [];
1056
+ lines.push(`Parse Tree (${flatNodes.length} statements):`);
1057
+ lines.push("\u2500".repeat(80));
1058
+ lines.push(
1059
+ ["ID".padEnd(6), "Type".padEnd(12), "Span".padEnd(12), "Hints".padEnd(20), "Raw Text"].join(" | ")
1060
+ );
1061
+ lines.push("\u2500".repeat(80));
1062
+ for (const n of flatNodes) {
1063
+ const span = `${n.from}-${n.to}`;
1064
+ const hints = (n.hints ?? []).join(", ");
1065
+ lines.push(
1066
+ [
1067
+ String(n.id).padEnd(6),
1068
+ n.type.padEnd(12),
1069
+ span.padEnd(12),
1070
+ (hints.length > 18 ? hints.slice(0, 15) + "..." : hints).padEnd(20),
1071
+ n.raw.replace(/\n/g, "\\n")
1072
+ ].join(" | ")
1073
+ );
1074
+ }
1075
+ return lines.join("\n");
1076
+ }
1077
+ function formatExecutionOutput(ir, format, pretty = true) {
1078
+ if (format === "json") {
1079
+ return JSON.stringify(ir, null, pretty ? 2 : void 0);
1080
+ }
1081
+ const { results, logs } = ir.data;
1082
+ const statements = logs;
1083
+ if (format === "csv") {
1084
+ const header = "id,outputType,started,ended,sourceBlockKey,stackLevel,completionReason,metrics,hints";
1085
+ const rows = statements.map(
1086
+ (s) => [
1087
+ s.id,
1088
+ s.outputType,
1089
+ s.timeSpan?.started ?? s.timestamp ?? 0,
1090
+ s.timeSpan?.ended ?? "",
1091
+ escapeCsvField2(s.sourceBlockKey),
1092
+ s.stackLevel,
1093
+ escapeCsvField2(s.completionReason ?? ""),
1094
+ escapeCsvField2(s.metrics.map(formatMetric).join("; ")),
1095
+ escapeCsvField2(s.hints?.join("; ") ?? "")
1096
+ ].join(",")
1097
+ );
1098
+ return [header, ...rows].join("\n");
1099
+ }
1100
+ const lines = [];
1101
+ lines.push(`Workout Execution Results:`);
1102
+ lines.push(` Duration: ${results.duration}ms`);
1103
+ lines.push(` Completed: ${results.completed ? "yes" : "no"}`);
1104
+ lines.push(` Start: ${formatDate(results.startTime)}`);
1105
+ lines.push(` End: ${formatDate(results.endTime)}`);
1106
+ lines.push(` Logs: ${statements.length} statements emitted`);
1107
+ lines.push("\u2500".repeat(80));
1108
+ lines.push(
1109
+ ["ID".padEnd(8), "Type".padEnd(12), "Block".padEnd(18), "Reason".padEnd(15), "Metrics"].join(" | ")
1110
+ );
1111
+ lines.push("\u2500".repeat(80));
1112
+ for (const s of statements) {
1113
+ const metricsStr = s.metrics.map(formatMetric).join(", ");
1114
+ lines.push(
1115
+ [
1116
+ String(s.id).padEnd(8),
1117
+ (s.outputType ?? "segment").padEnd(12),
1118
+ (s.sourceBlockKey ?? "-").padEnd(18),
1119
+ (s.completionReason ?? "-").padEnd(15),
1120
+ metricsStr
1121
+ ].join(" | ")
1122
+ );
1123
+ }
1124
+ return lines.join("\n");
1125
+ }
1126
+ function formatQueryOutput(ir, format, pretty = true) {
1127
+ if (format === "json") {
1128
+ return JSON.stringify(ir, null, pretty ? 2 : void 0);
1129
+ }
1130
+ const kind = ir.kind;
1131
+ if (kind === "rows-result") {
1132
+ const data2 = ir.data;
1133
+ if (format === "csv") {
1134
+ const header = "resultId,timestamp,outputType,eventId,metrics";
1135
+ const rows = [];
1136
+ for (const run of data2.runs) {
1137
+ for (const event of run.events) {
1138
+ rows.push(
1139
+ [
1140
+ run.resultId,
1141
+ run.timestamp,
1142
+ event.outputType,
1143
+ event.id,
1144
+ escapeCsvField2(event.metrics.map(formatMetric).join("; "))
1145
+ ].join(",")
1146
+ );
1147
+ }
1148
+ }
1149
+ return [header, ...rows].join("\n");
1150
+ }
1151
+ const lines2 = [];
1152
+ lines2.push(`Rows Query Result (${data2.runs.length} runs):`);
1153
+ if (data2.error) lines2.push(`Error: ${data2.error}`);
1154
+ lines2.push("\u2500".repeat(80));
1155
+ lines2.push(
1156
+ ["Result ID".padEnd(20), "Date".padEnd(24), "Events"].join(" | ")
1157
+ );
1158
+ lines2.push("\u2500".repeat(80));
1159
+ for (const run of data2.runs) {
1160
+ lines2.push(
1161
+ [
1162
+ run.resultId.padEnd(20),
1163
+ formatDate(run.timestamp).padEnd(24),
1164
+ String(run.events.length)
1165
+ ].join(" | ")
1166
+ );
1167
+ }
1168
+ return lines2.join("\n");
1169
+ }
1170
+ if (kind === "find-result") {
1171
+ const data2 = ir.data;
1172
+ if (format === "csv") {
1173
+ const header = "target,id,title_or_label,sourceId";
1174
+ const rows = [];
1175
+ for (const n of data2.notes) {
1176
+ rows.push(["note", n.id, escapeCsvField2(n.title), escapeCsvField2(n.sourceId ?? "")].join(","));
1177
+ }
1178
+ for (const b of data2.blocks) {
1179
+ rows.push(["block", b.id, escapeCsvField2(b.noteTitle), escapeCsvField2(b.sourceId ?? "")].join(","));
1180
+ }
1181
+ for (const e of data2.efforts ?? []) {
1182
+ rows.push(["effort", e.id, escapeCsvField2(e.label), escapeCsvField2(e.slug)].join(","));
1183
+ }
1184
+ return [header, ...rows].join("\n");
1185
+ }
1186
+ const lines2 = [];
1187
+ const matchedCount2 = data2.stages?.matched ?? 0;
1188
+ const selectedCount = data2.stages?.selected ?? 0;
1189
+ lines2.push(`Find Result (matched: ${matchedCount2} / selected: ${selectedCount}):`);
1190
+ lines2.push("\u2500".repeat(80));
1191
+ lines2.push(["Type".padEnd(8), "ID".padEnd(24), "Title / Label".padEnd(30), "Source ID"].join(" | "));
1192
+ lines2.push("\u2500".repeat(80));
1193
+ for (const n of data2.notes) {
1194
+ lines2.push(["note".padEnd(8), n.id.padEnd(24), n.title.padEnd(30), n.sourceId ?? "-"].join(" | "));
1195
+ }
1196
+ for (const b of data2.blocks) {
1197
+ lines2.push(["block".padEnd(8), b.id.padEnd(24), b.noteTitle.padEnd(30), b.sourceId ?? "-"].join(" | "));
1198
+ }
1199
+ for (const e of data2.efforts ?? []) {
1200
+ lines2.push(["effort".padEnd(8), e.id.padEnd(24), e.label.padEnd(30), e.slug].join(" | "));
1201
+ }
1202
+ return lines2.join("\n");
1203
+ }
1204
+ if (kind === "pipeline-result") {
1205
+ const data2 = ir.data;
1206
+ const sink = data2.chart ? `:${data2.chart.head}` : "data";
1207
+ if (format === "csv") {
1208
+ const header = "series,timestamp,date,value,unit";
1209
+ const rows = [];
1210
+ for (const s of data2.series ?? []) {
1211
+ for (const pt of s.points) {
1212
+ rows.push(
1213
+ [
1214
+ escapeCsvField2(s.label || s.key),
1215
+ pt.ts,
1216
+ formatDate(pt.ts),
1217
+ pt.value,
1218
+ escapeCsvField2(s.unit ?? data2.unit ?? "")
1219
+ ].join(",")
1220
+ );
1221
+ }
1222
+ }
1223
+ return [header, ...rows].join("\n");
1224
+ }
1225
+ const lines2 = [];
1226
+ if (data2.error) lines2.push(`Pipeline Error: ${data2.error}`);
1227
+ lines2.push(`Pipeline Result (\u2192 ${sink}):`);
1228
+ lines2.push("\u2500".repeat(80));
1229
+ if (data2.table) {
1230
+ lines2.push(`Table: ${data2.table.rows.length} of ${data2.table.totalCount} rows`);
1231
+ }
1232
+ for (const s of data2.series ?? []) {
1233
+ lines2.push(`${s.label || s.key}: ${s.points.length} points${s.unit ? ` (${s.unit})` : ""}`);
1234
+ }
1235
+ if (data2.notes?.length) lines2.push(`notes: ${data2.notes.length}`);
1236
+ if (data2.blocks?.length) lines2.push(`blocks: ${data2.blocks.length}`);
1237
+ if (data2.efforts?.length) lines2.push(`efforts: ${data2.efforts.length}`);
1238
+ if (data2.runs?.length) lines2.push(`runs: ${data2.runs.length}`);
1239
+ return lines2.join("\n");
1240
+ }
1241
+ const data = ir.data;
1242
+ if (data.scalar !== void 0 && data.series.length === 0) {
1243
+ if (format === "csv") {
1244
+ return `metric,scalar,unit
1245
+ ${escapeCsvField2(data.parsed.metric)},${data.scalar},${escapeCsvField2(data.unit ?? "")}`;
1246
+ }
1247
+ return `Scalar Result: ${data.scalar}${data.unit ? " " + data.unit : ""}`;
1248
+ }
1249
+ if (format === "csv") {
1250
+ const header = "series,timestamp,date,value,unit";
1251
+ const rows = [];
1252
+ for (const s of data.series) {
1253
+ for (const pt of s.points) {
1254
+ rows.push(
1255
+ [
1256
+ escapeCsvField2(s.label || s.key),
1257
+ pt.ts,
1258
+ formatDate(pt.ts),
1259
+ pt.value,
1260
+ escapeCsvField2(s.unit ?? data.unit ?? "")
1261
+ ].join(",")
1262
+ );
1263
+ }
1264
+ }
1265
+ return [header, ...rows].join("\n");
1266
+ }
1267
+ const lines = [];
1268
+ const matchedCount = data.stages?.matched ?? data.stages?.selected ?? data.matched?.length ?? 0;
1269
+ lines.push(`Query Result (${data.series.length} series, ${matchedCount} matched facts):`);
1270
+ lines.push(
1271
+ ["Series / Group".padEnd(20), "Timestamp".padEnd(16), "Date".padEnd(24), "Value".padEnd(12), "Unit"].join(" | ")
1272
+ );
1273
+ lines.push("\u2500".repeat(80));
1274
+ for (const s of data.series) {
1275
+ for (const pt of s.points) {
1276
+ lines.push(
1277
+ [
1278
+ (s.label || s.key).padEnd(20),
1279
+ String(pt.ts).padEnd(16),
1280
+ formatDate(pt.ts).padEnd(24),
1281
+ String(pt.value).padEnd(12),
1282
+ s.unit ?? data.unit ?? "-"
1283
+ ].join(" | ")
1284
+ );
1285
+ }
1286
+ }
1287
+ return lines.join("\n");
1288
+ }
1289
+ var nodePath = () => nodeRequire()("node:path");
1290
+ var nodeFs = () => nodeRequire()("node:fs");
1291
+ var nodeCrypto = () => nodeRequire()("node:crypto");
1292
+ function nodeRequire() {
1293
+ const g = globalThis;
1294
+ if (typeof g.process?.versions?.node === "undefined" && typeof g.Bun === "undefined") {
1295
+ throw new Error("SQLite operations are only supported in Node / Bun environments");
1296
+ }
1297
+ const req = g.require ?? (typeof __require === "function" ? __require : void 0);
1298
+ if (!req) throw new Error("SQLite operations require a CJS require shim (missing tsup banner?)");
1299
+ return req;
1300
+ }
1301
+ var sqlModulePromise;
1302
+ function loadSql() {
1303
+ sqlModulePromise ??= import(
1304
+ /* @vite-ignore */
1305
+ '@bitcobblers/wod-wiki-storage/sql'
1306
+ );
1307
+ return sqlModulePromise;
1308
+ }
1309
+ function resolveDbPath(dbPath) {
1310
+ const chosen = dbPath ?? process.env.WOD_WIKI_DB ?? "./wodwiki.db";
1311
+ return nodePath().resolve(process.cwd(), chosen);
1312
+ }
1313
+ async function openCliDb(dbPath) {
1314
+ const [{ createClient }, sql] = await Promise.all([
1315
+ import(
1316
+ /* @vite-ignore */
1317
+ '@libsql/client'
1318
+ ),
1319
+ loadSql()
1320
+ ]);
1321
+ const resolved = resolveDbPath(dbPath);
1322
+ nodeFs().mkdirSync(nodePath().dirname(resolved), { recursive: true });
1323
+ const db = sql.libsqlDatabase(createClient({ url: `file:${resolved}` }));
1324
+ await sql.initializeStore(db);
1325
+ await sql.initializeDomain(db);
1326
+ return { db, path: resolved, close: () => db.close() };
1327
+ }
1328
+ function formatV5Digest(digest) {
1329
+ const bytes = digest.slice(0, 16);
1330
+ bytes[6] = bytes[6] & 15 | 80;
1331
+ bytes[8] = bytes[8] & 63 | 128;
1332
+ const hex = [...bytes].map((b) => b.toString(16).padStart(2, "0"));
1333
+ 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("")}`;
1334
+ }
1335
+ function uuid5(namespace, name) {
1336
+ return formatV5Digest(nodeCrypto().createHash("sha1").update(namespace).update(name).digest());
1337
+ }
1338
+ var CLI_UUID_NAMESPACE = "c0ffee00-8b1a-4e2c-9d3f-5a6b7c8d9e0f";
1339
+ function stableNoteId(sourceLabel, content) {
1340
+ if (sourceLabel && sourceLabel !== "-") {
1341
+ return uuid5(CLI_UUID_NAMESPACE, `wod-cli:${nodePath().resolve(process.cwd(), sourceLabel)}`);
1342
+ }
1343
+ return uuid5(CLI_UUID_NAMESPACE, `wod-cli:stdin:${content.length}:${content.slice(0, 256)}`);
1344
+ }
1345
+ function randomId() {
1346
+ return nodeCrypto().randomUUID();
1347
+ }
1348
+ async function allRows(sql, db, store) {
1349
+ return sql.listRows(db, sql.storeDefinition(store));
1350
+ }
1351
+ async function rowsByIndex(sql, db, store, index, only) {
1352
+ return sql.listRows(db, sql.storeDefinition(store), { only }, void 0, index);
1353
+ }
1354
+ function sqliteEventStore(db) {
1355
+ const all = async () => await allRows(await loadSql(), db, "events");
1356
+ return {
1357
+ getEventsByTimeRange: async (start, end) => (await all()).filter((r) => r.timestamp >= start && r.timestamp <= end),
1358
+ getEventsByResult: async (resultId) => (await all()).filter((r) => r.resultId === resultId),
1359
+ getEventsForNote: async (noteId) => (await all()).filter((r) => r.noteId === noteId),
1360
+ getEventsByContent: async (blockContentId) => (await all()).filter((r) => r.blockContentId === blockContentId),
1361
+ scanAll: all,
1362
+ appendEvents: async (appended) => {
1363
+ const sql = await loadSql();
1364
+ await db.transaction(async (tx) => {
1365
+ const eventsDef = sql.storeDefinition("events");
1366
+ for (const row of appended) await sql.putRow(tx, eventsDef, row);
1367
+ });
1368
+ },
1369
+ finalizeSummaries: async (resultId, finals) => {
1370
+ const sql = await loadSql();
1371
+ await db.transaction(async (tx) => {
1372
+ const eventsDef = sql.storeDefinition("events");
1373
+ const existing = (await sql.listRows(tx, eventsDef)).filter(
1374
+ (r) => r.resultId === resultId && r.grain === "summary" && r.origin !== "user"
1375
+ );
1376
+ for (const row of existing) await sql.deleteRows(tx, eventsDef, row.id);
1377
+ for (const row of finals) await sql.putRow(tx, eventsDef, row);
1378
+ });
1379
+ },
1380
+ deleteEvents: async (ids) => {
1381
+ const sql = await loadSql();
1382
+ await db.transaction(async (tx) => {
1383
+ const eventsDef = sql.storeDefinition("events");
1384
+ for (const id of ids) await sql.deleteRows(tx, eventsDef, id);
1385
+ });
1386
+ }
1387
+ };
1388
+ }
1389
+ function sqliteNoteStore(db) {
1390
+ return {
1391
+ getAllNotes: async () => await allRows(await loadSql(), db, "notes"),
1392
+ getNoteIdsForTag: async (label) => {
1393
+ const sql = await loadSql();
1394
+ const tags = (await allRows(sql, db, "tags")).filter((t) => t.label === label);
1395
+ const ids = {};
1396
+ for (const tag of tags) {
1397
+ for (const row of await rowsByIndex(sql, db, "note_tags", "by-tag", tag.id)) {
1398
+ ids[row.noteId] = true;
1399
+ }
1400
+ }
1401
+ return new Set(Object.keys(ids));
1402
+ },
1403
+ getNoteTagLabels: async (noteId) => {
1404
+ const sql = await loadSql();
1405
+ const tagRows = await allRows(sql, db, "tags");
1406
+ const labelById = {};
1407
+ for (const t of tagRows) labelById[t.id] = t.label;
1408
+ const links = await rowsByIndex(sql, db, "note_tags", "by-note", noteId);
1409
+ return links.map((l) => labelById[l.tagId]).filter((l) => typeof l === "string");
1410
+ },
1411
+ queryDomain: async (request) => {
1412
+ const result = await (await loadSql()).handleDomainQuery(db, request);
1413
+ return result;
1414
+ }
1415
+ };
1416
+ }
1417
+ function sqliteBlockStore(db) {
1418
+ return {
1419
+ getAllBlocks: async () => await allRows(await loadSql(), db, "block_index"),
1420
+ queryDomain: async (request) => {
1421
+ const result = await (await loadSql()).handleDomainQuery(db, { ...request, plan: "blocks" });
1422
+ return result;
1423
+ }
1424
+ };
1425
+ }
1426
+ function sqliteEffortStore(db) {
1427
+ return {
1428
+ getAllEfforts: async () => {
1429
+ const stored = await allRows(await loadSql(), db, "efforts");
1430
+ const bySlug = {};
1431
+ for (const effort of stored) bySlug[effort.slug] = effort;
1432
+ for (const effort of bundledEfforts) {
1433
+ bySlug[effort.slug] ??= effort;
1434
+ }
1435
+ return Object.values(bySlug);
1436
+ }
1437
+ };
1438
+ }
1439
+ function sqliteIndexStore(db, store) {
1440
+ return {
1441
+ getAllFromIndex: async (index, key) => rowsByIndex(await loadSql(), db, store, index, key)
1442
+ };
1443
+ }
1444
+ async function getNoteBundle(db, noteId) {
1445
+ const sql = await loadSql();
1446
+ const notes = await allRows(sql, db, "notes");
1447
+ const note = notes.find((n) => n.id === noteId);
1448
+ const segments = await rowsByIndex(sql, db, "segments", "by-note", noteId);
1449
+ return { note, segments };
1450
+ }
1451
+ async function getSessionBundle(db, resultId) {
1452
+ const sql = await loadSql();
1453
+ const sessions = await allRows(sql, db, "sessions");
1454
+ const session = sessions.find((s) => s.id === resultId);
1455
+ if (!session) return void 0;
1456
+ const events = (await allRows(sql, db, "events")).filter((e) => e.resultId === resultId);
1457
+ return { session, events };
1458
+ }
1459
+ async function getEffortBySlug(db, slug) {
1460
+ const sql = await loadSql();
1461
+ const efforts = await allRows(sql, db, "efforts");
1462
+ return efforts.find((e) => e.slug === slug);
1463
+ }
1464
+ async function listTagsByType(db, type) {
1465
+ const sql = await loadSql();
1466
+ const tags = await allRows(sql, db, "tags");
1467
+ return tags.filter((t) => t.type === type);
1468
+ }
1469
+ function toSegmentDataType(section) {
1470
+ switch (section.type) {
1471
+ case "time":
1472
+ case "log":
1473
+ return "wod";
1474
+ case "title": {
1475
+ const level = Math.min(6, Math.max(1, section.level ?? 1));
1476
+ return `h${level}`;
1477
+ }
1478
+ case "frontmatter":
1479
+ return "frontmatter";
1480
+ case "markdown":
1481
+ default:
1482
+ return "markdown";
1483
+ }
1484
+ }
1485
+ function sectionSourceFragments(content, sections) {
1486
+ const starts = [0];
1487
+ for (let i = 0; i < content.length; i++) {
1488
+ if (content[i] === "\n") starts.push(i + 1);
1489
+ }
1490
+ return sections.map((section, index) => content.slice(
1491
+ starts[section.startLine],
1492
+ index + 1 < sections.length ? starts[sections[index + 1].startLine] : content.length
1493
+ ));
1494
+ }
1495
+ function scriptBlockContentId(data) {
1496
+ if (data && typeof data === "object" && "contentId" in data && typeof data.contentId === "string") {
1497
+ return data.contentId;
1498
+ }
1499
+ return void 0;
1500
+ }
1501
+ function extractEffortSlugs(statements) {
1502
+ const slugs = /* @__PURE__ */ new Set();
1503
+ for (const statement of statements) {
1504
+ if (statement.exerciseId) {
1505
+ slugs.add(statement.exerciseId);
1506
+ } else {
1507
+ const effortMetric = statement.metrics?.getMetric?.("effort");
1508
+ const effort = effortMetric && "effort" in effortMetric ? effortMetric.effort : void 0;
1509
+ if (typeof effort === "string") {
1510
+ const slug = effort.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
1511
+ if (slug) slugs.add(slug);
1512
+ }
1513
+ }
1514
+ }
1515
+ return [...slugs];
1516
+ }
1517
+ async function rowsByNote(sql, db, store, noteId) {
1518
+ return sql.listRows(db, sql.storeDefinition(store), { only: noteId }, void 0, "by-note");
1519
+ }
1520
+ function titleFromLabel(sourceLabel) {
1521
+ if (!sourceLabel || sourceLabel === "-") return "stdin";
1522
+ const stem = sourceLabel.split("/").pop() ?? sourceLabel;
1523
+ return stem.replace(/\.[^.]+$/, "") || sourceLabel;
1524
+ }
1525
+ async function persistNote(db, options) {
1526
+ const sql = await loadSql();
1527
+ const now = options.now ?? Date.now();
1528
+ const noteId = options.noteId ?? stableNoteId(options.sourceLabel, options.source);
1529
+ const title = options.title ?? titleFromLabel(options.sourceLabel);
1530
+ const sections = parseDocumentSections(options.source);
1531
+ const fragments = sectionSourceFragments(options.source, sections);
1532
+ const existingNote = await noteById(sql, db.db, noteId);
1533
+ const priorSegments = await rowsByNote(sql, db.db, "segments", noteId);
1534
+ const versionBySegmentId = {};
1535
+ for (const segment of priorSegments) {
1536
+ versionBySegmentId[segment.id] = Math.max(versionBySegmentId[segment.id] ?? 0, segment.version);
1537
+ }
1538
+ const createdAt = existingNote?.createdAt ?? now;
1539
+ const note = {
1540
+ id: noteId,
1541
+ title,
1542
+ type: existingNote?.type ?? "note",
1543
+ date: existingNote?.date ?? now,
1544
+ createdAt
1545
+ };
1546
+ const ops = [];
1547
+ const segments = sections.map((section, position) => {
1548
+ const id = `${noteId}:${section.id}`;
1549
+ const version = (versionBySegmentId[id] ?? 0) + 1;
1550
+ return {
1551
+ id,
1552
+ version,
1553
+ noteId,
1554
+ position,
1555
+ dataType: toSegmentDataType(section),
1556
+ data: section.scriptBlock || null,
1557
+ rawContent: section.displayContent,
1558
+ sourceContent: fragments[position],
1559
+ createdAt: version === 1 ? createdAt : now,
1560
+ updatedAt: now,
1561
+ isHistory: false
1562
+ };
1563
+ });
1564
+ for (const segment of priorSegments) {
1565
+ if (segment.isHistory !== true) {
1566
+ ops.push({ op: "put", store: "segments", value: { ...segment, isHistory: true, updatedAt: now } });
1567
+ }
1568
+ }
1569
+ ops.push({ op: "put", store: "notes", value: note });
1570
+ for (const segment of segments) ops.push({ op: "put", store: "segments", value: segment });
1571
+ const priorBlocks = await rowsByNote(sql, db.db, "block_index", noteId);
1572
+ const priorBlockEfforts = await rowsByNote(sql, db.db, "block_efforts", noteId);
1573
+ for (const row of priorBlocks) ops.push({ op: "delete", store: "block_index", key: row.id });
1574
+ for (const row of priorBlockEfforts) ops.push({ op: "delete", store: "block_efforts", key: row.id });
1575
+ let blockCount = 0;
1576
+ let effortCount = 0;
1577
+ for (const segment of segments) {
1578
+ const blockContentId = segment.dataType === "wod" ? scriptBlockContentId(segment.data) : void 0;
1579
+ ops.push({
1580
+ op: "put",
1581
+ store: "block_index",
1582
+ value: {
1583
+ id: `${noteId}:${segment.id}:${segment.version}`,
1584
+ noteId,
1585
+ segmentId: segment.id,
1586
+ segmentVersion: segment.version,
1587
+ position: segment.position,
1588
+ dataType: segment.dataType,
1589
+ blockContentId,
1590
+ rawContent: segment.rawContent,
1591
+ noteTitle: title,
1592
+ createdAt: segment.createdAt
1593
+ }
1594
+ });
1595
+ blockCount += 1;
1596
+ if (segment.dataType === "wod" && segment.rawContent) {
1597
+ const script = parseScript(segment.rawContent);
1598
+ for (const effortSlug of extractEffortSlugs(script.statements)) {
1599
+ ops.push({
1600
+ op: "put",
1601
+ store: "block_efforts",
1602
+ value: {
1603
+ id: `${noteId}:${segment.id}:${effortSlug}`,
1604
+ noteId,
1605
+ blockId: segment.id,
1606
+ blockContentId,
1607
+ effortSlug,
1608
+ createdAt: segment.createdAt
1609
+ }
1610
+ });
1611
+ effortCount += 1;
1612
+ }
1613
+ }
1614
+ }
1615
+ let tagCount = 0;
1616
+ if (options.tags) {
1617
+ const priorLinks = await rowsByNote(sql, db.db, "note_tags", noteId);
1618
+ for (const link of priorLinks) ops.push({ op: "delete", store: "note_tags", key: link.id });
1619
+ const tagByLabel = new Map(
1620
+ (await sql.listRows(db.db, sql.storeDefinition("tags"))).map((t) => [t.label, t])
1621
+ );
1622
+ for (const item of options.tags) {
1623
+ const label = item.label.trim();
1624
+ if (!label) continue;
1625
+ let tag = tagByLabel.get(label);
1626
+ if (!tag) {
1627
+ tag = { id: randomId(), label, type: item.type, createdAt: now };
1628
+ tagByLabel.set(label, tag);
1629
+ ops.push({ op: "put", store: "tags", value: tag });
1630
+ } else if (item.type && !tag.type) {
1631
+ tag = { ...tag, type: item.type };
1632
+ tagByLabel.set(label, tag);
1633
+ ops.push({ op: "put", store: "tags", value: tag });
1634
+ }
1635
+ tagCount += 1;
1636
+ ops.push({
1637
+ op: "put",
1638
+ store: "note_tags",
1639
+ // Deterministic junction id (unique [noteId, tagId] index).
1640
+ value: { id: uuid5(CLI_UUID_NAMESPACE, `note-tag:${noteId}:${tag.id}`), noteId, tagId: tag.id }
1641
+ });
1642
+ }
1643
+ }
1644
+ await db.db.transaction(async (tx) => {
1645
+ await sql.refreshDomain(tx, ops);
1646
+ });
1647
+ return {
1648
+ noteId,
1649
+ note,
1650
+ segments: segments.length,
1651
+ blocks: blockCount,
1652
+ efforts: effortCount,
1653
+ tags: tagCount,
1654
+ created: !existingNote
1655
+ };
1656
+ }
1657
+ async function noteById(sql, db, noteId) {
1658
+ const rows = await sql.listRows(db, sql.storeDefinition("notes"));
1659
+ return rows.find((row) => row.id === noteId);
1660
+ }
1661
+ async function persistRun(db, options) {
1662
+ const sql = await loadSql();
1663
+ const results = options.executionLog.results;
1664
+ const logs = options.executionLog.logs ?? [];
1665
+ const noteState = await persistNote(db, {
1666
+ source: options.source,
1667
+ sourceLabel: options.sourceLabel,
1668
+ title: options.title,
1669
+ now: options.now
1670
+ });
1671
+ const segments = (await rowsByNote(sql, db.db, "segments", noteState.noteId)).filter((s) => s.isHistory !== true && s.dataType === "wod").sort((a, b) => (a.position ?? 0) - (b.position ?? 0));
1672
+ const runSegment = segments[0];
1673
+ const blockContentId = runSegment ? scriptBlockContentId(runSegment.data) : void 0;
1674
+ const resultId = randomId();
1675
+ const session = {
1676
+ id: resultId,
1677
+ segmentId: runSegment?.id,
1678
+ segmentVersion: runSegment?.version,
1679
+ noteId: noteState.noteId,
1680
+ blockId: runSegment?.id,
1681
+ blockContentId,
1682
+ origin: "journal",
1683
+ status: results.completed === false ? "in-progress" : "completed",
1684
+ startTime: results.startTime,
1685
+ endTime: results.endTime,
1686
+ duration: results.duration ?? 0,
1687
+ completed: results.completed,
1688
+ createdAt: results.endTime || Date.now()
1689
+ };
1690
+ const identity = {
1691
+ noteId: noteState.noteId,
1692
+ resultId,
1693
+ segmentId: session.segmentId,
1694
+ segmentVersion: session.segmentVersion,
1695
+ blockContentId,
1696
+ origin: session.origin,
1697
+ pageId: session.pageId,
1698
+ workoutTimestamp: session.createdAt
1699
+ };
1700
+ const eventRows = toEventRows(logs, identity);
1701
+ const summaryRows = toSummaryEventRows(logs, identity);
1702
+ await db.db.transaction(async (tx) => {
1703
+ await sql.putRow(tx, sql.storeDefinition("sessions"), session);
1704
+ const eventsDef = sql.storeDefinition("events");
1705
+ for (const row of [...eventRows, ...summaryRows]) await sql.putRow(tx, eventsDef, row);
1706
+ });
1707
+ return {
1708
+ noteId: noteState.noteId,
1709
+ resultId,
1710
+ session,
1711
+ events: eventRows.length,
1712
+ summaries: summaryRows.length,
1713
+ noteCreated: noteState.created
1714
+ };
1715
+ }
1716
+ async function persistSessionBundle(db, session, events) {
1717
+ const sql = await loadSql();
1718
+ const existing = await noteById(sql, db.db, session.noteId);
1719
+ let noteCreated = false;
1720
+ if (!existing) {
1721
+ noteCreated = true;
1722
+ const now = session.createdAt || Date.now();
1723
+ await db.db.transaction(async (tx) => {
1724
+ await sql.putRow(tx, sql.storeDefinition("notes"), {
1725
+ id: session.noteId,
1726
+ title: `imported-${session.noteId.slice(0, 8)}`,
1727
+ createdAt: now,
1728
+ type: "note"
1729
+ });
1730
+ });
1731
+ }
1732
+ await db.db.transaction(async (tx) => {
1733
+ await sql.putRow(tx, sql.storeDefinition("sessions"), session);
1734
+ const eventsDef = sql.storeDefinition("events");
1735
+ for (const row of events) await sql.putRow(tx, eventsDef, row);
1736
+ });
1737
+ return { events: events.length, noteCreated };
1738
+ }
1739
+ async function persistEffort(db, effort) {
1740
+ const sql = await loadSql();
1741
+ await db.db.transaction(async (tx) => {
1742
+ await sql.putRow(tx, sql.storeDefinition("efforts"), effort);
1743
+ });
1744
+ }
1745
+ async function persistTypedTags(db, labels, type) {
1746
+ const sql = await loadSql();
1747
+ const now = Date.now();
1748
+ const tagByLabel = new Map(
1749
+ (await sql.listRows(db.db, sql.storeDefinition("tags"))).map((t) => [t.label, t])
1750
+ );
1751
+ const ops = [];
1752
+ let added = 0;
1753
+ for (const raw of labels) {
1754
+ const label = raw.trim();
1755
+ if (!label) continue;
1756
+ const tag = tagByLabel.get(label);
1757
+ if (!tag) {
1758
+ ops.push({ op: "put", store: "tags", value: { id: randomId(), label, type, createdAt: now } });
1759
+ added += 1;
1760
+ } else if (!tag.type) {
1761
+ ops.push({ op: "put", store: "tags", value: { ...tag, type } });
1762
+ }
1763
+ }
1764
+ if (ops.length > 0) {
1765
+ await db.db.transaction(async (tx) => {
1766
+ await sql.refreshDomain(tx, ops);
1767
+ });
1768
+ }
1769
+ return added;
1770
+ }
1771
+
1772
+ // src/cli/seed.ts
1773
+ var SEED_NAMESPACE = "1b671a64-40d5-491e-99b0-da01ff1f3341";
1774
+ var SEED_SCHEMA = 7;
1775
+ var CANVAS_CHUNK_ID = "canvas";
1776
+ var DASHBOARDS_CHUNK_ID = "dashboards";
1777
+ var EFFORTS_CHUNK_ID = "efforts";
1778
+ var TEMPLATE_CHUNK_ID = "template";
1779
+ var scalarOf2 = (value) => typeof value === "string" ? value : void 0;
1780
+ function seedNoteId(path) {
1781
+ return formatV5Digest(nodeCrypto().createHash("sha1").update(`${SEED_NAMESPACE}:${path}`).digest());
1782
+ }
1783
+ function titleFromPath(path) {
1784
+ const stem = path.split("/").pop() ?? path;
1785
+ return stem.replace(/\.md$/, "").replace(/[-_]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
1786
+ }
1787
+ function catalogForChunkId(chunkId) {
1788
+ const match = /^(?:collection|feed)\.(.+)$/.exec(chunkId);
1789
+ const value = match?.[1];
1790
+ return value && value !== "_root" ? value : void 0;
1791
+ }
1792
+ function canvasRouteSlug(content) {
1793
+ const fm = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content);
1794
+ if (fm?.[1] && !/^template:\s*canvas\s*$/m.test(fm[1])) return null;
1795
+ const route = fm?.[1]?.match(/^route:\s*(\S+)\s*$/m)?.[1];
1796
+ if (!route) return null;
1797
+ return route.replace(/^\//, "") || null;
1798
+ }
1799
+ function seedNoteSourceId(path, content, chunkId) {
1800
+ const parts = path.split("/");
1801
+ const stem = (parts[parts.length - 1] ?? "").replace(/\.md$/i, "");
1802
+ const under = parts.slice(2).map((p) => p.replace(/\.md$/i, "")).join("/");
1803
+ if (chunkId === CANVAS_CHUNK_ID) {
1804
+ const route = canvasRouteSlug(content);
1805
+ return route ? `guides:${route}` : `guides:${under}`;
1806
+ }
1807
+ if (chunkId === DASHBOARDS_CHUNK_ID) return "dashboards";
1808
+ if (chunkId === EFFORTS_CHUNK_ID) return "efforts";
1809
+ if (chunkId === TEMPLATE_CHUNK_ID) return "playground";
1810
+ if (chunkId.startsWith("collection.")) {
1811
+ const catalog = catalogForChunkId(chunkId);
1812
+ if (!catalog) return "collection:_root";
1813
+ return stem.toLowerCase() === "readme" ? `collection:${catalog}` : `collection:${catalog}/${stem}`;
1814
+ }
1815
+ if (chunkId.startsWith("feed.")) return `feed:feeds/${under}`;
1816
+ return void 0;
1817
+ }
1818
+ function buildNoteRecord(row, chunkId, seedVersion) {
1819
+ const id = seedNoteId(row.path);
1820
+ const createdAt = seedVersion;
1821
+ const meta = readFrontmatterMeta(row.content);
1822
+ const tags = extractTypedTags(row.content);
1823
+ let date;
1824
+ let page;
1825
+ let pageNote;
1826
+ const rawDate = scalarOf2(meta["date"]);
1827
+ if (rawDate && /^\d{4}-\d{2}-\d{2}$/.test(rawDate)) {
1828
+ date = (/* @__PURE__ */ new Date(`${rawDate}T12:00:00Z`)).getTime();
1829
+ }
1830
+ const slug = scalarOf2(meta["slug"]);
1831
+ if (slug) {
1832
+ const pageId = seedNoteId(`page:${slug}`);
1833
+ page = { id: pageId, slug, title: titleFromPath(row.path), createdAt };
1834
+ pageNote = { id: seedNoteId(`pagenote:${slug}:${row.path}`), pageId, noteId: id, createdAt };
1835
+ }
1836
+ const sourceId = seedNoteSourceId(row.path, row.content, chunkId);
1837
+ if (!sourceId) {
1838
+ throw new Error(`[wod seed] chunk "${chunkId}" row "${row.path}" has no source family \u2014 refusing to import it as journal`);
1839
+ }
1840
+ const metaTitle = scalarOf2(meta["title"]);
1841
+ return {
1842
+ note: {
1843
+ id,
1844
+ title: metaTitle?.trim() || titleFromPath(row.path),
1845
+ date,
1846
+ createdAt,
1847
+ type: chunkId === DASHBOARDS_CHUNK_ID ? "dashboard" : "note",
1848
+ sourceId,
1849
+ catalog: catalogForChunkId(chunkId),
1850
+ seedOrigin: "seed",
1851
+ seedVersion,
1852
+ seedChunkId: chunkId,
1853
+ sourcePath: row.path
1854
+ },
1855
+ segment: {
1856
+ id: `seed:${id}`,
1857
+ version: 1,
1858
+ noteId: id,
1859
+ position: 0,
1860
+ dataType: "markdown",
1861
+ data: null,
1862
+ rawContent: row.content,
1863
+ createdAt
1864
+ },
1865
+ tags,
1866
+ page,
1867
+ pageNote,
1868
+ rawContent: row.content,
1869
+ sourcePath: row.path
1870
+ };
1871
+ }
1872
+ async function metaRecord(sql, db, sourceId) {
1873
+ const rows = await sql.listRows(db, sql.storeDefinition("meta"));
1874
+ const existing = rows.find((r) => r.key === `seed:${sourceId}`);
1875
+ return existing ?? {
1876
+ key: `seed:${sourceId}`,
1877
+ schema: SEED_SCHEMA,
1878
+ seedVersion: 0,
1879
+ builtAt: "",
1880
+ importedAt: 0,
1881
+ chunks: {},
1882
+ claim: null
1883
+ };
1884
+ }
1885
+ async function applyOps(db, ops) {
1886
+ if (ops.length === 0) return;
1887
+ const sql = await loadSql();
1888
+ await db.db.transaction(async (tx) => {
1889
+ await sql.refreshDomain(tx, ops);
1890
+ });
1891
+ }
1892
+ async function importSeedDir(db, dir, sourceId) {
1893
+ const sql = await loadSql();
1894
+ const fs = nodeFs();
1895
+ const source = sourceId || nodePath().basename(dir);
1896
+ const manifest = JSON.parse(fs.readFileSync(nodePath().resolve(dir, "manifest.json"), "utf-8"));
1897
+ const prior = await metaRecord(sql, db.db, source);
1898
+ const notesById = new Map(
1899
+ (await sql.listRows(db.db, sql.storeDefinition("notes"))).map((row) => [row.id, row])
1900
+ );
1901
+ const tagRowByLabel = new Map(
1902
+ (await sql.listRows(db.db, sql.storeDefinition("tags"))).map((t) => [t.label, t])
1903
+ );
1904
+ const summary = {
1905
+ sourceId: source,
1906
+ version: manifest.version,
1907
+ notes: 0,
1908
+ efforts: 0,
1909
+ blocks: 0,
1910
+ blockEfforts: 0,
1911
+ deleted: 0,
1912
+ skippedUserOwned: 0
1913
+ };
1914
+ const nextMeta = {
1915
+ ...prior,
1916
+ schema: SEED_SCHEMA,
1917
+ seedVersion: manifest.version,
1918
+ builtAt: manifest.builtAt,
1919
+ importedAt: Date.now(),
1920
+ chunks: {}
1921
+ };
1922
+ const ordered = [
1923
+ ...manifest.chunks.filter((c) => (c.kind ?? "notes") === "notes"),
1924
+ ...manifest.chunks.filter((c) => c.kind === "block-index"),
1925
+ ...manifest.chunks.filter((c) => c.kind === "block-efforts")
1926
+ ];
1927
+ const bySourcePath = /* @__PURE__ */ new Map();
1928
+ const noteIdsByChunk = {};
1929
+ const blockIdsByChunk = {};
1930
+ for (const chunk of ordered) {
1931
+ const payload = JSON.parse(fs.readFileSync(nodePath().resolve(dir, chunk.path), "utf-8"));
1932
+ const ops = [];
1933
+ if ((chunk.kind ?? "notes") === "notes") {
1934
+ const rows2 = payload;
1935
+ const built = rows2.map((row) => buildNoteRecord(row, chunk.id, manifest.version));
1936
+ const noteIds = [];
1937
+ for (const record of built) {
1938
+ bySourcePath.set(record.sourcePath, record.note.id);
1939
+ const existing = notesById.get(record.note.id);
1940
+ if (existing && existing.seedOrigin !== "seed") {
1941
+ summary.skippedUserOwned += 1;
1942
+ continue;
1943
+ }
1944
+ noteIds.push(record.note.id);
1945
+ ops.push({ op: "put", store: "notes", value: record.note });
1946
+ ops.push({ op: "put", store: "segments", value: record.segment });
1947
+ if (record.page) ops.push({ op: "put", store: "page", value: record.page });
1948
+ if (record.pageNote) ops.push({ op: "put", store: "page_notes", value: record.pageNote });
1949
+ for (const item of record.tags) {
1950
+ const label = item.label;
1951
+ let tag = tagRowByLabel.get(label);
1952
+ if (!tag) {
1953
+ tag = { id: randomId(), label, type: item.type, createdAt: Date.now() };
1954
+ tagRowByLabel.set(label, tag);
1955
+ ops.push({ op: "put", store: "tags", value: tag });
1956
+ } else if (item.type && !tag.type) {
1957
+ tag = { ...tag, type: item.type };
1958
+ tagRowByLabel.set(label, tag);
1959
+ ops.push({ op: "put", store: "tags", value: tag });
1960
+ }
1961
+ const tagId = tag.id;
1962
+ ops.push({
1963
+ op: "put",
1964
+ store: "note_tags",
1965
+ // Deterministic junction id — re-seeds must upsert the same row
1966
+ // (note_tags has a unique [noteId, tagId] index).
1967
+ value: { id: uuid5(CLI_UUID_NAMESPACE, `note-tag:${record.note.id}:${tagId}`), noteId: record.note.id, tagId }
1968
+ });
1969
+ }
1970
+ if (chunk.id === EFFORTS_CHUNK_ID) {
1971
+ const effort = markdownToEffort(record.rawContent);
1972
+ if (effort) {
1973
+ ops.push({ op: "put", store: "efforts", value: { ...effort, registrySource: "bundled" } });
1974
+ summary.efforts += 1;
1975
+ }
1976
+ }
1977
+ }
1978
+ const priorIds = prior.chunks[chunk.id]?.noteIds ?? [];
1979
+ const goneIds = priorIds.filter((id) => !noteIds.includes(id) && notesById.get(id)?.seedOrigin === "seed");
1980
+ for (const id of goneIds) {
1981
+ ops.push(...await noteCascadeOps(sql, db, id));
1982
+ summary.deleted += 1;
1983
+ }
1984
+ noteIdsByChunk[chunk.id] = rows2.map((row) => seedNoteId(row.path));
1985
+ nextMeta.chunks[chunk.id] = { sha256: chunk.sha256, noteIds: noteIdsByChunk[chunk.id] };
1986
+ summary.notes += noteIds.length;
1987
+ await applyOps(db, ops);
1988
+ continue;
1989
+ }
1990
+ const rows = payload.filter((row) => row.isStatic === true);
1991
+ const rekeyed = rows.map((row) => {
1992
+ const noteId = row.sourcePath ? bySourcePath.get(row.sourcePath) : void 0;
1993
+ if (!noteId) {
1994
+ throw new Error(
1995
+ `[wod seed] static row ${row.id}: sourcePath "${row.sourcePath ?? ""}" has no imported note \u2014 chunk aborted`
1996
+ );
1997
+ }
1998
+ return chunk.kind === "block-efforts" ? { ...row, noteId } : { ...row, noteId, routeId: row.noteId };
1999
+ });
2000
+ const store = chunk.kind === "block-index" ? "block_index" : "block_efforts";
2001
+ for (const row of rekeyed) ops.push({ op: "put", store, value: row });
2002
+ const priorBlockIds = prior.chunks[chunk.id]?.blockIds ?? [];
2003
+ const writtenIds = new Set(rekeyed.map((row) => row.id));
2004
+ const goneBlocks = priorBlockIds.filter((id) => !writtenIds.has(id));
2005
+ for (const id of goneBlocks) ops.push({ op: "delete", store, key: id });
2006
+ summary.deleted += goneBlocks.length;
2007
+ blockIdsByChunk[chunk.id] = payload.map((row) => row.id);
2008
+ nextMeta.chunks[chunk.id] = { sha256: chunk.sha256, blockIds: blockIdsByChunk[chunk.id] };
2009
+ if (chunk.kind === "block-index") summary.blocks += rekeyed.length;
2010
+ else summary.blockEfforts += rekeyed.length;
2011
+ await applyOps(db, ops);
2012
+ }
2013
+ const manifestIds = new Set(manifest.chunks.map((c) => c.id));
2014
+ for (const [chunkId, checkpoint] of Object.entries(prior.chunks)) {
2015
+ if (manifestIds.has(chunkId)) continue;
2016
+ const sweep = [];
2017
+ for (const id of checkpoint.noteIds ?? []) {
2018
+ if (notesById.get(id)?.seedOrigin === "seed") {
2019
+ sweep.push(...await noteCascadeOps(sql, db, id));
2020
+ summary.deleted += 1;
2021
+ }
2022
+ }
2023
+ for (const id of checkpoint.blockIds ?? []) {
2024
+ sweep.push({ op: "delete", store: "block_index", key: id });
2025
+ summary.deleted += 1;
2026
+ }
2027
+ await applyOps(db, sweep);
2028
+ }
2029
+ await applyOps(db, [{ op: "put", store: "meta", value: nextMeta }]);
2030
+ return summary;
2031
+ }
2032
+ async function noteCascadeOps(sql, db, noteId) {
2033
+ const ops = [{ op: "delete", store: "notes", key: noteId }];
2034
+ for (const store of ["segments", "block_index", "block_efforts", "note_tags"]) {
2035
+ const rows = await sql.listRows(db.db, sql.storeDefinition(store), { only: noteId }, void 0, "by-note");
2036
+ for (const row of rows) {
2037
+ ops.push({ op: "delete", store, key: store === "segments" ? [row.id, row.version ?? 1] : row.id });
2038
+ }
2039
+ }
2040
+ return ops;
2041
+ }
2042
+ function defaultSeedDirs(cwd) {
2043
+ const base = nodePath().resolve(cwd, "apps", "playground", "public", "seed");
2044
+ return ["core", "community"].map((id) => nodePath().resolve(base, id)).filter((dir) => nodeFs().existsSync(nodePath().resolve(dir, "manifest.json")));
2045
+ }
2046
+
2047
+ // src/cli/runner.ts
2048
+ function getFs2() {
2049
+ if (typeof globalThis.process?.versions?.node === "undefined" && typeof globalThis.Bun === "undefined") {
2050
+ throw new Error("File system operations are only supported in Node / Bun environments");
2051
+ }
2052
+ return __require("fs");
2053
+ }
2054
+ var VERSION = "0.6.0";
2055
+ function parseCliArgs(args) {
2056
+ const result = {
2057
+ format: "json",
2058
+ packs: [],
2059
+ sources: []
2060
+ };
2061
+ let i = 0;
2062
+ while (i < args.length) {
2063
+ const arg = args[i];
2064
+ if (arg === "--help" || arg === "-h") {
2065
+ result.help = true;
2066
+ i++;
2067
+ } else if (arg === "--version" || arg === "-v") {
2068
+ result.version = true;
2069
+ i++;
2070
+ } else if (arg === "--corpus" || arg === "-c") {
2071
+ result.corpus = args[++i];
2072
+ i++;
2073
+ } else if (arg === "--stdin-log") {
2074
+ result.stdinLog = true;
2075
+ i++;
2076
+ } else if (arg === "--stdin-facts") {
2077
+ result.stdinFacts = true;
2078
+ i++;
2079
+ } else if (arg === "--format") {
2080
+ const fmt = args[++i];
2081
+ if (fmt === "json" || fmt === "table" || fmt === "csv") {
2082
+ result.format = fmt;
2083
+ }
2084
+ i++;
2085
+ } else if (arg.startsWith("--format=")) {
2086
+ const fmt = arg.slice("--format=".length);
2087
+ if (fmt === "json" || fmt === "table" || fmt === "csv") {
2088
+ result.format = fmt;
2089
+ }
2090
+ i++;
2091
+ } else if (arg === "-o" || arg === "--output") {
2092
+ result.output = args[++i];
2093
+ i++;
2094
+ } else if (arg.startsWith("--output=")) {
2095
+ result.output = arg.slice("--output=".length);
2096
+ i++;
2097
+ } else if (arg === "--pack") {
2098
+ const packSpec = args[++i];
2099
+ if (packSpec) result.packs.push(packSpec);
2100
+ i++;
2101
+ } else if (arg.startsWith("--pack=")) {
2102
+ result.packs.push(arg.slice("--pack=".length));
2103
+ i++;
2104
+ } else if (arg === "--sport") {
2105
+ result.sport = args[++i];
2106
+ i++;
2107
+ } else if (arg === "--unit") {
2108
+ result.unit = args[++i];
2109
+ i++;
2110
+ } else if (arg === "--db") {
2111
+ result.db = args[++i];
2112
+ i++;
2113
+ } else if (arg.startsWith("--db=")) {
2114
+ result.db = arg.slice("--db=".length);
2115
+ i++;
2116
+ } else if (arg === "--debug") {
2117
+ result.debug = "";
2118
+ i++;
2119
+ } else if (arg.startsWith("--debug=")) {
2120
+ result.debug = arg.slice("--debug=".length);
2121
+ i++;
2122
+ } else if (arg === "--at") {
2123
+ result.at = args[++i];
2124
+ i++;
2125
+ } else if (arg.startsWith("--at=")) {
2126
+ result.at = arg.slice("--at=".length);
2127
+ i++;
2128
+ } else if (arg === "--source") {
2129
+ const source = args[++i];
2130
+ if (source) result.sources.push(source);
2131
+ i++;
2132
+ } else if (arg.startsWith("--source=")) {
2133
+ result.sources.push(arg.slice("--source=".length));
2134
+ i++;
2135
+ } else if (arg === "--scaffold") {
2136
+ result.scaffold = true;
2137
+ i++;
2138
+ } else if (arg === "--template") {
2139
+ result.template = args[++i];
2140
+ i++;
2141
+ } else if (arg.startsWith("--template=")) {
2142
+ result.template = arg.slice("--template=".length);
2143
+ i++;
2144
+ } else if (arg === "--out") {
2145
+ result.output = args[++i];
2146
+ i++;
2147
+ } else if (arg.startsWith("--out=")) {
2148
+ result.output = arg.slice("--out=".length);
2149
+ i++;
2150
+ } else if (arg === "--dir") {
2151
+ result.dir = args[++i];
2152
+ i++;
2153
+ } else if (arg.startsWith("--dir=")) {
2154
+ result.dir = arg.slice("--dir=".length);
2155
+ i++;
2156
+ } else if (!result.subcommand) {
2157
+ if (arg === "parse" || arg === "run" || arg === "query" || arg === "download" || arg === "import" || arg === "build" || arg === "seed" || arg === "reset" || arg === "help" || arg === "version") {
2158
+ result.subcommand = arg;
2159
+ } else {
2160
+ result.target = arg;
2161
+ }
2162
+ i++;
2163
+ } else if (!result.target) {
2164
+ result.target = arg;
2165
+ i++;
2166
+ } else if (!result.target2) {
2167
+ result.target2 = arg;
2168
+ i++;
2169
+ } else {
2170
+ i++;
2171
+ }
2172
+ }
2173
+ return result;
2174
+ }
2175
+ function readBunStdinText() {
2176
+ const g = globalThis;
2177
+ if (g && typeof g === "object" && "Bun" in g) {
2178
+ const bun = g.Bun;
2179
+ if (bun && typeof bun === "object" && "stdin" in bun) {
2180
+ const stdin = bun.stdin;
2181
+ if (stdin && typeof stdin === "object" && "text" in stdin) {
2182
+ const text = stdin.text;
2183
+ if (typeof text === "function") return text;
2184
+ }
2185
+ }
2186
+ }
2187
+ return void 0;
2188
+ }
2189
+ async function readStdin() {
2190
+ const bunText = readBunStdinText();
2191
+ if (bunText) return bunText();
2192
+ return new Promise((resolve, reject) => {
2193
+ const chunks = [];
2194
+ process.stdin.on("data", (chunk) => {
2195
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
2196
+ });
2197
+ process.stdin.on("end", () => {
2198
+ resolve(Buffer.concat(chunks).toString("utf-8"));
2199
+ });
2200
+ process.stdin.on("error", (err) => {
2201
+ reject(err);
2202
+ });
2203
+ });
2204
+ }
2205
+ function getUsageText() {
2206
+ return `
2207
+ wod CLI \u2014 Headless Whiteboard Language & WQL Engine runner (v${VERSION})
2208
+
2209
+ USAGE:
2210
+ wod parse <file | -> [options]
2211
+ wod run <file | -> [options]
2212
+ wod query '<wql-string>' [options]
2213
+ wod download <note|session|effort|equipment> <id> [options]
2214
+ wod import <note|session|effort|equipment> <file> [options]
2215
+ wod build [dir] [options]
2216
+ wod seed [options]
2217
+ wod reset [options]
2218
+
2219
+ SUBCOMMANDS:
2220
+ parse <file | -> Parse Whiteboard Script, create a note in the database
2221
+ (segments + block index), optionally emit parse-tree IR.
2222
+ Markdown carrying a 'wod-note-id' frontmatter stamp
2223
+ (see download) imports back into that same note.
2224
+ run <file | -> Execute the script state machine, record a session +
2225
+ event/summary rows in the database, optionally emit
2226
+ execution-log IR
2227
+ query '<wql>' Evaluate WQL against the database (or an explicit
2228
+ dataset) and emit QueryResult / RowsQueryResult IR
2229
+ download note <id> Compose the note's markdown from its segment rows and
2230
+ write it to <id>.md. Use --at <version> to compose
2231
+ as-of a segment version (default: latest). The stamp
2232
+ in the frontmatter makes 'wod parse' re-import it.
2233
+ Frontmatter tags (general + typed equipment/quality/\u2026)
2234
+ are materialized into the tag graph on parse/import.
2235
+ download session <id>
2236
+ Write the session's generated metrics (event + summary
2237
+ rows, one CSV row per metric) to <id>.csv. Import back
2238
+ with 'wod import session'.
2239
+ download effort <slug>
2240
+ Write the effort registry row as corpus-style markdown
2241
+ (frontmatter id/slug/label/aliases/baseAttributes + body)
2242
+ to <slug>.md. Import back with 'wod import effort'.
2243
+ download equipment
2244
+ Write every equipment tag as a one-column CSV to
2245
+ equipment.csv. Import back with 'wod import equipment'.
2246
+ import note <file> Re-import exported note markdown (same as parse with
2247
+ the stamped identity).
2248
+ import session <file>
2249
+ Recreate a session + its events from a downloaded CSV
2250
+ into the same or a clean database (id-keyed upsert).
2251
+ import effort <file>
2252
+ Upsert an effort registry row from corpus-style markdown.
2253
+ import equipment <file>
2254
+ Create/upgrade equipment tags (CSV, one label per row).
2255
+ build [dir] Compile a seed corpus: validate every .md under
2256
+ <dir>/markdown (parity gate + dashboard format gate) and
2257
+ write manifest.json + content-hashed chunks/ +
2258
+ block-parent-map.json + report.html to <dir>/dist
2259
+ (override with -o/--output). Version stamps derive from
2260
+ <dir>'s git history (full checkout required).
2261
+
2262
+ BUILD OPTIONS (for 'build'):
2263
+ --scaffold Also scaffold markdown/efforts/discovered/*.md stubs for
2264
+ workout effort labels missing from the registry.
2265
+ --template <file> Compile an extra markdown file as the 'template' chunk
2266
+ (the host's playground intake template).
2267
+
2268
+ INPUT SOURCING (for 'query'; without these, the database is queried):
2269
+ -c, --corpus <path> Load historical corpus dataset from a versioned JSON file
2270
+ --stdin-log Read kind: 'execution-log' JSON payload from stdin
2271
+ --stdin-facts Read raw kind: 'fact-set' JSON payload from stdin
2272
+
2273
+ DATABASE (parse / run / query / download / import / seed / reset):
2274
+ --db <path> SQLite database file (default: $WOD_WIKI_DB or
2275
+ ./wodwiki.db). Created on demand by parse/run/seed.
2276
+
2277
+ SEED SOURCING (for 'seed'):
2278
+ --source <dir> Extra compiled seed directory (manifest.json + chunks);
2279
+ repeatable. Defaults to the bundled core + community
2280
+ artifacts under apps/playground/public/seed.
2281
+
2282
+ OPTIONS:
2283
+ --format <fmt> Output format: json (default), table, csv
2284
+ -o, --output <path> Write the IR payload (or download) to a path instead of
2285
+ the default; '-' writes to stdout
2286
+ --at <version> With 'download note': compose the markdown as-of the
2287
+ given segment version (default: latest)
2288
+ --debug[=name] Also write the IR payload to <name> (default
2289
+ wod-<subcommand>-debug.json); without it, parse/run
2290
+ print a one-line summary of what was persisted
2291
+ --pack <module> Dynamically load and register a Language Pack (repeatable)
2292
+ --sport <sport> Target sport fence dialect (e.g. climbing, crossfit)
2293
+ --unit <unit> Preferred display unit for queries (e.g. lb, kg)
2294
+ -h, --help Show help message
2295
+ -v, --version Show version number
2296
+
2297
+ EXIT CODES:
2298
+ 0 - OK / Success
2299
+ 1 - General / Runtime error
2300
+ 2 - Parse / Syntax error
2301
+ 3 - Language Pack load error
2302
+ `;
2303
+ }
2304
+ function printUsage(writer) {
2305
+ const text = getUsageText();
2306
+ if (writer) {
2307
+ writer(text);
2308
+ } else {
2309
+ process.stdout.write(text.endsWith("\n") ? text : text + "\n");
2310
+ }
2311
+ }
2312
+ async function cliMain(argv = process.argv.slice(2), io = {}) {
2313
+ const writeOut = io.stdout ?? ((t) => process.stdout.write(t + "\n"));
2314
+ const writeErr = io.stderr ?? ((t) => process.stderr.write(t + "\n"));
2315
+ const getStdin = io.readStdinFn ?? readStdin;
2316
+ const parsed = parseCliArgs(argv);
2317
+ if (parsed.version || parsed.subcommand === "version") {
2318
+ writeOut(`wod v${VERSION}`);
2319
+ return 0;
2320
+ }
2321
+ if (parsed.help || parsed.subcommand === "help") {
2322
+ printUsage(writeOut);
2323
+ return 0;
2324
+ }
2325
+ if (!parsed.subcommand) {
2326
+ printUsage(writeErr);
2327
+ return 1;
2328
+ }
2329
+ for (const packSpec of parsed.packs) {
2330
+ try {
2331
+ await loadLanguagePack(packSpec);
2332
+ } catch (err) {
2333
+ if (err instanceof PackLoadError) {
2334
+ writeErr(err.message);
2335
+ } else {
2336
+ writeErr(`[wod] Language pack load error: ${err instanceof Error ? err.message : String(err)}`);
2337
+ }
2338
+ return 3;
2339
+ }
2340
+ }
2341
+ const deliverOutput = (formattedText) => {
2342
+ if (parsed.output) {
2343
+ getFs2().writeFileSync(parsed.output, formattedText, "utf-8");
2344
+ } else {
2345
+ writeOut(formattedText);
2346
+ }
2347
+ };
2348
+ const debugTarget = (subcommand) => parsed.debug === void 0 ? void 0 : parsed.debug || `wod-${subcommand}-debug.json`;
2349
+ const writeDebugFile = (subcommand, ir) => {
2350
+ const target = debugTarget(subcommand);
2351
+ if (target !== void 0) {
2352
+ getFs2().writeFileSync(target, JSON.stringify(ir, null, 2) + "\n", "utf-8");
2353
+ }
2354
+ };
2355
+ try {
2356
+ if (parsed.subcommand === "parse") {
2357
+ if (!parsed.target) {
2358
+ writeErr('[wod parse] Error: Missing input file or "-" for stdin');
2359
+ return 1;
2360
+ }
2361
+ let source;
2362
+ if (parsed.target === "-") {
2363
+ source = await getStdin();
2364
+ } else {
2365
+ const fs = getFs2();
2366
+ if (!fs.existsSync(parsed.target)) {
2367
+ writeErr(`[wod parse] File not found: ${parsed.target}`);
2368
+ return 1;
2369
+ }
2370
+ source = fs.readFileSync(parsed.target, "utf-8");
2371
+ }
2372
+ const ir = runParse(source, { sport: parsed.sport, sourceLabel: `cli:wod parse ${parsed.target}` });
2373
+ const identity = extractNoteIdentity(source);
2374
+ const tags = extractTypedTags(source);
2375
+ const db = await openCliDb(parsed.db);
2376
+ try {
2377
+ const persisted = await persistNote(db, {
2378
+ source,
2379
+ sourceLabel: parsed.target,
2380
+ noteId: identity?.noteId,
2381
+ title: identity?.title,
2382
+ tags
2383
+ });
2384
+ writeOut(
2385
+ `[wod parse] note ${persisted.noteId}${persisted.created ? "" : " (updated)"} \u2014 ${persisted.segments} segments, ${persisted.blocks} blocks, ${persisted.efforts} efforts${persisted.tags > 0 ? `, ${persisted.tags} tags` : ""} \u2192 ${db.path}`
2386
+ );
2387
+ } finally {
2388
+ await db.close();
2389
+ }
2390
+ if (parsed.output) {
2391
+ const pretty = Boolean(process.stdout.isTTY);
2392
+ deliverOutput(formatParseOutput(ir, parsed.format, pretty));
2393
+ }
2394
+ writeDebugFile("parse", ir);
2395
+ return 0;
2396
+ }
2397
+ if (parsed.subcommand === "run") {
2398
+ if (!parsed.target) {
2399
+ writeErr('[wod run] Error: Missing input file or "-" for stdin');
2400
+ return 1;
2401
+ }
2402
+ let source;
2403
+ if (parsed.target === "-") {
2404
+ source = await getStdin();
2405
+ } else {
2406
+ const fs = getFs2();
2407
+ if (!fs.existsSync(parsed.target)) {
2408
+ writeErr(`[wod run] File not found: ${parsed.target}`);
2409
+ return 1;
2410
+ }
2411
+ source = fs.readFileSync(parsed.target, "utf-8");
2412
+ }
2413
+ const ir = await runExecution(source, { sport: parsed.sport, sourceLabel: `cli:wod run ${parsed.target}` });
2414
+ const db = await openCliDb(parsed.db);
2415
+ try {
2416
+ const persisted = await persistRun(db, {
2417
+ source,
2418
+ sourceLabel: parsed.target,
2419
+ executionLog: ir.data
2420
+ });
2421
+ writeOut(
2422
+ `[wod run] session ${persisted.resultId} \u2014 ${persisted.events} events, ${persisted.summaries} summaries on note ${persisted.noteId} \u2192 ${db.path}`
2423
+ );
2424
+ } finally {
2425
+ await db.close();
2426
+ }
2427
+ if (parsed.output) {
2428
+ const pretty = Boolean(process.stdout.isTTY);
2429
+ deliverOutput(formatExecutionOutput(ir, parsed.format, pretty));
2430
+ }
2431
+ writeDebugFile("run", ir);
2432
+ return 0;
2433
+ }
2434
+ if (parsed.subcommand === "query") {
2435
+ if (!parsed.target) {
2436
+ writeErr("[wod query] Error: Missing WQL query argument");
2437
+ return 1;
2438
+ }
2439
+ const useDb = !parsed.corpus && !parsed.stdinLog && !parsed.stdinFacts;
2440
+ let stdinLogContent;
2441
+ let stdinFactsContent;
2442
+ if (parsed.stdinLog) {
2443
+ stdinLogContent = await getStdin();
2444
+ } else if (parsed.stdinFacts) {
2445
+ stdinFactsContent = await getStdin();
2446
+ }
2447
+ let ir;
2448
+ if (useDb) {
2449
+ const dbPath = resolveDbPath(parsed.db);
2450
+ if (!nodeFs().existsSync(dbPath)) {
2451
+ writeErr(
2452
+ `[wod query] No input dataset and no database at ${dbPath}. Specify --corpus <file>, --stdin-log, --stdin-facts, or run \`wod seed\` first.`
2453
+ );
2454
+ return 1;
2455
+ }
2456
+ const db = await openCliDb(parsed.db);
2457
+ try {
2458
+ ir = await runQueryCli(parsed.target, {
2459
+ preferredUnit: parsed.unit,
2460
+ sourceLabel: `cli:wod query "${parsed.target}"`,
2461
+ stores: {
2462
+ eventStore: sqliteEventStore(db.db),
2463
+ noteStore: sqliteNoteStore(db.db),
2464
+ blockStore: sqliteBlockStore(db.db),
2465
+ effortStore: sqliteEffortStore(db.db),
2466
+ blockEffortsStore: sqliteIndexStore(db.db, "block_efforts"),
2467
+ tagsStore: sqliteIndexStore(db.db, "tags"),
2468
+ noteTagsStore: sqliteIndexStore(db.db, "note_tags")
2469
+ }
2470
+ });
2471
+ } finally {
2472
+ await db.close();
2473
+ }
2474
+ } else {
2475
+ if (!parsed.corpus && !stdinLogContent && !stdinFactsContent) {
2476
+ writeErr("[wod query] Error: Query requires input data. Specify --corpus <file>, --stdin-log, or --stdin-facts.");
2477
+ return 1;
2478
+ }
2479
+ ir = await runQueryCli(parsed.target, {
2480
+ corpusPath: parsed.corpus,
2481
+ stdinLog: stdinLogContent,
2482
+ stdinFacts: stdinFactsContent,
2483
+ preferredUnit: parsed.unit,
2484
+ sourceLabel: `cli:wod query "${parsed.target}"`
2485
+ });
2486
+ }
2487
+ const pretty = !parsed.output && Boolean(process.stdout.isTTY);
2488
+ const formatted = formatQueryOutput(ir, parsed.format, pretty);
2489
+ deliverOutput(formatted);
2490
+ writeDebugFile("query", ir);
2491
+ return 0;
2492
+ }
2493
+ if (parsed.subcommand === "download") {
2494
+ const kind = parsed.target;
2495
+ const id = parsed.target2;
2496
+ const DOWNLOAD_KINDS = ["note", "session", "effort", "equipment"];
2497
+ if (!id && kind !== "equipment" || !DOWNLOAD_KINDS.includes(kind ?? "")) {
2498
+ writeErr("[wod download] Usage: wod download <note|session|effort|equipment> <id> [--at <version>] [-o <path>]");
2499
+ return 1;
2500
+ }
2501
+ const targetId = id;
2502
+ const db = await openCliDb(parsed.db);
2503
+ try {
2504
+ if (kind === "note") {
2505
+ const bundle = await getNoteBundle(db.db, targetId);
2506
+ if (!bundle.note) {
2507
+ writeErr(`[wod download] Note not found in ${db.path}: ${id}`);
2508
+ return 1;
2509
+ }
2510
+ const at = parsed.at === void 0 ? void 0 : Number(parsed.at);
2511
+ if (parsed.at !== void 0 && (!Number.isInteger(at) || at < 1)) {
2512
+ writeErr(`[wod download] Invalid --at version: ${parsed.at}`);
2513
+ return 1;
2514
+ }
2515
+ const markdown = composeNoteMarkdown(bundle.note, bundle.segments, at);
2516
+ const target = parsed.output && parsed.output !== "-" ? parsed.output : `${id}${at ? `.v${at}` : ""}.md`;
2517
+ if (parsed.output === "-") {
2518
+ writeOut(markdown);
2519
+ } else {
2520
+ nodeFs().writeFileSync(target, markdown, "utf-8");
2521
+ }
2522
+ writeOut(
2523
+ `[wod download] note ${id}${at ? ` v${at}` : ""} \u2192 ${target} (${bundle.segments.length} segment rows)`
2524
+ );
2525
+ } else if (kind === "session") {
2526
+ const bundle = await getSessionBundle(db.db, targetId);
2527
+ if (!bundle) {
2528
+ writeErr(`[wod download] Session not found in ${db.path}: ${id}`);
2529
+ return 1;
2530
+ }
2531
+ const csv = sessionToCsv(bundle.session, bundle.events);
2532
+ const target = parsed.output && parsed.output !== "-" ? parsed.output : `${id}.csv`;
2533
+ if (parsed.output === "-") {
2534
+ writeOut(csv);
2535
+ } else {
2536
+ nodeFs().writeFileSync(target, csv, "utf-8");
2537
+ }
2538
+ writeOut(`[wod download] session ${id} \u2192 ${target} (${bundle.events.length} events)`);
2539
+ } else if (kind === "effort") {
2540
+ const effort = await getEffortBySlug(db.db, targetId);
2541
+ if (!effort) {
2542
+ writeErr(`[wod download] Effort not found in ${db.path}: ${id}`);
2543
+ return 1;
2544
+ }
2545
+ const markdown = effortToMarkdown(effort);
2546
+ const target = parsed.output && parsed.output !== "-" ? parsed.output : `${id}.md`;
2547
+ if (parsed.output === "-") {
2548
+ writeOut(markdown);
2549
+ } else {
2550
+ nodeFs().writeFileSync(target, markdown, "utf-8");
2551
+ }
2552
+ writeOut(`[wod download] effort ${id} \u2192 ${target}`);
2553
+ } else {
2554
+ const labels = (await listTagsByType(db.db, "equipment")).map((t) => t.label).sort();
2555
+ const csv = equipmentToCsv(labels);
2556
+ const target = parsed.output && parsed.output !== "-" ? parsed.output : "equipment.csv";
2557
+ if (parsed.output === "-") {
2558
+ writeOut(csv);
2559
+ } else {
2560
+ nodeFs().writeFileSync(target, csv, "utf-8");
2561
+ }
2562
+ writeOut(`[wod download] equipment \u2192 ${target} (${labels.length} labels)`);
2563
+ }
2564
+ } finally {
2565
+ await db.close();
2566
+ }
2567
+ return 0;
2568
+ }
2569
+ if (parsed.subcommand === "import") {
2570
+ const kind = parsed.target;
2571
+ const file = parsed.target2;
2572
+ const IMPORT_KINDS = ["note", "session", "effort", "equipment"];
2573
+ if (!file || !IMPORT_KINDS.includes(kind ?? "")) {
2574
+ writeErr("[wod import] Usage: wod import <note|session|effort|equipment> <file>");
2575
+ return 1;
2576
+ }
2577
+ const fs = nodeFs();
2578
+ if (!fs.existsSync(file)) {
2579
+ writeErr(`[wod import] File not found: ${file}`);
2580
+ return 1;
2581
+ }
2582
+ if (kind === "note") {
2583
+ const source = fs.readFileSync(file, "utf-8");
2584
+ const identity = extractNoteIdentity(source);
2585
+ const tags = extractTypedTags(source);
2586
+ const db2 = await openCliDb(parsed.db);
2587
+ try {
2588
+ const persisted = await persistNote(db2, {
2589
+ source,
2590
+ sourceLabel: file,
2591
+ noteId: identity?.noteId,
2592
+ title: identity?.title,
2593
+ tags
2594
+ });
2595
+ writeOut(
2596
+ `[wod import] note ${persisted.noteId}${persisted.created ? "" : " (updated)"} \u2014 ${persisted.segments} segments${persisted.tags > 0 ? `, ${persisted.tags} tags` : ""} \u2192 ${db2.path}`
2597
+ );
2598
+ } finally {
2599
+ await db2.close();
2600
+ }
2601
+ return 0;
2602
+ }
2603
+ if (kind === "effort") {
2604
+ const effort = markdownToEffort(fs.readFileSync(file, "utf-8"));
2605
+ if (!effort) {
2606
+ writeErr(`[wod import] Unparseable effort file (need frontmatter id/slug/label + numeric baseAttributes.met): ${file}`);
2607
+ return 1;
2608
+ }
2609
+ const db2 = await openCliDb(parsed.db);
2610
+ try {
2611
+ await persistEffort(db2, { ...effort, registrySource: "user" });
2612
+ writeOut(`[wod import] effort ${effort.slug} \u2014 ${effort.label} \u2192 ${db2.path}`);
2613
+ } finally {
2614
+ await db2.close();
2615
+ }
2616
+ return 0;
2617
+ }
2618
+ if (kind === "equipment") {
2619
+ const labels = csvToEquipmentLabels(fs.readFileSync(file, "utf-8"));
2620
+ if (labels.length === 0) {
2621
+ writeErr(`[wod import] No equipment labels found in ${file}`);
2622
+ return 1;
2623
+ }
2624
+ const db2 = await openCliDb(parsed.db);
2625
+ try {
2626
+ const added = await persistTypedTags(db2, labels, "equipment");
2627
+ writeOut(`[wod import] equipment \u2014 ${labels.length} labels (${added} new) \u2192 ${db2.path}`);
2628
+ } finally {
2629
+ await db2.close();
2630
+ }
2631
+ return 0;
2632
+ }
2633
+ const { session, events } = csvToSessionRows(fs.readFileSync(file, "utf-8"));
2634
+ const db = await openCliDb(parsed.db);
2635
+ try {
2636
+ const result = await persistSessionBundle(db, session, events);
2637
+ writeOut(
2638
+ `[wod import] session ${session.id} \u2014 ${result.events} events${result.noteCreated ? " (stub note created)" : ""} \u2192 ${db.path}`
2639
+ );
2640
+ } finally {
2641
+ await db.close();
2642
+ }
2643
+ return 0;
2644
+ }
2645
+ if (parsed.subcommand === "build") {
2646
+ const cwd = process.cwd();
2647
+ const root = nodePath().resolve(cwd, parsed.dir ?? parsed.target ?? ".");
2648
+ if (!nodeFs().existsSync(nodePath().resolve(root, "markdown"))) {
2649
+ writeErr(`[wod build] No markdown/ corpus under ${root} \u2014 run from the content repo root (or pass its directory).`);
2650
+ return 1;
2651
+ }
2652
+ const { generateSeed } = await import('./compiler-KTBR6OEL.mjs');
2653
+ try {
2654
+ const result = generateSeed({
2655
+ corpusRoot: root,
2656
+ ...parsed.output ? { outDir: nodePath().resolve(cwd, parsed.output) } : {},
2657
+ ...parsed.template ? { templateFile: nodePath().resolve(cwd, parsed.template) } : {},
2658
+ scaffoldDiscoveredEfforts: parsed.scaffold === true
2659
+ });
2660
+ const kb = (n) => `${(n / 1024).toFixed(0)} KB`;
2661
+ const totalBytes = result.manifest.chunks.reduce((s, c) => s + c.bytes, 0);
2662
+ writeOut(`[wod build] schema v${result.manifest.schema} version ${result.manifest.version} (${result.manifest.versionLabel}) \u2192 ${result.outDir}`);
2663
+ for (const c of result.manifest.chunks) {
2664
+ writeOut(` ${c.id.padEnd(48)} ${String(c.count).padStart(4)} ${c.kind === "block-index" ? "rows " : "files"} ${kb(c.bytes).padStart(8)}`);
2665
+ }
2666
+ 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`);
2667
+ return 0;
2668
+ } catch (error) {
2669
+ writeErr(`[wod build] ${error.message}`);
2670
+ return 1;
2671
+ }
2672
+ }
2673
+ if (parsed.subcommand === "seed") {
2674
+ const dirs = parsed.sources.length > 0 ? parsed.sources.map((s) => nodePath().resolve(process.cwd(), s)) : defaultSeedDirs(process.cwd());
2675
+ if (dirs.length === 0) {
2676
+ writeErr(
2677
+ "[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)."
2678
+ );
2679
+ return 1;
2680
+ }
2681
+ const db = await openCliDb(parsed.db);
2682
+ try {
2683
+ for (const dir of dirs) {
2684
+ const summary = await importSeedDir(db, dir, "");
2685
+ writeOut(
2686
+ `[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}`
2687
+ );
2688
+ }
2689
+ } finally {
2690
+ await db.close();
2691
+ }
2692
+ return 0;
2693
+ }
2694
+ if (parsed.subcommand === "reset") {
2695
+ const target = resolveDbPath(parsed.db);
2696
+ if (nodeFs().existsSync(target)) {
2697
+ nodeFs().unlinkSync(target);
2698
+ writeOut(`[wod reset] removed ${target}`);
2699
+ } else {
2700
+ writeOut(`[wod reset] nothing to reset \u2014 ${target} does not exist`);
2701
+ }
2702
+ return 0;
2703
+ }
2704
+ writeErr(`[wod] Unknown subcommand "${parsed.subcommand}"`);
2705
+ return 1;
2706
+ } catch (error) {
2707
+ if (error instanceof ParseSyntaxError || error instanceof WqlSyntaxError) {
2708
+ writeErr(error.message);
2709
+ return 2;
2710
+ }
2711
+ if (error instanceof PackLoadError) {
2712
+ writeErr(error.message);
2713
+ return 3;
2714
+ }
2715
+ writeErr(`[wod] Error: ${error instanceof Error ? error.message : String(error)}`);
2716
+ return 1;
2717
+ }
2718
+ }
2719
+
2720
+ export { NOTE_ID_FRONTMATTER_KEY, PackLoadError, ParseSyntaxError, SESSION_CSV_HEADER, WqlSyntaxError, buildStatementTree, buildWorkoutResults, cliMain, composeNoteMarkdown, createIRFile, csvToEquipmentLabels, csvToSessionRows, defineLanguagePack, effortToMarkdown, equipmentToCsv, extractNoteIdentity, extractTypedTags, formatExecutionOutput, formatParseOutput, formatQueryOutput, getRegisteredLanguagePacks, inMemoryEventStore, injectIdentityFrontmatter, isIRFile, listLanguagePacks, loadLanguagePack, loadQueryData, markdownToEffort, parseCliArgs, parseCsvLine, readFrontmatterMeta, readStdin, registerLanguagePack, runExecution, runParse, runQueryCli, sessionToCsv, splitCsvRecords, statementToNode, unregisterLanguagePack };
2721
+ //# sourceMappingURL=chunk-B2OYLNEZ.mjs.map
2722
+ //# sourceMappingURL=chunk-B2OYLNEZ.mjs.map