# npm-floor — 利用者ゼロでも付く DL は何件か。自分のパッケージはそれを超えているか

npm のパッケージには、誰も使っていなくてもミラー・CI のキャッシュ・セキュリティスキャナからの DL が付きます。`npm-floor` は、その件数（＝床）を自分のパッケージについて出し、実測がそこから抜け出しているかを 1 コマンドで答えます。

```
npx npm-floor <自分のパッケージ> --lang ja
```

インストール不要・アカウント不要・トークン不要。Node.js 20 以上。読むだけ・依存パッケージなし・MIT。英語の README（正）は [`README.md`](https://unpkg.com/npm-floor@0.1.1/README.md) です。

別売りで、月次表のもとになった **DL の床のデータセット**（¥980）があります → [販売ページ](https://lab-cws.pages.dev/npm-floor/)。この道具そのものは無料です。

## 何が出るか

`left-pad` の実出力（2026-09-18）:

<!-- example:left-pad（実出力・2026-09-18） -->
```
npm-floor 0.1.0  left-pad
窓                            2026-08-18 .. 2026-09-16  (30 日・UTC)

実測 DL                            6,360,903
床（ミラー＋スキャナ）                    17   p10〜p90  9 .. 24
超過（実測 − 床）                  6,360,886
窓内に公開した版                           0   （全 15 版）
判定                          明確に床の上   実測 ≥ 3 × 床 p90（73）

床の内訳
  版ごとの初動                窓内に版なし
  滴り                        0.67 件/日 × 26 日 = 17   [9 〜 24]  （コホート 2026-07・n が少ない・表より後（最後の月で代用））
  コホート表                  同梱・作成 2026-09-18（2024-01 〜 2026-07。npm-floor explain で表示）

注意
  - この窓のうち 4 日（2026-09-03・2026-09-07・2026-09-08・2026-09-15）は npm の日次がレジストリ全体で 0 でした（npm で照合）。実測はその分だけ過小で、滴りは残りの 26 日に掛けています。
  - コホート表の範囲外の月があり、いちばん近い月の値で代用しました。
  - 使ったコホート月の件数が目標の 30 件に届いていません（2026-07: 22）。その分だけ帯の信頼性は下がります。
  - このパッケージは repository・README・複数版のどれかを持ちます。コホート（単一版・repository 無し）より多くの自動 DL を集めるはずで、床は下限です。「床を超える」は弱い証拠です。
  - 「超過」は床で説明できない分であって、本当の利用者数ではありません。
```
<!-- /example:left-pad -->

見るのは「判定」の行です。答えは 3 つ。

| 判定 | 意味 |
|---|---|
| `床と区別できない` | 自動化だけで説明がつく数 |
| `床を超える` | 床より多いが、大きくはない。弱い証拠 |
| `明確に床の上` | 床の上端（p90）の 3 倍以上 |

「床」は中央値と p10〜p90 の帯、「超過」は実測から床の中央値を引いた数です。その下は床をどう組んだかと、このパッケージで信頼性が下がる要因です。

## 使い方

**1. パッケージを測る**

```
npx npm-floor <package> --lang ja
```

既定の窓は 2 日前を末日とする 30 日（npm の直近 1〜2 日は 0 で返ることがあるため）。スコープ付き（`@scope/name`）も同じです。日本語ロケールの OS なら `--lang ja` を付けなくても日本語で出ます（環境変数 `NPM_FLOOR_LANG=ja` でも可）。

**2. JSON で受け取る**

```
npx npm-floor <package> --json
```

表と同じ内容: `actual`・`floor {median,p10,p90}`・`excess`・`verdict`（`indistinguishable` / `above` / `clearly-above`）・`thresholds`・`versionsInWindow`・`notes[]` と内訳。終了コード 0。

**3. 窓を選ぶ**

```
npx npm-floor <package> --window 7                       # 2 日前を末日とする 7 日
npx npm-floor <package> --end 2026-08-31                 # その日を末日とする 30 日
npx npm-floor <package> --start 2026-08-01 --window 31   # 固定窓
npx npm-floor <package> --start 2026-08-01 --end 2026-08-31
```

`--window` は 1〜540 日。`--start` / `--end` / `--window` は最大 2 つまで。

**4. 床の元になった表を見る**

```
npx npm-floor explain --lang ja
```

同梱の表を出します。2024-01 以降の公開月ごとに、練習用パッケージ（単一版・repository 無し・自称 "my first package" / "test package" …）が公開後 4 日で何件、その後 1 日に何件 DL されたか。それが床です。

他のオプション: `--lang en|ja`、`--cohorts <file>`（自分で作った表を使う。下の「表を自分で作り直す」）、`--help`、`--version`。

## 床はどこから来るか（短い版）

npm の DL 数は公式に「naive by design」で、人と自動化を分けません。誰も使っていないパッケージでも公開初月に数十〜百数十件が付き、版を出すたびに 100〜150 件ほど増えます。

`npm-floor` は実在する練習用パッケージを公開月ごとに測った表を同梱しています: `S` ＝ 公開後 4 日の DL、`T` ＝ 15〜30 日目の 1 日あたり DL。自分のパッケージには、窓内に公開した版ごとに `S` を 1 つ、窓内にパッケージが存在した日ごとに `T` を足し、その合計の p90 と実測を比べます。表が月ごとなので、床は npm 側の変化に追随します（練習用パッケージの初月の中央値は 2024 年から 2026 年半ばでおよそ 2 倍になりました）。

測り方の全文は [`docs/method.md`](https://unpkg.com/npm-floor@0.1.1/docs/method.md)（パッケージに同梱）、対照 20 件・大手 5 件・nightly 1 件で実際に動かした記録は [`docs/field-log.md`](https://unpkg.com/npm-floor@0.1.1/docs/field-log.md) にあります。

---

## 詳しい仕様

ここから下は数字を確かめたい人向けです。道具を使うだけなら読まなくて構いません。

### 数字が言えること・言えないこと

- **「超過（実測 − 床）」は本当の利用者数ではありません。**床で説明できない分、つまり「自動化以外の説明が要る分」です。npm の公開データから人とボットを分けることはできません。
- **床は下限です。**もっとも素朴な未使用パッケージから測っています。README・repository・複数版を持つ普通のパッケージは、スキャナがリンクを辿り、ミラーが版ごとに取り直すので、**もっと多くの**自動 DL を集めます。「床を超える」は「利用者がいる」ではありません。出力もそう注記します（`floor-is-lower-bound`）。
- **床は動きます。**同梱の表は公開月ごとで、表の範囲外の月は最初／最後の月で代用し、その旨を出力に書きます。表が古くなったら `calibrate` で作り直せます。
- 日付は npm の API と同じ UTC の暦日です。

### 床の計算

```
床(窓) = Σ[窓内に公開した版] S(その版の公開月) × 按分
       + T(窓内の最後の版の月。版が無ければ窓の末日の月) × 窓内でパッケージが存在した日数

S(m) = 公開後 day 0..3（公開日を含む 4 日）の DL —— 月 m に公開された練習用パッケージの中央値／p10／p90
按分  = 1。ただし版の公開から窓の末日までが 4 日未満なら、S のうち最初の 1／2／3 日に乗る割合（0.86／0.93／0.97。コホートの中央値）
T(m) = 公開後 day 15..30 の 1 日あたり平均 DL —— 同じコホート
```

帯（p10・p90）も同じ足し方です。滴りの月を窓の末日ではなく窓内の最後の版に合わせるのは、月末に公開したパッケージを自分の月のコホートと比べるためです。作られる前の日には滴りを付けず、窓の最後の数日に出した版は初動のうち窓に入る割合だけを足します。

| 判定 | 条件 |
|---|---|
| `indistinguishable`（床と区別できない） | 実測 ≤ 床 p90 |
| `above`（床を超える） | 床 p90 < 実測 < 3 × 床 p90 |
| `clearly-above`（明確に床の上） | 実測 ≥ 3 × 床 p90 |

版ごとの p90 をそのまま足すのは意図的に保守的な扱いです（独立な項の和の p90 はそれより小さい）。道具は「区別できない」側に倒れます。作りからして、練習用パッケージのおよそ 10 件に 1 件は自分のコホートの p90 を超えます: コホートの 563 件そのものに式を当てると **59 件 = 10.5%** が `above`（月別では 0〜33%。最悪は 9〜12 件しかない月）。`docs/field-log.md` の対照 20 件では 18 件が `indistinguishable`、2 件が `above`（超過は 2・4 件）——期待どおりの 2 件です。

### 同梱の表

<details>
<summary><code>npx npm-floor explain --lang ja</code>（実出力・2026-09-18）</summary>

<!-- example:explain（実出力・2026-09-18。npx npm-floor explain --lang ja） -->
```
npm-floor 0.1.0 コホート表（同梱・作成 2026-09-18）

  母集団: npm レジストリ検索 32 語 × 12 段 × 250 件（スコープ無しの名前のみ）
  抽出条件: 単一版／repository 無し／実行日の 37 日以上前に公開／文面がプレースホルダでない／滴り T < 3 件/日／同じ公開日から 3 件まで／同じ文面の一括公開で仲間が使われ続けているものの残りでない
  全体: n=563  S median 64  T/day 0.31  30d median 75
  除外: placeholder 600 / sustained-use 25 / day-cluster 110 / description-cluster 2   レジストリ全体が 0 の日: 11

月        n   S 中央値   p10    p90   T/日   p10   p90   30日 中央値  印
2024-01   29         56    53     66    0.25  0.13  0.38           65  thin
2024-02   26         60    54     69    0.31  0.19  0.44           72  thin
2024-03   24         56    47     64    0.38  0.25  0.44           65  thin
2024-04   24         50    45     57    0.31  0.25  0.38           63  thin
2024-05   23         49    43     62    0.31  0.19  0.44           64  thin
2024-06   18         51    47     61    0.31  0.25  0.44           64  thin
2024-07   22         60    50     68    0.50  0.25  0.62           77  thin
2024-08   17         53    49     73    0.31  0.19  0.40           72  thin
2024-09   21         66    58     71    0.19  0.06  0.31           77  thin
2024-10   12         65    61     71    0.19  0.13  0.25           73  thin
2024-11   17         64    59     68    0.25  0.13  0.40           73  thin
2024-12   28         64    59     70    0.38  0.25  0.52           76  thin
2025-01   21         64    61     67    0.44  0.13  0.75           78  thin
2025-02   16         63    56     72    0.19  0.13  0.28           68  thin
2025-03   17         59    49     66    0.33  0.14  0.63           72  thin
2025-04   10         52    34     59    0.69  0.50  0.95           76  thin
2025-05   11         50    43     54    0.56  0.44  0.88           67  thin
2025-06   22         50    45     56    0.50  0.26  0.63           71  thin
2025-07   14         66    60     69    0.31  0.14  0.36           76  thin
2025-08   27         66    61     70    0.19  0.10  0.46           75  thin
2025-09   17         69    65     73    0.19  0.13  0.38           76  thin
2025-10   22         75    10     97    0.10  0.07  0.19           83  thin
2025-11    9         90    83    108    0.15  0.06  0.55          119  thin
2025-12   16         89    87     99    0.29  0.19  0.47          103  thin
2026-01   13         85    74    100    0.31  0.20  0.38          103  thin
2026-02   14         87    79    103    0.19  0.13  0.25           99  thin
2026-03   11         91    88    105    0.44  0.25  0.63          109  thin
2026-04    9        116    94    126    0.50  0.38  0.93          128  thin
2026-05   17        131   110    153    0.56  0.25  0.78          146  thin
2026-06   14        125    42    140    0.77  0.54  1.19          144  thin
2026-07   22        130    24    156    0.67  0.34  0.94          156  thin

床(窓) = Σ[窓内に公開した版] S(その版の月) + T(窓内の最後の版の月。版が無ければ窓の末日の月) × 窓内でパッケージが存在した日数
  S = 公開後 4 日の DL（その月に公開された練習用・単一版パッケージの中央値／p10／p90。窓の末日がその 4 日の途中なら按分）
  T = 公開 15〜30 日目の平均 DL／日（同じコホート）
  実測 ≤ 床 p90 → 床と区別できない／それ以上 → 床を超える／実測 ≥ 3 × 床 p90 → 明確に床の上／窓内 20 版以上 → version pumping の疑い
  注: 2025-10・2026-07 の S p10 が中央値から大きく離れているのは、同じ日に同じ教室から出て初動がほぼ付かなかった練習パッケージのためです。判定に使う p90 には効きません
```
<!-- /example:explain -->

</details>

表が言っていること（2026-09-18 の実行）: **31 か月・563 件**（月あたり 9〜29 件・中央値 17）。**すべての月が 30 件に届かず `thin`** で、いちばん薄い月の p90 は 1〜3 件のパッケージに乗っています。全体で、練習用パッケージは**公開後 4 日で中央値 64 件**（p10 49・p90 108）、その後 **0.31 件/日**（p90 0.63）、**最初の 30 日で中央値 75 件**。床は上がっている——公開後 4 日の中央値は 2024 年初め ≈ 55、2025 年末 ≈ 90、2026 年半ば ≈ 125〜130。この表では、2026-09-18 の既定の窓で版が窓に無いパッケージは実測 **約 24 件**までが「床と区別できない」、約 73 件から「明確に床の上」です。

公開月ごとに 1 行: 測った件数 `n`、`S`（中央値／p10／p90）、`T`（1 日あたり）、30 日合計、印——`thin`（30 件未満）／`interp`（5 件未満。前後の月から補間）。`explain --json` で表そのものが出ます。表にパッケージ名は入っていません（他人の練習用パッケージなので意図して外しています）。

**コホートに入るもの・入らないもの。**検索語（"placeholder package"・"test package" …）は、**名前を取られないために置かれた**パッケージ——dependency-confusion 対策のプレースホルダ、予約名、typosquat の実証——も拾います。自称は「使うな」でも、CI・スキャナの実トラフィックの**標的**です。`calibrate` はこれを文面で除外し、さらに 15〜30 日目に 1 日 3 件以上取られ続けたものを落とし、同じ公開日からは 3 件までにし、同じ文面の一括公開は 1 つのキャンペーンとして扱います。件数は `explain` の `除外:` 行、規則とその経緯は `docs/method.md` にあります。

**レジストリ全体が 0 の日。**npm の日次 API は、ときどき**レジストリ全体**が 0 の日を返します（2026-09 だけで 4 日）。対象が 0 の日があるときは参照パッケージ `npm` の同じ窓を 1 要求だけ引き、両方 0 の日を `registry-zero-days` として注記し、滴りを残りの日数に掛けます。`calibrate` はそういう日をコホートの分母から外します。

**version pumping。**窓内に 20 版以上あると `！ version pumping の疑い` の行が付きます。版を出すだけで 1 版あたり 100〜150 件ほどの自動 DL が付く（Tenable・2026-05「download pumping」）ので、短期間の大量リリースは利用者ゼロでも数字を膨らませます。nightly を毎日出すパッケージにも付きます——注意喚起であって告発ではありません。

**注記**として出るもの: `window-recent`（末日が直近 2 日以内。値が変わりうる）・`window-before-created`（窓の末日より後に作られた。必ず 0）・`created-in-window`（作られる前の日には滴りを付けない）・`version-near-window-end`（初動のうち窓に入る割合だけ足した）・`no-download-records`（レジストリにはあるが DL の記録がまだ無い。全日 0・終了コード 0）・`series-incomplete`（npm が一部の日を返さなかった。0 として数えた）・`registry-zero-days`・`cohort-extrapolated`（表の範囲外の月。最寄りの月で代用）・`cohort-thin`（`thin` / `interpolated` の月を使った。注記に月と件数が出る）・`floor-is-lower-bound`（repository・README・複数版のどれかを持つ）または `like-cohort`（単一版・repository 無し・README 無し。床を測ったコホートと同じ条件）・`pumping`。

### 表を自分で作り直す（任意・約 1 時間）

```
npx npm-floor calibrate --out ./calib
npx npm-floor <package> --cohorts ./calib/cohorts.json
```

`calibrate` は、練習用パッケージが自称する 32 語でレジストリを検索し（1 語 12 段 × 250 件）、**単一版・`repository` 無し・公開 37 日以上前・文面が placeholder / security / reserved / typosquat / do not use 等でない**スコープ無しパッケージだけを残し（同じ公開日からは 3 件まで）、各件の公開日 −1〜+45 日の日次 DL を取り、**15〜30 日目に 1 日 3 件以上**取られ続けていたものを除外して（生の系列には `excluded` の印つきで残す）、月ごとに最大 30 件（乱数種つきの順）の S／T／30 日合計を `cohorts.json` に書きます。オプション: `--from YYYY-MM`（既定 2024-01）・`--to YYYY-MM`（既定は「末日が 37 日以上前である最後の月」）・`--per-month N`・`--rate <req/s>`（既定 1・最大 5）・`--max-checks N`・`--seed N`・`--quiet`。

所要: 検索だけで約 400 要求・**8〜10 分**（検索 API はときどき 429 を返し、道具は待ちます）、その後の照合が約 50 分。毎秒 `--rate` 件を超えて要求せず、HTTP 429 / 5xx は待って再試行し、20 件ごとに途中経過を保存するので、**同じ `--out` で再実行すると続きから走ります**（終了コード 2 は「途中で止まった。再実行で続き」、0 は「完了」）。`--out` の外には何も書きません。作った表は `--cohorts <file>`（または `NPM_FLOOR_COHORTS`）で同梱の `data/cohorts.json` の代わりに使います。

### 止まる条件（壊れた入力）

次の場合、`error: …`（`--lang ja` なら `エラー: …`）を 1〜2 行出して終了コード 1 で止まります。スタックトレースは出しません。

- パッケージが npm に無い／公開された版が無い（unpublish 済み）
- npm のパッケージ名として読めない／名前を 2 つ以上渡した
- 日付が `YYYY-MM-DD` でない・暦にない日付・窓が未来で終わる・`--end` が `--start` より前・`--window` が 1〜540 の整数でない、または `--start`/`--end` と合わない
- 知らないオプション・値の無いオプション・`--lang` が `en` / `ja` 以外
- コホート表（`--cohorts`・`NPM_FLOOR_COHORTS`・同梱ファイル）が無い・JSON でない・形が違う（理由を印字）
- npm の API に届かない（ネットワーク／プロキシ／タイムアウト）・再試行しても 429 が続く・再試行してもサーバーエラーが続く——どれかと、どうすればよいかを書きます
- Node.js が 20 未満（この 1 行の文言が出るのは Node 14.8 以上。それより古いと入口ファイルの構文で止まります）

`calibrate` は `--out` が無ければ動かず、ネットワーク断や 429 の継続では途中経過を保存してから止まります。

### 制限・未検証

- 動作確認は Windows 11・Node 24 のみ。macOS／Linux・Node 20／22 は未実行（標準機能だけなので動く見込み）
- 同梱の表の範囲は `explain` に出る月まで。範囲外の月は最寄りの端の月で代用します（`cohort-extrapolated`）。2026-09-18 の表はすべての月が `thin`（n は 9〜29）です。除外規則で標本の約 3 分の 1 が減りました。30 件まで埋め直すには検索語を足す必要があり（0.2 で予定）、それまで薄い月（2025-11: 9・2026-04: 9）の帯はごく少数のパッケージに乗っています
- 帯は素の分位（R type 7）で、刈り込んでいません。`S` の p10 は、初動がほぼ無かった数件（同じ教室から同日に出たものが多い）のせいでいくつかの月で一桁に落ちています（`explain` の脚注にその月を出します）
- 判定の「利用者ゼロでも `above`」の率はきっかり 1 割ではありません。コホート自身では全体 10.5% ですが月別には 0〜33% です。9〜12 件しかない月（2024-10・2025-04・2025-05・2025-11・2026-03・2026-04）の `above` はいちばん軽く読んでください
- レジストリ全体が 0 の日は、コホートの分母から比例で外しています（n／(n − k) 倍）。4 日の初動 `S` では、欠けた日が day 0 のときにこれは過大になります。同梱の表で当たるのは 1 件（2026-07・day 2 が欠け）で、その月の p90 には乗っていません
- 滴り `T` は 15〜30 日目の値で、その後は減ります（31〜45 日目は約 0.7 倍）。最後の公開からずっと後の窓の床は高め＝判定には保守側です
- UA 別の内訳は出しません（npm の API に無い）。推定もしません
- npm の直近の日次は後から変わることがあります（既定の窓はそのために 2 日前で終わります）
- 対照は「自分で練習用と名乗っているパッケージ」に限られます。何も書いていない練習用パッケージは含まれていません。逆に、プレースホルダを名乗るものは誰も取っていなくても除外されます
- 帯の p10 に意味があるのは窓内の版が 1 つのときだけです（p10 も p90 と同じ足し方で、20 版では本当の p10 よりずっと小さく出ます）。0.2 で生の標本から帯を作る予定です

### 有償のデータセット（¥980・販売中）

CLI と同梱の月別コホート表は無料（MIT）です。別売りで **「npm DL 床データセット」（¥980・ZIP）** を販売しています: 表の元になった生の日次系列（測った全パッケージの公開日 −1〜+45 日・匿名化・CSV と JSON）、月別の表（CSV/JSON）、手順書、床の推移のグラフ（SVG）。研究・記事・自分のダッシュボード用で、CLI で自分のパッケージを判定するだけなら要りません。

**入手方法**: [販売ページ](https://lab-cws.pages.dev/npm-floor/)の「¥980 で購入する」から Stripe で決済すると、決済完了の画面から `npm-floor-dataset.zip` のダウンロードページに移ります。届かない場合の連絡先は販売ページの「特定商取引法に基づく表記」にあります。

### 開発者向け

ソースコードは npm のパッケージに入っています（`npm pack npm-floor`）。公開リポジトリと不具合の報告先は準備中です（できたらここと `package.json` に書きます）。`npm test`・`fieldlog`・`build:dataset` は tarball に入っていない `tests/`・`scripts/` が要ります。

```
npm test                 # node --test: 床の式・判定 3 段・API 応答の解析・窓の境界・壊れた入力・偽 API での calibrate・除外規則
npm run calibrate -- --out out/calibrate-YYYY-MM-DD   # 表の作り直し（約 1 時間・再開可）
npm run fieldlog         # 実データの記録を docs/field-log.md に書き直す
npm run build:dataset    # out/calibrate-*/ から有償 ZIP を dist/ に組む（決定的。sha256 を印字）
npm run demo             # pack → 空の prefix に install → この README のコマンドを上から順に実行 → out/readme_demo_<日付>.txt
```

ライブラリとして: `npm-floor`（`resolveWindow`・`loadCohorts`・`computeFloor`・`judge`・`analyze`・`zeroRange`・`registryZeroDays`）、`npm-floor/api`（`getPackument`・`getDailyRange`・`getReferenceRange`・`searchPage`・`RateLimiter`・`ApiError`）、`npm-floor/calibrate`（`runCalibrate`・`aggregate`・`seriesStats`・`qualify`・`prefilter`・`isPlaceholder`・`findZeroDays`・`markDescriptionClusters`・`defaultToMonth`）、`npm-floor/dates`、`npm-floor/cohorts`（同梱の表の JSON）。

### ライセンス

MIT。npm は npm, Inc. の商標です。この道具は非公式であり、npm, Inc. および GitHub の承認・提携を受けたものではありません。数字は実行時に npm の公開 API が返した値そのままです。対照パッケージは他人の練習用アップロードなので、その名前はこのパッケージのどこにも入れていません。
