mcpField
N
node-version-discipline

node-version-discipline

github.com/fudesign2008開発671 回インストール1 日前に更新
リポジトリ

node-version-discipline について

Node プロジェクトを扱うコーディングエージェント向けのスキルです。インストールやビルド、 lint、型チェック、テストの前に宣言済みの Node バージョンを調べて合わせ、未宣言の場合は所定の手順で確認します。

主な機能

  1. 01優先順にバージョンファイルと CI 設定を確認
  2. 02実行前に Node バージョンを合わせる
  3. 03切り替え後に有効なバージョンを確認
  4. 04未宣言の場合はユーザーに確認
  5. 05承認後にバージョン宣言を保存

活用シーン

  1. 01依存関係のインストール前に Node 制約を確認
  2. 02ビルドやテスト結果の誤判定を防止
  3. 03Node 未宣言のプロジェクトに対応

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/fudesign2008/open-skills --skill node-version-discipline

インストール後、Claude Code で/node-version-disciplineを入力して読み込まれたか確認します。

同梱ファイルevals/evals.json

詳細

Node バージョンの管理

Node プロジェクトでビルド/lint/型チェック/テスト/インストールのコマンドを実行する前に、プロジェクトで宣言されているバージョンに Node のバージョンを合わせてください(次の順に一連の設定を確認します:.nvmrc → .node-version → .tool-versions → volta.node → engines.node → CI 設定)。そうしないと、検証結果は信頼できません。

1. バージョンを合わせる必要がある理由

ホストのデフォルトの Node バージョン(例:v22)は、プロジェクトの .nvmrc で指定されたバージョン(例:v14)と一致しないことがよくあります。デフォルトのバージョンで tsc/eslint/npm run build/npm test を実行すると、次のような問題が発生します。

リスク影響
誤った成功新しい Node はより寛容なため、tsc/eslint は成功しても、指定バージョンでは実行時にクラッシュすることがあります
誤った失敗新しい Node はレガシーなツールチェーン(例:Webpack 4 のネイティブ依存関係)を壊し、コードとは無関係なビルドエラーを発生させて、調査の方向を誤らせることがあります
動作の差異古いバージョンには API(structuredClone、AbortController など)がなく、実行時の動作が異なります
依存関係の解決における差異node_modules には、その時点で有効な Node 用のネイティブモジュールがインストールされます。バージョンが一致しないと、誤ったバリアントがインストールされます

重要な教訓:新しいバージョンでたまたま成功し、結果が偶然一致したとしても、.nvmrc で指定されたバージョンが唯一の基準です。「新しいバージョンで成功した」ことは、バージョンを合わせない理由にはなりません。

2. 判断ルール

バージョンを合わせるタイミング

プロジェクトで何らかの方法(以下の優先順位を参照)により Node バージョンが宣言されている場合は、次のコマンドを実行する前にバージョンを合わせてください。

  • tsc/npx tsc/npm run type-check
  • eslint/npm run lint
  • npm run build/webpack/その他のビルドスクリプト
  • npm test/jest/その他のテストコマンド
  • npm install/npm ci(誤ったバージョンで依存関係をインストールすると、誤ったネイティブモジュールが取得されます。確認とバージョン調整が完了する前に、絶対にインストールしないでください)
  • npm run <script>(スクリプト内部で、バージョンに依存するツールが実行されることがあります)

npm install/npm ci の出力に Exit handler never called!(または同様のインストール破損を示すメッセージ)が含まれている場合は、プロセスの終了コードが 0 でも、インストールは失敗したものとして扱ってください。その node_modules の使用を中止し、Node のバージョンを合わせてから、クリーンインストールを行い、その後で作業を続けてください。問題が疑われるツリーで tsc/ビルド/テストを続行しないでください。

バージョン宣言の優先順位チェーン

.nvmrc は nvm の規約ですが、唯一の方法ではありません。Node のバージョンマネージャーによって読み取るファイルが異なるため、次のチェーンを順番にすべて確認します。最初に見つかった権威ある情報源を採用します。

