Axiom Graph Engine
Programmable Markdown Document Model in Spry.
Introduction
The axiom module is Spry's core engine that transforms markdown documents into executable, queryable graphs. Think of it as the translator between your markdown runbooks and Spry's execution system—it reads your markdown structure and converts it into a network of interconnected nodes that can be analyzed, filtered, and executed.
What Problem Does Axiom Solve?
When you write a runbook in markdown, you're creating:
- Hierarchical structure (headings and sections)
- Executable code blocks (tasks)
- Dependencies between tasks
- Metadata and configuration
Axiom takes all of this and creates a directed graph where:
- Every element (heading, code block, paragraph) becomes a node
- Relationships (containment, dependencies) become edges
- The entire document becomes queryable and executable
This graph representation enables Spry to:
- Execute tasks in the correct order based on dependencies
- Filter and select specific parts of your runbook
- Generate different views (tree, runbook, custom)
- Validate document structure and rules
Directory Structure
lib/axiom/
├── edge/ # Graph edge rules - define how nodes connect
│ └── rule/ # Individual classification rules
├── fixture/ # Test fixtures for development
├── io/ # Input/output handling for resources
├── mdast/ # Markdown Abstract Syntax Tree utilities
├── projection/ # Different views of the same graph
├── remark/ # Remark plugin integration for parsing
├── text-ui/ # Terminal/CLI interfaces
├── web-ui/ # Web-based interfaces
├── graph.ts # Core graph data structures
└── mod.ts # Public module exportsCore Concepts
1. Graph Structure
At its heart, axiom represents your markdown as a directed graph:
# Deploy Application
## Prerequisites
```bash task setup
npm install
```
## Deploy
```bash task deploy --depends setup
npm run deploy
```┌─────────────────┐
│ Deploy App (h1) │
└────────┬────────┘
│ contained-in
┌────┴────┬────────────┐
│ │ │
┌───▼────┐ ┌──▼────┐ ┌───▼────┐
│ Prereq │ │ setup │ │ deploy │
│ (h2) │ │ task │ │ task │
└────────┘ └───────┘ └───┬────┘
│ depends
└────────→ setupKey Types
interface GraphNode {
id: string; // Unique identifier
type: string; // 'heading', 'code', 'paragraph', etc.
data: unknown; // Node-specific data
edges: GraphEdge[]; // Outgoing connections
}
interface GraphEdge {
type: string; // 'contained-in', 'depends', etc.
target: string; // Target node ID
}Building a Graph
import { buildGraph } from "./graph.ts";
const markdownSource = `
# My Runbook
## Task 1
\`\`\`bash task1
echo "First task"
\`\`\`
`;
const graph = await buildGraph(markdownSource);
// graph now contains nodes and edges representing the document2. Edge Rules
Edge rules are the intelligence that determines how nodes connect. Each rule examines the document and creates specific types of edges.
| Rule | Purpose | Example |
|---|---|---|
contained-in-heading | Creates parent-child relationships based on heading hierarchy | H2 is contained in H1 |
contained-in-section | Groups content within sections | Code blocks belong to their section |
frontmatter-classification | Parses YAML frontmatter at document top | Extracts metadata, tags, config |
node-dependency | Resolves --depends flags in tasks | task2 --depends task1 creates edge |
nodes-classification | Classifies nodes by type | Marks code blocks as 'task' vs 'content' |
governance | Validates document structure | Ensures required sections exist |
section-frontmatter | Parses section-level metadata | Configuration per section |
Example of How Rules Work
---
project: my-app
---
# Setup
```bash install --depends check-env
npm install
```
# Deploy
```bash deploy --depends install
npm run deploy
```Rules create:
- frontmatter-classification: Extracts
project: my-app - contained-in-heading: Links tasks to their headings
- nodes-classification: Marks code blocks as tasks
- node-dependency: Creates edges:
install → check-env,deploy → install
3. Projections
A projection is a specific view or representation of the graph optimized for different purposes. The same graph can be projected multiple ways.
| Projection | Purpose | Use Case |
|---|---|---|
runbook | Task execution view with dependencies | Running tasks in correct order |
tree | Hierarchical tree structure | Displaying document outline |
flexible | Customizable projection | Building custom views/filters |
tree-text | Text-based tree rendering | CLI display of structure |
Runbook Projection
import { runbookProjection } from "./projection/runbook.ts";
const tasks = runbookProjection(graph);
// tasks is now an array of executable task objects
for (const task of tasks) {
console.log(`Task: ${task.identity}`);
console.log(` Dependencies: ${task.dependencies.join(", ")}`);
console.log(` Command: ${task.command}`);
}
// Output:
// Task: install
// Dependencies: check-env
// Command: npm install
// Task: deploy
// Dependencies: install
// Command: npm run deployTree Projection
import { treeProjection } from "./projection/tree.ts";
const tree = treeProjection(graph);
// Render as hierarchical structure
// Setup
// └─ install [task]
// Deploy
// └─ deploy [task]Module Organization
Core Files
graph.ts
The foundation of axiom. Defines the graph data structure and provides the main buildGraph() function that orchestrates the entire parsing pipeline.
Key exports:
buildGraph(markdown: string): Promise<Graph>- Graph and Node type definitions
- Edge type definitions
mod.ts
Public API exports. This is what other modules import from axiom.
Subdirectories
edge/rule/
Contains individual rule implementations. Each file is a self-contained rule that knows how to create one type of edge.
| File | What It Does |
|---|---|
contained-in-heading.ts | Links content to their parent headings (H2 under H1, H3 under H2) |
contained-in-section.ts | Groups all content within document sections |
frontmatter-classification.ts | Extracts and parses YAML frontmatter at document start |
governance.ts | Validates document structure meets requirements |
node-dependency.ts | Parses --depends flags and creates dependency edges |
nodes-classification.ts | Determines node types (task, content, metadata) |
section-frontmatter.ts | Handles section-specific metadata blocks |
section-semantic-id.ts | Generates human-readable IDs for sections |
selected-nodes-classification.ts | Filters nodes based on classification criteria |
mdast/
Utilities for working with Markdown Abstract Syntax Trees (MDAST). These are helper functions that extract information from AST nodes.
| File | Purpose |
|---|---|
code-frontmatter.ts | Extracts frontmatter from code block info strings |
data-bag.ts | Provides metadata storage for nodes |
node-content.ts | Gets textual content from any node type |
node-issues.ts | Tracks validation issues and warnings |
node-src-text.ts | Retrieves original source text |
statistics.ts | Calculates document statistics (word count, task count) |
projection/
Different ways to view and traverse the graph.
| File | Creates |
|---|---|
runbook.ts | Execution-ready task list with dependency order |
tree.ts | Generic hierarchical tree structure |
tree-text.ts | ASCII/Unicode text rendering of trees |
flexible.ts | Base for custom projection implementations |
remark/
Integration with the Remark markdown processor. These are plugins that run during parsing.
| File | Handles |
|---|---|
code-directive-candidates.ts | Identifies code blocks that are directives |
doc-frontmatter.ts | Processes document-level frontmatter |
import-placeholders-generator.ts | Creates placeholders for external imports |
import-specs-resolver.ts | Resolves and loads external markdown files |
node-decorator.ts | Adds metadata and IDs to nodes during parsing |
spawnable-code-candidates.ts | Marks code blocks that can be executed |
text-ui/
Command-line interfaces for interacting with graphs.
| File | Provides |
|---|---|
cli.ts | Axiom graph viewer CLI tool |
runbook.ts | Main runbook execution CLI |
io/
Input/output abstractions for reading resources.
Complete Usage Examples
Example 1: Building and Querying a Graph
import { MarkdownDoc } from "../markdown/fluent-doc.ts";
import { buildGraph } from "./graph.ts";
import { visit } from "unist-util-visit";
// Create markdown programmatically
const doc = new MarkdownDoc();
doc.h1("Database Migration");
doc.h2("Backup");
doc.codeTag("bash backup --descr 'Backup database'")`
pg_dump mydb > backup.sql
`;
doc.h2("Migrate");
doc.codeTag("bash migrate --depends backup")`
./run-migrations.sh
`;
// Build the graph
const graph = await buildGraph(doc.write());
// Query: Find all tasks
const tasks: string[] = [];
visit(graph.ast, "code", (node) => {
if (node.meta?.includes("task")) {
tasks.push(node.lang + " task");
}
});
console.log("Found tasks:", tasks);
// Output: Found tasks: ['bash task', 'bash task']Example 2: Executing Tasks in Dependency Order
import { buildGraph } from "./graph.ts";
import { runbookProjection } from "./projection/runbook.ts";
import { executionPlan } from "../universal/task.ts";
const markdown = `
# Deployment Pipeline
\`\`\`bash build
npm run build
\`\`\`
\`\`\`bash test --depends build
npm test
\`\`\`
\`\`\`bash deploy --depends test
./deploy.sh
\`\`\`
`;
// Build graph
const graph = await buildGraph(markdown);
// Get task projection
const tasks = runbookProjection(graph);
// Generate execution plan (topological sort by dependencies)
const plan = executionPlan(tasks);
// Execute in order
for (const task of plan) {
console.log(`Executing: ${task.identity}`);
// await executeTasks([task], state);
}
// Output:
// Executing: build
// Executing: test
// Executing: deployExample 3: Filtering Tasks with CQL
import { buildGraph } from "./graph.ts";
import { runbookProjection } from "./projection/runbook.ts";
import { compileCqlMini } from "../markdown/notebook/cql.ts";
const graph = await buildGraph(myMarkdown);
const tasks = runbookProjection(graph);
// Create filter: only bash tasks with tag "deploy"
const filter = compileCqlMini("lang=bash AND tag=deploy");
const deployTasks = filter(tasks);
console.log("Deploy tasks:", deployTasks.map(t => t.identity));Integration with Other Modules
With markdown/
Axiom uses markdown utilities for parsing and querying:
import { compileCqlMini } from "../markdown/notebook/cql.ts";
const filter = compileCqlMini("lang=python");
const pythonTasks = filter(tasks);With task/
Task execution leverages axiom's dependency graph:
import { executeTasks } from "../task/mod.ts";
import { executionPlan } from "../universal/task.ts";
const plan = executionPlan(runbookTasks);
await executeTasks(plan, executionState);With sqlpage/
Web interfaces can render axiom graphs:
import { SqlPagePlaybook } from "../sqlpage/playbook.ts";
const playbook = SqlPagePlaybook.instance(project);
const webFiles = playbook.sqlPageFiles({
mdSources: ["Spryfile.md"]
});With interpolate/
Template interpolation works with axiom's parsed structure:
import { runbookProjection } from "./projection/runbook.ts";
const tasks = runbookProjection(graph);
// Tasks contain interpolated values from environmentTesting
# Run all axiom tests
deno test lib/axiom/
# Run specific component tests
deno test lib/axiom/graph_test.ts
deno test lib/axiom/projection/
# Run with coverage
deno test --coverage=coverage/ lib/axiom/
deno coverage coverage/Key Takeaways
Axiom is the core transformation engine in Spry:
- Axiom is a transformer: Markdown → Graph → Executable structure
- Rules define relationships: Edge rules create the intelligence in the graph
- Projections enable flexibility: One graph, many views for different purposes
- Integration is key: Axiom connects markdown authoring to task execution
This document provides a comprehensive overview of the Axiom Graph Engine, its architecture, and how it powers Spry's markdown-to-executable transformation system.
How is this guide?
Last updated on