ruledoc
v0.2.1
Published
Business rules documentation generator — extract @rule() annotations from code and generate Markdown, JSON and HTML docs.
Maintainers
Readme
ruledoc
┌─┐┬ ┬┬ ┌─┐┌┬┐┌─┐┌─┐
├┬┘│ ││ ├┤ │││ ││
┴└─└─┘┴─┘└─┘─┴┘└─┘└─┘Extract @rule() annotations from your codebase and generate living business rules documentation.
Business rules are scattered everywhere — constants, guards, validators, middleware. ruledoc finds them all and produces a structured, searchable doc.
Try it
npx ruledocZero install, zero config. Scans ./src and generates your doc instantly.
Install
Once you're sold, add it as a dev dependency:
npm install -D ruledocRequires Node.js 20.19.0 or later.
Quick start
1. Annotate your code
// @rule(billing.plans, critical): Free plan is limited to 50 links
export const FREE_PLAN_LINK_LIMIT = 50;
// @rule(auth.session): Session expires after 24h of inactivity
const SESSION_TTL_MS = 24 * 60 * 60 * 1000;
// @rule(redirect.chain, FLEW-234, warning): Max 5 hops to prevent loops
export const MAX_REDIRECT_CHAIN = 5;2. Run npx ruledoc
3. Get your doc — BUSINESS_RULES.md + BUSINESS_RULES.json at the root.
Annotation format
// @rule(scope): Description
// @rule(scope.sub): With subscope
// @rule(scope.sub, critical): With severity
// @rule(scope.sub, critical, JIRA-123): With ticket
// @rule(scope.sub, JIRA-123, critical): Order doesn't matter
// @rule(scope.sub, JIRA-123): Ticket only — severity defaults to info| Param | Required | Description |
|-------|----------|-------------|
| scope | ✅ | Dotted path like billing.plans, auth.session |
| severity | ❌ | One of info (default), warning, critical — or custom |
| ticket | ❌ | Any string like JIRA-123, FLEW-234, #42 |
Smart detection: severity and ticket can be in any order. ruledoc matches against the known severity list and figures out which is which.
Warnings
ruledoc catches annotation issues without blocking:
◆ ruledoc 28 rules · 5 scopes · 7 critical · 5 warning
⚠ auth/session.ts:4 — unknown severity "crtical", did you mean "critical"? (defaulting to info)
⚠ billing/limits.ts:12 — empty descriptionWarnings appear in the terminal, and are included in the generated Markdown and JSON output.
Output formats
| Format | Flag | Description |
|--------|------|-------------|
| Markdown | md | Grouped by scope, TOC, summary table, severity badges, warnings |
| JSON | json | Structured tree for tooling, includes warnings |
| HTML | html | Standalone dark-themed page with search, scope/severity filters |
| Context | context | Flat, token-efficient text for LLM prompts (CLAUDE.md, .cursorrules) |
ruledoc --format md,json,html,contextContext format
The context format produces a flat text file optimized for injecting into LLM prompts. Rules are sorted by severity (critical first) and output one per line with file references.
ruledoc --format contextOutput (BUSINESS_RULES.context):
# Business Rules (auto-generated by ruledoc — do not edit)
# 28 rules · 5 scopes · Generated 2026-03-16
[critical] billing.plans: Free plan is limited to 50 links (billing/limits.ts:1)
[warning] redirect.chain: Max 5 hops to prevent loops (redirect/handler.ts:12)
[info] auth.session: Session expires after 24h of inactivity (auth/session.ts:3)Wire it into your LLM context file:
cat BUSINESS_RULES.context >> CLAUDE.mdConfigure filtering and limits via ruledoc.config.json:
{
"formats": ["md", "json", "context"],
"context": {
"maxRules": 50,
"severities": ["critical", "warning"]
}
}Interactive diff
Each run compares with the previous JSON output and shows what changed:
◆ ruledoc 28 rules · 5 scopes · 7 critical · 5 warning
+ Refunds must be processed within 48h [critical] billing.refund → billing/limits.ts:22
- Old trial rule [info] billing.trial → billing/limits.ts:8
→ ./BUSINESS_RULES.md
→ ./BUSINESS_RULES.jsonCI check
ruledoc --checkExits with code 1 if the generated doc differs from the existing file. Combine with --quiet for clean CI output:
ruledoc --check --quiet# .github/workflows/rules.yml
- run: npx ruledoc --check --quietRule protection
Prevent accidental removal of critical rules:
ruledoc --check --protect criticalWhen a protected rule is removed, --check exits with code 2:
✗ [critical] billing.plans: Free plan limited to 50 items
was in billing/limits.ts
✗ ruledoc: 1 critical rule(s) removed — build blocked
To allow removal, use --allow-removal or add a @rule-removed() comment.You can protect multiple severity levels:
ruledoc --check --protect critical,warningOr in config:
{
"protect": ["critical"]
}Acknowledging removals
When you intentionally remove a protected rule, add a @rule-removed() annotation to acknowledge it:
// @rule-removed(billing.plans, JIRA-456): Migrated to config serviceThis tells ruledoc the removal was deliberate and unblocks the build.
Bypassing protection
To skip all protection checks (e.g. during a large refactor):
ruledoc --check --protect critical --allow-removalHistory
ruledoc tracks removed rules as tombstones in BUSINESS_RULES_HISTORY.json. This gives you a changelog of what rules existed and when they were removed.
Removed rules also appear in the Markdown output under a "Removed rules" section, with their removal date and acknowledgment details (if any).
To disable history tracking:
ruledoc --no-historyOr in config:
{
"history": false
}Pro features
Rule protection, history, and context export work for free on projects with fewer than 50 rules — no license needed. Beyond 50 rules, a Pro license unlocks everything.
| Feature | < 50 rules | 50+ rules |
|---------|:-:|:-:|
| Scanning, Markdown, JSON, HTML | ✅ Free | ✅ Free |
| Interactive diff | ✅ Free | ✅ Free |
| CI check (--check) | ✅ Free | ✅ Free |
| Rule protection (--protect) | ✅ Free | 🔑 Pro |
| History / tombstones | ✅ Free | 🔑 Pro |
| Context format (--format context) | ✅ Free | 🔑 Pro |
| GitHub Action PR comments | ✅ Free | 🔑 Pro |
Set your license key via environment variable (recommended for CI) or config:
export RULEDOC_LICENSE=RULEDOC-XXXX-XXXX-XXXX{
"license": "RULEDOC-XXXX-XXXX-XXXX"
}GitHub Action
Use the built-in composite action to automate rule checking and PR comments:
# .github/workflows/rules.yml
name: Rules
on:
pull_request:
branches: [main]
jobs:
ruledoc:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- uses: fmekkaoui/ruledoc/action@main
with:
protect: critical
allow-removal: 'false'The action will:
- Run
ruledoc --checkto verify docs are up to date - Post a PR comment summarizing added/removed rules
- Fail the check if protected rules are removed without acknowledgment
Ignore & Exclude
ruledoc uses a three-layer ignore system to keep noise out of your docs:
Layer 1 — .gitignore (on by default): Automatically respects your project's .gitignore patterns so build output, node_modules, and vendor code are never scanned.
Layer 2 — Test file exclusion (on by default): Skips *.test.*, *.spec.*, and __tests__/** files since test annotations are usually not real business rules.
Layer 3 — Extra patterns: Add custom glob patterns to exclude additional files.
Config
{
"extraIgnore": ["**/generated/**", "**/vendor/**"],
"ignoreTests": true,
"gitignore": true
}CLI flags
ruledoc --extra-ignore "**/generated/**,**/vendor/**"
ruledoc --no-ignore-tests # include test files
ruledoc --no-gitignore # don't respect .gitignoreThe --extra-ignore flag appends to any patterns set in the config file (it never replaces them). The existing --ignore flag for directory names continues to work as before.
CLI
ruledoc [options]
Options:
-s, --src <dir> Source directory (default: ./src)
-o, --output <file> Output file (default: ./BUSINESS_RULES.md)
-f, --format <formats> md,json,html,context (default: md,json)
-e, --extensions <exts> .ts,.vue,.py (default: .ts,.tsx,.js,.jsx,.mjs,.cjs,.vue,.svelte)
--ignore <dirs> Directories to skip
--extra-ignore <globs> Additional glob patterns to exclude (comma-separated)
--no-ignore-tests Include test files (*.test.*, *.spec.*, __tests__)
--no-gitignore Don't respect .gitignore patterns
-t, --tag <name> Annotation tag (default: rule → @rule(...))
--severities <list> Severity levels, first is default (default: info,warning,critical)
-p, --pattern <regex> Custom regex (overrides --tag)
-c, --check CI mode: exit 1 if docs are stale
--protect <severities> Block removal of rules at these severity levels (comma-separated)
--allow-removal Bypass all protection checks
-q, --quiet Suppress all output except errors
--no-history Don't track removed rules in history file
--verbose List every rule found
--init Setup guide and example config
-h, --help Show this help
-v, --version Show versionQuiet mode
--quiet suppresses all terminal output except errors. Useful in CI or when piping.
ruledoc --quietVerbose mode
--verbose lists every rule found, grouped by scope:
◆ ruledoc 28 rules · 5 scopes · 7 critical
Auth (7)
● [critical] Session expires after 24h → auth/session.ts:3
● Password must be 8+ chars → auth/password.ts:1
...
Billing (5)
● [critical] Free plan limited to 50 links → billing/limits.ts:1
● [warning] Trial lasts 14 days FLEW-102 → billing/limits.ts:7
...Config
Config is loaded in this order (later wins):
ruledoc.config.json"ruledoc"field inpackage.json- CLI flags
Only JSON config files are supported — no JS/TS configs (no eval, no code execution).
{
"src": "./src",
"output": "./BUSINESS_RULES.md",
"formats": ["md", "json", "html"],
"tag": "rule",
"severities": ["info", "warning", "critical"],
"extensions": [".ts", ".tsx", ".js", ".jsx", ".vue"],
"extraIgnore": ["**/generated/**"]
}Or in package.json:
{
"ruledoc": {
"src": "./app",
"formats": ["md", "json", "html"]
}
}Config validation
ruledoc validates the entire config before running and gives clear error messages:
✗ Invalid config:
• unknown format "xml" — valid formats: md, json, html, context
• extension "ts" must start with a dot (e.g. ".ts")
• pattern is not a valid regex: Unterminated character classCustom tag
Use a different annotation name:
{ "tag": "bizrule" }Now ruledoc matches @bizrule(...) instead of @rule(...).
Custom severities
{ "severities": ["low", "medium", "high", "blocker"] }The first value is the default when severity is omitted.
Custom regex
For full control, provide a regex with two capture groups — (1) params inside parens and (2) description:
{
"pattern": "(?:\\/\\/|#)\\s*@business\\(([^)]+)\\)\\s*:\\s*(.+)"
}When pattern is set, it overrides tag.
Use with Turborepo
// package.json
{ "scripts": { "rules": "ruledoc" } }// turbo.json
{
"tasks": {
"rules": {
"inputs": ["src/**/*.ts", "src/**/*.tsx"],
"outputs": ["BUSINESS_RULES.md", "BUSINESS_RULES.json"]
}
}
}Dev server + rules watcher side by side:
turbo watch dev rules --filter=webProgrammatic API
Use ruledoc as a library in your own tools:
import { extractRules, resolveConfig, generateMarkdown } from "ruledoc";
const config = resolveConfig([], process.cwd());
const { rules, warnings } = extractRules(config);
const md = generateMarkdown(rules, warnings, config.src);Security
ruledoc is designed to be safe:
- No eval — config is loaded from JSON only, never executed as code
- No runtime deps — zero third-party dependencies in production
- Read-only scanning — source files are never modified
- No network — everything runs locally (Pro license validation is the only exception, and it's cached)
Example
See examples/saas-billing/ for a working demo with ~10 annotated rules across billing, auth, and notification modules.
License
MIT
