agent-linters

lintp · یک خط برای هر ایراد

خروجی لینتر را ایجنت می‌خواند، نه آدم

خروجی پیش‌فرض لینترها برای آدمی نوشته شده که یک خطا را می‌خواند، پس هر ایراد با یک قاب کد و یک فلش اشاره‌گر و یک بند راهنما می‌آید. ایجنت برای هر ایراد چهارده خط از همین‌ها می‌گیرد و کانتکستش را خرج قالب‌بندی می‌کند. lintp فقط دستور را چاپ می‌کند و بقیه را می‌اندازد دور.

$ lintp src/
src/api.py:1     remove unused import `os`
src/api.py:14    return str, not int
src/ui.tsx:8     add a `key` prop to the list element
src/theme.css:1  remove the duplicate CSS property

4 findings, 2 auto-fixable (lintp --fix)

سه ایراد یکسان؛ ۳۱ خط یا ۵ خط

چیزی که ایجنت امروز می‌خواند

۳۱ خط · ۵۸۷ بایت · خروجی ruff check و mypy

$ ruff check src/api.py
I001 [*] Import block is un-sorted or un-formatted
 --> src/api.py:1:1
  |
1 | / import os
2 | | import json
  | |___________^
help: Organize imports
  |
1 + import json
2 | import os
  - import json
3 |
  |

F401 [*] `os` imported but unused
 --> src/api.py:1:8
  |
1 | import os
  |        ^^
2 | import json
  |
help: Remove unused import: `os`
  |
  - import os
1 | import json
  |

Found 2 errors.
[*] 2 fixable with the `--fix` option.
$ mypy src/api.py
src/api.py:11: error: Incompatible return value type (got "str", expected "int")  [return-value]
Found 1 error in 1 file (checked 1 source file)

چیزی که lintp تحویلش می‌دهد

۵ خط · ۱۵۵ بایت · خروجی lintp

$ lintp src/api.py
src/api.py:1   sort the import block
src/api.py:1   remove unused import `os`
src/api.py:11  return int, not str

3 findings, 2 auto-fixable (lintp --fix)
دو فرمان، دو قالب خروجی، و بیشترِ اولی نقاشی اَسکی است. lintp فایل و شمارهٔ خط و فعل را نگه می‌دارد. چیزی از خودش درنمی‌آورد و چیزی را هم قورت نمی‌دهد: قاعده‌ای که عبارت دست‌نویس ندارد، با پیام خودِ لینتر عبور می‌کند.

یک فرمان، چهار لینتر

مسیر را به lintp بده. بر اساس پسوند تصمیم می‌گیرد و هر ایراد را در یک قالب چاپ می‌کند، مهم نیست کدام ابزار پیدایش کرده. کدهای خروج هم به کار می‌آیند: 0 تمیز، 1 ایراد دارد، 2 یکی از لینترها اجرا نشد.

نوع فایل و لینتر متناظرش
پسوندلینتر
.py .pyiruff + mypy
.js .mjs .cjs .jsx .ts .tsx .mts .ctsoxlint
.css .scss .json .jsoncbiome

biome خودش JS و TS را هم لینت می‌کند، پس آن ایرادها حذف می‌شوند. صاحب‌شان oxlint است و پرامپت تکراری از هیچ بدتر است.

فلگ‌ها

فلگ‌های lintp
فلگکارکرد
--fixاول اصلاح‌های بی‌خطر را اعمال کن، بعد باقی‌مانده را گزارش بده
--codesنام قاعده را به آخر هر خط اضافه کن
--strictقاعده‌های pedantic و style از oxlint را هم روشن کن
--toolsفقط زیرمجموعه‌ای را اجرا کن، مثل --tools ruff,mypy

کانفیگ خودِ پروژه حرف آخر را می‌زند

