// TypeScript のコメントを検出・集計・安全に削除する CLI // detect, report and safely remove TypeScript comments
「レビューで直しました」は、
コードに書かない。
Fixes go in code.
Stories go in git.
コーディングエージェントが焼き込む“開発の経緯”コメントを、TypeScript の AST で検出して安全に削除する
CLI。ビルドを支える 92 種類のディレクティブは残したまま、
--diff でエージェントが触ったファイルだけを掃除します。
A CLI that detects and safely removes the narration comments coding agents bake into TypeScript — via the
AST. It keeps all 92 directive kinds your build depends on, and
--diff scopes the sweep to the files the agent just touched.
// なぜ要るのか// why this exists
エージェントは“経緯”をコードに焼き込む Agents bake the meeting minutes into the code
エージェントにレビュー往復をさせると、修正のたびに「指摘に従って直した」という報告文が コメントとして積まれていきます。CLAUDE.md やフックで指示しても止まらないことは公式 issue でも報告されている挙動です。コードの制約を書くコメントは資産ですが、開発の出来事を 引用形式で語るコメントは、半年後の読者には何の情報でもありません。経緯なら git blame と PR スレッドが持っています。 Send an agent through a review loop and every round deposits another “per the review feedback, we now …” into your source. Instructions in CLAUDE.md or hooks don't reliably stop it — there are open issues about exactly this. A comment that states a constraint is an asset; a comment that narrates what happened during development tells a reader six months from now nothing. git blame and the PR thread already keep the story.
// Empty string means "--diff with no value": // fall back to comparing against HEAD. if (input === "") return "HEAD";
// Note: Codex review flagged this case. // Per that feedback, we now guard it. if (input === "") return "HEAD";
// できること// what it does
検出・集計から、壊さない削除まで From detection to deletion that doesn't break
検出と集計Detect & report
.ts / .tsx / .mts / .cts を再帰的にスキャンし、行・ブロックコメントを位置情報つきで text / json / github の 3 形式で出力。 Recursively scans .ts / .tsx / .mts / .cts and reports line and block comments with positions, as text, JSON or GitHub annotations.
安全な削除Safe removal
AST のコメント範囲だけを削除。ディレクティブとライセンスヘッダーはデフォルトで保持し、 削除後は再スキャンで検証。 Splices only AST comment ranges. Directives and license headers are kept by default, and the result is re-scanned to verify.
エージェントの作業範囲だけOnly what the agent touched
git が変更ありと報告するファイルだけに絞り込み。未追跡の新規ファイルも含み、 .gitignore は尊重。 Narrows the sweep to files git reports as changed — untracked files included, .gitignore respected.
ディレクティブを知っているKnows the directives
@ts-expect-error から webpackChunkName: まで、消すと挙動が変わるコメントを 92 本のルールで判定。 From @ts-expect-error to webpackChunkName:, 92 rules identify the comments whose removal would change behaviour.
CI ゲートA gate for CI
コメント検出時に終了コード 1。--format github なら PR の該当行にアノテーションが出ます。 Exit code 1 when comments are reported; with --format github the offending lines are annotated right in the PR.
ライブラリとしてもAlso a library
scanComments / removeComments / changedFiles などを TypeScript API として利用可能。 scanComments, removeComments, changedFiles and friends are available as a typed API.
// 安全装置// the guard rails
「壊れないコメント削除」を、真面目にやる Comment removal that refuses to break your build
正規表現でコメントを消すと、文字列の中の // やテンプレートリテラル、JSX を平気で壊します。削除を真面目にやると意外と深い — このツールが踏んでいる地雷の一部: Regex-based stripping happily corrupts strings, template literals and JSX. Done properly, removal turns out to be deep — a sample of the mines this tool steps around:
a/* x */b はトークンが結合してしまう
a/* x */b would merge two tokens
a b)
a space is inserted where they would join (→ a b)
{/* … */} を消すと
{ } が残る。外すとテキストの描画が変わることも
removing JSX's {/* … */} leaves { } behind — dropping it can change rendered
text
{" "} か
{} を残し、TypeScript・esbuild・Babel の規則で描画を検証
removed braces and all; only where rendering would change does {" "} or
{} stay, verified under TypeScript, esbuild and Babel rules
/** @internal */ を消すと stripInternal で隠していた内部 API が
.d.ts に漏れる
deleting /** @internal */ leaks the internal APIs stripInternal kept out of
the .d.ts
@internal はディレクティブとして保持。タグのない Copyright (c) … や
Licensed under … の著作権ヘッダーも法的コメントとして保持
@internal is kept as a directive, and so are untagged Copyright (c) … /
Licensed under … headers, as legal comments
catch の // ignore を消すと、ESLint の
no-empty がブロックを報告する
deleting the // ignore of an empty catch makes ESLint's
no-empty report the block
switch のコメントと、コードの無いファイルの
/// コメントはディレクティブとして保持
comments in a block, function body or switch that holds nothing else, and the
/// comments of a file with no code, are kept as directives
import の間のコメント行を消すと、organizeImports や dprint
が別々に並べ替えていたグループが 1 つになる
deleting a comment line between imports merges the groups organizeImports and dprint sort
separately
import 直下のコメント行を消すと、eslint-plugin-import や oxlint の
newline-after-import が空行の欠落を報告する
deleting the comment line right below the imports makes the newline-after-import rule of
eslint-plugin-import and oxlint report a missing empty line
import や
require と次の文を隔てる空行に置き換える(空行が残るときはそのまま)
the comment goes and an empty line takes its place below the import or
require, unless a blank line stays
@ts-expect-error 直下の行が消えると適用先がズレる
deleting the line under @ts-expect-error shifts its target
.js の /** @type {…} */ や @typedef を消すと、tsc
の型チェック(checkJs)の結果が変わる
deleting a /** @type {…} */ or a @typedef from a .js file
changes what tsc reports under checkJs
@type・@param・@typedef
など)を持つコメントをディレクティブとして保持。説明だけの JSDoc は削除
in JavaScript, a JSDoc comment holding a tag tsc reads (@type, @param,
@typedef, …) is kept as a directive; one that only documents goes
// この挙動は 1377 本のテストと 100% カバレッジ(statements / branches / functions / lines)で固定しています // pinned by 1377 tests at 100% coverage — statements, branches, functions and lines
// 92 のディレクティブ// the 92 directives
消すとビルドが壊れるコメントを、知っている It knows which comments your build depends on
判定は「それっぽい文字列を含むか」ではなく、各ツールの実際のパーサー実装(正規表現・文字列比較)に合わせてあります。 Detection is not “contains a similar-looking string” — each rule mirrors the actual parser implementation of the tool it protects.
- rspackChunkName: ブロックコメントの途中にあっても有効 — Rspack 1.x と 2.1 以降は文章や JSDoc の * の後ろにあるキーも拾うため、webpack では無効な /** webpackChunkName: … */ もディレクティブとして保持。rspackChunkName のような rspack 接頭辞のキーは 2.1 から。 Live anywhere in a block comment — Rspack 1.x and 2.1+ pick the key out past prose or a JSDoc star, so /** webpackChunkName: … */, void to webpack, is kept as a directive. Keys with the rspack prefix work since 2.1.
- codeql[...] 改行を含まないコメントのみ — CodeQL の AlertSuppression クエリは 1 行に収まるコメントしか読まないため、/* lgtm[...] */ は有効、複数行にまたがるブロックコメントは普通のコメント扱い。 Single-line comments only — CodeQL's AlertSuppression query reads only comments without a line break, so /* lgtm[...] */ counts while a block comment spanning lines stays ordinary.
- //// @ts-ignore tsc 7 だけが有効とみなす — Go 移植版の tsc 7 は余分なスラッシュをいくつでも読み飛ばすため、tsc 6 では普通のコメントでもディレクティブとして保持。 Live to tsc 7 alone — the Go port skips any number of extra slashes, so the comment is kept although tsc 6 reads it as an ordinary one.
- /*jslint マーカー直後に空白があると無効 — JSLint はコメント開始直後の文字列しか見ないため、空白ありは普通のコメント扱い。 Void if a space follows the marker — JSLint only honours the pragma glued to the comment opener.
// 使い方// how to use it
まず見る、それから消す Look first, then delete
# 何がどこにあるかを見る # see what is where
# エージェントが触ったファイルだけ、まず削除対象を確認 # preview the sweep, scoped to files the agent touched
# 問題なければ削除(ディレクティブとライセンスは保持される) # then delete — directives and license headers survive
# CI で経緯コメントの混入を止める(PR の該当行に注釈が出る) # gate CI — offending lines get annotated in the PR
-
絞るScope
--diff HEADでエージェントが直近触った範囲だけを対象にする。--diff HEADnarrows the target to what the agent just touched. -
確かめるPreview
--dry-runで削除対象を人間が最終確認する。元からあった資産コメントを守るのはここ。--dry-runputs a human in the loop — this is where pre-existing comment assets get protected. - 掃除するSweep 削除して、本当に必要な知見は経緯抜きの Why コメントとして書き直させる。 Delete, and have real insights rewritten as Why comments — without the narration.
終了コード: 0 成功 · 1 --fail-on-comment でコメント検出 · 2 引数・実行時エラー / Node.js ≥ 20 / MIT Exit codes: 0 success · 1 comments found with --fail-on-comment · 2 usage or runtime error / Node.js ≥ 20 / MIT