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, 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.jsto 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.jsdoes a realnpm 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.jsjob.
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:
python3andpipavailable in PATH (forpip-licenses)
Workflow
- Ask which package to generate NOTICE for (or
--allfor all packages) - Source
.envin the shell - Run the generator script — this writes NOTICE files directly
- Only use
--dry-runif 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
source .env
node .agents/skills/qv-notice-generate/scripts/generate-notice.js <package-dir-name>
Example: node .agents/skills/qv-notice-generate/scripts/generate-notice.js sdk
For registry sub-packages use the full path:
registry-server/clientregistry-server/shared
Generate NOTICE for all packages
source .env
node .agents/skills/qv-notice-generate/scripts/generate-notice.js --all
Dry-run (no file writes, safe for testing)
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
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
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
- Per-package
NOTICEfile inside each scanned package directory (fromgenerate-notice.js) NOTICE_FULL_REPORT.txtlicense overview report (fromgenerate-report.js, gitignored)NOTICE_LOG.txtat 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 | license-checker (auto-installed via npx) |
| 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.txtorpyproject.tomlin 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:licensesinpackages/registry-server-- verifies model licenses inmodels.prod.jsonagainst 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