--- name: qv-notice-generate description: Generate NOTICE files with third-party attributions for all packages in the monorepo. --- # NOTICE File Generator Generate deterministic, sorted NOTICE files for individual packages or all packages at once, covering model, JS, Python, and C++ dependency attributions. ## Relationship to the CI license gate (this SKILL is the fallback) As of QVAC-21554, license/compliance enforcement on Tier-1 PRs is primarily a **CI gate** — [`.github/workflows/license-compliance.yml`](../../../.github/workflows/license-compliance.yml), delegating to the org reusable workflow `public-reusable-license.yml` (design: `tetherto/qvac-actions/docs/license-compliance-ci.md`). The gate deterministically classifies newly added PR dependencies against the org policy (allow/deny/review), honours `.github/license-allowlist.yml`, and posts a PR comment. This SKILL is now the **human fallback** for the long tail the gate cannot decide: - **Novel / unclassifiable licenses.** When the gate blocks a High finding it cannot classify, run `check-forbidden-licenses.js` to investigate, then record the decision in `.github/license-allowlist.yml` (CODEOWNERS-reviewed) or remove/replace the dependency. The gate is deterministic from then on. - **Full transitive audit.** The CI gate reads GitHub's dependency graph, which is manifest-only in this repo (package lockfiles are gitignored), so it does not resolve the full transitive tree. `check-forbidden-licenses.js` does a real `npm install` + `license-checker` (plus Python/C++/model scans) and is the tool for a complete audit — e.g. before a release, or when the gate's coverage is insufficient. - **NOTICE authoring.** The gate only *verifies NOTICE presence* (advisory). Generating/updating the NOTICE files themselves remains this SKILL's `generate-notice.js` job. ## When to use this skill **Use when:** - Generating or updating NOTICE files for any package - Adding new third-party dependencies that need attribution - Preparing a release that requires up-to-date NOTICE files - User invokes `/qv-notice-generate` ## Prerequisites Before running, ensure `.env` is sourced and contains: - `GH_TOKEN` -- GitHub token (access to private repos and GitHub API) - `HF_TOKEN` -- HuggingFace token (model license verification) - `NPM_TOKEN` -- npm registry token (private package resolution) System requirements for Python scanning: - `python3` and `pip` available in PATH (for `pip-licenses`) ## Workflow 1. Ask which package to generate NOTICE for (or `--all` for all packages) 2. Source `.env` in the shell 3. Run the generator script — this writes NOTICE files directly 4. If JS stdout is `Found 0 JS dependencies` while `HEAD`'s NOTICE still has a JS section, restore that JS block from `HEAD`. (`npm install failed` is written to gitignored `NOTICE_LOG.txt`, not stdout.) Keep successful model-scan additions. Do not commit a wiped JS section. 5. Only use `--dry-run` if the user explicitly asks for it **Do NOT commit changes.** The user will review and commit manually. ## Running the scripts ### Generate NOTICE for a specific package ```bash source .env node .agents/skills/qv-notice-generate/scripts/generate-notice.js ``` Example: `node .agents/skills/qv-notice-generate/scripts/generate-notice.js sdk` For registry sub-packages use the full path: - `registry-server/client` - `registry-server/shared` ### Generate NOTICE for all packages ```bash source .env node .agents/skills/qv-notice-generate/scripts/generate-notice.js --all ``` ### Dry-run (no file writes, safe for testing) ```bash source .env node .agents/skills/qv-notice-generate/scripts/generate-notice.js --all --dry-run node .agents/skills/qv-notice-generate/scripts/generate-notice.js sdk --dry-run ``` In dry-run mode: - No files are written (NOTICE, NOTICE_LOG.txt, FORBIDDEN_LICENSES.txt) - All scans run fully (npm install, license-checker, pip-licenses, GitHub API, models) - NOTICE content is previewed in the console instead of written to disk ### Check for disallowed licenses ```bash source .env node .agents/skills/qv-notice-generate/scripts/check-forbidden-licenses.js --all --dry-run node .agents/skills/qv-notice-generate/scripts/check-forbidden-licenses.js --all ``` Uses an **allowlist** approach. The `ALLOWED_LICENSES` array in `config.js` controls which licenses pass: - **Empty list (default)** -- every license is allowed (open gate). Useful while you are still cataloguing your deps. - **Populated list** -- only those SPDX identifiers pass; anything else is a violation. License strings from all sources (npm, PyPI, GitHub, models) are normalized to canonical SPDX ids before comparison, so adding `apache-2.0` to the list automatically covers `Apache 2.0`, `Apache Software License`, `Apache License 2.0`, etc. If violations are found, writes `FORBIDDEN_LICENSES.txt` to the repo root and exits with code 1. **Important:** The agent should NOT edit `ALLOWED_LICENSES` directly. Present the scan results to the user and let them decide which licenses to allow. The allowlist and normalization map live in `.agents/skills/qv-notice-generate/scripts/constants.js`. ### Generate license overview report ```bash node .agents/skills/qv-notice-generate/scripts/generate-report.js ``` Reads existing NOTICE files across all packages (no scanning, no tokens needed) and produces `NOTICE_FULL_REPORT.txt` with: - Global license distribution with counts and percentages - Per-package breakdown by dependency type (models, JS, Python, C++) - Packages with no dependencies listed separately ### What it produces 1. **Per-package `NOTICE`** file inside each scanned package directory (from `generate-notice.js`) 2. **`NOTICE_FULL_REPORT.txt`** license overview report (from `generate-report.js`, gitignored) 3. **`NOTICE_LOG.txt`** at the repo root with errors/warnings (gitignored) ## Scan types | Type | What | Tool | |------|------|------| | Models | Model attributions from `models.prod.json` | Direct JSON parsing | | JS | Production npm dependencies (no peers / extraneous) | `npm install --omit=dev --omit=peer` + `license-checker`, intersected with `npm ls` | | Python | Benchmark/script Python deps | `pip-licenses` (auto-installed in temp virtualenv) | | C++ | vcpkg native dependencies | GitHub API + local portfile parsing | ## Package coverage - **Models (full list)**: `sdk`, `inference`, `registry-server/client` - **Models (by engine)**: All addon packages, mapped by engine name - **JS**: Every package with dependencies in `package.json` - **Python**: Packages with `requirements.txt` or `pyproject.toml` in benchmarks/scripts - **C++**: Packages with `vcpkg.json` ## Addon-to-engine mapping | Package directory | Engine | |---|---| | `embed-llamacpp` | `@qvac/embed-llamacpp` | | `llm-llamacpp` | `@qvac/llm-llamacpp` | | `translation-nmtcpp` | `@qvac/translation-nmtcpp` | | `tts-onnx` | `@qvac/tts-onnx` | | `asr-ggml` | `@qvac/transcription-whispercpp`, `@qvac/asr-ggml` | | `diffusion-cpp` | `@qvac/diffusion-cpp` | `asr-ggml` carries two engine keys because the whisper + parakeet packages were unified: `models.prod.json` still names the retired `@qvac/transcription-whispercpp` engine until the SDK/registry repoint lands. ## Sorting guarantee All entries within every NOTICE file section are sorted deterministically using locale-independent collation. Re-runs on identical input always produce identical output, resulting in clean git diffs. ## Related scripts - **Model license verification**: `npm run verify:licenses` in `packages/registry-server` -- verifies model licenses in `models.prod.json` against HuggingFace/GitHub APIs (dry-run only, console output, fails on unverifiable). ## References - Constants (allowlist, normalization, copyright): `.agents/skills/qv-notice-generate/scripts/constants.js` - Package definitions & internal wiring: `.agents/skills/qv-notice-generate/scripts/lib/config.js` - SDK pod ownership: `.github/teams/sdk.json`