Add automatic fixing capability to the linter binary, allowing developers to automatically fix common linting issues before committing code.
- Reduce friction: Developers can fix most linting issues with a single command
- Maintain quality: Still report errors that cannot be auto-fixed
- Improve workflow: Integrate auto-fix into the pre-commit checklist
- Simple feedback: Report only remaining errors (developers use git to see what changed)
Add a --fix flag to the linter binary that will:
- Attempt to automatically fix issues for linters that support auto-fix
- Run the linter check after auto-fix to verify and report remaining issues
- Report only remaining errors that need manual attention
- Exit with non-zero code if any errors remain after auto-fix
Note: Developers can use git diff or git status to see what files were changed by the auto-fix.
We chose Option 3 (add --fix flag to existing linter) over alternatives because:
Rationale:
- Single workflow: One command does both fix and check
- Integrated experience: Fix attempt happens automatically before showing errors
- Industry standard: Most linters work this way (prettier, eslint, rustfmt, etc.)
- Pre-commit friendly: Fits naturally into the pre-commit checklist
- Less cognitive load: Developers only need to remember one command
Usage:
# Check only (current behavior)
cargo run --bin linter all
# Try to fix, then check
cargo run --bin linter all --fix
# Individual linters with fix
cargo run --bin linter markdown --fix
cargo run --bin linter yaml --fixWhy discarded:
- Creates friction in the workflow
- Requires extra manual step
- Users might forget to run the fix command
- Breaks the "pre-commit must pass" principle
- Not aligned with modern linting tool UX
Why discarded:
- More commands to remember (
linterandlinter-fix) - Additional binary to maintain
- Splits related functionality
- Users might not discover the fix tool
- Duplicates command-line argument parsing logic
| Linter | Auto-fix Support | Fix Command | Notes |
|---|---|---|---|
| markdown | ✅ Yes | npx markdownlint-cli --fix <file> |
Fixes most formatting issues |
| yaml | ✅ Yes | yamlfmt <file> |
Uses yamlfmt for YAML formatting |
| clippy | ✅ Yes | cargo clippy --fix --allow-dirty --allow-staged |
Fixes many clippy warnings |
| rustfmt | ✅ Yes | cargo fmt |
Already auto-formats (no change needed) |
| shellcheck | ❌ No | N/A | No native auto-fix support |
| taplo | ✅ Yes | taplo fmt <file> |
TOML formatting |
| cspell | ❌ No | N/A | Spelling requires human judgment |
For linters without auto-fix support (shellcheck, cspell):
- Strategy: Skip auto-fix phase, run check only
- Output: Report errors normally with manual fix guidance
- No special handling: Treat as if
--fixwasn't specified for that linter
1. For each linter:
a. If auto-fix supported:
- Run auto-fix command on relevant files
- Log what was fixed
b. Run linter check (always, even after fix)
c. Report remaining errors (if any)
2. Exit code:
- 0 if all errors fixed or no errors found
- Non-zero if errors remain after auto-fix attempt
1. For each linter:
a. Run linter check
b. Report errors
2. Exit code:
- 0 if no errors
- Non-zero if errors found
Note: All output uses the tracing crate following the current logging pattern. Flat logging with targets is used (no tracing spans needed).
Verbosity: Minimal - show only a summary of files fixed per linter, not individual file details.
$ cargo run --bin linter all --fix
2025-10-02T10:30:45.123456Z INFO linter: Running All Linters (with auto-fix)
2025-10-02T10:30:45.234567Z INFO markdown: Fixed 3 files
2025-10-02T10:30:45.345678Z INFO markdown: Scanning markdown files...
2025-10-02T10:30:45.456789Z INFO markdown: All markdown files passed linting!
2025-10-02T10:30:45.567890Z INFO yaml: Fixed 2 files
2025-10-02T10:30:45.678901Z INFO yaml: Scanning YAML files...
2025-10-02T10:30:45.789012Z INFO yaml: All YAML files passed linting!
2025-10-02T10:30:45.890123Z INFO clippy: Fixed 1 file
2025-10-02T10:30:46.012345Z INFO clippy: Running Rust clippy linter...
2025-10-02T10:30:47.123456Z INFO clippy: Clippy check passed!
2025-10-02T10:30:47.234567Z INFO rustfmt: Running Rust formatter check...
2025-10-02T10:30:47.345678Z INFO rustfmt: All Rust code is properly formatted!
2025-10-02T10:30:47.456789Z INFO toml: Fixed 1 file
2025-10-02T10:30:47.567890Z INFO toml: Scanning TOML files...
2025-10-02T10:30:47.678901Z INFO toml: All TOML files passed linting!
2025-10-02T10:30:47.789012Z INFO shellcheck: Scanning shell scripts...
2025-10-02T10:30:47.890123Z INFO shellcheck: All shell scripts passed linting!
2025-10-02T10:30:47.901234Z INFO cspell: Running spell checker...
2025-10-02T10:30:48.012345Z INFO cspell: Spell checking passed!
# Developers can check what was changed with:
$ git status
$ git diff$ cargo run --bin linter all --fix
2025-10-02T10:30:45.123456Z INFO linter: Running All Linters (with auto-fix)
2025-10-02T10:30:45.234567Z INFO markdown: Fixed 2 files
2025-10-02T10:30:45.345678Z INFO markdown: Scanning markdown files...
docs/deployment.md:42 MD001/heading-increment: Heading levels should only increment by one level at a time [Expected: h2; Actual: h3]
2025-10-02T10:30:45.456789Z ERROR markdown: Markdown linting failed. Please fix the issues above.
2025-10-02T10:30:45.567890Z INFO yaml: Fixed 1 file
2025-10-02T10:30:45.678901Z INFO yaml: Scanning YAML files...
ansible/inventory.yml:15:1: [error] found duplicate key (key-duplicates)
2025-10-02T10:30:45.789012Z ERROR yaml: YAML linting failed. Please fix the issues above.
2025-10-02T10:30:45.890123Z INFO clippy: No files needed fixing
2025-10-02T10:30:46.012345Z INFO clippy: Running Rust clippy linter...
2025-10-02T10:30:47.123456Z INFO clippy: Clippy check passed!
2025-10-02T10:30:47.234567Z INFO rustfmt: Running Rust formatter check...
2025-10-02T10:30:47.345678Z INFO rustfmt: All Rust code is properly formatted!
2025-10-02T10:30:47.456789Z INFO toml: No files needed fixing
2025-10-02T10:30:47.567890Z INFO toml: Scanning TOML files...
2025-10-02T10:30:47.678901Z INFO toml: All TOML files passed linting!
2025-10-02T10:30:47.789012Z INFO shellcheck: Scanning shell scripts...
2025-10-02T10:30:47.890123Z INFO shellcheck: All shell scripts passed linting!
2025-10-02T10:30:47.901234Z INFO cspell: Running spell checker...
2025-10-02T10:30:48.012345Z INFO cspell: Spell checking passed!
2025-10-02T10:30:48.123456Z ERROR linter: Some linters failed
# Developers can check what was auto-fixed with:
$ git status
$ git diffKey Principles:
- Use
tracingcrate for all output (consistent with current implementation) - Flat logging with targets (no tracing spans needed for simplicity)
- Minimal verbosity: show only summary of files fixed per linter
- Only show errors that still need attention after auto-fix
- Developers use git to see what was changed
- Maintain current logging format and targets
Approach: Auto-install missing tools (matches current linter behavior)
When a linter tool is not found, the binary will:
- Detect the missing tool
- Automatically install it (npm packages or cargo install)
- Continue with linting operation
- Log the installation process for visibility
Examples:
2025-10-02T10:30:45.123456Z WARN markdown: markdownlint-cli not found, installing...
2025-10-02T10:30:47.234567Z INFO markdown: markdownlint-cli installed successfully
2025-10-02T10:30:47.345678Z INFO markdown: Fixed 2 filesThis matches the current behavior where linters automatically install dependencies when missing.
If an auto-fix command fails:
- Log the error with context
- Skip the fix phase for that linter
- Continue to the check phase (to show remaining issues)
- Exit with non-zero code if errors persist
Example:
2025-10-02T10:30:45.123456Z ERROR yaml: Auto-fix command failed: yamlfmt returned exit code 1
2025-10-02T10:30:45.234567Z INFO yaml: Scanning YAML files...
ansible/inventory.yml:15:1: [error] found duplicate key (key-duplicates)- Non-destructive: Auto-fix only modifies files in safe, reversible ways
- Git integration: Changes are unstaged, allowing review before commit
- Verification: Always run check after fix to ensure no issues introduced
- Minimal output: Only show errors that need attention, rely on git for change visibility
- Tool isolation: Auto-installed tools are local to the project (npm/cargo)
Current Implementation: Linters run sequentially (one after another)
Execution Time: ~13 seconds for all linters
Performance: Acceptable for pre-commit workflow
Note: There is a separate feature for parallel linter execution that could reduce execution time by ~30% (13s → 9s).
Auto-fix Compatibility: Auto-fix works safely with parallel execution because linters operate on different file types:
| Linter | File Types | Auto-fix Support | Notes |
|---|---|---|---|
| markdown | *.md |
✅ Yes | No conflicts |
| yaml | *.yml, *.yaml |
✅ Yes | No conflicts |
| toml | *.toml |
✅ Yes | No conflicts |
| clippy | *.rs |
✅ Yes | Conflicts with rustfmt (see below) |
| rustfmt | *.rs |
✅ Yes | Conflicts with clippy (see below) |
| shellcheck | *.sh |
❌ No | Read-only checker |
| cspell | All text files | ❌ No | Read-only checker |
Key Insights:
- ✅ Most linters can auto-fix independently (different file types)
⚠️ clippy --fixandrustfmtboth modify.rsfiles - must run sequentially to avoid conflicts- ✅ Auto-fix is safe - no risk of file corruption or data loss
Implementation: Auto-fix will run linters sequentially (current approach), which naturally avoids any potential file conflicts.
For parallel execution details: See the separate Parallel Linter Execution Feature for analysis of running linters in parallel. This is a future optimization that is compatible with auto-fix but not required for auto-fix functionality.
Decision: Do not implement --dry-run flag in initial version
Rationale:
- Adds complexity without clear immediate benefit
- Git already provides safety (changes are unstaged)
- Can be added later if users request it
- YAGNI principle - implement when needed
Decision: Do not implement interactive fix confirmation
Rationale:
- Would slow down the workflow
- Auto-fixes are safe and reviewable via git
- Can be added later if needed
Decision: --fix applies to all specified linters, no per-linter control
Rationale:
- Simplifies UX and implementation
- Users can run individual linters if selective fix needed
- Example:
cargo run --bin linter markdown --fix(only markdown)
Update docs/contributing/commit-process.md:
# Before committing: Run linters with auto-fix
cargo run --bin linter all --fixCI should run without --fix flag:
# CI - fail if code is not already formatted
cargo run --bin linter allThis ensures developers format code locally before pushing.
Strategy: Implement auto-fix for all linters, but develop and test one linter at a time
Process:
- Add
--fixflag to CLI (applies to all linters) - Implement auto-fix for one linter
- Test thoroughly (unit + integration + E2E + manual)
- Commit and push
- Move to next linter
- Repeat until all linters support auto-fix
Benefits:
- Easier to review and test changes
- Smaller, focused commits
- Can deploy partially completed feature (some linters work with
--fix) - Reduces risk of bugs
- Easier to debug issues
Order (suggested, based on complexity):
- rustfmt - Already works, just add flag support
- toml (taplo) - Simple formatting tool
- markdown - Straightforward npm tool
- yaml (yamlfmt) - New tool, needs verification
- clippy - Most complex (requires
--allow-dirty --allow-stagedflags)
-
--fixflag added to linter binary CLI - Auto-fix implemented for supported linters (markdown, yaml, clippy, taplo)
- Rustfmt continues to work as before
- Linters without auto-fix (shellcheck, cspell) skip fix phase
- Clear output showing fixed vs manual issues
- Exit codes correct (0 if all pass, non-zero if errors remain)
- Works for both
alland individual linter commands - Documentation updated in
docs/contributing/commit-process.md - All existing tests pass
- Manual testing completed for each linter
- Test CLI argument parsing for
--fixflag - Test auto-fix logic for each supported linter
- Test skip behavior for unsupported linters
- Test end-to-end workflow with
--fixflag - Test that fixes are applied correctly
- Test that checks run after fix
- Test exit codes in various scenarios
- Test with actual files containing fixable issues
- Verify output messages are clear and helpful
- Confirm git shows unstaged changes after fix
- Test both
alland individual linter modes
Potential future additions (implement only if needed):
-
Parallel execution: Run linters in parallel for better performance (~30% faster, 13s → 9s)
- This is a separate feature with its own specification
- See Parallel Linter Execution Feature for details
- Compatible with auto-fix but not required for auto-fix functionality
- Priority: Low (current performance is acceptable)
-
Dry-run mode: Preview what would be fixed without applying changes
-
Interactive mode: Confirm each fix before applying
-
Fix statistics: Detailed report of what was fixed
-
Per-linter fix control:
--fix-only=markdown,yaml -
Configuration file: Allow customizing auto-fix behavior