From fdd5165fbcbcf7727d347384247a31d03a643eaa Mon Sep 17 00:00:00 2001 From: "worktree-hook (osvetnik)" Date: Mon, 6 Jul 2026 18:44:08 +0300 Subject: [PATCH 1/3] chore: bin/worktree provision subcommand + .agents/provision hook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Factor 'new''s provisioning steps (.envrc symlink + uv venv + uv sync --all-groups + direnv allow) into 'bin/worktree provision [dir]', which provisions an EXISTING worktree in place and is idempotent; 'new' now calls it. Add executable .agents/provision at the repo root — a 2-line shim over 'bin/worktree provision' — so an agent harness can auto-set-up a freshly-cut worktree after a bare 'git worktree add'. Verified: provisioned a plain 'git worktree add' scratch tree; pytest 8.3.5, mypy 1.19.0, and 'import dimos' all resolve from the new .venv. --- .agents/provision | 3 +++ bin/worktree | 27 ++++++++++++++++++++++++--- 2 files changed, 27 insertions(+), 3 deletions(-) create mode 100755 .agents/provision diff --git a/.agents/provision b/.agents/provision new file mode 100755 index 0000000000..3510cc56b5 --- /dev/null +++ b/.agents/provision @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +# Agent worktree provisioning hook: set this tree up (uv venv/sync, .envrc, direnv). +exec "$(dirname "$0")/../bin/worktree" provision "$@" diff --git a/bin/worktree b/bin/worktree index 5bd5d776e9..5b518854b5 100755 --- a/bin/worktree +++ b/bin/worktree @@ -6,9 +6,12 @@ # bin/worktree new --dir ../path # override the worktree directory # bin/worktree ls # list worktrees (branch = value for rm) # bin/worktree rm [-b] # remove a worktree (-b also deletes the branch) +# bin/worktree provision [dir] # (re)provision an existing worktree in place # -# `new` (aka create/add) provisions the worktree so mypy/pytest/pre-commit work -# out of the box (uv venv + `uv sync --all-groups`, `.envrc` symlink, direnv allow). +# `new` provisions the worktree so mypy/pytest/pre-commit work out of the box +# (uv venv + `uv sync --all-groups`, `.envrc` symlink, direnv allow) by calling +# `provision` — which is idempotent, so you can also run it on a bare tree +# (e.g. a plain `git worktree add`) to set it up after the fact. # # Examples: # bin/worktree new feat/ivan/foo # new branch off current HEAD -> ../dimos-foo @@ -21,7 +24,7 @@ set -euo pipefail REPO_ROOT=$(git rev-parse --path-format=absolute --git-common-dir) REPO_ROOT=$(cd "$(dirname "$REPO_ROOT")" && pwd) # main worktree root -usage() { sed -n '2,18p' "$0" | sed 's/^# \{0,1\}//'; } +usage() { sed -n '2,21p' "$0" | sed 's/^# \{0,1\}//'; } # Default worktree dir for a branch: ../dimos-, sanitized. default_dir() { @@ -125,6 +128,23 @@ cmd_add() { GIT_LFS_SKIP_SMUDGE=1 git worktree add -b "$BRANCH" "$DIR" "${BASE:-HEAD}" fi + cmd_provision "$DIR" +} + +cmd_provision() { + # Provision an EXISTING worktree in place so mypy/pytest/pre-commit work out + # of the box: .envrc symlink + uv venv + `uv sync --all-groups` + direnv + # allow. Idempotent, so it's safe to re-run and `new` calls it after adding + # the tree; you can also point it at a bare `git worktree add` dir to set it + # up after the fact. Defaults to the current directory. + local DIR="${1:-.}" resolved="" + # realpath -m (resolve non-existent paths) is GNU-only; fall back for BSD/macOS. + if resolved=$(realpath -m "$DIR" 2>/dev/null); then + DIR=$resolved + else + DIR=$(cd "$(dirname "$DIR")" && echo "$PWD/$(basename "$DIR")") + fi + [[ -d "$DIR" ]] || { echo "not a directory: $DIR" >&2; exit 1; } cd "$DIR" # Per-worktree env (.envrc is gitignored, so each worktree needs its own). @@ -161,5 +181,6 @@ case "${1:-}" in ls|list) shift; cmd_ls "$@" ;; rm|remove) shift; cmd_rm "$@" ;; new|create|add) shift; cmd_add "$@" ;; + provision) shift; cmd_provision "$@" ;; *) echo "unknown command: $1 (did you mean 'new'?)" >&2; echo >&2; usage >&2; exit 1 ;; esac From 998d3ff151767a2a3722831130b51c0907476256 Mon Sep 17 00:00:00 2001 From: "worktree-provision-fix (osvetnik)" Date: Mon, 6 Jul 2026 19:11:33 +0300 Subject: [PATCH 2/3] fix: resolve provision paths from the script's own tree, not caller cwd Review findings on #2732: - .agents/provision resolved bin/worktree via a relative dirname "$0", which breaks when invoked from PATH or another cwd; resolve absolutely. - `worktree provision` with no argument provisioned the caller's cwd, so running it from the main checkout would uv-venv/sync the main tree; default to the root of the tree the script lives in instead (explicit dir argument unchanged). REPO_ROOT is likewise anchored on the script location so the shim works from any cwd. - uv venv --allow-existing keeps re-provisioning idempotent with uv versions that refuse an existing .venv. --- .agents/provision | 3 ++- bin/worktree | 13 ++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.agents/provision b/.agents/provision index 3510cc56b5..45b83a473b 100755 --- a/.agents/provision +++ b/.agents/provision @@ -1,3 +1,4 @@ #!/usr/bin/env bash # Agent worktree provisioning hook: set this tree up (uv venv/sync, .envrc, direnv). -exec "$(dirname "$0")/../bin/worktree" provision "$@" +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +exec "$SCRIPT_DIR/../bin/worktree" provision "$@" diff --git a/bin/worktree b/bin/worktree index 5b518854b5..760a5b7309 100755 --- a/bin/worktree +++ b/bin/worktree @@ -21,7 +21,8 @@ # bin/worktree rm feat/ivan/foo -b # remove worktree and delete the branch set -euo pipefail -REPO_ROOT=$(git rev-parse --path-format=absolute --git-common-dir) +SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) +REPO_ROOT=$(git -C "$SCRIPT_DIR" rev-parse --path-format=absolute --git-common-dir) REPO_ROOT=$(cd "$(dirname "$REPO_ROOT")" && pwd) # main worktree root usage() { sed -n '2,21p' "$0" | sed 's/^# \{0,1\}//'; } @@ -136,8 +137,10 @@ cmd_provision() { # of the box: .envrc symlink + uv venv + `uv sync --all-groups` + direnv # allow. Idempotent, so it's safe to re-run and `new` calls it after adding # the tree; you can also point it at a bare `git worktree add` dir to set it - # up after the fact. Defaults to the current directory. - local DIR="${1:-.}" resolved="" + # up after the fact. With no argument, provisions the worktree this script + # lives in (not the caller's cwd). + local DIR="${1:-}" resolved="" + [[ -n "$DIR" ]] || DIR=$(git -C "$SCRIPT_DIR/.." rev-parse --show-toplevel) # realpath -m (resolve non-existent paths) is GNU-only; fall back for BSD/macOS. if resolved=$(realpath -m "$DIR" 2>/dev/null); then DIR=$resolved @@ -159,9 +162,9 @@ cmd_provision() { # the system toolchain directly. echo ">>> uv venv + sync via $envrc (may take a few min on first run)" if [[ "$envrc" == .envrc.nix ]]; then - nix develop --command bash -c 'uv venv --python 3.12 && uv sync --all-groups' + nix develop --command bash -c 'uv venv --python 3.12 --allow-existing && uv sync --all-groups' else - uv venv --python 3.12 && uv sync --all-groups + uv venv --python 3.12 --allow-existing && uv sync --all-groups fi # Let direnv auto-activate it next time you cd in. From 5b3eaa8560f34b4c9cd3e24162ef7136652374a2 Mon Sep 17 00:00:00 2001 From: "worktree-provision-fix (osvetnik)" Date: Mon, 6 Jul 2026 19:14:55 +0300 Subject: [PATCH 3/3] fix: resolve .agents/provision when invoked as a bare command name When the shim is run by basename via PATH, $0 has no slash and dirname yields "." (the caller's cwd again); look the name up with command -v first so SCRIPT_DIR is the script's real directory. --- .agents/provision | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.agents/provision b/.agents/provision index 45b83a473b..692bb9b7c2 100755 --- a/.agents/provision +++ b/.agents/provision @@ -1,4 +1,9 @@ #!/usr/bin/env bash # Agent worktree provisioning hook: set this tree up (uv venv/sync, .envrc, direnv). -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +SELF=$0 +case "$SELF" in + */*) ;; + *) SELF=$(command -v -- "$SELF") ;; +esac +SCRIPT_DIR="$(cd "$(dirname "$SELF")" && pwd)" exec "$SCRIPT_DIR/../bin/worktree" provision "$@"