---
name: audit-licenses
description: サードパーティライセンスを監査し THIRDPARTY_LICENSES.txt を生成する
command: true
---

# audit-licenses コマンド

Python 依存パッケージとフロントエンド（frontend/）の全サードパーティライセンスを監査し、
GPL/AGPL 混入がないことを確認した上で `THIRDPARTY_LICENSES.txt` をプロジェクトルートに生成する。
リリース前に必ず実行すること。

## 使い方

```
/audit-licenses
```

## 実行手順

### Step 1: Python側のライセンス監査

1. `pip-licenses` を仮想環境にインストールする。

   ```bash
   cd alpha-visualizer
   uv pip install pip-licenses
   ```

2. 全パッケージのライセンスを取得する。

   ```bash
   uv run pip-licenses --format=markdown --order=license
   ```

3. 出力を確認し、以下のいずれかが**ランタイム依存**として存在する場合は**直ちに中断**してユーザーに報告する。

   - ライセンス名に `GPL` または `AGPL` を含むパッケージ
   - ただし以下は**開発専用（dev）**のため除外して判定する:
     - `pytest`, `pytest-cov`, `ruff`, `httpx`, `bump-my-version`, `hypothesis`,
       `mypy`, `pandas-stubs`, `types-pyyaml`（devツール）
     - `pip-licenses`, `prettytable`（監査ツール）
   - 除外リストは `pyproject.toml` の `[dependency-groups]` と乖離しやすいので、
     実行時に必ず突合し、乖離があればこのファイルも更新すること。

4. LGPL はランタイム混入でも即中断ではなく、
   後述の THIRDPARTY_LICENSES.txt にLGPL告知を記載する形で続行する。

### Step 2: フロントエンド側のライセンス監査

1. visualizer ディレクトリで `license-checker` を実行する。

   ```bash
   cd alpha-visualizer/frontend
   npx license-checker --summary
   ```

2. GPL または AGPL を含むパッケージを発見した場合は**直ちに中断**してユーザーに報告する。
   （`frontend@0.0.0` の `UNLICENSED` はプロジェクト自身のため無視する）

### Step 3: THIRDPARTY_LICENSES.txt の生成

Step 1・Step 2 で問題がなければ以下を実行する。

1. Python ライセンス情報を JSON で取得する。

   ```bash
   cd alpha-visualizer
   uv run pip-licenses --format=json --with-license-file --no-license-path > /tmp/py-licenses.json
   ```

