@d3lm/lint-preset 1.4.6 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -231,6 +231,172 @@ createOxlintConfig({
231
231
  });
232
232
  ```
233
233
 
234
+ ## Comment Formatting (`comments`)
235
+
236
+ The preset ships a `comments` CLI that checks how comments in JavaScript and TypeScript files fit the configured width. Line breaks are left to the tool, except where only the meaning of the prose shows whether a line ends with the start of a sentence. `list`, `check`, and `wrap` never change files. By default, `agent` and the hook send an agent only the comments with hints, plus comments with issues in parts the tool never rewraps (see [Text Kept as Written](#text-kept-as-written)). That default is a cheap triage, not a full review of the language. With `--all` or `agent.scope: 'all'`, the agent reviews the wording of every comment in scope against its rules. The agent fixes the wording and moves the start of a sentence to the next line where a hint applies. Then the tool applies its exact rewrap to every comment in scope that still has a layout issue. The rewrap only changes whitespace in prose, like line breaks, runs of spaces, trailing spaces, and the indentation of continuation lines. It never changes a word, a punctuation character, a code span, or text kept as written, and it keeps the line endings of the file. Comments are found with a real parser, so strings, template literals, and regexes never produce false matches. Only block and JSDoc comments are checked, and line comments (`//`) are always left alone.
237
+
238
+ ```bash
239
+ comments list # comments in uncommitted changes, with line lengths and issues
240
+ comments list src --suspicious # only comments with issues or hints
241
+ comments check --base main # print layout violations and exit with code 1 if any remain
242
+ comments list src/a.ts:42 --json # the comment on line 42, with its rewrap suggestion
243
+ comments wrap src/a.ts:42 # print the exact rewrap of that comment to stdout
244
+ comments agent # hand suspicious comments to an agent, then verify the result
245
+ comments check --cwd ../other-app # run as if started in another project
246
+ ```
247
+
248
+ Without targets, only comments in uncommitted changes and untracked files are checked. `--staged` limits that to staged changes, `--base main` checks everything since the merge base with `main`, and `--repo` checks every file. Targets can be files, directories, `file:line`, or `file:start-end`. A comment that contains `comments-ignore` is skipped. A file with a syntax error is skipped as a whole and named in a warning, because the parser stops at the error and never sees the comments after it. JSX is read in `.js` files too.
249
+
250
+ With `--json`, each paragraph of a comment comes with its issues and a `suggestion` that holds the exact rewrapped lines. Code blocks, `@example`, tables, drawings, directives, and other [text kept as written](#text-kept-as-written) have no suggestion.
251
+
252
+ ### Issues
253
+
254
+ Verdicts are layout issues that can be measured without reading the prose. They fail `comments check`:
255
+
256
+ - `overflow` means a measured prose line exceeds `maxWidth`
257
+ - `underfill` means a line ends early, so the paragraph needs more lines than it should
258
+ - `widow` means fewer than three words sit on the last line of a paragraph
259
+ - `unbalanced` means the lines are less even than in the suggested rewrap
260
+
261
+ Hints come from heuristics and can be false positives, because only the meaning of the prose shows whether the rule applies. A hint only sends a comment to the agent, which decides whether to change it. `check` only prints comments with verdicts, so a passing check stays quiet apart from its summary, even when hints remain after review. Use `list --suspicious` to inspect hints, which are marked with `(hint)` and never fail a check:
262
+
263
+ - `orphan` means a line ends with the first word of a sentence, or the first of two lines ends with the start of a sentence that fits on the second without making it longer than the first
264
+ - `em-dash` means the comment uses `—`
265
+ - `dash` means the comment uses `–` or `--` between words
266
+ - `semicolon` means the comment joins clauses with a semicolon
267
+ - `label-colon` means the comment uses the "label: explanation" pattern
268
+ - `emphasis` means the comment uses `*emphasis*` or `**bold**`
269
+ - `banner` means the comment is a divider made of repeated characters
270
+
271
+ A paragraph with an `orphan` hint is not reported as `unbalanced`, because the suggested rewrap moves the word that the hint is about, and the agent may rightly keep that word where it is. A line that ends early is still an `underfill`, whatever its last word is.
272
+
273
+ Hints are only checked in prose, never in code spans, code blocks, `@example`, or URLs. Rules that can't be measured, like writing complete sentences, are part of the agent's prompt (see `DEFAULT_RULES` in `src/comments/agent.ts`). The prompt treats the style rules as preferences, so the agent keeps punctuation that carries meaning or reads clearly, and never changes code, code spans, literals, patterns, or quotations.
274
+
275
+ ### Text Kept as Written
276
+
277
+ The tool rewraps prose paragraphs, list items, and JSDoc tag descriptions. It keeps everything else exactly as written. Those parts have no suggestion, and any fix to them is manual. A paragraph is a run of lines that ends at an empty line, a code fence, a JSDoc tag, or a list item. Which checks run in a paragraph that is kept depends on its syntax, never on what the text means:
278
+
279
+ - Code gets no checks besides `banner`. This covers fenced and indented code blocks, the content of `@example`, `@default`, and `@defaultValue`, directives like `eslint-disable-next-line` or `@ts-expect-error` (only their exact names, so a line that starts with a package name like `eslint-config-prettier` stays prose), legal comments (`/*!`, `@license`, `@preserve`, and copyright notices, outside of code spans), and paragraphs that parse as JavaScript.
280
+ - A line that only looks like code gets `overflow` but no hints, since the hints would point at the code. The whole paragraph is kept as written. This covers lines that end with `{`, start with `}`, `]`, or `)`, declare a `const`, `let`, or `var`, or are an `import` or `export` with `from` or braces, as well as lines that end with `;` and hold code characters or no spaces. Prose can look like that too, like a line that starts with `import them from`, so the width is still measured.
281
+ - Layout syntax gets hints but no verdicts. It exempts the whole paragraph that holds one of these lines. That covers a line that starts with `|` and a Markdown table delimiter row like `--- | ---`, and any box-drawing character from `─` to `╿` (U+2500 to U+257F). It also covers a line that starts with four or more of the same character among `-=*#_~+/\` or ends with four or more among `-=*#_~` (but not a Markdown heading or a row of column underlines like `---- ----`), and aligned columns, meaning a gap of two or more spaces after which text continues at the same column on at least two lines.
282
+ - Text laid out by hand gets hints and `overflow`. This covers a Markdown hard line break, meaning a line that ends with two or more spaces or with a backslash and is followed by another line of the same paragraph. The whole paragraph is kept byte for byte, trailing spaces included. Like in Markdown, a backslash that another backslash escapes, as in `\\`, and trailing spaces on the last line of a paragraph don't break a line, so they're rewrapped like any other prose. It also covers text that may be laid out by hand: a Markdown heading, link definition, HTML tag, or `>` quote at the start of a line (prose can start a line like `<T> is the type` or `# of retries` too), a line with two or more `|`, arrows like `-->`, `<--`, or `→`, rules like `---`, `+--`, or `====`, and two or more lines that each start with a term and a dash or colon, like `width - the maximum width`. Single-line block comments and block comments without a `*` on each line are checked the same way. So is prose that the tool can't rewrap without changing how it reads or adding a line that is too long. That is a paragraph whose rewrap would itself read as code or layout, like one that would end with `;` on the same line as a `>`. It is also a paragraph with a code span or `{...}` group that continues on the next line and doesn't fit on one line, which is what a stray backtick or brace looks like too. JSDoc tags whose content isn't a description, like `@typedef`, are never rewrapped but still have to fit the width.
283
+
284
+ A comment with an issue in a part that is kept is handed to the agent, which can fix the prose by hand. To keep a free-form drawing that is wider than the width, put it in a code fence, or add `comments-ignore` to the comment.
285
+
286
+ ### Configuration
287
+
288
+ `width` is the preferred line length; `maxWidth` is the hard maximum, including indentation and the comment prefix. The CLI defaults to a maximum of 120 columns (or `width`, if larger). A balanced parenthetical can extend a line past `width` when this keeps it together within `maxWidth`. In longer paragraphs, the wrapper balances all lines together and may split a parenthetical when that produces a better layout. If the parenthetical cannot fit on a line of its own, it wraps normally. For example, `width: 80, maxWidth: 120` keeps `(JSDoc types, \`{@link ...}\`)` together in the example below:
289
+
290
+ ```ts
291
+ /**
292
+ * Splits prose into words, keeping backtick code spans and `{...}` groups (JSDoc types, `{@link ...}`)
293
+ * intact so that they are never broken across lines.
294
+ */
295
+ ```
296
+
297
+ The separate `singleLineWidth` allowance is capped by `maxWidth`. Code and documented layout regions retain their exemptions. An indivisible token in measured prose that exceeds `maxWidth` is reported as overflow; the wrapper preserves it, so resolving that case requires changing the maximum or the comment manually. The legacy `tolerance` option sets a maximum of `width + tolerance` when `maxWidth` is omitted; an explicit `maxWidth` takes precedence. Direct `analyzeSource` callers can omit `maxWidth` to retain the legacy width/tolerance behavior.
298
+
299
+ Like prettier, `comments` looks for a config file in the current directory and its parents, up to the repository root. The config can be a `comments.config.{ts,mts,js,mjs,cjs}`, `.commentsrc`, or `.commentsrc.json` file, or a `comments` key in `package.json`. All options are optional.
300
+
301
+ ```ts
302
+ // comments.config.ts
303
+ import { defineConfig } from '@d3lm/lint-preset/comments';
304
+
305
+ export default defineConfig({
306
+ // defaults to prettier's printWidth, or 80
307
+ width: 100,
308
+ maxWidth: 120,
309
+ // a comment that is one paragraph stays on one line up to this length, and wraps to `width` beyond it
310
+ singleLineWidth: 120,
311
+ avoidSentenceOrphans: true,
312
+ // look for possible semicolons, em-dashes, dashes, label colons, emphasis, and banners as hints,
313
+ // which only decides what the agent sees, since its rules apply to every comment it gets
314
+ style: true,
315
+ ignore: ['src/generated/**'],
316
+ agent: {
317
+ runner: 'claude', // or 'codex', 'cursor'
318
+ model: 'haiku',
319
+ timeout: 300, // seconds per agent
320
+ batchSize: 20, // most comments per agent
321
+ maxFiles: 10, // most files per agent
322
+ maxAgents: 6, // most agents running at once
323
+ scope: 'suspicious', // a triage, or 'all' to review the wording of every changed comment
324
+ instructions: 'Rewording is allowed when a sentence reads awkwardly.',
325
+ },
326
+ hook: {
327
+ mode: 'agent', // or 'report', 'off'
328
+ },
329
+ });
330
+ ```
331
+
332
+ Most options also have a command line flag that overrides the config file for a single run. See `comments --help` for the full list.
333
+
334
+ Comments are split across agents, because long prompts make an agent slower and less careful. All comments of a file go to the same agent, so two agents never edit the same file. Up to `maxAgents` agents run at once, and the rest wait their turn.
335
+
336
+ In a terminal, `comments agent` shows a live table of the agents while they run. Afterwards, it lists the comments that still have issues and lets you open each agent's reply. `--no-interactive` skips that list, so the command shows the agents while they run and exits when they're done. Without a terminal, for example in a hook or in CI, it prints plain progress lines and only prints the replies of agents that failed. `--verbose` prints every reply in both cases.
337
+
338
+ `agent.rules` points to a Markdown file that replaces the built-in formatting rules. `agent.command` runs any other agent CLI, like `['my-agent', '--prompt', '{prompt}']`. Without a `{prompt}` placeholder, the prompt goes to stdin.
339
+
340
+ ### Agent Stop Hooks
341
+
342
+ `comments hook` runs when an agent finishes. It finds suspicious comments in the changed lines and hands them to a short-lived agent that doesn't keep a session. That catches what the hints point at, but it isn't a full review. With `--all` or `agent.scope: 'all'`, the agent reviews the wording of every changed comment, which costs an agent run whenever a comment changed. This repository uses `all`. Afterwards, the hook checks the edited comments again and reports any that still have verdicts. It also reports files where the agent changed code. In a git repository, it reports every other file that changed while the agents ran too, since an agent may only edit the comments it was given. That report can't tell an agent's edit from one of yours, so it never counts as a failure. When nothing is suspicious, no agent runs. The hook always exits successfully, so it never blocks the agent. That includes errors like an option it doesn't know, which it reports as a message instead.
343
+
344
+ Cursor (`.cursor/hooks.json`):
345
+
346
+ ```json
347
+ {
348
+ "version": 1,
349
+ "hooks": {
350
+ "stop": [{ "command": "node_modules/.bin/comments hook --host cursor", "timeout": 360 }]
351
+ }
352
+ }
353
+ ```
354
+
355
+ Claude Code (`.claude/settings.json`):
356
+
357
+ ```json
358
+ {
359
+ "hooks": {
360
+ "Stop": [
361
+ {
362
+ "hooks": [
363
+ {
364
+ "type": "command",
365
+ "command": "\"$CLAUDE_PROJECT_DIR\"/node_modules/.bin/comments hook --host claude",
366
+ "timeout": 360
367
+ }
368
+ ]
369
+ }
370
+ ]
371
+ }
372
+ }
373
+ ```
374
+
375
+ Codex (`.codex/hooks.json`, then trust it once via `/hooks`):
376
+
377
+ ```json
378
+ {
379
+ "hooks": {
380
+ "Stop": [
381
+ {
382
+ "hooks": [
383
+ {
384
+ "type": "command",
385
+ "command": "\"$(git rev-parse --show-toplevel)\"/node_modules/.bin/comments hook --host codex",
386
+ "timeout": 360,
387
+ "statusMessage": "Formatting comments"
388
+ }
389
+ ]
390
+ }
391
+ ]
392
+ }
393
+ }
394
+ ```
395
+
396
+ The host stops a hook that runs longer than its timeout. Leave room for one agent's `timeout` (300 seconds by default), and raise it if your changes often need more than `maxAgents` agents, because each extra round of agents can take up to `timeout` again.
397
+
398
+ By default, each host uses its own CLI as the agent. Set `agent.runner` to always use the same one. Claude Code and Codex show the hook's summary in the UI, but Cursor only shows it in the hook output log.
399
+
234
400
  ## Development
235
401
 
236
402
  ```bash