package_branches() now reports "{branch} version unknown" instead of a bare "unknown" when a configured branch is absent from the remote repo, using vectorized fcase() conditions that stay NA-safe for missing versions.
Testing
Added test coverage for cache memoization, core_metadata(), and utility helpers.
CI coverage now attributes results correctly with with_mocked_bindings() and indexes the exported get_core_packages() alias in the pkgdown reference.
package_branches() now reports an accurate local_status column (behind/ahead/up-to-date, plus “Not in local”, “not in repo”, and “unknown” for unparseable versions).
New pip_snapshot() writes a committed team lock manifest (PIP_LOCK.csv with package,branch,sha columns). init_metapip() is now lock-driven: it installs each package at its recorded SHA and falls back to branch HEAD (with a pip_snapshot() suggestion) when the lock is absent. update_pip_packages() refreshes the lock and installs outdated packages at their new SHAs.
get_branches() now paginates through all branches (.limit = Inf), so repos with more than 30 branches are fully returned.
Least-privilege token handling: read-only GitHub calls (get_branches, core_metadata, get_branch_info, get_latest_branch_update, package_branches) no longer require a PAT, and print(check_github_token()) shows a redacted token.
Bug Fixes
package_branches() no longer fails at runtime: join_and_get_status() previously referenced a local_status column that the join never produced, and malformed remote Version strings now degrade to "unknown" instead of crashing the report.
install_branch() now pins to the resolved branch HEAD SHA by default (session-level determinism) with an idempotency short-circuit (force = TRUE bypasses pinning and installs live HEAD; sha = overrides the resolved SHA).
README codecov badge no longer carries a ?token= parameter.
Performance
Per-session memoization of GitHub API calls in get_branches(), latest_commit_for_branch(), and core_metadata(). Repeated calls in the same R session hit the cache instead of the API (~80 calls reduced to ~3 for a full core_metadata() refresh). Only successful responses are cached so transient failures are retried.