Markdown-driven spec files with dependency graphs, feature tracking, and group clustering — visualized as an interactive, explorable graph.
M21 lets you define project contracts as Markdown files with YAML frontmatter. A spec can define a concern's data model, semantic interfaces, architectural contract, dependencies, and optional Gherkin .feature files. Dependent specs declare which behavioral capabilities they use, creating traceable contracts between concerns. M21 renders everything as a live, interactive dependency graph in the browser.
M21 is an experiment in higher-level software tooling for building maintainable, extensible, reliable, professional, clean, and understandable software. It treats a project as a network of purposeful concerns with explicit models, interfaces, responsibilities, dependencies, and executable behavior.
The goal is living architecture: data models define shared meaning, interfaces define semantic surfaces, specs describe intent, Gherkin scenarios demonstrate behavior, dependency edges explain which capabilities are used, and test results close the loop.
Read the full vision in VISION.md.
npm install -g @moejay/m21Or run directly:
npx @moejay/m21 ./spec/M21 ships with skills for spec authoring and brownfield adoption. Install them with:
npx skills install moejay/m21This installs two skills:
- m21 — helps you create and maintain spec files, dependencies, and feature files
- m21-init — analyzes an existing codebase and generates specs + features from it (brownfield adoption)
myproject/
├── spec/
│ ├── bootstrap.md
│ ├── persistence.md
│ └── repos.md
└── features/
├── bootstrap/
│ ├── project-scaffolding.feature
│ └── health-endpoint.feature
├── persistence/
│ ├── data-storage.feature
│ └── query-interface.feature
└── repos/
└── repo-onboarding.feature
Each spec is a markdown file with YAML frontmatter:
---
name: bootstrap
description: One-time project scaffolding
group: foundation
tags: [setup, init]
depends_on: []
features: features/bootstrap/
---
# Bootstrap
## Data model
```m21-model
entities:
ProjectConfiguration:
fields:
root: { type: string, required: true }
ready: { type: boolean, required: true }operations:
project-scaffolding:
input: ProjectConfiguration
output: ProjectConfiguration
failures: [InvalidProjectRoot]
Describe responsibilities, non-goals, invariants, dependencies, decisions, and guarantees.
The sections are optional, but when present they follow **Data model → Interfaces → Contract**. Fenced `m21-model` and `m21-interface` YAML blocks are parsed and enforced; surrounding prose remains human-owned. Skip empty sections. Existing free-form Markdown bodies remain valid.
### 3. Declare dependencies with feature tracking
Specs declare which features they use from their dependencies:
```markdown
---
name: persistence
description: SQLite database layer
group: infrastructure
tags: [database, storage]
depends_on:
- name: bootstrap
uses: [project-scaffolding, health-endpoint]
features: features/persistence/
---
The uses array references Feature: names from the parent spec's .feature files. This creates a traceable contract — you know exactly which capabilities each module relies on.
| Field | Required | Description |
|---|---|---|
name |
Yes | Unique identifier for the spec |
description |
No | Short summary shown in the info panel |
group |
No | Logical grouping — specs in the same group are visually clustered |
tags |
No | Array of tags for categorization |
depends_on |
No | Dependencies — simple strings or objects with name and uses |
features |
No | Path to a directory of .feature files |
Simple (backwards compatible):
depends_on:
- bootstrap
- configRich (with feature references):
depends_on:
- name: bootstrap
uses: [project-scaffolding]
- name: persistence
uses: [data-storage, query-interface]Mixed:
depends_on:
- name: persistence
uses: [data-storage]
- server-apiFeatures provide executable examples of observable interface behavior or evidence-backed guarantees; they do not replace model or interface definitions. Feature names must be kebab-case:
Feature: project-scaffolding
The project compiles and all tooling works from a fresh checkout.
Scenario: Clean build with zero warnings
Given a fresh clone of the repository
When I run the build command
Then the build succeeds# Start the dev server (default: http://localhost:3333)
npx @moejay/m21 ./spec/
# Auto-create the spec directory
npx @moejay/m21 ./spec/ -y
# Custom port
npx @moejay/m21 ./spec/ --port 4000
# Export a static HTML file instead
npx @moejay/m21 ./spec/ --output graph.html
# Overlay test results (auto-detected from results/, reports/, test-results/ when omitted)
npx @moejay/m21 ./spec/ --results results/cucumber.jsonM21 never runs your tests — it ingests a test report and overlays the outcomes. Two formats are accepted, auto-detected by shape:
- Cucumber JSON — emitted by every Gherkin runner (
cucumber-js,cucumber-jvm,behave,cucumber-ruby,godog, Reqnroll,cucumber-rs), so it stays language-agnostic. - Jest / vitest JSON — the
--reporter=jsonoutput common across the JS ecosystem. Forvitest-cucumber/jest-cucumberruns theFeature:/Scenario:titles join directly; for plaindescribe/itsuites the top-leveldescribeis the feature and theitis the scenario.
# vitest / jest example
npx vitest run --reporter=json --outputFile=results/vitest-results.json
npx @moejay/m21 ./spec/ # auto-detectedResults join onto specs by feature name and scenario name: each node is ringed green (all passing), red (any failing), or amber (pending/skipped) and shows a passed/total count (e.g. 15/19) inside the circle, and the side panel shows per-scenario ✓/✗ pills plus a passed / total summary. A legend appears whenever any spec has test data. In dev-server mode the overlay live-updates as the report file changes.
- Interactive graph — D3 dependency visualization with zoom, pan, and drag
- Group clustering — Specs in the same group are visually clustered with colored hulls
- Feature tracking — See which features flow along each dependency edge
- Test results overlay — Point M21 at a Cucumber JSON or Jest/vitest JSON report and the graph colours each node by pass/fail, shows a
passed/totalcount inside every circle, and lists per-scenario status pills in the side panel. Click a scenario to inspect step statuses and, for source-backed Jest/vitest reports, the Given/When/Then definition snippets. Auto-detected fromresults/,reports/,test-results/, or pass--results <file>. Live-updates as tests re-run - Tree-and-groups layout — Default architecture view combines dependency depth with group clustering. Nodes start unlocked for manual positioning, group labels drag whole groups, and checkboxes can lock nodes or reverse the tree direction
- Side panel — Click any node to see description, group, tags, dependencies with used features, rendered markdown body, and Gherkin scenarios
- Enforceable models — Parse and validate language-neutral
m21-modelandm21-interfaceblocks, including cross-spec references - Schema export — Export normalized contracts or JSON Schema for runtime validation, generators, and CI
- Ordered contract stack — Evolve data model → semantic interfaces → architectural spec → executable features before implementation
- Composable specs — Specs own clear model and interface contracts; dependencies identify the behavioral capabilities they consume
- Live reload — Dev server watches your spec and feature files, pushes changes via SSE instantly
- Inline editing — Edit spec bodies and feature files directly in the browser (dev server mode)
- Static export — Generate a self-contained HTML file with
--output - Version check — Notifies you when a new version is available
npx @moejay/m21 <directory> Start dev server with live reload (default)
npx @moejay/m21 <directory> --output <file> Save graph to a static HTML file
npx @moejay/m21 <directory> --port <number> Custom port for dev server (default: 3333)
npx @moejay/m21 <directory> --results <file> Overlay Cucumber, Jest, or vitest JSON test results
npx @moejay/m21 <directory> -y Auto-create spec directory if missing
npx @moejay/m21 --help Show help
Each subcommand also accepts --json for machine-readable output.
npx @moejay/m21 list <directory> Print all specs (group, dep count, feature count)
npx @moejay/m21 show <directory> <name> Print one spec's full info — deps, dependents, features, body
npx @moejay/m21 features <directory> [<name>] List features (across all specs, or scoped to one)
npx @moejay/m21 deps <directory> <name> Print forward + reverse dependency tree
npx @moejay/m21 validate <directory> Lint graph, model, interface, and feature contracts
npx @moejay/m21 model <directory> [<name>] Export normalized models and interfaces
npx @moejay/m21 schema <directory> [<name>] Export entity contracts as JSON Schema
Already have a codebase? Install the skills (npx skills install moejay/m21) and use m21-init to analyze your existing code. It discovers model ownership first, semantic interfaces second, architectural concerns and dependencies third, and executable features last.
M21 is itself spec-driven: every module has a spec in spec/ and Gherkin scenarios in features/, and every feature file is bound to executable tests (via @amiceli/vitest-cucumber), so the scenarios are the contract.
npm test # run the suite once; also writes results/vitest-results.json
npm run test:watch
npm run docs:graph # refresh docs/graph-export.html, including test overlay when results exist
npm run docs:graph:check # CI check: generated static export must match docs/graph-export.html
npm run setup:githooks # install the local pre-commit hook for static export refreshesnpm test emits a results/vitest-results.json report, so you can dogfood the overlay on M21 itself:
npm test
npx . ./spec/ # the graph lights up green with each module's passed/total count
npm run docs:graph # writes the same overlay into docs/graph-export.html for GitHub PagesRequires Node ≥ 20.
M21 can be used as a library. Entry points are exposed through the package exports map:
import { parseSpecDirectory } from "@moejay/m21"; // parse specs + features
import { generateHTML } from "@moejay/m21/generator"; // render the graph HTML
import { createM21Server } from "@moejay/m21/server"; // run the dev server
import { parseResultsFile, mergeResults } from "@moejay/m21/results"; // overlay test results
import { analyzeGraph } from "@moejay/m21/cycles"; // dependency + cycle analysis
import { validateContractRegistry, generateJsonSchema } from "@moejay/m21/contracts";
// Parse a spec directory and render a self-contained graph.
const specs = await parseSpecDirectory("./spec", { projectRoot: "." });
const issues = validateContractRegistry(specs);
const { schema } = generateJsonSchema(specs);
const html = generateHTML(specs);
// Overlay Cucumber/vitest results onto the specs, then render.
const lookup = await parseResultsFile("./results/cucumber.json");
if (lookup) mergeResults(specs, lookup);
const withStatus = generateHTML(specs);
// Start the dev server (loopback-only by default).
const server = await createM21Server({ specDir: "./spec", port: 3333 });
console.log(server.address);
await server.close();The default import (@moejay/m21) is the parser. generateHTML produces a fully self-contained document — the rendering libraries are inlined, so it works offline.
MIT