A new developer joins the team. They're reviewing the billing module and see this:
export const FREE_PLAN_DAILY_LIMIT = 100;
They ask: "Why 100? Who decided this? Can I change it?"
Nobody knows. The constant was added 18 months ago by someone who left. There's no Notion page, no Confluence doc, no comment explaining the reasoning. The answer lives in a Slack thread that's long gone.
This happens everywhere. Business rules — the most important logic in your app — are the least documented. They hide as constants, guard clauses, magic numbers, and config values scattered across dozens of files.
The real cost
When business rules are invisible:
- Bugs happen silently. Someone changes a limit without realizing three other features depend on it.
- Onboarding is slow. New devs spend days asking "why does this work this way?"
- Compliance becomes painful. When an auditor asks "show me your business rules," you can't just grep for them.
- Documentation rots. Even if someone writes a wiki page, it goes stale the moment someone pushes a code change without updating it.
"Just use comments" doesn't cut it either. Comments aren't searchable, aren't structured, and there's no way to get a bird's-eye view of all the rules in your system.
What if documentation lived next to the code?
The idea is simple: annotate business rules right where they're implemented, in a structured format that a tool can extract.
// @rule(billing.plans, critical): Free plan is limited to 100 API requests per day
export const FREE_PLAN_DAILY_LIMIT = 100;
This is a regular comment — your code runs exactly the same. But now that rule is machine-readable: it has a scope (billing.plans), a severity (critical), and a description.
When you annotate your codebase this way, you get living documentation — docs generated from the code itself, so they can never go stale.
Introducing ruledoc
ruledoc is a CLI that scans your source files for @rule() annotations and generates structured documentation.
The workflow is three steps:
1. Annotate your code
// @rule(billing.plans, critical): Free plan is limited to 100 API requests per day
export const FREE_PLAN_DAILY_LIMIT = 100;
// @rule(auth.session): Session expires after 24 hours of inactivity
const SESSION_TTL_MS = 24 * 60 * 60 * 1000;
// @rule(billing.refunds, critical): Refunds must be requested within 30 days
export const REFUND_WINDOW_DAYS = 30;
2. Run it
npx ruledoc
Zero install, zero config. That's it.
3. Get your docs
ruledoc generates a BUSINESS_RULES.md grouped by scope, with a table of contents and summary table:
# Business Rules
> 10 rules · 3 scopes
## Summary
| Scope | Sub | Count | Critical | Warning |
|-------|-----|------:|---:|---:|
| Auth | Password | 2 | — | 1 |
| Auth | Session | 2 | 1 | 1 |
| Billing | Plans | 3 | 2 | — |
| Billing | Refunds | 1 | 1 | — |
## Billing
### Plans
- [critical] Free plan is limited to 100 API requests per day
billing/plans.ts:1
- [critical] Pro plan allows up to 10,000 API requests per day
billing/plans.ts:4
It also generates BUSINESS_RULES.json for tooling, and optionally a standalone HTML page with search and filters.
It catches your mistakes
ruledoc doesn't just extract — it validates. Misspell a severity?
// @rule(auth.session, crtical): Session expires after 24h
⚠ auth/session.ts:4 — unknown severity "crtical", did you mean "critical"? (defaulting to info)
It tracks what changed
Each run diffs against the previous output and shows what moved:
◆ ruledoc 28 rules · 5 scopes · 7 critical · 5 warning
+ Refunds must be processed within 48h [critical] billing.refund
- Old trial rule [info] billing.trial
So in your terminal, you immediately see which rules were added or removed since the last run.
CI integration
Add one line to your GitHub Actions workflow and ruledoc will fail the build if the docs are out of date:
- run: npx ruledoc --check --quiet
--check compares the generated output against the committed file and exits with code 1 if they differ. Combined with --quiet, you get clean CI output with no noise.
If you're using Turborepo, you can run ruledoc as a watched task alongside your dev server:
{
"tasks": {
"rules": {
"inputs": ["src/**/*.ts"],
"outputs": ["BUSINESS_RULES.md", "BUSINESS_RULES.json"]
}
}
}
turbo watch dev rules
The annotation format
The full syntax supports scopes, severity levels, and ticket references:
// @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
Severity and ticket can be in any order — ruledoc figures out which is which. You can also customize the tag name, define your own severity levels, or provide a full custom regex.
Why not X?
Why not JSDoc / TSDoc? Those are for API documentation, not business rules. They describe what a function does, not why a business decision was made.
Why not a wiki? Wikis go stale. Business rules in code stay next to their implementation. When the code changes, the annotation changes with it — or --check catches the drift.
Why not ADRs? Architecture Decision Records are great for high-level decisions. @rule() is for the specific, concrete rules that live in code — the limits, the thresholds, the policies.
Get started
npx ruledoc --init
This creates a config file and shows you the annotation format. Then sprinkle @rule() comments across your codebase and run npx ruledoc.
Zero dependencies. Zero config. Works with TypeScript, JavaScript, Vue, Svelte.
GitHub: github.com/fmekkaoui/ruledoc
npm: npmjs.com/package/ruledoc
Top comments (0)