dictator 0.16.3

Dictator - structural linter framework with native and WASM decree support
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
# The Dictator

**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.

## TL;DR

- Run **fast, structural checks** before slow linters.
- Enforce **file/line limits, naming, ordering, and hygiene**.
- Drive **LLM workflows**, **CI**, and **monorepos** with one config: `.dictate.toml`.
- Extend via **WASM decrees** and an **MCP server** for AI assistants.

## Installation

### Crates.io (Recommended)

Install the published crate (fastest):

```bash
cargo install dictator
```

To force the latest release and respect the lockfile:

```bash
cargo install dictator --locked
```

### Binary Release

Download and install a pre-built binary for your platform:

```bash
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:

```bash
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 options

Example:
```bash
curl -fsSL https://raw.githubusercontent.com/seuros/dictator/master/scripts/install.sh | bash -- --prefix ~/.cargo/bin
```

### Build from Source

Requires Rust 1.91+:

```bash
cargo install --git https://github.com/seuros/dictator
```

Or from this repository:

```bash
cargo install --path crates/dictator
```

### Runtime Requirements

- **Linux**: glibc 2.31+ (most modern distributions)
- **macOS**: 11.0+ (Intel or Apple Silicon)

## The Problem

**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.

## What Dictator Enforces

**File boundaries:**
- Maximum line count (ignoring comments)
- Trailing whitespace, tabs vs spaces
- Final newline presence
- Line ending consistency (LF vs CRLF)

**Naming conventions:**
- Folder names (kebab-case, snake_case, etc.)
- File names matching patterns
- Function/class name conventions

**Ordering discipline:**
- Frontmatter field order (slug after title, pubDate in position 2)
- Method visibility sections (public → protected → private)
- Import/require statement grouping
- YAML/TOML key ordering

**Basic hygiene:**
- No emojis in source code (structural noise)
- Copyright/license header presence (structural requirement)
- Comment formatting (`#foo``# foo`)

