A tree-sitter based linter with WASM plugin support, written in Rust.
  • Rust 97.9%
  • Nix 1.6%
  • Shell 0.5%
Find a file
natsukium 9be6317e2f
feat(nixpkgs): add derivation-rec rule to detect rec attrset pattern
Nixpkgs style guide recommends using finalAttrs pattern instead of rec
attrset in derivation functions for better maintainability and to avoid
issues with attribute evaluation order.
2026-01-15 00:23:21 +09:00
blocks feat(nixpkgs): add derivation-rec rule to detect rec attrset pattern 2026-01-15 00:23:21 +09:00
crates feat(lsp): implement workspace diagnostics with async background scanning 2026-01-02 00:39:23 +09:00
docs feat(lsp): implement workspace diagnostics with async background scanning 2026-01-02 00:39:23 +09:00
scripts feat: implement 1 block = n pieces architecture with WASM Component Model 2025-12-28 19:14:54 +09:00
timbers chore: add dual-license structure (AGPL-3.0 + MIT/Apache-2.0) 2025-12-30 12:51:45 +09:00
wit refactor: embed timber crates directly into blocks 2025-12-29 21:54:26 +09:00
.gitignore perf: optimize diagnostic checking with O(1) piece lookup and caching 2026-01-02 00:39:23 +09:00
Cargo.lock feat(lsp): implement workspace diagnostics with async background scanning 2026-01-02 00:39:23 +09:00
Cargo.toml chore: add dual-license structure (AGPL-3.0 + MIT/Apache-2.0) 2025-12-30 12:51:45 +09:00
flake.lock chore: build with nix 2025-12-29 13:28:50 +09:00
flake.nix fix(nix): add pkg-config and openssl to linux build env 2026-01-01 18:41:00 +09:00
LICENSE chore: add dual-license structure (AGPL-3.0 + MIT/Apache-2.0) 2025-12-30 12:51:45 +09:00
LICENSE-APACHE chore: add dual-license structure (AGPL-3.0 + MIT/Apache-2.0) 2025-12-30 12:51:45 +09:00
LICENSE-MIT chore: add dual-license structure (AGPL-3.0 + MIT/Apache-2.0) 2025-12-30 12:51:45 +09:00
README.md chore: add dual-license structure (AGPL-3.0 + MIT/Apache-2.0) 2025-12-30 12:51:45 +09:00

Yosegi

yosegi (寄木): A Japanese woodwork technique of assembling small pieces of wood into intricate patterns.

A tree-sitter based linter with WASM plugin support, written in Rust.

Features

  • WASM Plugin Architecture: Linting rules are distributed as WebAssembly modules (Blocks), enabling safe, sandboxed execution and easy distribution via OCI registries
  • Composable Rules: Each Block contains multiple Pieces (individual lint rules) that can be independently enabled/disabled
  • Multi-language Support: Built on tree-sitter for accurate AST parsing. Currently supports Nix (first-class) and Rust
  • LSP Integration: Real-time linting feedback in your editor with diagnostics, hover information, and quick fixes
  • Extensibility: Write custom linting rules in Rust using the Piece Development Kit (PDK)

Quick Start

Installation

# Build from source
cargo build --release

# The binary will be at ./target/release/yosegi

Basic Usage

# Lint files or directories
yosegi check path/to/code

# Lint with specific blocks
yosegi check --blocks ./path/to/block.wasm path/to/code

# Use JSON output format
yosegi check -f json path/to/code

# Filter by severity
yosegi check --severity error path/to/code

Configuration

Create a yosegi.toml in your project root:

[settings]
severity-threshold = "warning"

[blocks.nixpkgs]
source = "ghcr.io/natsukium/yosegi-nixpkgs:latest"
enabled = true

# Disable specific pieces
[blocks.nixpkgs.pieces.no-with]
enabled = false

# Override severity for a piece
[blocks.nixpkgs.pieces.description-no-period]
severity = "error"

Core Concepts

Blocks

A Block is a WASM module containing one or more linting rules. Blocks are distributed as OCI artifacts and can be fetched from container registries.