اگر مخزن .oxlintrc.json داشته باشد، lintp همان را با -c پاس می‌دهد و هیچ فلگ دسته‌ای از خودش اضافه نمی‌کند. دسته‌ای که به‌زور اعمال شود، قاعده‌هایی را برمی‌گرداند که پروژه عمداً خاموش کرده بود، و از همین‌جا است که یک wrapper همان توصیه‌ای را می‌دهد که پروژه قبلاً ردش کرده.

نصب

سه مرحله، به همین ترتیب. هیچ‌چیز اینجا کامپایل نمی‌شود.

  1. لینترهایی که می‌خواهی را نصب کن

    هیچ‌کدام همراه agent-linters نمی‌آیند.

    $ uv tool install ruff && uv tool install mypy
    $ npm install -g oxlint @biomejs/biome
  2. مخزن را کلون کن و نصب‌کننده را اجرا کن

    $ git clone https://github.com/cocodedk/agent-linters ~/projects/agent-linters
    $ cd ~/projects/agent-linters && ./install.sh

    پوشه‌های bin/ و shims/ و config/ را به ~/.local و ~/.config سیم‌لینک می‌کند و یک خط source به پروفایل شل اضافه می‌کند. اگر فایل واقعی سرِ راه باشد، اول به <name>.bak منتقل می‌شود.

  3. به Claude Code خبر بده

    Claude Code بلوک env خودش را می‌خواند، نه پروفایل شل تو. پس دو متغیر را به ~/.claude/settings.json اضافه کن، یا بگذار ./install.sh --claude با jq این کار را بکند.

    "env": {
      "RUFF_OUTPUT_FORMAT": "concise",
      "RUFF_CACHE_DIR": "/home/YOU/.cache/ruff"
    }

./install.sh --uninstall همه‌اش را برمی‌گرداند و بکاپ‌ها را سر جایشان می‌گذارد. اجرای دوبارهٔ نصب‌کننده هم بی‌خطر است. نیازمندی‌ها: sh از نوع POSIX، پایتون ۳.۸ یا بالاتر، و خودِ لینترها.

چهار تله که واقعاً وقت دیباگ برد

هر کدام از این‌ها باعث شد یک لینتر به چیز اشتباهی اشاره کند. به همین دلیل کار lintp به ست کردن یک متغیر محیطی خلاصه نمی‌شود.

  • هیچ‌وقت یک MYPY_CACHE_DIR مشترک نگذار

    دو پروژه که هر کدام یک utils.py دارند، در کش مشترک با هم تصادم می‌کنند و mypy ایراد را به فایلِ آن یکی پروژه نسبت می‌دهد. ایجنتی که به مسیر اعتماد کند، فایلی را در مخزن اشتباه ویرایش می‌کند. شیم به هر پوشهٔ کاری کش خودش را می‌دهد. MYPY_CACHE_DIR ارثی هنوز برنده است، چون تنظیم صریح باید برنده باشد، پس اگر یک‌بار مشترک اکسپورتش کرده‌ای، unset کن.

  • ruff هر فایلی را که نام ببری لینت می‌کند

    ruff check app.js یک صفحه خطای سینتکس پایتونِ خیالی برمی‌گرداند. قبل از صدا زدن هر ابزاری، مسیرها را با پسوند فیلتر کن. lintp این کار را در select() می‌کند.

  • مسیرهای mypy نسبت به هدف‌اند، نه به مسیر جاری

    یک a.py بدون مسیر در خروجی mypy می‌تواند به فایل هم‌نامِ دیگری در پوشهٔ جاری برسد. اول مسیرهای گزارش‌شده را نسبت به ریشهٔ هدف حساب کن.

  • oxlint کانفیگ را فقط از مسیر جاری می‌خواند

    ruff و mypy و biome همه رو به بالا جست‌وجو می‌کنند، oxlint نه. از یک زیرپوشه اجرایش کن و بی‌صدا همهٔ استثناهایی را از دست می‌دهی که کانفیگ مخزن برای همان‌ها نوشته شده. lintp خودش بالا می‌رود و -c را پاس می‌دهد.