**What Dictator does NOT do:**
- Code quality analysis (use RuboCop, ESLint, Clippy for that)
- Context-dependent enforcement (focus markers, risky patterns—might be legitimate)
- Type checking (use your language's type system)
- Complexity metrics (use dedicated tools)
- Performance analysis

Dictator checks structure. The VIPs (RuboCop, ESLint) check quality and context.

## Why This Matters

**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:
- All `.gitignore` files in the hierarchy (parent dirs, subdirs)
- `.git/info/exclude` (per-repo exclusions)
- Global gitignore config (`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.

## Why Decrees, Not Rules?

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.

## Example: Frontmatter Order

Your blog posts need consistent frontmatter. LLMs swap field order randomly:

```yaml
---
pubDate: 2025-12-01
title: "My Post"
slug: my-post
---
```

Dictator enforces order:
```yaml
---
title: "My Post"
slug: my-post
pubDate: 2025-12-01
---
```

Compiles either way. Dictator doesn't tolerate the first. Structure is not negotiable.

## Example: Ruby Comment Hygiene

LLMs generate comments without proper spacing. Dictator catches it:

```ruby
#bad comment    # ❌ Missing space after #
# good comment  # ✓ Correct
```

Dictator auto-fixes `#bad` → `# bad`. RuboCop checks style. Dictator checks structure.

## Architecture

```
.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:**
- Sandboxed (decrees can't corrupt Dictator)
- Distributable (share company-specific decrees)
- Extensible (add new languages without rebuilding)
- Isolated (no dependency conflicts)

**Decree Versioning.** Every decree exports metadata including ABI version:
- Dictator validates decree compatibility at load time
- Incompatible decrees fail fast with clear errors
- Pre-1.0: decrees built with different ABI versions won't load
- Future-proof: supports API evolution (fix(), streaming, config at lint-time)

**Speed first.** No heavy AST parsing. Pattern matching, line counting, regex. Fast enough for watch mode.

## How Dictator Works (You Press Enter, It Starts Yelling)

At a high level:

1. **You point Dictator at some paths.**
   `dictator lint .`, `dictator watch sandbox/`, or whatever mess your LLM or your team just hallucinated.

2. **It reads `.dictate.toml`.**
   This is the decree book. It decides:
   - which decrees are enabled (`decree.supreme`, `decree.ruby`, `decree.typescript`, …)
   - what the limits are (max lines, allowed line endings, naming rules, etc.)

3. **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”.

4. **It assigns each file to decrees.**
   - Every file is judged by **`decree.supreme`** (whitespace, line endings, length, final newline).
   - Language files get additional judges: `*.rb``decree.ruby`, `*.ts``decree.typescript`, `*.go``decree.golang`, etc.

5. **It runs all decrees in parallel as WASM.**
   Each decree is a sandboxed WASM component. Dictator:
   - feeds it the file contents + config
   - waits for diagnostics (violations) to come back
   - never lets decrees touch your filesystem or spawn surprise subprocesses

6. **It surfaces diagnostics.**
   Dictator reports:
   - file + line + column
   - which decree complained
   - a short, rude description of what you did wrong (trailing whitespace, file too long, wrong visibility order, etc.)

7. **(Optional) It fixes what it can.**
   In auto-fix modes, Dictator will happily:
   - strip trailing whitespace
   - normalize line endings
   - add missing final newlines
   and then leave the harder, semantic work to your “real” linters.

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.

## Usage

Dictator reads `.dictate.toml`, loads the configured decrees (WASM components), and runs enforcement.

```bash
# 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: Real-Time Enforcement

Watch mode monitors file changes and validates instantly:

```bash
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.

## Configuration: Decrees

`.dictate.toml`:
```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.

## Current Decrees

**`decree.supreme`** (universal):
- Trailing whitespace detection
- Tab vs space enforcement
- Missing final newline
- Line ending consistency (LF/CRLF)
- Max line length

**`decree.ruby`**:
- File line count limits (ignoring comments/blank lines)
- Comment spacing (`#foo``# foo`)
- Tab detection (Ruby uses spaces)
- Blank line whitespace cleanup

More decrees coming. Each language gets its own WASM decree for structural enforcement.

## Roadmap

There is no roadmap. The Dictator does not make promises.

Contributions are welcome if accompanied by:
- **Concrete benchmarks** — prove your decree is fast
- **Real usage** — show it solves an actual problem you have

## Building Custom WASM Decrees

See **`decree.kjr`** (Kim Jong Rails) in [DECREES.md](DECREES.md) for a complete example of building a custom decree using only the `dictator-decree-abi` crate.

The KJR decree demonstrates:
- Implementing the `Plugin` trait
- Emitting `Diagnostic` violations
- Building as a WASM component
- Loading via `.dictate.toml` config

## MCP Server

In 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:

- **Watch and snitch**`dictator watch` monitors your files and reports structural violations as you edit.
- **Surprise inquisition**`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:
  - In **`kimjongrails`** mode, it fixes native structural errors (LF/CRLF, trailing spaces, missing final newlines, etc.).
  - In **`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.

### Installation

Add to your Claude Code MCP configuration (`~/.claude/settings.json`):

```json
{
  "mcpServers": {
    "dictator": {
      "command": "/path/to/dictator"
    }
  }
}
```

MCP mode is auto-detected when stdin is a pipe and no CLI arguments are provided.

### Available Tools

| 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"`)

### Example Usage

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:
1. Detect file types in the provided paths (`.rb` → ruby, `.ts` → typescript, etc.)
2. Check which decrees have configured linters
3. Execute configured linters for detected file types
4. Return combined output

### Safety Features

**Multi-layer protection against destructive operations:**

1. **Git repository requirement**: `dictator` tool only exposed when `.git` directory exists
2. **Working directory boundary**: Destructive tools (`dictator`, `stalint_watch`) reject paths outside cwd
3. **Sandbox mode support**: Dictator advertises `codex/sandbox-state` capability and hides destructive tools in read-only mode
4. **Dynamic mode detection**: `supremecourt` mode only available if external linters are installed

**Examples:**
- Run from `/tmp` (no git) → LLM only sees `stalint` (read-only)
- Run from project (has git) → LLM sees `dictator` but it rejects `/home` or `/etc`
- No rubocop/eslint → `supremecourt` mode hidden from LLM

**Note:** As of 2025, some MCP clients don't send sandbox notifications. See Claude Code issues [#3315](https://github.com/anthropics/claude-code/issues/3315), [#3174](https://github.com/anthropics/claude-code/issues/3174), [#3141](https://github.com/anthropics/claude-code/issues/3141) for related discussion.

### Configuring External Linters

The MCP server reads linter configurations from `.dictate.toml`:

```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 json`
- `biome``lint --write --reporter json`
- `eslint``--fix --format json`
- `ruff``check --fix --output-format json`
- `gofmt``-w` (lists changed files, then fixes)
- `clippy``--fix --allow-dirty --message-format json`

**How it works:**
- MCP server detects file types in provided paths
- Maps extensions to decree names (`.rb``ruby`, `.ts`/`.js``typescript`, `.py``python`, `.go``golang`, etc.)
- Executes configured linter with Dictator-controlled args
- Parses JSON output to unified diagnostics (🔧 fixed, ⚠️ warning, ❌ error)
- `supremecourt` mode only appears in tool list if at least one decree has a configured linter installed

**Security:** Linters run as subprocesses with provided file paths as arguments. User is responsible for ensuring configured commands are safe.

## Use Cases

**Pre-linter CI stage:**
```yaml
- name: Structural checks (fast)
  run: dictator lint .  # Fails fast if structure is wrong
- name: Quality checks (slow)
  run: bundle exec rubocop
```

**Pre-commit workflow:**
```yaml
# .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.

## License

MIT

---

**Dictator**: Snitches on your structure. Takes all the credit.