This commit adds the read path (Cortex) to complement the write path (Spine): ## Crates - stemedb-api: HTTP API with axum + utoipa OpenAPI - /v1/assert, /v1/query, /v1/epoch, /v1/skeptic, /v1/trace, /v1/audit - Metered endpoints with quota enforcement - Ed25519 signature verification - stemedb-lens: Truth resolution lenses - RecencyLens, ConsensusLens, ConfidenceLens - VoteAwareConsensusLens (Ballot Box pattern) - TrustAwareAuthorityLens (The Hive pattern) - SkepticLens (conflict analysis) - EpochAwareLens (paradigm-safe queries) - stemedb-query: Query engine with materialized views ## Storage Extensions - VoteStore: Vote aggregation with cached counts - TrustRankStore: Agent reputation with decay - AuditStore: Query audit trail - IndexStore: SP/P/S index structures - SupersessionStore: Epoch supersession chains ## SDKs - sdk/go/steme: Go HTTP client with Ed25519 signing - sdk/go/adk: ADK-Go tools for AI agents ## Documentation - Updated CLAUDE.md, architecture.md, roadmap.md - New ai-lookup entries for all services - Use case docs for consumer health intelligence - Arena roadmap for simulation advancement Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
243 lines
5.9 KiB
TypeScript
243 lines
5.9 KiB
TypeScript
#!/usr/bin/env npx tsx
|
|
/**
|
|
* Presentation Generator
|
|
*
|
|
* Reads YAML slide data and generates:
|
|
* 1. Mermaid sequence diagrams (.mmd) for sequence slides
|
|
* 2. JSON for Reveal.js slides
|
|
*
|
|
* Usage: npx tsx scripts/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 Step {
|
|
id: string;
|
|
from: string;
|
|
to: string;
|
|
action: string;
|
|
label: string;
|
|
note?: string;
|
|
callout?: string;
|
|
danger?: boolean;
|
|
warning?: boolean;
|
|
success?: boolean;
|
|
}
|
|
|
|
interface TitleSlide {
|
|
type: 'title';
|
|
id: string;
|
|
}
|
|
|
|
interface HookSlide {
|
|
type: 'hook';
|
|
id: string;
|
|
line: string;
|
|
}
|
|
|
|
interface ProblemSlide {
|
|
type: 'problem';
|
|
id: string;
|
|
title: string;
|
|
points: string[];
|
|
}
|
|
|
|
interface SectionTitleSlide {
|
|
type: 'section-title';
|
|
id: string;
|
|
title: string;
|
|
subtitle: string;
|
|
description: string;
|
|
}
|
|
|
|
interface SequenceSlide {
|
|
type: 'sequence';
|
|
id: string;
|
|
title: string;
|
|
actors: string[];
|
|
steps: Step[];
|
|
}
|
|
|
|
interface InsightSlide {
|
|
type: 'insight';
|
|
id: string;
|
|
title: string;
|
|
points: string[];
|
|
}
|
|
|
|
interface VisionSlide {
|
|
type: 'vision';
|
|
id: string;
|
|
title: string;
|
|
points: string[];
|
|
tagline?: string;
|
|
}
|
|
|
|
type Slide = TitleSlide | HookSlide | ProblemSlide | SectionTitleSlide | SequenceSlide | InsightSlide | VisionSlide;
|
|
|
|
interface PresentationData {
|
|
meta: {
|
|
id: string;
|
|
title: string;
|
|
subtitle: string;
|
|
version: string;
|
|
};
|
|
actors: Record<string, Actor>;
|
|
slides: Slide[];
|
|
annotations: Record<string, {
|
|
color: string;
|
|
label: string;
|
|
icon: string;
|
|
}>;
|
|
}
|
|
|
|
// Generate Mermaid sequence diagram for a sequence slide
|
|
function generateMermaid(data: PresentationData, sequence: SequenceSlide): string {
|
|
const lines: string[] = [];
|
|
|
|
lines.push('sequenceDiagram');
|
|
lines.push(` %% ${sequence.title}`);
|
|
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 - pass through slides with actor resolution
|
|
function generateRevealJson(data: PresentationData): object {
|
|
return {
|
|
meta: data.meta,
|
|
actors: data.actors,
|
|
annotations: data.annotations,
|
|
slides: data.slides,
|
|
};
|
|
}
|
|
|
|
// Main
|
|
function main() {
|
|
const args = process.argv.slice(2);
|
|
if (args.length < 1) {
|
|
console.error('Usage: npx tsx scripts/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
|
|
let data: PresentationData;
|
|
try {
|
|
const yamlContent = fs.readFileSync(inputPath, 'utf-8');
|
|
data = yaml.parse(yamlContent) as PresentationData;
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
console.error(`Failed to read/parse ${inputPath}: ${message}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
// Validate required fields
|
|
if (!data.meta || !data.actors || !data.slides) {
|
|
console.error('Invalid presentation data: missing required fields (meta, actors, slides)');
|
|
process.exit(1);
|
|
}
|
|
|
|
// Output directory
|
|
const outputDir = path.resolve(inputDir, '..', 'generated');
|
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
|
|
// Find all sequence slides
|
|
const sequenceSlides = data.slides.filter((s): s is SequenceSlide => s.type === 'sequence');
|
|
|
|
try {
|
|
// Generate Mermaid for each sequence
|
|
for (const sequence of sequenceSlides) {
|
|
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
|
|
if (sequenceSlides.length > 0) {
|
|
const allMermaid = sequenceSlides
|
|
.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}`);
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
console.error(`Failed to write output files: ${message}`);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
main();
|