# List configured blocks
yosegi blocks list

# Show block details
yosegi blocks info nixpkgs

# Pre-fetch blocks for offline use
yosegi blocks fetch

# Manage cache
yosegi blocks cache info
yosegi blocks cache clear

Pieces

A Piece is an individual linting rule within a Block. Each piece can be independently:

  • Enabled or disabled
  • Configured with custom severity (error, warning, info, hint)
  • Customized with piece-specific options

Timbers

A Timber is a language support crate that provides:

  • Tree-sitter grammar integration
  • AST traversal and query helpers
  • Language-specific utilities

Timbers are embedded into Blocks at compile time, enabling fast native testing during development.

Available Blocks

nixpkgs

Linting rules for Nix/nixpkgs packages (21 pieces):

Category Pieces
Style no-with, description-no-period, description-no-article, description-capitalization, pname-normalization, flags-not-list, optional-with-list
Correctness build-tool-in-build-inputs, redundant-stdenv-package
Python versioned-dependency, redundant-native-build-input, deprecated-test-tool, deprecated-format, non-functional-test-tool, package-in-native-build-inputs, pytest-cov, pytest-benchmark, bare-pytest, unnecessary-wheel, missing-pyproject

rust

Linting rules for Rust code (2 pieces).

LSP Server

Start the LSP server for editor integration:

yosegi lsp

Editor Configuration

Neovim (with nvim-lspconfig):

vim.api.nvim_create_autocmd("FileType", {
  pattern = "nix",
  callback = function()
    vim.lsp.start({
      name = "yosegi",
      cmd = { "yosegi", "lsp" },
      root_dir = vim.fs.dirname(vim.fs.find({ "yosegi.toml", "flake.nix" }, { upward = true })[1]),
    })
  end,
})

VS Code: Configure in settings.json or use a custom language server client.

Developing Custom Blocks

Create custom linting rules using the Piece Development Kit (PDK):

use yosegi_pdk::prelude::*;
use yosegi_timber_nix as timber;

struct MyPiece;

impl DynPiece for MyPiece {
    fn info(&self) -> PieceInfo {
        PieceInfo::new("my-rule", "My Rule", "Description of the rule")
            .category("style")
            .language("nix")
            .severity(Severity::Warning)
    }

    fn check(&self, source: &str) -> Vec<Diagnostic> {
        // Use timber for AST queries
        let matches = timber::query("(with_expression) @target").unwrap_or_default();

        matches.into_iter().map(|m| {
            Diagnostic {
                rule_id: "my-rule".into(),
                message: "Found a with expression".into(),
                severity: Severity::Warning,
                span: m.span,
                fix: None,
            }
        }).collect()
    }
}

struct MyBlock;

impl Block for MyBlock {
    fn info() -> BlockInfo {
        BlockInfo::new("my-block", "My Block", "My custom linting rules")
    }

    fn pieces() -> Vec<&'static dyn DynPiece> {
        vec![&MyPiece]
    }
}

Build the block as a WASM component:

cargo build --target wasm32-wasip1 --profile release-wasm

Project Structure

yosegi/
├── crates/
│   ├── yosegi/          # CLI binary
│   ├── yosegi-core/     # Core types (Diagnostic, Span, Severity)
│   ├── yosegi-config/   # TOML configuration parsing
│   ├── yosegi-block/    # WASM block loader (Wasmtime)
│   ├── yosegi-pdk/      # Piece Development Kit
│   ├── yosegi-engine/   # Linting engine orchestration
│   ├── yosegi-registry/ # OCI registry client
│   └── yosegi-lsp/      # Language Server Protocol
├── timbers/
│   ├── nix/             # Nix language support
│   └── rust/            # Rust language support
├── blocks/
│   ├── nixpkgs/         # Nixpkgs linting rules
│   └── rust/            # Rust linting rules
└── wit/
    └── world.wit        # WebAssembly Interface Types definition

Documentation

License

This project uses a dual-license structure:

Using yosegi as a linting tool does not affect your code's license. The AGPL obligations apply only if you modify and distribute yosegi itself, or provide it as a network service.