#情報源種類ツール間の注意点
1.nvmrcexactnvm と fnm はネイティブ対応。asdf では legacy_version_file が必要。Volta は無視
2.node-versionexactfnm/nodenv/nvs はネイティブ対応。asdf では legacy フラグが必要。nvm は無視
3.tool-versionsexactasdf はネイティブ対応
4package.json → "volta".nodeexactVolta のネイティブ形式。Volta が読み取る唯一の情報源
5package.json → engines.node範囲退化していない場合のみ(下記参照)。engine-strict が有効でない限り npm は警告を表示します。範囲内にあるインストール済みのバージョンを選びます
6CI/Docker の Node バージョン指定exact推定値です。レポートに情報源を明記します。候補:.gitlab-ci.yml、.github/workflows/*、Jenkinsfile、一般的な Dockerfile/Dockerfile.* という名前。image: node:<ver>、node-version:、NODE_VERSION、FROM node:<ver> を認識します(イメージ/バージョンのトークンを囲む YAML の引用符は任意)

ファイル(1~3行目)はディレクトリツリーを上方向にたどって確認します。package.json と CI/Docker の設定はプロジェクトルートから読み取ります。1行目と2行目には、ツール間で見落とされやすい点が2つあります。§4 を参照してください。

退化した engines.node: 現代のプロジェクトのバージョン指定として使えないプレースホルダーや古すぎる下限値は、権威のない情報として扱います。最低限、>=0.8.0、>0.8.0、>=0.10.0 および同様に古い下限値が該当します。チェーンの確認を続けます。ユーザーに尋ねる際、実際の CI 指定や README にある事例と並べて候補として提示しないでください。

CI/Docker のシェル安全な確認方法: [ -f path ] を使って各候補パスを確認します(または find や nullglob に安全なループを使用します)。zsh の nomatch が有効な状態で、必須のリテラルと省略可能なグロブを1つの展開される argv に入れてはいけません(例:ls .gitlab-ci.yml Dockerfile*。Dockerfile* が見つからないと、.gitlab-ci.yml の確認前に処理が中断されます)。少数の固定した名前リスト(Dockerfile、Dockerfile.web、…)と、パスごとのテストを使う方法を推奨します。プロジェクト固有の名前は、危険な複数グロブの ls ではなく、個別に [ -f ] で確認します。

宣言が存在しない場合

チェーンをたどっても何も見つからない場合、デフォルトの Node に黙ってフォールバックしたり、ユーザーに代わってバージョンを選んだりしないでください。それこそが、このスキルで防ぐ false-pass/false-fail の罠であり、バージョンを推測すると、誤った Node で検証全体を実行するリスクがあります。今回限りの確認で済むよう、ユーザーの回答を永続的なプロジェクト資産にしてください。

  1. 作業を止めて、使用する Node のバージョンをユーザーに尋ねます。 次のように質問してください。「このプロジェクトでは Node のバージョンが宣言されていません(.nvmrc/.node-version/.tool-versions/volta/engines.node/CI の固定指定が見つかりませんでした)。[tsc / build / test / ...] を実行する前に、どの Node バージョンに合わせればよいですか?」 ユーザーの回答を待ち、推測したバージョンで先に進まないでください。
  2. 読み取り専用の detect_manager スニペット(§2.2)でツールチェーンを調べ、このプロジェクト/環境に適した宣言ファイルを提案します。スニペットが出力するのはシグナルだけで、何も書き込みません。
  3. 永続化する前にユーザーの確認を得ます。 ユーザーがたった今指定した正確なバージョンを含む、推奨する宣言ファイルの書き込みを提案してください(ファイルの種類は§2.1で選び、バージョンはユーザーが指定した完全な X.Y.Z 形式にします。例:22.5.0。メジャーバージョンだけにはしません)。ユーザーが同意した場合にのみ永続化します。同意しない場合は書き込みを省略し、このセッションではアラインメントのみ行ってください。いずれの場合も、実際のコマンドを実行する前に、指定されたバージョンに合わせてください(SOP §3)。
  4. 純粋なファイル I/O/git/grep(Node ランタイムを使用しないもの)には、アラインメントも宣言ファイルも必要ありません。

Active LTS をデフォルトにせず尋ねる理由:プロジェクトにとって「正しい」Node は、AI が把握していないコンテキストによって異なります。たとえば、本番環境のランタイム、CI マトリックス、チームメイトの環境、レガシーな制約などです。誤った推測は、その後のすべての結果をひそかに無効にします。質問に必要なのは簡単な確認だけですが、false-pass/false-fail は調査全体を誤った方向に導く可能性があります。

回答を永続化する理由(1回だけ合わせるのではなく):ユーザーが選んだバージョンは、価値の高いエンジニアリング情報です。会話の中にしか残らなければ、今後のセッション、CI 実行、共同作業者が毎回同じ「宣言なし」の壁にぶつかり、別のバージョンを選ぶ可能性があります。小さな宣言ファイルを1つ書けば、この問題を解消できます。次のセッションでは、最初の確認時にそのファイルを読み取り、再度尋ねる必要がなくなります。

2.1 書き込む宣言ファイルを選ぶ

detect_manager(§2.2)が報告したシグナルに基づいて、ファイルの種類を選びます。最初に一致した行を採用します。.nvmrc は、nvm/fnm(および legacy_version_file を設定した asdf)がすべて読み取るため、幅広いツールをカバーするデフォルトです。

シグナル(§2.2 より)書き込むファイル内容の形式このファイルを選ぶ理由
package.json にすでに volta ブロックがある、またはホストに volta がインストールされているpackage.json → volta.node フィールド"volta": { "node": "22.5.0" }(既存の volta オブジェクトにマージ)Volta が読み取るのはこの設定のみです。.nvmrc は Volta ユーザーには認識されません
ホストに asdf がインストールされている、またはプロジェクトに他の言語用の .tool-versions がすでにある.tool-versions(行を追加)nodejs 22.5.0asdf のネイティブ設定です。すべての言語のバージョン指定を1つのファイルにまとめられます
fnm または nodenv がインストールされている(かつ上記のいずれにも該当しない).node-version22.5.0fnm/nodenv のネイティブ設定です。nvm はこのファイルを無視するため、nvm のみを使う場合は選ばないでください
上記のいずれにも該当しない/不明.nvmrc22.5.0最も幅広く対応します。nvm と fnm はネイティブに対応し、asdf は legacy_version_file で対応します。安全なデフォルトです

バージョン形式 — パッチバージョンまで正確に指定します。 ユーザーが指定した完全な X.Y.Z を使用します(または、ユーザーが「インストール済みのバージョンを使う」と言った場合は、node -v で取得し、先頭の v を削除した正確なインストール済みバージョンを使用します)。メジャーバージョンのみ(22)や範囲(>=20)は指定しないでください。宣言ファイルの役割は、再現可能な単一のバージョンを固定し、すべての環境で同じバージョンを使用することです。.nvmrc/.node-version には 22.5.0 のみを指定します(v 接頭辞なし。nvm の規約です)。.tool-versions には nodejs 22.5.0、engines.node/volta.node には引用符付き文字列 "22.5.0" を指定します。(後からユーザーが engines.node に範囲を指定したい場合は、別途意識的に選択する必要があります。この手順では正確なバージョンを固定します。)

宣言ファイルの作成後にインストールします。 まず宣言ファイルを書き込み、その後にインストールを行います。このファイルは CI や共同作業者に対する意図の表明であり、ローカルマシンにそのバージョンがインストール済みである必要はありません。書き込み後、ls ~/.nvm/versions/node/ を実行してそのバージョンがローカルにないことが分かった場合は、SOP §3 ステップ2に従って nvm install <version>(または使用しているバージョン管理ツールの同等のコマンド)を促してから、先に進みます。これにより、ユーザーがインストールを後回しにしても、プロジェクトのファイルは作成されます。

書き込み場所。 プロジェクトのルート(package.json と同じ階層)です。.nvmrc/.node-version/.tool-versions は新しいファイルを作成します。volta.node/engines.node は既存の package.json にマージします。ファイルを上書きせず、必ず既存の JSON を編集してください。

2.2 detect_manager — 読み取り専用のツールチェーン確認

そのまま貼り付けて実行できます。読み取り専用です。§2.1に従って AI が適切なファイルを選べるよう、シグナルを表示します。ファイルへの書き込みは行いません。書き込みはユーザーの確認を得たうえで別の手順として行います(§2 ステップ3)。

# Read-only: detect which Node version manager this project/env uses.
# Prints one signal per line. AI maps them to a declaration file via §2.1.
# Does NOT write anything — persistence is a separate, user-confirmed step.
[ -f package.json ] && node -e "require('./package.json').volta && console.log('volta-struct')" 2>/dev/null
command -v volta  >/dev/null && echo "volta-installed"
command -v asdf   >/dev/null && echo "asdf-installed"
[ -f .tool-versions ] && echo "tool-versions-exists"
command -v fnm    >/dev/null && echo "fnm-installed"
command -v nodenv >/dev/null && echo "nodenv-installed"
# No output above → default to .nvmrc (broadest cross-tool coverage).

この確認ではバージョンではなく、バージョン管理ツールを検出します。バージョンは常にユーザーの指定から取得します(§2 ステップ1)。ホストのデフォルトから取得してはいけません。

3. 標準作業手順書(SOP)

ステップ1 — バージョン指定を確認する
# Prefer .nvmrc (walk up the tree)
cat .nvmrc 2>/dev/null
# Fallback to package.json engines
node -e "console.log(require('./package.json').engines?.node || 'no engines constraint')" 2>/dev/null

上のスニペットでは、分かりやすさのため、最も一般的な2つの情報源を示しています。ツール横断の完全な優先順位(.nvmrc → .node-version → .tool-versions → volta.node → engines.node → CI 設定)は、§7のそのまま貼り付けて実行できるスクリプトに実装されています。実際の作業ではそちらを使用してください。

ステップ2 — 対象バージョンがインストール済みか確認する
ls ~/.nvm/versions/node/ | grep <target-version>

未インストールの場合は、ユーザーに nvm install <version> の実行を促し、そこで終了します。

ステップ3 — 1つのコマンド内で切り替えて実行する(推奨)
source ~/.nvm/nvm.sh && nvm use <target-version> >/dev/null 2>&1 && node -v && <actual-command>

シェルの状態は保持されません。nvm use が影響するのは、現在のシェルプロセスのみです。Bash ツールを呼び出すたびに独立したシェルが起動する場合があるため、nvm use の設定は次の呼び出しには引き継がれません。バージョンに依存するコマンドはすべて、同じ呼び出し内で source ~/.nvm/nvm.sh && nvm use <version> && <command> を実行する必要があります。「先に nvm use を実行し、その後にコマンドを実行する」というように分けてはいけません。

ステップ 4 — 切り替えが成功したことを確認する

切り替えた後は、実際のコマンドを実行する前に必ず node -v を実行してください。nvm use は、対象のバージョンがインストールされていない場合など、失敗しても何も表示しないことがあります。確認を省略すると、実際には切り替わっていないのに切り替わったと思い込むおそれがあります。

4. よくあるエラーパターン(呼び出し側で必ず検出すること)

パターン結果正しい対応
.nvmrc を無視して、直接 npx tsc --noEmit を実行する信頼できない結果まず .nvmrc を検出し、nvm use を実行してからコマンドを実行する
2 つの手順に分ける:nvm use 14 を実行し、次の呼び出しで tsc を実行するシェルの状態が失われ、デフォルトに戻る1 回の呼び出しで実行する:source && nvm use && cmd
node -v を実行せずに nvm use 14 を実行する失敗に気づかず、成功と誤認する切り替え後は必ず node -v を実行する
インストールされていないバージョンで nvm use を実行するエラーまたは何も表示されないまま失敗するまず ls ~/.nvm/versions/node/ を実行し、存在しなければ nvm install を促す
nvm use node / nvm use default.nvmrc のバージョンにならないことがある正確なバージョンを指定するか、nvm use $(cat .nvmrc) を実行する
「より新しいバージョンで成功した」≡「固定バージョンで成功した」誤った成功判定固定バージョンを正とする
プロジェクトに .node-version があるが、nvm を使用しているnvm は .node-version を無視する → デフォルトに暗黙にフォールバックするチェーン全体を調べる(§2)。.node-version しかない場合は、nvm use $(cat .node-version) を実行する
プロジェクトが Volta(package.json の "volta")でバージョンを固定しているが、nvm/fnm を使用しているVolta の固定バージョンは nvm/fnm から認識されないpackage.json の "volta".node を読み取り、その正確なバージョンで nvm use を実行する
ls .gitlab-ci.yml Dockerfile*(zsh で Dockerfile がない場合)グロブに失敗 → コマンド全体が実行されない → 「CI なし」と誤判定するパスごとに [ -f ] を使うか、§7 の CI 検出を使う。リテラルと一致しないグロブを混在させない
engines: ">=0.8.0" を固定バージョンとして信頼する実際の CI での固定バージョンを見落とし、的外れな候補を提示するengines が無効な場合は、チェーンの確認を続ける(§2)
バージョンを合わせる前に npm install を実行する。終了コードが 0 のときに Exit handler never called! を無視するnode_modules / lockfile が破損し、後続の確認が信頼できなくなる先にバージョンを合わせる。そのメッセージが出たらインストール失敗として扱う(§2)

5. 検証結果の報告

検証結果を報告するときは、成功・失敗だけでなく Node のバージョンも明記してください。

## Code quality
- Node version (.nvmrc v14.21.3): ✅ switched via nvm use
- tsc --noEmit: ✅ 0 errors
- eslint: ✅ no errors/warnings

プロジェクトでバージョンが宣言されておらず、ユーザーがバージョンを指定した場合は、情報源を監査できるよう、その事実を明確に記載してください。宣言を永続化した(次のセッションで自動的にバージョンが合う)か、今回だけ適用したかも区別してください。

- Node version (user-specified v20.11.0, no prior project declaration): ✅ aligned; persisted to .nvmrc (22.5.0) — future sessions will auto-align

ユーザーが永続化を拒否した場合は、ギャップが見えるよう、そのことを明確に記載してください。

- Node version (user-specified v20.11.0, no project declaration): ✅ aligned for this run; user declined to persist — declaration still missing, recommend adding .nvmrc

切り替えずにコマンドを実行した場合は、⚠️ を付けて再実行してください。

6. 2 つの統合契約

このスキルは依存先であり、参照方法を 2 つ提供します。

6.1 ハード依存契約(コアスキル)

バージョンの整合が絶対条件となるケース(tsc / eslint / build)向け:

# in the caller's frontmatter
dependencies:
  - node-version-discipline

呼び出し側は起動時に前提条件を確認します(solve-workflow と同じパターン)。このスキルがなければ、直ちに中止してインストールを促します。暗黙の機能低下はありません — あらゆる環境で信頼できる検証を保証します。

6.2 ソフト参照契約(拡張スキル)

バージョンの整合がベストプラクティスとなるケース(npm test / ブラウザのデバッグ / 一般的なエンジニアリングコマンド)向けに、呼び出し側の本文の該当する手順に次の 1 行を追加します。

実行前に node-version-discipline スキルを呼び出し、Node のバージョンを .nvmrc に合わせてください。

dependencies の宣言も前提条件の確認も不要です。この手順で AI が意識的に呼び出すことを前提とします。

7. 自己完結型の検出スクリプト(そのまま貼り付け可能)

# Probe the full version-declaration chain (cross-tool), including CI/Docker, then switch via nvm.
# First authoritative hit wins. Shell-safe: per-path [ -f ], no mixed literal+glob ls.
# See §2 for priority, degenerate engines, and CI rules.
probe_file() {  # $1 = filename; prints a version walking up the tree
  for d in . .. ../.. ../../..; do
    [ -f "$d/$1" ] && { cat "$d/$1" | grep -Eo 'v?[0-9]+(\.[0-9]+){0,2}' | head -1 | sed 's/^v//'; return; }
  done
}
extract_node_pin() {  # stdin → first node major/minor/patch from CI/Docker idioms
  # Strip YAML/Actions quotes so `node-version: '18'` / `image: "node:14"` still match.
  sed -E "s/[\"']//g" \
    | grep -Eo '(image:[[:space:]]*node:|FROM[[:space:]]+node:|node-version:[[:space:]]*|NODE_VERSION[=:[:space:]]+)v?[0-9]+(\.[0-9]+){0,2}' \
    | head -1 | grep -Eo 'v?[0-9]+(\.[0-9]+){0,2}' | head -1 | sed 's/^v//'
}
probe_ci() {
  # Fixed literals only — never `ls … Dockerfile*` under zsh nomatch.
  for f in .gitlab-ci.yml Jenkinsfile Dockerfile Dockerfile.web Dockerfile.node; do
    [ -f "$f" ] || continue
    ver="$(extract_node_pin < "$f")"
    [ -n "$ver" ] && { echo "$ver"; return; }
  done
  if [ -d .github/workflows ]; then
    # find avoids shell glob expansion failures
    while IFS= read -r f; do
      [ -f "$f" ] || continue
      ver="$(extract_node_pin < "$f")"
      [ -n "$ver" ] && { echo "$ver"; return; }
    done < <(find .github/workflows -type f \( -name '*.yml' -o -name '*.yaml' \) 2>/dev/null)
  fi
}
is_degenerate_engines() {
  case "$1" in
    '>=0.8.0'|'>0.8.0'|'>=0.10.0'|'>0.10.0'|'>=0.12.0') return 0 ;;
    *) return 1 ;;
  esac
}
NODE_VER="$(probe_file .nvmrc)"
[ -z "$NODE_VER" ] && NODE_VER="$(probe_file .node-version)"
[ -z "$NODE_VER" ] && NODE_VER="$(probe_file .tool-versions)"
if [ -z "$NODE_VER" ] && [ -f package.json ]; then
  NODE_VER="$(node -e "const p=require('./package.json'); console.log(p.volta?.node || '')" 2>/dev/null)"
fi
if [ -z "$NODE_VER" ] && [ -f package.json ]; then
  ENG="$(node -e "const p=require('./package.json'); console.log((p.engines&&p.engines.node)||'')" 2>/dev/null)"
  if [ -n "$ENG" ] && ! is_degenerate_engines "$ENG"; then
    NODE_VER="$(printf '%s' "$ENG" | grep -Eo '[0-9]+(\.[0-9]+){0,2}' | head -1)"
  fi
fi
[ -z "$NODE_VER" ] && NODE_VER="$(probe_ci)"
if [ -n "$NODE_VER" ]; then
  source ~/.nvm/nvm.sh 2>/dev/null && nvm use "$NODE_VER" >/dev/null 2>&1
  echo "✅ Node: $(node -v) (aligned to $NODE_VER)"
else
  echo "ℹ️ No declaration found — STOP and ask the user which Node version to use (do not guess). Current host default: $(node -v)"
  echo "   Then run detect_manager (§2.2) to pick a declaration file, confirm with the user, and persist it (§2.1) so future sessions auto-align."
fi

8. nvm 以外の環境でのフォールバック

この SOP は nvm(Unix/macOS)を前提としています。それ以外の環境では、次のように対応します。

環境フォールバックの対応
Windowsユーザーに手動で切り替えるよう促します(nvm-windows/volta use)。レポートには「自動で整合されていないため、バージョンを手動で確認してください」と ⚠️ を付けて記載します
fnm / volta / asdfnvm use を同等のコマンド(fnm use/volta pin/asdf local nodejs <version>)に置き換えます
コンテナー/CINode のバージョンはイメージによって固定されています。イメージのバージョンが .nvmrc と一致していることを確認します

整合が取れたかのように装ってはいけません。自動で整合されなかった場合は、必ずその旨を明示してください。

9. 関連項目

  • 起源:モバイルプロジェクトで .nvmrc が v14.21.3 に固定されていた一方、ホストのデフォルトが v22 だったため、新しいバージョンで tsc/eslint が誤って合格となった実際の問題への対応です。
  • CI の偽陰性:Node のバージョン指定が .gitlab-ci.yml の image: node:14 のみだったプロジェクトが、zsh のグロブ展開の中断後に「未宣言」と誤って報告されました。§2 のシェルセーフな検出と §7 の probe_ci により、この問題を解消します。
  • バージョン未宣言の場合:プロジェクトで Node のバージョンがまったく宣言されていない場合、§2 ではユーザーの回答を永続的な宣言ファイルに変換します(§2.1 では §2.2 のツールチェーン検出により種類を選択します)。これにより次回のセッションでは、再度尋ねることなく最初の検出で読み取れるようになり、処理が完結します。
  • 参照元:frontmatter の dependencies を介した PDCA ホストおよび関連ツール(ホストが SoT であり、このスキルでは統合ツール名の一覧を管理しません)。test-suite-ensure はテスト前に整合性の確認を任意で参照できます。

コメント