stemedb/docs/presentations/scripts/generate.ts
jordan 3cfaa1e1d3 feat: Complete Phase 1 (The Spine) - storage foundation
Phase 1 delivers the complete durability and storage layer:

- WAL with crash recovery: Append-only journal with BLAKE3 checksums,
  fsync guarantees, and proper seek-to-EOF on reopen
- Storage engine: sled-backed KVStore with scan_prefix for range queries
- Content-addressed storage: H:{hash}, V:{hash}, E:{hash} key patterns
- Ingestor: Background worker tailing WAL, writing to KV with 8-byte
  aligned record headers for rkyv zero-copy deserialization
- Comprehensive tests: 31 tests covering crash recovery, round-trips,
  and multi-cycle durability

New crates: stemedb-wal, stemedb-storage, stemedb-ingest

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 14:15:34 -07:00

227 lines
5.5 KiB
TypeScript

#!/usr/bin/env npx ts-node
/**
* Presentation Generator
*
* Reads YAML sequence data and generates:
* 1. Mermaid sequence diagrams (.mmd)
* 2. JSON for Reveal.js slides
*
* Usage: npx ts-node generate.ts ../data/agile-agent-team.yaml
*/
import * as fs from 'fs';
import * as path from 'path';
import * as yaml from 'yaml';
interface Actor {
id: string;
label: string;
description: string;
color: string;
}
interface StepData {
[key: string]: unknown;
}
interface Step {
id: string;
from: string;
to: string;
action: string;
label: string;
data?: StepData;
note?: string;
callout?: string;
danger?: boolean;
warning?: boolean;
success?: boolean;
}
interface Sequence {
id: string;
title: string;
subtitle: string;
description: string;
steps: Step[];
}
interface PresentationData {
meta: {
id: string;
title: string;
subtitle: string;
version: string;
};
actors: Record<string, Actor>;
sequences: Sequence[];
annotations: Record<string, {
color: string;
label: string;
icon: string;
}>;
}
// Generate Mermaid sequence diagram for a sequence
function generateMermaid(data: PresentationData, sequence: Sequence): string {
const lines: string[] = [];
lines.push('sequenceDiagram');
lines.push(` %% ${sequence.title}: ${sequence.subtitle}`);
lines.push('');
// Collect unique actors used in this sequence
const usedActors = new Set<string>();
for (const step of sequence.steps) {
usedActors.add(step.from);
usedActors.add(step.to);
}
// Add participant declarations
for (const actorKey of usedActors) {
const actor = data.actors[actorKey];
if (actor) {
lines.push(` participant ${actor.id} as ${actor.label}`);
}
}
lines.push('');
// Add steps
for (const step of sequence.steps) {
const fromActor = data.actors[step.from];
const toActor = data.actors[step.to];
if (!fromActor || !toActor) continue;
// Determine arrow style
let arrow = '->>';
if (step.action === 'response') {
arrow = '-->>';
}
// Add the message
const label = step.label.replace(/"/g, "'");
lines.push(` ${fromActor.id}${arrow}${toActor.id}: ${label}`);
// Add note if present
if (step.note) {
const noteText = step.note.replace(/"/g, "'");
const position = step.from === step.to ? 'over' : 'right of';
const noteActor = step.from === step.to ? fromActor.id : toActor.id;
if (step.danger) {
lines.push(` Note ${position} ${noteActor}: ⚠️ ${noteText}`);
} else if (step.success) {
lines.push(` Note ${position} ${noteActor}: ✓ ${noteText}`);
} else {
lines.push(` Note ${position} ${noteActor}: ${noteText}`);
}
}
lines.push('');
}
return lines.join('\n');
}
// Generate JSON for Reveal.js
function generateRevealJson(data: PresentationData): object {
const slides: object[] = [];
// Title slide
slides.push({
type: 'title',
title: data.meta.title,
subtitle: data.meta.subtitle,
});
// Generate slides for each sequence
for (const sequence of data.sequences) {
// Sequence title slide
slides.push({
type: 'section-title',
title: sequence.title,
subtitle: sequence.subtitle,
description: sequence.description,
});
// Create step slides - group steps for animation
const stepSlide = {
type: 'sequence',
sequenceId: sequence.id,
title: sequence.title,
actors: {} as Record<string, Actor>,
steps: sequence.steps.map((step, index) => ({
...step,
index,
fromActor: data.actors[step.from],
toActor: data.actors[step.to],
})),
};
// Collect actors for this sequence
for (const step of sequence.steps) {
if (data.actors[step.from]) {
stepSlide.actors[step.from] = data.actors[step.from];
}
if (data.actors[step.to]) {
stepSlide.actors[step.to] = data.actors[step.to];
}
}
slides.push(stepSlide);
}
return {
meta: data.meta,
actors: data.actors,
annotations: data.annotations,
slides,
};
}
// Main
function main() {
const args = process.argv.slice(2);
if (args.length < 1) {
console.error('Usage: npx ts-node generate.ts <input.yaml>');
process.exit(1);
}
const inputPath = path.resolve(args[0]);
const inputDir = path.dirname(inputPath);
const baseName = path.basename(inputPath, '.yaml');
// Read and parse YAML
const yamlContent = fs.readFileSync(inputPath, 'utf-8');
const data = yaml.parse(yamlContent) as PresentationData;
// Output directory
const outputDir = path.resolve(inputDir, '..', 'generated');
fs.mkdirSync(outputDir, { recursive: true });
// Generate Mermaid for each sequence
for (const sequence of data.sequences) {
const mermaid = generateMermaid(data, sequence);
const mermaidPath = path.join(outputDir, `${baseName}-${sequence.id}.mmd`);
fs.writeFileSync(mermaidPath, mermaid);
console.log(`Generated: ${mermaidPath}`);
}
// Generate combined Mermaid
const allMermaid = data.sequences
.map(seq => generateMermaid(data, seq))
.join('\n\n---\n\n');
const combinedMermaidPath = path.join(outputDir, `${baseName}.mmd`);
fs.writeFileSync(combinedMermaidPath, allMermaid);
console.log(`Generated: ${combinedMermaidPath}`);
// Generate JSON for Reveal.js
const revealJson = generateRevealJson(data);
const jsonPath = path.join(outputDir, `${baseName}.json`);
fs.writeFileSync(jsonPath, JSON.stringify(revealJson, null, 2));
console.log(`Generated: ${jsonPath}`);
}
main();