跳到正文
DEV Community· MrOops·· 3 小時前AI 評分58

subedit 為智能體撰寫的註解與文件提供 AI 原生 Linter

An AI-Native Linter for the Comments and Docs Your Agent Writes

AI 導讀

subedit 是一款為智能體撰寫的程式註解與文件而設的 AI 原生 Linter,會在編輯後檢查每個句子。它會靜默修正排版,只把需要改寫語義的問題連同修改指示交回模型。工具可配合 Claude Code、Codex 或 CI 使用,並讀取專案現有的 ruff、Black、Prettier 或 Biome 寬度設定。

正文

I keep a writing style guide for my agents in a skill. Twenty minutes into a session it stops holding. The agent breaks a comment at column 80 in the middle of a phrase, hand-wraps a Markdown paragraph that GitHub then shows as four short lines, and leaves a ticket number in a comment that nobody outside the team can open.

The code never drifts like this, because a formatter and a linter check every edit. The comments and the docs are the part nothing checks.

subedit is the tool that does, an AI-native linter for the prose agents write. A post-edit hook checks every sentence an agent writes, autofixes the layout without a word, and hands back to the model only what needs rewriting, with the fix stated.

AI-Native by Design

Ruff and ESLint were built for a person reading a terminal, who remembers the rules, runs the checker, and fixes a finding from a short label. A model does none of these reliably, so subedit is built the other way round.

  • Feedback It Cannot Skip: an agent does not call a checker for a rule it has forgotten, so subedit is a post-edit hook that runs on every file the agent writes, in Claude Code or Codex. The check happens whether or not the agent remembers the rule.
  • Silent Autofix: a fix that changes a few characters or where a line breaks is applied without a message. Every message costs the model attention, and fifty layout complaints would crowd out the work it was doing.
  • Rewrites as Instructions: only a finding that needs the sentence's meaning goes back, and it names the rule, measures the problem, and states the fix. The hook blocks the edit only for these, and passes a judgment call such as a date in a comment as context instead.
  • Judgment Shipped Along: the Claude Code plugin ships a skill with the criteria for each finding no tool can fix, such as when a spec identifier stays and a ticket number goes.

subedit pipeline. An author or agent runs the CLI or the agent hook, which reads subedit.toml. subedit parses the comments, docstrings, and Markdown, lints them with its own rules, Vale, and AutoCorrect, then triages each finding by fix kind and level. An autofix is written back silently, and a rewrite is sent back to the author with its fix stated.

Compared With Ruff and Friends

The tools a repository already runs treat a comment as a line of code. subedit treats it as a sentence.

Tool What It Does With Prose What It Leaves
ruff format, Black, Prettier Keeps comment text exactly as written A comment broken mid clause stays broken
ruff, ESLint Flags a line past the width, or a missing docstring No fix, and no idea where the sentence may break
Vale Checks wording against a style Measures the extracted text rather than the source line, and fixes nothing

subedit runs alongside them. It reads the width from the ruff, Black, Prettier, or Biome config the project already has, and bundles a Vale style and AutoCorrect for word-level and CJK spacing rules, so all three engines share one report.

Examples

Each example below is real output from subedit.

A Comment

An editor wraps a comment at whatever column it reaches.

def reflow(comment: Comment, width: int) -> Comment:
    # A sentence that fits the width stays on one line of the comment, and a longer
    # one breaks only at a comma that ends a clause.
    return comment.reflow(width)

subedit fixes it and says nothing. The sentence is too long for one line, so it breaks at the comma where its first clause ends.

def reflow(comment: Comment, width: int) -> Comment:
    # A sentence that fits the width stays on one line of the comment,
    # and a longer one breaks only at a comma that ends a clause.
    return comment.reflow(width)

A Markdown Note

An agent writes a note the way it writes a comment, hard-wrapped, with a bold heading and lowercase labels.

## **how the cache works**

The cache keeps one entry per file (keyed by its path), and drops the entry
when `mtime` changes, so a stale read never reaches `load_config()`.

- **eviction policy**: the oldest entry goes first once the cache holds
  more than `max_entries` items.

```python
def load(path: Path) -> Entry:
    # Read the entry from disk when the cache misses it, and store the result for
    # the next caller.
    return _store.get(path) or _read(path)
```

One pass fixes all of it without a message. Each paragraph and list item takes one line, the heading loses its bold and gains Title Case, and the label is cased. Inline code such as load_config() is left exactly as written. With code-blocks = true, the comment inside the fenced block is checked as Python and joined too.

## How the Cache Works

The cache keeps one entry per file (keyed by its path), and drops the entry when `mtime` changes, so a stale read never reaches `load_config()`.

- **Eviction Policy**: the oldest entry goes first once the cache holds more than `max_entries` items.

```python
def load(path: Path) -> Entry:
    # Read the entry from disk when the cache misses it, and store the result for the next caller.
    return _store.get(path) or _read(path)
```

The heading still reads as a question, and only the author can rename it, so the hook passes that on as a warning the agent may act on without being blocked.

A Rewrite by the Agent

Some findings need the sentence's meaning, so no tool can fix them. The agent that wrote the sentence can, as long as something tells it what is wrong and how to fix it. Here a public function carries a plain comment, which never shows on hover, and the comment joins two clauses with a semicolon.

# Retry a request with backoff; gives up after five attempts.
def retry(request: Request) -> Response:
    return _send_with_backoff(request, attempts=5)

The hook blocks the edit and hands the agent this.

2 findings need rewriting in retry.py.
Each states the fix. Apply it in place, do not add a suppression comment.

line 1  [plain-comment-on-public]
  retry is public but documented with a plain comment. Move the text into a """ doc comment so tooling renders it on hover.
line 1  [subedit.Semicolon]
  Semicolon found. Split into two sentences or use a comma.

The agent rewrites the comment in its next step, and the check passes. Nobody reminded it of the style guide, and nobody had to review the comment for it.

def retry(request: Request) -> Response:
    """Retry a request with backoff, and give up after five attempts."""
    return _send_with_backoff(request, attempts=5)

The House Style

Tree-sitter finds the prose in Python, TypeScript, JavaScript, YAML, JSON, and Markdown, so code and commented-out code are never touched. Every rule can be set to error, warning, or off by name.

  • Layout: a comment breaks only at a clause comma, and a Markdown paragraph takes one source line.
  • Placement: a public symbol gets a docstring or JSDoc, with no banner and no restated signature.
  • Wording: no change narration, ticket numbers, or dates in comments, and no mid-sentence colon, semicolon, or dash.
  • Headings: Title Case noun phrases, not bold, each opened by a lead sentence.

Getting Started

The Claude Code plugin needs only uv, and acts in any project with a subedit.toml, which may be empty.

/plugin marketplace add mroops0111/subedit
/plugin install subedit@mroops

For Codex or CI, install the CLI. The README covers the Codex hook.

uv tool install 'subedit[vale]'
subedit fix src/ --write      # apply the autofixes
subedit check src/            # report what still needs rewriting

Closing

This post went through subedit before it went up. If it gets a sentence wrong in your codebase, or your house style needs a rule it lacks, open an issue with the sentence.

來源:DEV Community · dev.to