| Crates.io | dictator |
| lib.rs | dictator |
| version | 0.16.2 |
| created_at | 2025-12-04 07:18:45.720683+00 |
| updated_at | 2026-01-23 11:01:12.156722+00 |
| description | Dictator - structural linter framework with native and WASM decree support |
| homepage | https://github.com/seuros/dictator |
| repository | https://github.com/seuros/dictator |
| max_upload_size | |
| id | 1966087 |
| size | 304,665 |
Fast structural enforcement, before the linters.
Dictator is a pre‑linter structural gatekeeper for your codebase. It doesn't replace RuboCop, ESLint, or Clippy — it runs before them. While those tools analyze code quality, Dictator enforces the boundaries: file structure, naming conventions, ordering, and basic hygiene.
Canonical lore (Timeline 7) says Dictator was manifested in Rust 60 and then backported to Rust 1.91. In this timeline, it is a conventional Rust crate that you build and run with a modern stable toolchain.
Think of it as border control for your codebase: everything must satisfy basic structural discipline before the expensive tools run.
.dictate.toml.Install the published crate (fastest):
cargo install dictator
To force the latest release and respect the lockfile:
cargo install dictator --locked
Download and install a pre-built binary for your platform:
curl -fsSL https://raw.githubusercontent.com/seuros/dictator/master/scripts/install.sh | bash
This installs dictator to ~/.local/bin by default. Make sure this directory is on your PATH:
export PATH="$HOME/.local/bin:$PATH"
Installation options:
--prefix <dir> — Install to a custom directory--version <tag> — Install a specific release version--help — Show all optionsExample:
curl -fsSL https://raw.githubusercontent.com/seuros/dictator/master/scripts/install.sh | bash -- --prefix ~/.cargo/bin
Requires Rust 1.91+:
cargo install --git https://github.com/seuros/dictator
Or from this repository:
cargo install --path crates/dictator
Expensive linters are slow. Running RuboCop on a large Rails codebase takes minutes. ESLint on a monorepo crawls.
LLMs generate structural chaos. Claude creates 300 files. All compile. All have wrong structure: inconsistent naming, files with 2000 lines, frontmatter fields in random order, private methods in wrong positions.
You need fast boundary checks first. Catch structural violations in milliseconds, not minutes. Then run the expensive linters on code that already passes basic discipline.
File boundaries:
Naming conventions:
Ordering discipline:
Basic hygiene:
#foo → # foo)What Dictator does NOT do:
Dictator checks structure. The VIPs (RuboCop, ESLint) check quality and context.
Speed. Dictator runs structural checks in milliseconds. Catch obvious violations instantly without waiting for heavy linters.
LLM workflows. When Claude generates 300 files, Dictator validates structure immediately:
Claude generates code → Dictator checks structure → Fix → RuboCop checks quality → Done
CI optimization. Fail fast on structural violations before expensive linter passes:
git push → Dictator (2ms) → ❌ File too long → Fix locally
git push → Dictator (2ms) → ✓ → RuboCop (45s) → ✓ → Deploy
Monorepo enforcement. One binary, one config, all languages. Consistent structural rules across Ruby services, TS frontends, YAML configs.
Git-aware filtering. Dictator enforces .gitignore boundaries. Your build artifacts, dependencies, and editor cruft don't exist to Dictator. target/, node_modules/, .DS_Store—invisible.
Dictator uses the ignore crate (from ripgrep) for full git semantics:
.gitignore files in the hierarchy (parent dirs, subdirs).git/info/exclude (per-repo exclusions)core.excludesFile)Outside git repositories: Dictator still works. It just won't find gitignore files to respect. If you're not in a git repo, every file is visible.
Overriding gitignore: Explicit file paths bypass gitignore. dictator lint target/debug/foo.rs lints that file even if target/ is ignored. Directories respect gitignore. Single files don't negotiate.
Territorial enforcement. Dictator can critique any file in the filesystem. dictator lint /etc/nginx.conf reports violations. dictator dictate /etc/nginx.conf modifies the file—you're in control via CLI.
Via MCP (AI assistants), destructive operations are restricted to the working directory and require a git repository. This prevents your helpful-but-misaligned AI from "fixing" /etc/passwd or reformatting your entire home directory because it detected trailing whitespace. You won't get "Oops 😅, my bad. Let me reformat your computer." Dictator critiques other states' policies but won't intervene outside project boundaries when an LLM is driving.
Rules are meant to be broken. That's why you have 1000 linters with 10000 rules and everyone disables half of them.
Decrees are absolute. The Dictator does not negotiate. Your file ends with a newline or it doesn't pass. Your methods are ordered correctly or they aren't. No "warn", no "suggestion", no "consider maybe perhaps".
This is structural discipline, not style advice.
Your blog posts need consistent frontmatter. LLMs swap field order randomly:
---
pubDate: 2025-12-01
title: "My Post"
slug: my-post
---
Dictator enforces order:
---
title: "My Post"
slug: my-post
pubDate: 2025-12-01
---
Compiles either way. Dictator doesn't tolerate the first. Structure is not negotiable.
LLMs generate comments without proper spacing. Dictator catches it:
#bad comment # ❌ Missing space after #
# good comment # ✓ Correct
Dictator auto-fixes #bad → # bad. RuboCop checks style. Dictator checks structure.
.dictate.toml (decree configuration)
↓
dictator (Rust CLI, single binary)
↓
dictator-core (wasmtime, parallel execution)
↓
dictator-decree-abi (shared ABI: Plugin/Diagnostic types)
↓
WASM decrees (decree.supreme, decree.ruby, decree.golang, ...)
↓
Diagnostics (JSON/SARIF/stdout)
Decree-driven enforcement. .dictate.toml declares which decrees are active. Dictator loads corresponding WASM components and runs them in parallel.
All WASM. Every decree is a WASM component:
decree.supreme: Universal structure (spacing, whitespace, line endings)decree.<language>: Language-specific structure (method ordering, naming, conventions)Why WASM:
Decree Versioning. Every decree exports metadata including ABI version:
Speed first. No heavy AST parsing. Pattern matching, line counting, regex. Fast enough for watch mode.
At a high level:
You point Dictator at some paths.
dictator lint ., dictator watch sandbox/, or whatever mess your LLM or your team just hallucinated.
It reads .dictate.toml.
This is the decree book. It decides:
decree.supreme, decree.ruby, decree.typescript, …)It walks the filesystem. Dictator does a fast pass over the files you pointed at: no ASTs, no type-checking, just “what files exist, what are their extensions, how big are they”.
It assigns each file to decrees.
decree.supreme (whitespace, line endings, length, final newline).*.rb → decree.ruby, *.ts → decree.typescript, *.go → decree.golang, etc.It runs all decrees in parallel as WASM. Each decree is a sandboxed WASM component. Dictator:
It surfaces diagnostics. Dictator reports:
(Optional) It fixes what it can. In auto-fix modes, Dictator will happily:
The entire pipeline is “cheap first, expensive later”: Dictator slaps your structure into shape, then your quality linters and type-checkers show up once the room is already clean.
Dictator reads .dictate.toml, loads the configured decrees (WASM components), and runs enforcement.
# Initialize config (creates .dictate.toml)
dictator occupy
dictator init # alias
# Lint files (read-only, reports violations)
dictator lint src/
dictator stalint src/ # alias
# Fix structural issues (trailing whitespace, CRLF→LF, final newline)
dictator dictate src/
dictator kjr src/ # alias
# Watch mode (re-check on every save)
dictator watch .
# Specify custom config
dictator --config .dictate.dev.toml lint src/
| Command | Alias | Mode | Description |
|---|---|---|---|
occupy |
init |
Setup | Creates .dictate.toml with default config |
lint |
stalint |
Read-only | Reports violations without modifying files |
dictate |
kjr |
Destructive | Fixes whitespace, line endings, final newline |
watch |
- | Read-only | Monitors files and reports on change |
Watch mode monitors file changes and validates instantly:
dictator watch .
LLM workflow:
You: "Claude, generate user auth module"
Claude: *creates 15 files*
dictator stalint . → ❌ auth_helper.rb has trailing whitespace
dictator dictate . → 🔧 Fixed 3 files
dictator stalint . → ✓ All structural checks pass
RuboCop: *runs expensive quality checks*
Human workflow:
Save file → dictator stalint (50ms) → dictator dictate → Done
Dictator reports. You fix. Pre-commit hooks can auto-fix if you want automation.
.dictate.toml:
[decree.supreme]
# Universal structural rules (all files, all languages)
trailing_whitespace = "deny"
tabs_vs_spaces = "spaces"
tab_width = 2
final_newline = "require"
line_endings = "lf"
max_line_length = 120
# Ignore specific rules for specific files/extensions
# (Makefiles require tabs; Markdown code blocks may contain tabs)
[decree.supreme.ignore.tab-character]
filenames = ["Makefile", "GNUmakefile", "makefile"]
extensions = ["md", "mdx"]
[decree.ruby]
# Ruby-specific structural enforcement
max_lines = 300
[decree.golang]
# Go uses tabs, not spaces — overrides decree.supreme
tabs_vs_spaces = "tabs"
max_lines = 500
[decree.frontmatter]
# Frontmatter ordering (Markdown, Astro, etc.)
order = ["title", "slug", "pubDate", "tags"]
required = ["title", "slug"]
Language overrides. Language decrees can override supreme settings. Go files use tabs even when supreme says spaces. The override applies per-file based on extension.
Rule ignores. Any decree can ignore specific rules for specific filenames/extensions via [decree.<name>.ignore.<rule>]. This is useful for cases like Makefile (tab-indented recipes) or documentation files that embed code blocks.
Decrees are WASM components. Each decree enforces structural boundaries for its domain. decree.supreme applies universally. Language decrees handle specific conventions.
decree.supreme (universal):
decree.ruby:
#foo → # foo)More decrees coming. Each language gets its own WASM decree for structural enforcement.
There is no roadmap. The Dictator does not make promises.
Contributions are welcome if accompanied by:
See decree.kjr (Kim Jong Rails) in DECREES.md for a complete example of building a custom decree using only the dictator-decree-abi crate.
The KJR decree demonstrates:
Plugin traitDiagnostic violations.dictate.toml configIn Timeline 7, everything runs on KIMFS (Kim File System). Files cannot be structurally unsound — the filesystem itself rejects malformed structure at write time. Trailing whitespace? Denied. Wrong line endings? Denied. Methods in wrong order? Believe it or not, denied. Dictator is not a linter there, it's a fundamental law of physics.
In this timeline, files are not sentient and Dictator is a normal CLI binary. It only starts enforcing structure once it sees a configuration file:
.dictate.toml — not YAML, not XML. TOML is the contract.Once configuration exists, Dictator has two operational modes:
dictator watch monitors your files and reports structural violations as you edit.dictator lint walks your tree and reports every violation in a single pass.AI coding assistants like Claude Code and OpenAI Codex can use Dictator not to “align” the LLM itself, but to align the files the LLM produces. Through MCP, they get two tools:
stalint (Static Lint). Despite the name, it doesn’t “lint” in the classic sense — it just reports structural violations: trailing whitespace, line endings, file size, etc. Read-only. No surprises.dictator. This one actually does things:
kimjongrails mode, it fixes native structural errors (LF/CRLF, trailing spaces, missing final newlines, etc.).supremecourt mode, it escalates to whatever primitive linters you already trust (RuboCop, ESLint, Prettier, Ruff, …) as defined in .dictate.toml.From the AI’s point of view, Dictator is the one calling the shots: external linters do the heavy lifting, Dictator orchestrates them, and then takes the credit.
Dictator includes an MCP (Model Context Protocol) server so these tools are discoverable and callable from compatible AI coding assistants.
Add to your Claude Code MCP configuration (~/.claude/settings.json):
{
"mcpServers": {
"dictator": {
"command": "/path/to/dictator"
}
}
}
MCP mode is auto-detected when stdin is a pipe and no CLI arguments are provided.
| Tool | Description | Mode | Availability |
|---|---|---|---|
stalint |
Check files for structural violations (trailing whitespace, tabs/spaces, line endings, file size). Returns diagnostics without modifying files. Can check any path. | Read-only | Always |
dictator |
Auto-fix structural issues. Mode kimjongrails fixes whitespace/newlines. Mode supremecourt runs configured external linters from .dictate.toml. |
Destructive | Git repos only |
stalint_watch |
Watch paths for file changes. Runs stalint every 60s when changes detected. Restricted to cwd. | Read-only | Always |
Tool modes are dynamic:
kimjongrails: Always available (basic structural fixes)supremecourt: Only available if decrees have configured linters (e.g., decree.ruby.linter.command = "rubocop")From your AI assistant:
Check sandbox/ for structural violations
The assistant will call stalint and report violations with file, line, column, rule, and message.
To auto-fix:
Fix structural issues in sandbox/
The assistant will call dictator which fixes trailing whitespace, missing final newlines, and CRLF→LF conversions.
Using supremecourt mode:
If you have configured linters in .dictate.toml, the assistant can run them via supremecourt mode:
Fix structural issues in sandbox/ using supremecourt mode
The MCP server will:
.rb → ruby, .ts → typescript, etc.)Multi-layer protection against destructive operations:
dictator tool only exposed when .git directory existsdictator, stalint_watch) reject paths outside cwdcodex/sandbox-state capability and hides destructive tools in read-only modesupremecourt mode only available if external linters are installedExamples:
/tmp (no git) → LLM only sees stalint (read-only)dictator but it rejects /home or /etcsupremecourt mode hidden from LLMNote: As of 2025, some MCP clients don't send sandbox notifications. See Claude Code issues #3315, #3174, #3141 for related discussion.
The MCP server reads linter configurations from .dictate.toml:
[decree.ruby.linter]
command = "rubocop"
[decree.typescript.linter]
command = "biome" # or "eslint" for existing ESLint configs
[decree.python.linter]
command = "ruff"
[decree.golang.linter]
command = "gofmt"
Dictator controls the args. You only specify the command. Dictator adds the appropriate flags for auto-fix and JSON output parsing:
rubocop → -A --format jsonbiome → lint --write --reporter jsoneslint → --fix --format jsonruff → check --fix --output-format jsongofmt → -w (lists changed files, then fixes)clippy → --fix --allow-dirty --message-format jsonHow it works:
.rb → ruby, .ts/.js → typescript, .py → python, .go → golang, etc.)supremecourt mode only appears in tool list if at least one decree has a configured linter installedSecurity: Linters run as subprocesses with provided file paths as arguments. User is responsible for ensuring configured commands are safe.
Pre-linter CI stage:
- name: Structural checks (fast)
run: dictator lint . # Fails fast if structure is wrong
- name: Quality checks (slow)
run: bundle exec rubocop
Pre-commit workflow:
# .pre-commit-config.yaml
- repo: local
hooks:
- id: dictator
name: Structural enforcement
entry: dictator lint
language: system
pass_filenames: true
LLM code generation guard:
AI generates code → dictator stalint → dictator dictate → Quality linters run
Monorepo boundary enforcement: One config, all languages. Ruby services, TS apps, YAML configs—same structural rules.
Development speed:
Instant feedback on saves. dictator stalint reports in milliseconds. dictator dictate fixes them.
MIT
Dictator: Snitches on your structure. Takes all the credit.