2. 以下の Python スクリプトを実行して SECTION 1（Python 依存）を生成する。

   ```python
   import json
   from datetime import date

   DEV_ONLY = {
       "alpha-visualizer",
       "pytest", "pytest-cov", "ruff", "httpx", "bump-my-version", "hypothesis",
       "mypy", "pandas-stubs", "types-pyyaml",
       "pip-licenses", "prettytable",
   }

   with open("/tmp/py-licenses.json") as f:
       py_data = json.load(f)

   # pip-licenses が返す配布名は表記ゆれする（nuitka → "Nuitka" 等）。PyPI の
   # 正規化規則（大小無視・"_" / "." を "-" に畳む）で突き合わせないと、dev 専用
   # パッケージが配布告知に漏れる。alpha-forge 側で Nuitka (AGPLv3+) の混入が
   # 実際に発生したため、同じ判定を持つこちらも合わせる。
   def _norm(name: str) -> str:
       return name.lower().replace("_", "-").replace(".", "-")

   DEV_ONLY_NORM = {_norm(n) for n in DEV_ONLY}

   py_pkgs = [p for p in sorted(py_data, key=lambda x: x["Name"].lower())
              if _norm(p["Name"]) not in DEV_ONLY_NORM]

   SEP = "=" * 80
   lines = [
       SEP, "THIRDPARTY_LICENSES.txt", SEP,
       f"Generated: {date.today().isoformat()}", "",
       "This product includes third-party software components listed below.",
       "These components are subject to their respective licenses.", "",
   ]

   # LGPL告知（該当パッケージが存在する場合）
   lgpl_pkgs = [p for p in py_pkgs if "LGPL" in p["License"] or "lgpl" in p["License"].lower()]
   if lgpl_pkgs:
       lines += [
           "IMPORTANT LGPL NOTICE", "-" * 40,
           "This distribution includes the following LGPL-licensed libraries:",
       ]
       for p in lgpl_pkgs:
           lines.append(f"  - {p['Name']} ({p['License']})")
       lines += [
           "",
           "In compliance with LGPL-3.0, the source code of these libraries is",
           "available at their respective repositories listed in this file.",
           "Users may replace these libraries with modified versions by rebuilding",
           "from source. Contact us if you require object files for relinking.",
           "",
       ]

   lines += [SEP, "", "SECTION 1: PYTHON DEPENDENCIES", SEP,
             f"Total: {len(py_pkgs)} packages", ""]
   for p in py_pkgs:
       lines += [f"  {p['Name']} {p['Version']}", f"  License: {p['License']}"]
       text = p.get("LicenseText", "").strip()
       if text and text not in ("UNKNOWN", ""):
           lines.append("  License Text:")
           for line in text.splitlines()[:30]:
               lines.append(f"    {line}")
           if len(text.splitlines()) > 30:
               lines.append("    [... full text available at PyPI ...]")
       lines.append("")

   # SECTION 2（フロントエンド依存）は scripts/generate_thirdparty_js.py が
   # 生成する（issue #187）。ここでは置換対象の空ブロックだけ出力する。
   lines += [SEP, "", "SECTION 2: FRONTEND DEPENDENCIES (Vite + React)", SEP,
             "Total: 0 packages", ""]
   lines.append(SEP)

   output = "\n".join(lines)
   with open("THIRDPARTY_LICENSES.txt", "w") as f:
       f.write(output)
   print(f"Generated THIRDPARTY_LICENSES.txt ({len(output)} bytes, Python: {len(py_pkgs)})")
   ```

   ```bash
   cd alpha-visualizer
   uv run python -c "<上記スクリプト>"
   ```

3. SECTION 2（フロントエンド依存）を生成スクリプトで埋める（issue #187）。
   CI が `--check` で drift を検証しているため、依存更新時もこのスクリプトを使うこと。

   ```bash
   cd alpha-visualizer
   uv run python scripts/generate_thirdparty_js.py
   ```

4. 生成されたファイルの先頭を表示して内容を確認する。

   ```bash
   head -30 THIRDPARTY_LICENSES.txt
   ```

5. **生成後の GPL 系チェック**（`DEV_ONLY` の取りこぼし検知）。
   生成前の目視では除外リストの表記ゆれを検出できないため、生成物そのものを見る。

   ```bash
   grep "^  License:.*GPL" THIRDPARTY_LICENSES.txt | sort -u
   ```

   現状の期待値は 0 行。1 行でも出たら、除外リストの表記ゆれか新規依存の混入を疑う。

## 完了後の確認事項

- [ ] `THIRDPARTY_LICENSES.txt` がプロジェクトルートに生成されていること
- [ ] GPL/AGPL パッケージが**ランタイム依存**に含まれていないこと
- [ ] **生成後のファイルにも** GPL/AGPL が無いこと（Step 3-5 の grep）
- [ ] LGPL パッケージがある場合、ファイル先頭に LGPL 告知が記載されていること
- [ ] ファイルを `git add THIRDPARTY_LICENSES.txt` してリリースコミットに含めること

## 注意

- Python の仮想環境（`.venv`）が存在し `uv sync` 済みであることを前提とする。
- `pip-licenses` と `prettytable` はこのコマンド専用の監査ツールであり、`pyproject.toml` の
  依存には含めない（`uv pip install` で都度インストールする）。
- フロントエンドの `node_modules/` が存在しない場合は `pnpm install` を先に実行すること。
