Skip to content
mlavrinenkoPublic

About

Language-aware file size linter powered by Tokei.

Resources

Contributing

Stars

2 stars

Watchers

0 watching

Forks

Repository files navigation

linecop

CI crates.io

Language-aware file size linter. It patrols your code base for per-language line limits, counted by tokei.

Installation

Cargo

cargo install linecop

Nix flake

nix run github:mlavrinenko/linecop

Binary releases

Linux and macOS binaries are on the releases page.

Quick start

linecop init        # creates .linecop.yaml with sensible defaults
linecop             # scan current directory

Usage

linecop [PATH] [OPTIONS]
linecop [PATH] <COMMAND>

Commands:

Command Description
init Generate a starter .linecop.yaml
schema Print JSON Schema for config validation

Options:

Option Description
-c, --config <FILE> Config file path (default: auto-detected)
--config-search <repo|root> How far up to look for the config: repo (default) stops at the repository root, root walks to the filesystem root
-q, --quiet Suppress output (exit code only)
--format <text|json|paths> Output format (default: text)
--baseline <PERCENT> Report files at or above this percentage of their limit, 1-100 (default: 100)
--hidden Scan dot-prefixed files and directories (overrides include_hidden)
--color <auto|always|never> Control color output
--no-config-warning Suppress the warning when no config file is found

Exit codes

Code Meaning
0 No file exceeds its limit
1 Violations found — the codebase needs attention
2 Operational error — the check never ran: malformed or unreadable config, unknown language, invalid override glob, missing scan path, I/O failure, bad CLI arguments

In a gate recipe, treat 2 as a broken gate rather than as violations.

Examples

# Scan a specific directory
linecop src/

# Use a custom config
linecop --config my-config.yaml

# JSON output for CI
linecop --format json --quiet

# Report files at 90%+ of their limit (early warning)
linecop --baseline 90

# Paths only, one per line, for piping to other tools
linecop --baseline 90 --format paths | ejectest apply src/ --files-from -

# Include dot-directories such as .just/scripts or .github/scripts
linecop --hidden

# Generate JSON Schema for editor validation
linecop schema > linecop-schema.json

JSON output carries lines, limit and baseline-limit (the threshold after --baseline) for each file, plus bytes, max_bytes and baseline-max-bytes for a file whose override sets max_bytes.

Configuration

Create a .linecop.yaml in your project root. linecop walks up from the scanned path to the nearest .linecop.yaml, else .config/linecop.yaml, and stops after the repository root (.git, .jj, .hg or .svn), or at the working directory outside one. --config-search root walks on to /; --config skips the search.

limits:
  Rust: 500
  Markdown: 200
  Python: 400

count_mode: total  # total | code | code-comments

overrides:
  - pattern: "src/generated_*.rs"
    limit: 1000
  - pattern: "CONTRIBUTING.md"  # loaded whole by agents
    max_bytes: 14000
  - pattern: "RESEARCH.md"
    exclude: true

exclude_dirs:
  - target
  - node_modules

include_hidden: false  # scan .just/scripts, .github/scripts, ...

Language names follow tokei in any case, so Bash caps what tokei calls BASH; naming one language twice is an error. tokei goes by file name, extension, then shebang: a hook with no extension and #!/usr/bin/env bash counts as BASH.

An override pattern is a .gitignore line, read from the config's root (the directory holding the config file, unless you set one), so it matches the same files whatever you scan and wherever you run linecop:

Pattern Matches
RESEARCH.md that name at any depth
src/*.rs .rs files directly in src/
src/**/*.rs .rs files anywhere under src/
/build.rs build.rs at the top only
vendor/ every file under any vendor directory

The first matching override wins. ! negation is not supported; order the overrides instead.

Config in .config/

Keep your tool configs in .config/? Point root back at the repo. It's a path from the config file's own directory, every pattern reads from it, and a bare linecop scans it. Leave it out and both stay at the config file's directory.

# .config/linecop.yaml
root: ..  # the repo, one level up
limits:
  Markdown: 200
overrides:
  - pattern: "CHANGELOG.md"  # read from root, so it reaches ./CHANGELOG.md
    exclude: true

A bare linecop finds it there and judges as a top-level .linecop.yaml would. Under -c it reads the same, so it needs root: .. either way.

An override sets limit, max_bytes, or both. A line limit does not bound a file whose lines are paragraphs, so max_bytes caps its size too; the file breaches when it exceeds either, and the report names which: --- CONTRIBUTING.md: 31204 bytes (max_bytes: 14000, +17204 over). Without limit an override keeps the language limit, and --baseline applies to both. Its caps need no limits entry: docs/*.typ with limit: 300 holds even with no Typst key.

Dot-prefixed files and directories are skipped unless include_hidden: true (or --hidden) is set. With it on, .git, .hg, .svn and .jj stay out of the scan, and gitignored paths are still ignored — so a hidden build directory gets limits without dragging repository metadata in.

Agent skill

skills/linecop teaches a coding agent to check headroom with --baseline before growing a file, instead of meeting the limit when the gate fails. Copy or link it into your agent's skills directory, e.g. ~/.claude/skills/linecop.

Contributing

See CONTRIBUTING.md for coding conventions and guidelines.

License

MIT

About

Language-aware file size linter powered by Tokei.

Resources

Contributing

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages