From 13aea80d3ee96d7e48dffbeaad8c4eb7250053d2 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Sun, 13 Sep 2026 16:39:16 -0700 Subject: [PATCH 1/4] fix(scripts): keep setup-hooks.sh from binding the shared hook to a worktree Git hooks live in the repository's common directory and every worktree shares them, and `pre-commit install` writes the interpreter that runs it into the hook. setup-hooks.sh ran it wherever it was invoked, so running it in a worktree bound every checkout's commits to that worktree's virtual environment, and once the worktree was removed every commit failed with "`pre-commit` not found". It now installs the hook only in the primary checkout. A worktree checks that the shared hook is bound to the primary checkout's environment and says how to rebind it if not, and leaves it alone either way. It also syncs on every run. The stamp file it gated on had been committed, so a fresh worktree checked the stamp out and whether it got an environment at all came down to the order in which checkout wrote files. The stamp is now untracked and ignored. --- .deps-installed | 0 .gitignore | 1 + DEVELOPMENT.md | 3 +- setup-hooks.sh | 87 +++++++++++++++++++++++++++++++++++++++---------- 4 files changed, 72 insertions(+), 19 deletions(-) delete mode 100644 .deps-installed diff --git a/.deps-installed b/.deps-installed deleted file mode 100644 index e69de29b..00000000 diff --git a/.gitignore b/.gitignore index cf89f56b..57adb759 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ xnotes/* .env .envrc .venv +.deps-installed .vscode/settings.json # mypy diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 1a9e72c7..6c57bc53 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -280,7 +280,8 @@ To install pre-commit hooks: ./setup-hooks.sh ``` -This installs dependencies (if needed) and configures git pre-commit hooks. +This syncs dependencies and, in the primary checkout, installs the git pre-commit hook. Run from a worktree, it syncs that worktree's own environment and checks the shared hook instead of reinstalling it: hooks are shared by every worktree and record the +interpreter that installed them, so a hook installed from a worktree stops working when that worktree is removed. ## Workspace layout diff --git a/setup-hooks.sh b/setup-hooks.sh index 3143a50b..dfa8ae0a 100755 --- a/setup-hooks.sh +++ b/setup-hooks.sh @@ -1,31 +1,82 @@ #!/bin/bash +# +# Prepare this checkout for development: install dependencies and make sure the +# git pre-commit hook is in place. Pass --update to upgrade the lockfile first. +# +# Safe to run from the primary checkout and from a linked worktree, which differ +# in the one respect that matters here. Git hooks live in the repository's common +# directory and every worktree shares them, and `pre-commit install` writes the +# interpreter that runs it into the hook. Installing from a worktree therefore +# binds every checkout's commits to that worktree's virtual environment -- which +# is deleted with the worktree, after which every commit fails with "`pre-commit` +# not found". So only the primary checkout installs the hook. A worktree checks +# that the shared hook is bound to the primary checkout and says how to fix it if +# not, and leaves it alone either way. -# Check if we should force update (pass --update flag) FORCE_UPDATE=false -if [[ "$1" == "--update" ]]; then +if [[ "${1:-}" == "--update" ]]; then FORCE_UPDATE=true fi -# Ensure dependencies are installed first -if [[ ! -f ".deps-installed" ]] || [[ "pyproject.toml" -nt ".deps-installed" ]] || [[ "uv.lock" -nt ".deps-installed" ]] || [[ "$FORCE_UPDATE" == "true" ]]; then - echo "Installing/updating dependencies..." - - if [[ "$FORCE_UPDATE" == "true" ]]; then - echo "Forcing update to latest versions..." - uv lock --upgrade - uv sync - else - uv sync +if [[ "$FORCE_UPDATE" == "true" ]]; then + echo "Forcing update to latest versions..." + if ! uv lock --upgrade; then + echo "Failed to upgrade the lockfile. Please check the output above." + exit 1 fi +fi - if [[ $? -ne 0 ]]; then - echo "Failed to install dependencies. Please check the output above." +# Every run syncs. uv is idempotent and clones packages from its cache, so an +# environment that is already current costs little. This used to be gated on a +# stamp file that had been committed to the repository, which meant a fresh +# worktree checked the stamp out and whether it got an environment at all came +# down to the order in which checkout happened to write files. +echo "Installing/updating dependencies..." +if ! uv sync; then + echo "Failed to install dependencies. Please check the output above." + exit 1 +fi + +git_dir=$(git rev-parse --absolute-git-dir) +common_dir=$(git rev-parse --path-format=absolute --git-common-dir) + +if [[ "$git_dir" == "$common_dir" ]]; then + if ! uv run pre-commit install; then + echo "Failed to install the pre-commit hook. Please check the output above." exit 1 fi - touch .deps-installed + echo "Git hooks installed successfully!" + exit 0 fi -# Install pre-commit hooks -uv run pre-commit install +# A linked worktree from here on. +primary_root=$(dirname "$common_dir") +hook=$(git rev-parse --path-format=absolute --git-path hooks/pre-commit) + +if [[ ! -f "$hook" ]]; then + echo "No pre-commit hook is installed for this repository yet. Install it from the" + echo "primary checkout, where it outlives every worktree:" + echo " (cd \"$primary_root\" && ./setup-hooks.sh)" + exit 0 +fi + +install_python=$(sed -n "s/^INSTALL_PYTHON=//p" "$hook") +install_python=${install_python#\'} +install_python=${install_python%\'} + +if [[ "$install_python" != "$primary_root"/.venv/* ]]; then + echo "The shared pre-commit hook runs with ${install_python:-an unknown interpreter}, not the" + echo "primary checkout's environment, so commits will fail once that environment is gone." + echo "Rebind it from the primary checkout:" + echo " (cd \"$primary_root\" && ./setup-hooks.sh)" + exit 0 +fi + +if [[ ! -x "$install_python" ]]; then + echo "The shared pre-commit hook runs with $install_python, which no longer exists, so" + echo "commits will fail. Reinstall it from the primary checkout:" + echo " (cd \"$primary_root\" && ./setup-hooks.sh)" + exit 0 +fi -echo "Git hooks installed successfully!" +echo "Shared pre-commit hook is bound to the primary checkout; leaving it alone." From df08bd60cabe2e4212c2413f92620e645b021951 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Sun, 13 Sep 2026 16:39:49 -0700 Subject: [PATCH 2/4] feat(scripts): add new-worktree.sh Creating a worktree correctly takes several steps that are easy to skip, and skipping them fails quietly. scripts/new-worktree.sh [] does them in order: it fetches origin, fast-forwards local main where that is safe and refuses when main holds commits origin/main lacks, creates ../span-panel-api- on a branch that tracks no remote so a bare push can never land on main, links .env from the primary checkout, and runs setup-hooks.sh inside it. The worktree gets a virtual environment of its own rather than a link to the primary checkout's. This repository installs its own distributions in editable mode, each recording one checkout's source path, so a shared environment imports whichever checkout synced last and tests pass against the wrong code without an error. Main is moved with update-ref rather than `branch --force`, which would also rewrite its upstream, and a checked-out main is fast-forwarded only when it has no uncommitted tracked changes. The setup script beside this one runs rather than the new worktree's copy, because a branch from origin/main can predate the worktree-aware setup. --- DEVELOPMENT.md | 11 +++++ scripts/new-worktree.sh | 102 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100755 scripts/new-worktree.sh diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 6c57bc53..f437cdc4 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -283,6 +283,17 @@ To install pre-commit hooks: This syncs dependencies and, in the primary checkout, installs the git pre-commit hook. Run from a worktree, it syncs that worktree's own environment and checks the shared hook instead of reinstalling it: hooks are shared by every worktree and record the interpreter that installed them, so a hook installed from a worktree stops working when that worktree is removed. +## Worktrees + +Create worktrees with the script rather than `git worktree add`: + +```bash +./scripts/new-worktree.sh [] # defaults to feat/ +``` + +It fetches `origin`, fast-forwards `main` where that is safe, creates `../span-panel-api-` on a branch that does not track a remote, links `.env` from the primary checkout, and runs `setup-hooks.sh` inside it. The worktree gets a virtual environment +of its own rather than a link to the primary checkout's: editable installs record one checkout's source path, so a shared environment would import whichever checkout synced last. + ## Workspace layout This repository is a uv workspace publishing more than one distribution: the bootstrap (`span-panel-api`, at the root) and one parser package per panel schema (`packages/schema-N/`). `uv sync` installs the workspace, so the test suite runs against every diff --git a/scripts/new-worktree.sh b/scripts/new-worktree.sh new file mode 100755 index 00000000..888ff6c9 --- /dev/null +++ b/scripts/new-worktree.sh @@ -0,0 +1,102 @@ +#!/bin/bash +# +# Create a worktree for a branch of work beside the primary checkout, and prepare +# it with setup-hooks.sh. +# +# Usage: scripts/new-worktree.sh [] +# +# The worktree is created at ../-, beside the primary +# checkout: the layout the integration's developer notes expect when +# installing a library worktree into its environment. +# The branch to create. Defaults to feat/. +# +# The branch starts from origin/main as just fetched and does not track it, so a +# bare `git push` can never land on main. Local main is fast-forwarded as well where +# that is safe, and the script refuses when local main holds commits origin/main +# lacks, because branching from origin/main would silently leave them out. +# +# The worktree gets a virtual environment of its own rather than a link to the +# primary checkout's. This repository installs its own distributions in editable +# mode, and each records the path of one checkout's sources, so a shared +# environment imports whichever checkout synced last and tests pass against the +# wrong code without an error. uv clones packages from its cache, so the second +# environment costs little. Local configuration that is not bound to a path, `.env`, +# is linked instead. + +set -euo pipefail + +usage() { + echo "Usage: $0 []" >&2 + exit 2 +} + +[[ $# -ge 1 && $# -le 2 ]] || usage +name=$1 +if [[ ! "$name" =~ ^[A-Za-z0-9._-]+$ ]]; then + echo "Worktree name '$name' may use only letters, digits, dots, underscores and hyphens." >&2 + exit 2 +fi +branch=${2:-feat/$name} + +script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +checkout=$(cd "$script_dir/.." && pwd) +common_dir=$(git -C "$checkout" rev-parse --path-format=absolute --git-common-dir) +primary_root=$(dirname "$common_dir") +target="$(dirname "$primary_root")/$(basename "$primary_root")-$name" + +if [[ -e "$target" ]]; then + echo "$target already exists." >&2 + exit 1 +fi +if git -C "$primary_root" show-ref --verify --quiet "refs/heads/$branch"; then + echo "Branch $branch already exists." >&2 + exit 1 +fi + +echo "Fetching origin..." +git -C "$primary_root" fetch origin + +if git -C "$primary_root" show-ref --verify --quiet refs/heads/main; then + ahead=$(git -C "$primary_root" rev-list --count origin/main..main) + if ((ahead > 0)); then + echo "Local main has $ahead commit(s) that origin/main does not. Reconcile main first;" >&2 + echo "branching from origin/main would silently leave them out." >&2 + exit 1 + fi + + # Where main is checked out, move it only if that checkout is clean. + main_checkout=$(git -C "$primary_root" worktree list --porcelain | + awk '/^worktree /{wt = substr($0, 10)} /^branch refs\/heads\/main$/{print wt}') + if [[ -z "$main_checkout" ]]; then + # update-ref rather than `branch --force`: that would also rewrite main's + # upstream to origin/main, silently re-pointing a main that tracks another + # remote. The old value guards against main moving since it was checked. + git -C "$primary_root" update-ref -m "new-worktree.sh: fast-forward main to origin/main" \ + refs/heads/main "$(git -C "$primary_root" rev-parse refs/remotes/origin/main)" \ + "$(git -C "$primary_root" rev-parse refs/heads/main)" + elif [[ -n "$(git -C "$main_checkout" status --porcelain --untracked-files=no)" ]]; then + echo "main is checked out with uncommitted changes at $main_checkout; leaving it as it is." + elif ! git -C "$main_checkout" merge --ff-only --quiet origin/main; then + # Untracked files are not counted as changes above, because a fast-forward + # refuses to overwrite one; this is where that refusal lands. + echo "Could not fast-forward main at $main_checkout; leaving it as it is." + fi +fi + +echo "Creating $target on $branch from origin/main..." +git -C "$primary_root" worktree add --no-track -b "$branch" "$target" origin/main + +if [[ -f "$primary_root/.env" && ! -e "$target/.env" ]]; then + ln -s "$primary_root/.env" "$target/.env" + echo "Linked .env from the primary checkout." +fi + +# The setup script beside this one rather than the target's copy. The branch starts +# from origin/main, which can predate the worktree-aware setup, and running an older +# copy there would rebind the shared hook to the new worktree. +echo "Preparing the worktree..." +(cd "$target" && bash "$checkout/setup-hooks.sh") + +echo +echo "Worktree ready: $target" +echo "Branch: $branch, from $(git -C "$target" rev-parse --short HEAD), not tracking a remote" From ae08c30ad8e0e3dee2578a74ea96b0e49e8dff34 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Mon, 14 Sep 2026 08:15:47 -0700 Subject: [PATCH 3/4] feat(scripts): install the shared pre-commit hook before creating a worktree A worktree commits through the one hook in the common directory. When that hook was missing, new-worktree.sh only warned after the fact and the worktree's commits skipped every check without a word; when it was bound to an environment that no longer exists, they failed. setup-hooks.sh gains --check-hook, which exits non-zero when the shared hook is missing or not bound to the primary checkout. new-worktree.sh runs it before creating the worktree, installs the hook from the primary checkout when it fails, and refuses to create the worktree if the hook is still not bound afterwards. --- DEVELOPMENT.md | 7 +-- scripts/new-worktree.sh | 19 +++++++ setup-hooks.sh | 106 ++++++++++++++++++++++++++-------------- 3 files changed, 92 insertions(+), 40 deletions(-) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index f437cdc4..0cc96cf7 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -281,7 +281,7 @@ To install pre-commit hooks: ``` This syncs dependencies and, in the primary checkout, installs the git pre-commit hook. Run from a worktree, it syncs that worktree's own environment and checks the shared hook instead of reinstalling it: hooks are shared by every worktree and record the -interpreter that installed them, so a hook installed from a worktree stops working when that worktree is removed. +interpreter that installed them, so a hook installed from a worktree stops working when that worktree is removed. `./setup-hooks.sh --check-hook` only checks the shared hook, and exits non-zero when it is missing or not bound to the primary checkout. ## Worktrees @@ -291,8 +291,9 @@ Create worktrees with the script rather than `git worktree add`: ./scripts/new-worktree.sh [] # defaults to feat/ ``` -It fetches `origin`, fast-forwards `main` where that is safe, creates `../span-panel-api-` on a branch that does not track a remote, links `.env` from the primary checkout, and runs `setup-hooks.sh` inside it. The worktree gets a virtual environment -of its own rather than a link to the primary checkout's: editable installs record one checkout's source path, so a shared environment would import whichever checkout synced last. +It fetches `origin`, fast-forwards `main` where that is safe, installs the shared pre-commit hook from the primary checkout if it is missing or bound elsewhere, creates `../span-panel-api-` on a branch that does not track a remote, links `.env` from +the primary checkout, and runs `setup-hooks.sh` inside it. The worktree gets a virtual environment of its own rather than a link to the primary checkout's: editable installs record one checkout's source path, so a shared environment would import whichever +checkout synced last. ## Workspace layout diff --git a/scripts/new-worktree.sh b/scripts/new-worktree.sh index 888ff6c9..b75e4b6c 100755 --- a/scripts/new-worktree.sh +++ b/scripts/new-worktree.sh @@ -22,6 +22,9 @@ # wrong code without an error. uv clones packages from its cache, so the second # environment costs little. Local configuration that is not bound to a path, `.env`, # is linked instead. +# +# The shared pre-commit hook is installed from the primary checkout first when it is +# missing or bound elsewhere, so no worktree starts out committing unchecked. set -euo pipefail @@ -83,6 +86,22 @@ if git -C "$primary_root" show-ref --verify --quiet refs/heads/main; then fi fi +# Every worktree commits through the one hook in the common directory. Missing, its +# commits skip every check without a word; bound to an environment that is gone, +# they fail. The primary checkout is the one place it can be installed to last, so +# install it there before the worktree exists, and refuse if that does not take. +if ! (cd "$checkout" && bash ./setup-hooks.sh --check-hook >/dev/null); then + echo "Installing the shared pre-commit hook from the primary checkout..." + if ! (cd "$primary_root" && ./setup-hooks.sh); then + echo "Could not install the pre-commit hook from $primary_root; not creating the worktree." >&2 + exit 1 + fi + if ! (cd "$checkout" && bash ./setup-hooks.sh --check-hook); then + echo "The pre-commit hook is still not bound to $primary_root; not creating the worktree." >&2 + exit 1 + fi +fi + echo "Creating $target on $branch from origin/main..." git -C "$primary_root" worktree add --no-track -b "$branch" "$target" origin/main diff --git a/setup-hooks.sh b/setup-hooks.sh index dfa8ae0a..25f87ab7 100755 --- a/setup-hooks.sh +++ b/setup-hooks.sh @@ -1,7 +1,13 @@ #!/bin/bash # # Prepare this checkout for development: install dependencies and make sure the -# git pre-commit hook is in place. Pass --update to upgrade the lockfile first. +# git pre-commit hook is in place. +# +# Usage: ./setup-hooks.sh [--update | --check-hook] +# +# --update Upgrade the lockfile before syncing. +# --check-hook Only check the shared pre-commit hook, and exit non-zero when it +# is missing or not bound to the primary checkout. # # Safe to run from the primary checkout and from a linked worktree, which differ # in the one respect that matters here. Git hooks live in the repository's common @@ -13,12 +19,64 @@ # that the shared hook is bound to the primary checkout and says how to fix it if # not, and leaves it alone either way. -FORCE_UPDATE=false -if [[ "${1:-}" == "--update" ]]; then - FORCE_UPDATE=true +usage() { + echo "Usage: $0 [--update | --check-hook]" >&2 + exit 2 +} + +mode=setup +case "${1:-}" in + "") ;; + --update) mode=update ;; + --check-hook) mode=check-hook ;; + *) usage ;; +esac +[[ $# -le 1 ]] || usage + +git_dir=$(git rev-parse --absolute-git-dir) || exit 1 +common_dir=$(git rev-parse --path-format=absolute --git-common-dir) || exit 1 +primary_root=$(dirname "$common_dir") + +# Whether the shared hook exists and runs with the primary checkout's environment. +# Silent when it does; otherwise says what is wrong and how to fix it. +check_shared_hook() { + local hook install_python + + hook=$(git rev-parse --path-format=absolute --git-path hooks/pre-commit) + if [[ ! -f "$hook" ]]; then + echo "No pre-commit hook is installed for this repository yet, so commits skip every" + echo "check. Install it from the primary checkout, where it outlives every worktree:" + echo " (cd \"$primary_root\" && ./setup-hooks.sh)" + return 1 + fi + + install_python=$(sed -n "s/^INSTALL_PYTHON=//p" "$hook") + install_python=${install_python#\'} + install_python=${install_python%\'} + + if [[ "$install_python" != "$primary_root"/.venv/* ]]; then + echo "The shared pre-commit hook runs with ${install_python:-an unknown interpreter}, not the" + echo "primary checkout's environment, so commits will fail once that environment is gone." + echo "Rebind it from the primary checkout:" + echo " (cd \"$primary_root\" && ./setup-hooks.sh)" + return 1 + fi + + if [[ ! -x "$install_python" ]]; then + echo "The shared pre-commit hook runs with $install_python, which no longer exists, so" + echo "commits will fail. Reinstall it from the primary checkout:" + echo " (cd \"$primary_root\" && ./setup-hooks.sh)" + return 1 + fi +} + +if [[ "$mode" == "check-hook" ]]; then + check_shared_hook || exit 1 + echo "Shared pre-commit hook is bound to the primary checkout." + exit 0 fi -if [[ "$FORCE_UPDATE" == "true" ]]; then +if [[ "$mode" == "update" ]]; then echo "Forcing update to latest versions..." if ! uv lock --upgrade; then echo "Failed to upgrade the lockfile. Please check the output above." @@ -37,46 +95,20 @@ if ! uv sync; then exit 1 fi -git_dir=$(git rev-parse --absolute-git-dir) -common_dir=$(git rev-parse --path-format=absolute --git-common-dir) - if [[ "$git_dir" == "$common_dir" ]]; then if ! uv run pre-commit install; then echo "Failed to install the pre-commit hook. Please check the output above." exit 1 fi + if ! check_shared_hook; then + echo "Failed to bind the pre-commit hook to this checkout's environment." + exit 1 + fi echo "Git hooks installed successfully!" exit 0 fi # A linked worktree from here on. -primary_root=$(dirname "$common_dir") -hook=$(git rev-parse --path-format=absolute --git-path hooks/pre-commit) - -if [[ ! -f "$hook" ]]; then - echo "No pre-commit hook is installed for this repository yet. Install it from the" - echo "primary checkout, where it outlives every worktree:" - echo " (cd \"$primary_root\" && ./setup-hooks.sh)" - exit 0 -fi - -install_python=$(sed -n "s/^INSTALL_PYTHON=//p" "$hook") -install_python=${install_python#\'} -install_python=${install_python%\'} - -if [[ "$install_python" != "$primary_root"/.venv/* ]]; then - echo "The shared pre-commit hook runs with ${install_python:-an unknown interpreter}, not the" - echo "primary checkout's environment, so commits will fail once that environment is gone." - echo "Rebind it from the primary checkout:" - echo " (cd \"$primary_root\" && ./setup-hooks.sh)" - exit 0 +if check_shared_hook; then + echo "Shared pre-commit hook is bound to the primary checkout; leaving it alone." fi - -if [[ ! -x "$install_python" ]]; then - echo "The shared pre-commit hook runs with $install_python, which no longer exists, so" - echo "commits will fail. Reinstall it from the primary checkout:" - echo " (cd \"$primary_root\" && ./setup-hooks.sh)" - exit 0 -fi - -echo "Shared pre-commit hook is bound to the primary checkout; leaving it alone." From 2edfe9fac78b57db9d5499e9265f321bbc96bc48 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Sat, 24 Oct 2026 11:28:06 -0700 Subject: [PATCH 4/4] docs(agents): create worktrees with new-worktree.sh AGENTS.md gains a Worktrees section: create worktrees with scripts/new-worktree.sh rather than `git worktree add` or a built-in worktree tool, never share a virtual environment between checkouts, and install the pre-commit hook from the primary checkout. CLAUDE.md already imports AGENTS.md, so the section reaches every agent without further change. --- AGENTS.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 4511e753..28ddd2cb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -13,6 +13,22 @@ Do not attribute work to Claude, Copilot or any other AI agent anywhere in this This rule takes precedence over any default tool behaviour that would add attribution. +## Worktrees + +Create worktrees with `scripts/new-worktree.sh`, never with `git worktree add` or an agent's built-in worktree tool: + +```bash +./scripts/new-worktree.sh [] # defaults to feat/ +``` + +It creates `../span-panel-api-` beside the primary checkout from a freshly fetched `origin/main`, prepares it with `setup-hooks.sh`, and links `.env`. That location is the one the integration's developer notes expect when installing a library +worktree into its environment. [DEVELOPMENT.md](DEVELOPMENT.md#worktrees) has the details. + +- **Never share a virtual environment between checkouts.** This repository installs its own distributions in editable mode, each recording one checkout's source path, so a shared `.venv` imports whichever checkout synced last and tests pass against the + wrong code without an error. +- **Install the pre-commit hook from the primary checkout.** Hooks are shared by every worktree and record the interpreter that installed them. `new-worktree.sh` installs the hook from the primary checkout when it is missing or bound elsewhere, and refuses + to create the worktree if that fails. `setup-hooks.sh` in a worktree checks the shared hook rather than rebinding it to an environment that is deleted with the worktree. + ## The rule every field follows A value the panel did not publish is `None` (or unknown), never a constant. A `0.0`, `False`, `""` or empty collection in a snapshot field is a reading the panel published, or an answer the library can derive from what the panel declared, and the docstring