Spry LogoSpry Docs

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 exports

Core 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
                           └────────→ setup

Key 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 document

2. Edge Rules

Edge rules are the intelligence that determines how nodes connect. Each rule examines the document and creates specific types of edges.

RulePurposeExample
contained-in-headingCreates parent-child relationships based on heading hierarchyH2 is contained in H1
contained-in-sectionGroups content within sectionsCode blocks belong to their section
frontmatter-classificationParses YAML frontmatter at document topExtracts metadata, tags, config
node-dependencyResolves --depends flags in taskstask2 --depends task1 creates edge
nodes-classificationClassifies nodes by typeMarks code blocks as 'task' vs 'content'
governanceValidates document structureEnsures required sections exist
section-frontmatterParses section-level metadataConfiguration 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:

  1. frontmatter-classification: Extracts project: my-app
  2. contained-in-heading: Links tasks to their headings
  3. nodes-classification: Marks code blocks as tasks
  4. 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.

ProjectionPurposeUse Case
runbookTask execution view with dependenciesRunning tasks in correct order
treeHierarchical tree structureDisplaying document outline
flexibleCustomizable projectionBuilding custom views/filters
tree-textText-based tree renderingCLI 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 deploy

Tree 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.

FileWhat It Does
contained-in-heading.tsLinks content to their parent headings (H2 under H1, H3 under H2)
contained-in-section.tsGroups all content within document sections
frontmatter-classification.tsExtracts and parses YAML frontmatter at document start
governance.tsValidates document structure meets requirements
node-dependency.tsParses --depends flags and creates dependency edges
nodes-classification.tsDetermines node types (task, content, metadata)
section-frontmatter.tsHandles section-specific metadata blocks
section-semantic-id.tsGenerates human-readable IDs for sections
selected-nodes-classification.tsFilters 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.

FilePurpose
code-frontmatter.tsExtracts frontmatter from code block info strings
data-bag.tsProvides metadata storage for nodes
node-content.tsGets textual content from any node type
node-issues.tsTracks validation issues and warnings
node-src-text.tsRetrieves original source text
statistics.tsCalculates document statistics (word count, task count)

projection/

Different ways to view and traverse the graph.

FileCreates
runbook.tsExecution-ready task list with dependency order
tree.tsGeneric hierarchical tree structure
tree-text.tsASCII/Unicode text rendering of trees
flexible.tsBase for custom projection implementations

remark/

Integration with the Remark markdown processor. These are plugins that run during parsing.

FileHandles
code-directive-candidates.tsIdentifies code blocks that are directives
doc-frontmatter.tsProcesses document-level frontmatter
import-placeholders-generator.tsCreates placeholders for external imports
import-specs-resolver.tsResolves and loads external markdown files
node-decorator.tsAdds metadata and IDs to nodes during parsing
spawnable-code-candidates.tsMarks code blocks that can be executed

text-ui/

Command-line interfaces for interacting with graphs.

FileProvides
cli.tsAxiom graph viewer CLI tool
runbook.tsMain 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: deploy

Example 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 environment

Testing

# 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:

  1. Axiom is a transformer: Markdown → Graph → Executable structure
  2. Rules define relationships: Edge rules create the intelligence in the graph
  3. Projections enable flexibility: One graph, many views for different purposes
  4. 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

On this page