Minimal MDX content block compiler implementing the cspec reusable-block subset.
cspec lets you define named content blocks in MDX files, reference those blocks from other MDX files, and compile everything to plain Markdown. It is useful for prompts, process docs, and instructions where a block of text should have one source of truth.
This repository is a Turbo monorepo. The CLI package is published as @modularcloud/cspec, the shared parser/compiler lives in @modularcloud/cspec-core, and the CodeMirror editor/app are local workspace packages.
npm install --save-dev @modularcloud/cspecRun with:
npx cspec buildCreate a source document:
import { S } from "@modularcloud/cspec"
<S id="writing">
Writing requirements.
<S id="writing.beSpecific">
Be specific and avoid vague language.
</S>
<S id="writing.askOnAmbiguity">
Ask for clarification when the requested behavior is ambiguous.
</S>
</S>Reference it from another document:
import PROCESS from "./PROCESS.cspec"
# Agent Instructions
Follow this rule:
{PROCESS.writing.beSpecific.$}Build:
npx cspec buildThe compiler writes:
PROCESS.cspec.ts
PROCESS.mdand the consuming Markdown becomes:
# Agent Instructions
Follow this rule:
Be specific and avoid vague language.npx cspec buildCompiles configured .mdx files, writes generated .cspec.ts modules, and writes .md files when Markdown emission is enabled.
npx cspec checkValidates the workspace and fails if generated files are stale.
npx cspec idsLists block IDs by source file.
npx cspec ids --tree
npx cspec ids --jsonPrints IDs as a tree or JSON.
Add cspec.config.ts:
import { defineConfig } from "@modularcloud/cspec"
export default defineConfig({
specs: {
docs: ["docs/**/*.mdx"],
prompts: ["prompts/**/*.mdx"]
},
markdown: {
emit: true
}
})Without a config file, cspec defaults to:
{
specs: {
default: ["**/*.mdx"]
},
markdown: {
emit: true
}
}Use <S> or <Spec> to define a block:
<S id="tone">
Use clear, direct language.
</S>Nested block IDs must be structural:
<S id="writing">
Writing rules.
<S id="writing.beSpecific">
Be specific.
</S>
</S>The parent block value includes its full subtree, so writing.$ includes both Writing rules. and Be specific.
Embed an external block with .$:
import RULES from "./RULES.cspec"
{RULES.writing.beSpecific.$}Embed a local block with ref("id").$:
import { ref } from "@modularcloud/cspec"
{ref("writing.beSpecific").$}For non-identifier-safe segments, use bracket access:
{RULES.writing["be-specific"].$}For each source file:
RULES.mdxcspec emits:
RULES.cspec.tsThe generated module has a nested object shape:
RULES.$
RULES.writing.$
RULES.writing.beSpecific.$These values are string literals where practical, so TypeScript can catch missing paths in consuming code.
cspec validates:
- missing block IDs
- invalid structural nesting
- invalid ID segments
- duplicate IDs
- unresolved imports
- unknown local or external references
- dynamic
ref(...)calls - embedding cycles
- stale generated output during
cspec check
npm install
npm test
npm run check
npm run devWorkspace packages:
packages/cspec-core: shared parser, validator, resolver, compiler, generated module builder, and workspace helpers.packages/cspec-cli:cspeccommand-line entrypoint and runtime re-export.packages/cspec-editor: CodeMirror extensions, diagnostics, block/reference index helpers, autocomplete, jump-to-definition, source/hybrid widgets, and source-preserving edit helpers.packages/cspec-app: Vite/React editor app with file tree, source mode, problems/references/build panels, save/check/build controls, and compiled Markdown preview.
The editor keeps the CodeMirror document as canonical cspec source text. No-op editor save uses the source buffer directly, and roundtrip tests assert byte-for-byte preservation.