Files
wehub-resource-sync 09e9f3545f
Test / Code Quality (push) Has been cancelled
Test / Test (macos-latest, Python 3.10) (push) Has been cancelled
Test / Test (macos-latest, Python 3.11) (push) Has been cancelled
Test / Test (macos-latest, Python 3.12) (push) Has been cancelled
Test / Test (macos-latest, Python 3.13) (push) Has been cancelled
Test / Test (macos-latest, Python 3.14) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.10) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.11) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.12) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.13) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.14) (push) Has been cancelled
Test / Test (windows-latest, Python 3.10) (push) Has been cancelled
Test / Test (windows-latest, Python 3.11) (push) Has been cancelled
Test / Test (windows-latest, Python 3.12) (push) Has been cancelled
Test / Test (windows-latest, Python 3.13) (push) Has been cancelled
Test / Test (windows-latest, Python 3.14) (push) Has been cancelled
CodeQL / Analyze (push) Has been cancelled
dependency-audit / pip-audit (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:30:13 +08:00

13 KiB
Raw Permalink Blame History

Contributing to notebooklm-py

For Human Contributors

Getting Started

# Canonical contributor install (respects uv.lock)
uv sync --frozen --extra browser --extra dev --extra markdown
source .venv/bin/activate
uv run playwright install chromium
pre-commit install

Run the full pre-commit suite (matches what CI runs). IMPORTANT: use the broad . scope, not src/ tests/ — the pre-commit hook in CI invokes ruff-format on the whole tree and is stricter than a narrow scope.

uv run ruff format --check . && \
    uv run ruff check . && \
    uv run mypy src/notebooklm --ignore-missing-imports && \
    uv run pytest --cov=src/notebooklm --cov-report=term-missing --cov-fail-under=90

No uv? Plain pip works as a fallback (won't enforce the lockfile, so you may resolve newer dep versions than CI):

python -m venv .venv && source .venv/bin/activate
pip install -e ".[all]"   # [all] = browser + dev + markdown + mcp + server (no cookies; see installation.md)
playwright install chromium
pre-commit install

For full prerequisites, headless setup, optional extras ([cookies], [markdown], [mcp], [server]), and platform notes, see docs/installation.md#e-contributor.

Install-doc parity. docs/installation.md is the canonical install guide; this file mirrors a small contributor-focused subset. Every fenced bash block in installation.md must EITHER appear verbatim in CONTRIBUTING.md, OR be marked with <!-- not mirrored: <reason> --> on the line directly before its opening fence. CI enforces this via scripts/check_ci_install_parity.py so a stale block can't drift in unnoticed. When you edit installation.md, decide on the spot whether the new content also belongs in this file.

The browser extra is part of the contributor install because the default unit suite imports and patches playwright.sync_api. The command uv sync --frozen --extra dev is only the test/lint toolchain; it is not enough for uv run pytest.

Architecture & testing context. Once installed, read docs/development.md for the layered RPC/Core/Client/CLI design, test-tree layout, and release workflow before touching src/notebooklm/.

Code Quality

This project uses ruff for linting and formatting:

# Check for lint issues
ruff check .

# Auto-fix lint issues
ruff check --fix .

# Check formatting
ruff format --check .

# Apply formatting
ruff format .

Pre-commit hooks (included in the [dev] extra; install once after the canonical setup):

pre-commit install                              # one-time, after the canonical install
pre-commit run --all-files                      # manual run on the whole tree (matches the CI lint gate)

Caveat: if pre-commit install errors with Cowardly refusing to install hooks with core.hooksPath set, your git is configured to use a custom hooks directory (common with Husky / nx / shared dev configs). Workaround: git config --unset core.hooksPath then re-run pre-commit install, or run pre-commit run --all-files manually before each commit. CI runs the same hook either way, so a clean local hook is convenience, not correctness.

CI parity. The local pre-commit one-liner above matches the CI lint gate (uv run pre-commit run --all-files in .github/workflows/test.yml). CI additionally runs the full test matrix on multiple Python versions (3.103.14) and asserts a 90% coverage floor (pytest --cov=src/notebooklm --cov-report=term-missing --cov-fail-under=90). The lint+test failure modes are caught locally; the multi-Python-version drift is not — uv run pytest --cov=src/notebooklm --cov-report=term-missing --cov-fail-under=90 here uses your local Python version only.

Pull Request Process

  1. Create a feature branch from main
  2. Make your changes with clear commit messages
  3. Ensure tests pass: pytest
  4. Ensure lint passes: ruff check .
  5. Ensure formatting: ruff format --check .
  6. Submit a PR with a description of changes

Pull Request Quality Expectations

  • Reference an issue: PRs should link to an existing issue or clearly describe the problem being solved. If no issue exists, open one first for discussion.
  • AI-assisted contributions: Welcome, but the submitter must review, understand, and test the code before submitting. PRs that appear to be unreviewed AI output will be closed.
  • No duplicates: Check existing open PRs before submitting. Duplicate PRs for the same issue will be closed in favor of the first or best submission.
  • Accurate severity: Claims of "critical" bugs must include evidence (stack trace, reproduction steps, affected users). Routine edge cases are not critical.
  • Tested locally: All PRs must include evidence of local testing. The PR template includes a checklist for this.

Dependency upper bounds

Every runtime and [project.optional-dependencies] entry in pyproject.toml must have an upper bound — typically <currentmajor + 1 (or <currentminor + 1 for pre-1.0 packages like httpx). The bound protects downstream installs from a breaking new release that lands before we have time to test it.

When you bump a cap (e.g. moving pytest>=8.0,<10 to pytest>=8.0,<11):

  1. Run uv lock --refresh and uv sync --frozen --extra browser --extra dev --extra markdown locally.
  2. Run the full pre-commit one-liner above.
  3. Mention the upgrade rationale in the PR description.

The dependency-audit workflow (.github/workflows/dependency-audit.yml) runs pip-audit --strict --require-hashes against the locked env on every push to main and nightly. It is a hard gate (no continue-on-error): a CVE in the locked environment fails the workflow. New deps must pass pip-audit cleanly when introduced; pin a fixed version or record a tracked exception via pip-audit's --ignore-vuln if no fix is yet available.

Test tiers

The test suite is split into three tiers by network/auth dependency. Place new tests in the tier that matches their isolation profile — a tier-enforcement hook will eventually fail PRs that mis-tier a test.

Tier Location What lives here Network Auth
Unit tests/unit/ Pure-Python tests + pytest_httpx (httpx_mock) request-level mocks. Encoder/decoder, dataclasses, helpers, CLI boundary, and httpx_mock-driven API tests. None (mocked) None
Integration tests/integration/ VCR cassette replay only — @pytest.mark.vcr / notebooklm_vcr.use_cassette(...) against recorded fixtures in tests/cassettes/. None (replayed) None
E2E tests/e2e/ Real NotebookLM API. Marked @pytest.mark.e2e; excluded from the default pytest run via addopts = --ignore=tests/e2e. Real Required (notebooklm login)

Run a tier explicitly:

uv run pytest tests/unit
uv run pytest tests/integration
uv run pytest tests/e2e -m e2e        # requires auth

Fast local loop (skip repo-wide audit checks)

A subset of unit tests are repo-wide audit / release-gate checks (cassette shape lint, public-surface scans, CI-script audits, doc-sync guards) that scan many files and add ~3045s to the local tests/unit tests/integration loop. They're marked @pytest.mark.repo_lint so you can opt out while iterating:

# Fast feedback loop — drops repo_lint audits (~40s savings).
uv run pytest tests/unit tests/integration -m "not repo_lint"

# Run only the repo_lint audits (what you'd typically skip above).
uv run pytest tests/unit tests/integration -m "repo_lint"

Run the full suite (including repo_lint) before pushing — CI runs everything by default, so repo_lint failures still block merge. The default uv run pytest invocation does not filter the marker out.

Quick guidance:

  • Reach for httpx_mock when you need to assert on outgoing request shape (headers, body, cookies, URL) or stub a small response — put the test under tests/unit/.
  • Reach for VCR when you want to replay a recorded server response end-to-end against the real RPC decoder — put the test under tests/integration/ and record the cassette per docs/rpc-development.md.
  • Reach for E2E only when you need to validate a live round-trip against Google's servers — put the test under tests/e2e/ and mark it @pytest.mark.e2e.

Documentation Rules for AI Agents

IMPORTANT: All AI agents (Claude, Gemini, etc.) must follow these rules when working in this repository.

File Creation Rules

  1. No Root Rule - Never create .md files in the repository root unless explicitly instructed by the user.

  2. Modify, Don't Fork - Edit existing files; never create FILE_v2.md, FILE_REFERENCE.md, or FILE_updated.md duplicates.

  3. Scratchpad Protocol - All analysis, investigation logs, and intermediate work go in docs/scratch/ with date prefix: YYYY-MM-DD-<context>.md

  4. Consolidation First - Before creating new docs, search for existing related docs and update them instead.

Protected Sections

Some sections within files are critical and must not be modified without explicit user approval.

Inline markers (source of truth):

<!-- PROTECTED: Do not modify without approval -->
## Critical Section Title
Content that should not be changed by agents...
<!-- END PROTECTED -->

For code files:

# PROTECTED: Do not modify without approval
class RPCMethod(Enum):
    ...
# END PROTECTED

Rule: Never modify content between PROTECTED and END PROTECTED markers unless explicitly instructed by the user.

Design Decision Lifecycle

Design decisions should be captured where they're most useful, not in separate documents that become stale.

When Where What to Include
Feature work PR description Design rationale, edge cases, alternatives considered
Specific decisions Commit message Why this approach was chosen
Large discussions GitHub Issue Link from PR, spans multiple changes
Investigation/debugging docs/scratch/ Temporary work, delete when done

Why not design docs? Separate design documents accumulate and become stale. PR descriptions stay attached to the code changes, are searchable in GitHub, and don't clutter the repository.

Scratch files (docs/scratch/) - Temporary investigation logs and intermediate work. Format: YYYY-MM-DD-<context>.md. Periodically cleaned up.

Naming Conventions

Type Format Example
Root GitHub files UPPERCASE.md README.md, CONTRIBUTING.md
Agent files UPPERCASE.md CLAUDE.md, AGENTS.md
Subfolder README README.md docs/adr/README.md
All other docs/ files lowercase-kebab.md cli-reference.md, contributing.md
Scratch files YYYY-MM-DD-context.md 2026-01-06-debug-auth.md

Status Headers

All documentation files should include status metadata:

**Status:** Active | Deprecated
**Last Updated:** YYYY-MM-DD

Agents should ignore files marked Deprecated.

Information Management

  1. Link, Don't Copy - Reference README.md sections instead of repeating commands. Prevents drift between docs.

  2. Scoped Instructions - Subfolders like examples/ may have their own README.md with folder-specific rules.


Documentation Structure

docs/
├── adr/                   # Architectural Decision Records (ADRs)
├── architecture.md        # Layered architecture and repository map
├── auth-cookie-lifecycle.md      # Cookie expiration mitigation strategies and keepalive loops
├── cli-exit-codes.md      # CLI exit-code convention (binding contract for scripts/CI)
├── cli-reference.md       # CLI command reference
├── configuration.md       # Storage, profiles, and settings
├── deprecations.md        # Staged API deprecations tracker
├── development.md         # Architecture, testing, and VCR cassette practices
├── installation.md        # Canonical install guide (personas, extras, platform notes)
├── mcp-guide.md           # MCP server setup, tools, and troubleshooting
├── python-api.md          # Python API reference
├── refactor-history.md    # Historical record of the Tier 12/13 refactor + downstream migration tables
├── releasing.md           # Release checklist
├── rpc-development.md     # RPC capture and debugging
├── rpc-reference.md       # RPC payload structures and Content Type Codes
├── stability.md           # API versioning and stability policy
└── troubleshooting.md     # Common issues and solutions

Runnable example scripts live at the repository root under examples/.

When adding or modifying a CLI command, follow the CLI Exit-Code Convention — the policy table and the two intentional exceptions (source stale, source wait) are binding.