From 7b27397912a42754a0240494f7ca004cfa8c2b18 Mon Sep 17 00:00:00 2001 From: Timor Gruber Date: Wed, 21 Oct 2026 17:34:03 +0300 Subject: [PATCH 1/3] add bare worktree layout for cross-worktree agent sessions Sandboxed agents could only reach the directory they started in, so worktrees created by wt next to the repository were out of reach for the running session. Keeping each repository as a container that holds a bare .git and one folder per worktree lets the launcher grant the whole container up front. Add git wtclone to clone straight into that layout and git wtmigrate to convert existing clones, both with man pages. The nono launcher now grants the folder holding .git, and the worktrunk template places new worktrees inside the container. --- dot_agents/shell/nono.zsh.tmpl | 9 +- private_dot_local/bin/executable_git-wtclone | 125 +++++++++++++++++ .../bin/executable_git-wtmigrate | 130 ++++++++++++++++++ .../share/man/man1/git-wtclone.1 | 113 +++++++++++++++ .../share/man/man1/git-wtmigrate.1 | 93 +++++++++++++ run_after_ensure-worktrunk-config.sh | 5 +- 6 files changed, 472 insertions(+), 3 deletions(-) create mode 100644 private_dot_local/bin/executable_git-wtclone create mode 100644 private_dot_local/bin/executable_git-wtmigrate create mode 100644 private_dot_local/share/man/man1/git-wtclone.1 create mode 100644 private_dot_local/share/man/man1/git-wtmigrate.1 diff --git a/dot_agents/shell/nono.zsh.tmpl b/dot_agents/shell/nono.zsh.tmpl index 1d0a980f..bed2ca77 100644 --- a/dot_agents/shell/nono.zsh.tmpl +++ b/dot_agents/shell/nono.zsh.tmpl @@ -50,7 +50,14 @@ function _nono_agent_run() { local -a git_common_dir_args=() local git_common_dir if git_common_dir="$(command git rev-parse --path-format=absolute --git-common-dir 2>/dev/null)"; then - git_common_dir_args=(--allow "$git_common_dir") + # The folder holding .git is the main checkout, or in the bare worktree + # layout (git wtclone) the container of every worktree. Never widen to + # $HOME or / when a repository lives there. + if [[ "${git_common_dir:t}" == .git && "${git_common_dir:h}" != "$HOME" && "${git_common_dir:h}" != / ]]; then + git_common_dir_args=(--allow "${git_common_dir:h}") + else + git_common_dir_args=(--allow "$git_common_dir") + fi fi local -a acp_args=() diff --git a/private_dot_local/bin/executable_git-wtclone b/private_dot_local/bin/executable_git-wtclone new file mode 100644 index 00000000..7b7b9687 --- /dev/null +++ b/private_dot_local/bin/executable_git-wtclone @@ -0,0 +1,125 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Clone a repository into the bare worktree layout: +# /.git bare repository +# / worktree of the default branch, submodules initialized +# Arguments match git clone: git wtclone [] [] + +die() { + echo "git-wtclone: $*" >&2 + exit 1 +} + +usage() { + echo "usage: git wtclone [] []" >&2 + exit 2 +} + +# git clone options that take their value as a separate argument. +takes_value() { + case "$1" in + -o | --origin | -b | --branch | -u | --upload-pack | --reference | --reference-if-able | \ + --depth | --shallow-since | --shallow-exclude | -c | --config | --server-option | \ + -j | --jobs | --filter | --bundle-uri | --template | --ref-format | --revision) + return 0 + ;; + esac + return 1 +} + +clone_opts=() +positional=() +single_branch=false +while (( $# > 0 )); do + case "$1" in + --) + shift + positional+=("$@") + break + ;; + -h | --help) + usage + ;; + --bare | --mirror | --separate-git-dir | --separate-git-dir=*) + die "$1 conflicts with the worktree layout" + ;; + --recurse-submodules | --recurse-submodules=* | --recursive) + # Submodules are always initialized in the worktree after cloning. + shift + ;; + --single-branch) + single_branch=true + clone_opts+=("$1") + shift + ;; + -*) + if takes_value "$1"; then + (( $# >= 2 )) || usage + clone_opts+=("$1" "$2") + shift 2 + else + clone_opts+=("$1") + shift + fi + ;; + *) + positional+=("$1") + shift + ;; + esac +done + +(( ${#positional[@]} >= 1 && ${#positional[@]} <= 2 )) || usage +repo="${positional[0]}" +dir="${positional[1]:-}" + +if [[ -z "$dir" ]]; then + # Same naming rule as git clone: last path component without .git. + dir="${repo%/}" + dir="${dir%/.git}" + dir="${dir%.git}" + dir="${dir##*/}" + dir="${dir##*:}" + [[ -n "$dir" ]] || die "cannot derive a directory name from '$repo'; pass " +fi + +if [[ -e "$dir" && -n "$(ls -A "$dir")" ]]; then + die "destination path '$dir' already exists and is not an empty directory" +fi + +git clone --bare ${clone_opts[@]+"${clone_opts[@]}"} -- "$repo" "$dir/.git" + +git_dir="$(cd "$dir/.git" && pwd -P)" +container="${git_dir%/.git}" + +g() { + git --git-dir="$git_dir" "$@" +} + +remote="$(g remote | head -n 1)" +branch="$(g symbolic-ref --short HEAD)" + +# A bare clone maps remote branches straight onto local branches and sets no +# fetch refspec. Track them as remote-tracking branches instead, like a normal +# clone, and keep only the checked-out branch local. +if [[ "$single_branch" == true ]]; then + g config "remote.$remote.fetch" "+refs/heads/$branch:refs/remotes/$remote/$branch" +else + g config "remote.$remote.fetch" "+refs/heads/*:refs/remotes/$remote/*" +fi +g fetch --quiet "$remote" +g remote set-head "$remote" --auto >/dev/null 2>&1 || + echo "git-wtclone: could not set $remote/HEAD; run 'git remote set-head $remote --auto' later" >&2 + +g for-each-ref --format='%(refname)' refs/heads/ | while IFS= read -r ref; do + [[ "$ref" == "refs/heads/$branch" ]] || g update-ref -d "$ref" +done +g branch --quiet --set-upstream-to="$remote/$branch" "$branch" + +# Folder name follows worktrunk's sanitize filter: / and \ become -. +worktree="$container/${branch//[\/\\]/-}" +g worktree add --quiet "$worktree" "$branch" +git -C "$worktree" submodule update --init --recursive --quiet + +echo "Cloned into $worktree" diff --git a/private_dot_local/bin/executable_git-wtmigrate b/private_dot_local/bin/executable_git-wtmigrate new file mode 100644 index 00000000..67e750ac --- /dev/null +++ b/private_dot_local/bin/executable_git-wtmigrate @@ -0,0 +1,130 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Convert a regular clone into the bare worktree layout used by git wtclone: +# /.git the former .git directory, now a bare repository +# / the former main checkout, now a linked worktree +# Linked worktrees on a branch move to / as well. Uncommitted, +# staged, untracked, and ignored files stay in place. +# Usage: git wtmigrate [] (default: the repository of the current directory) + +die() { + echo "git-wtmigrate: $*" >&2 + exit 1 +} + +warn() { + echo "git-wtmigrate: $*" >&2 +} + +# Folder name follows worktrunk's sanitize filter: / and \ become -. +sanitize() { + local name="$1" + echo "${name//[\/\\]/-}" +} + +(( $# <= 1 )) || die "usage: git wtmigrate []" +target="${1:-.}" + +common="$(git -C "$target" rev-parse --path-format=absolute --git-common-dir)" || + die "'$target' is not inside a git repository" +[[ "$(basename "$common")" == .git && -d "$common" ]] || + die "expected the repository data in a .git directory, found $common" +[[ "$(git --git-dir="$common" config --bool --get core.bare || echo false)" == false ]] || + die "$(dirname "$common") already uses the bare layout" +[[ -z "$(git --git-dir="$common" config --get core.worktree || true)" ]] || + die "core.worktree is set; this layout is not supported" +[[ ! -d "$common/modules" ]] || + die "repositories with initialized submodules are not supported" + +for state in MERGE_HEAD CHERRY_PICK_HEAD REVERT_HEAD BISECT_LOG rebase-merge rebase-apply sequencer; do + [[ ! -e "$common/$state" ]] || + die "an operation is in progress ($state); finish or abort it first" +done + +repo="$(dirname "$common")" +branch="$(git --git-dir="$common" symbolic-ref --quiet --short HEAD)" || + die "the main checkout has a detached HEAD; check out a branch first" +main_dir="$(sanitize "$branch")" + +# Collect linked worktrees that sit on a branch; others stay where they are. +linked_paths=() +linked_dests=() +used_names="$main_dir" +wt_path="" +wt_branch="" +wt_skip=false +flush_worktree() { + if [[ -n "$wt_path" && "$wt_path" != "$repo" ]]; then + if [[ "$wt_path" == "$repo"/* ]]; then + die "worktree $wt_path is nested inside the main checkout; move it out first" + fi + if [[ "$wt_skip" == true || -z "$wt_branch" ]]; then + warn "leaving $wt_path in place (detached, locked, or missing)" + else + local name + name="$(sanitize "$wt_branch")" + if grep -Fxq -- "$name" <<<"$used_names"; then + die "two worktrees map to the folder name '$name'" + fi + used_names+=$'\n'"$name" + linked_paths+=("$wt_path") + linked_dests+=("$repo/$name") + fi + fi + wt_path="" + wt_branch="" + wt_skip=false +} +while IFS= read -r line; do + case "$line" in + "worktree "*) wt_path="${line#worktree }" ;; + "branch refs/heads/"*) wt_branch="${line#branch refs/heads/}" ;; + locked* | prunable*) wt_skip=true ;; + "") flush_worktree ;; + esac +done < <(git --git-dir="$common" worktree list --porcelain) +flush_worktree + +[[ "$main_dir" != .git ]] || die "a branch named '.git' cannot become a folder" + +parent="$(dirname "$repo")" +tmp="$(mktemp -d "$parent/.$(basename "$repo").wtmigrate.XXXXXX")" +trap 'warn "failed partway; pieces of the repository may be in $repo and $tmp"' ERR + +# Move the main checkout aside, then put its .git back as the bare repository. +mv "$repo" "$tmp/checkout" +mkdir "$repo" +mv "$tmp/checkout/.git" "$common" +git --git-dir="$common" config core.bare true + +# Register the checkout as a linked worktree. Keep its index (staged changes) +# and HEAD reflog, then swap the real files in under the new .git file. +[[ ! -f "$common/index" ]] || mv "$common/index" "$tmp/index" +git --git-dir="$common" worktree add --quiet --no-checkout "$repo/$main_dir" "$branch" +admin="$(git -C "$repo/$main_dir" rev-parse --path-format=absolute --git-dir)" +[[ ! -f "$tmp/index" ]] || mv "$tmp/index" "$admin/index" +if [[ -f "$common/logs/HEAD" ]]; then + mkdir -p "$admin/logs" + mv "$common/logs/HEAD" "$admin/logs/HEAD" +fi +mv "$repo/$main_dir/.git" "$tmp/checkout/.git" +rmdir "$repo/$main_dir" +mv "$tmp/checkout" "$repo/$main_dir" +rmdir "$tmp" +trap - ERR + +git -C "$repo/$main_dir" update-index -q --refresh >/dev/null || true + +for (( i = 0; i < ${#linked_paths[@]}; i++ )); do + src="${linked_paths[$i]}" + dest="${linked_dests[$i]}" + if [[ -e "$dest" ]]; then + warn "leaving $src in place; $dest already exists" + elif ! git --git-dir="$common" worktree move "$src" "$dest"; then + warn "could not move $src to $dest" + fi +done + +echo "Migrated $repo; the former main checkout is now $repo/$main_dir" +echo "Shells inside the old checkout should cd to the new path." diff --git a/private_dot_local/share/man/man1/git-wtclone.1 b/private_dot_local/share/man/man1/git-wtclone.1 new file mode 100644 index 00000000..2b126adb --- /dev/null +++ b/private_dot_local/share/man/man1/git-wtclone.1 @@ -0,0 +1,113 @@ +.TH GIT-WTCLONE 1 2026-10-21 dotfiles "User Commands" +.SH NAME +git-wtclone \- clone a repository into the bare worktree layout +.SH SYNOPSIS +.B git wtclone +.RI [ "clone options" ] +.I repo +.RI [ dir ] +.SH DESCRIPTION +Clones +.I repo +the way +.BR git\-clone (1) +does, but lays it out so every worktree sits in one folder: +.PP +.RS +.nf +/.git bare repository (git data only) +/ worktree of the checked-out branch +.fi +.RE +.PP +Worktrees that +.BR wt (1) +creates later land next to it as +.IR / . +The nono agent launchers grant the whole +.I +folder, so one agent session can work across all worktrees. +.PP +After cloning, +.B git wtclone +sets up the bare repository to behave like a normal clone: +.IP \(bu 2 +remote branches are fetched as remote-tracking branches +.RI ( origin/* ), +and +.I origin/HEAD +points at the remote's default branch; +.IP \(bu 2 +only the checked-out branch exists as a local branch, tracking its remote +branch; +.IP \(bu 2 +submodules are initialized recursively in the new worktree. +.SH ARGUMENTS +.TP +.I repo +The repository to clone, in any form +.BR git\-clone (1) +accepts. +.TP +.I dir +The container folder. Defaults to the last path component of +.I repo +without +.IR .git , +created in the current folder. It must not exist or must be empty. +.SH OPTIONS +Options are passed on to +.BR git\-clone (1), +with these exceptions: +.TP +.BR \-\-bare ", " \-\-mirror ", " \-\-separate\-git\-dir +Rejected; they conflict with the layout. +.TP +.BR \-\-recurse\-submodules ", " \-\-recursive +Ignored; submodules are always initialized. +.TP +.BI \-b " branch" +Checks out +.I branch +instead of the remote's default branch. The worktree folder is named after it. +.TP +.B \-\-single\-branch +Fetches only the checked-out branch, now and on later fetches. +.SH FOLDER NAMES +Worktree folders use the branch name with +.B / +and +.B \e +replaced by +.BR \- , +matching the +.B sanitize +filter of +.BR wt (1). +For example, branch +.I feature/login +becomes the folder +.IR feature\-login . +.SH EXAMPLES +.TP +.B git wtclone git@github.com:acme/foo.git +Creates +.I ./foo/.git +and +.I ./foo/main +(if +.I main +is the default branch). +.TP +.B git wtclone \-\-depth 1 git@github.com:acme/foo.git ~/projects/acme/foo +Shallow clone into +.IR ~/projects/acme/foo . +.TP +.B wt switch \-\-create feature/login +Run inside the container or a worktree afterwards; creates +.IR foo/feature\-login . +.SH SEE ALSO +.BR git\-wtmigrate (1), +.BR git\-clone (1), +.BR git\-worktree (1), +.BR wt (1) diff --git a/private_dot_local/share/man/man1/git-wtmigrate.1 b/private_dot_local/share/man/man1/git-wtmigrate.1 new file mode 100644 index 00000000..94aaa832 --- /dev/null +++ b/private_dot_local/share/man/man1/git-wtmigrate.1 @@ -0,0 +1,93 @@ +.TH GIT-WTMIGRATE 1 2026-10-21 dotfiles "User Commands" +.SH NAME +git-wtmigrate \- convert an existing clone into the bare worktree layout +.SH SYNOPSIS +.B git wtmigrate +.RI [ path ] +.SH DESCRIPTION +Converts a regular clone into the layout that +.BR git\-wtclone (1) +creates. For a clone at +.I foo +whose main checkout is on branch +.IR main : +.PP +.RS +.nf +before after +foo/ foo/.git (bare repository) +foo/.git/ foo/main/ (former main checkout) +foo-feature-x/ (worktree) foo/feature-x/ (moved worktree) +.fi +.RE +.PP +Every file in the main checkout stays as it was, including uncommitted, +staged, untracked, and ignored files. The former +.I .git +folder becomes the bare repository in place, so worktrees that are not +moved keep working. +.PP +Linked worktrees that are on a branch move to +.IR foo/ . +Detached or locked worktrees, and worktrees whose target folder already +exists, stay where they are, with a warning. +.PP +Shells that were inside the old main checkout keep a stale path; run +.B cd +to the new folder afterwards. +.SH ARGUMENTS +.TP +.I path +Any folder inside the repository to convert, including a linked worktree. +Defaults to the current folder. +.SH REFUSED CASES +.B git wtmigrate +stops without changing anything when: +.IP \(bu 2 +the repository already uses the bare layout; +.IP \(bu 2 +the main checkout has a detached HEAD; +.IP \(bu 2 +a merge, rebase, cherry-pick, revert, or bisect is in progress; +.IP \(bu 2 +the repository has initialized submodules; +.IP \(bu 2 +.I core.worktree +is set; +.IP \(bu 2 +a linked worktree sits inside the main checkout; +.IP \(bu 2 +two worktrees would get the same folder name. +.SH FAILURE +The checkout is first moved to a temporary folder named +.I ..wtmigrate.XXXXXX +next to the repository. If a step fails after that, the error names both +locations, so the pieces can be put back by hand. +.SH NOTES +The command moves the repository folder, so it needs write access to the +folder that contains it. Run it outside agent sandboxes. +.PP +Folder names follow the same rule as +.BR git\-wtclone (1): +.B / +and +.B \e +in branch names become +.BR \- . +.SH EXAMPLES +.TP +.B git wtmigrate ~/projects/acme/foo +Converts +.I foo +from anywhere. +.TP +.B git wtmigrate && cd ~/projects/acme/foo/main +Run inside +.IR foo ; +converts it, then moves the shell into the former main checkout (here on +branch +.IR main ). +.SH SEE ALSO +.BR git\-wtclone (1), +.BR git\-worktree (1), +.BR wt (1) diff --git a/run_after_ensure-worktrunk-config.sh b/run_after_ensure-worktrunk-config.sh index 5b39d102..ef7d01c3 100644 --- a/run_after_ensure-worktrunk-config.sh +++ b/run_after_ensure-worktrunk-config.sh @@ -11,8 +11,9 @@ set -euo pipefail CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/worktrunk" CONFIG_FILE="$CONFIG_DIR/config.toml" -# Sibling worktrees joined with a dash; nono cannot open directories with dots. -WORKTREE_PATH_LINE='worktree-path = "{{ repo_path }}/../{{ repo }}-{{ branch | sanitize }}"' +# Bare worktree layout (git wtclone / git wtmigrate): repo_path is /.git, +# so every worktree lands in / next to the bare repository. +WORKTREE_PATH_LINE='worktree-path = "{{ repo_path }}/../{{ branch | sanitize }}"' HOOK_NAME="agents-settings" LEGACY_HOOK_NAME="claude-settings" From 2558c7aa8b1d0095c616cd6103c60d76f257c253 Mon Sep 17 00:00:00 2001 From: Timor Gruber Date: Wed, 21 Oct 2026 17:49:30 +0300 Subject: [PATCH 2/3] support classic clones in the worktree layout Some repositories live where another tool puts them, such as the chezmoi source directory, so they cannot become a bare container. With the container-only template, wt would place their worktrees in the parent folder, outside what the agent sandbox grants. The worktrunk template now sends worktrees of classic clones to a central folder under ~/.local/share/worktrees, keyed by the checkout path. The nono launcher derives the same key and grants that folder alongside the checkout, so a session can work across those worktrees too. The git wtclone and git wtmigrate man pages describe this case. --- dot_agents/shell/nono.zsh.tmpl | 13 ++++++++++ .../share/man/man1/git-wtclone.1 | 25 +++++++++++++++++++ .../share/man/man1/git-wtmigrate.1 | 4 +++ run_after_ensure-worktrunk-config.sh | 5 +++- 4 files changed, 46 insertions(+), 1 deletion(-) diff --git a/dot_agents/shell/nono.zsh.tmpl b/dot_agents/shell/nono.zsh.tmpl index bed2ca77..a6bb83c3 100644 --- a/dot_agents/shell/nono.zsh.tmpl +++ b/dot_agents/shell/nono.zsh.tmpl @@ -58,6 +58,19 @@ function _nono_agent_run() { else git_common_dir_args=(--allow "$git_common_dir") fi + + # A classic (non-bare) clone keeps its worktrees in a central folder keyed + # by the main checkout path, matching the worktree-path template that + # run_after_ensure-worktrunk-config.sh writes for worktrunk. + if [[ "$(command git --git-dir="$git_common_dir" config --bool core.bare 2>/dev/null)" != true ]]; then + local main_checkout + main_checkout="$(command git worktree list --porcelain 2>/dev/null | command sed -n '1s/^worktree //p')" + if [[ -n "$main_checkout" ]]; then + local worktrees_dir="$HOME/.local/share/worktrees/${${main_checkout//[\/\\]/-}//./-}" + command mkdir -p "$worktrees_dir" && + git_common_dir_args+=(--allow "$worktrees_dir") + fi + fi fi local -a acp_args=() diff --git a/private_dot_local/share/man/man1/git-wtclone.1 b/private_dot_local/share/man/man1/git-wtclone.1 index 2b126adb..adbab0a4 100644 --- a/private_dot_local/share/man/man1/git-wtclone.1 +++ b/private_dot_local/share/man/man1/git-wtclone.1 @@ -88,6 +88,31 @@ For example, branch .I feature/login becomes the folder .IR feature\-login . +.SH CLASSIC REPOSITORIES +Repositories that must stay a regular clone, because another tool decides +where they live, keep working. For them, +.BR wt (1) +creates worktrees in a central folder instead: +.PP +.RS +.nf +~/.local/share/worktrees// +.fi +.RE +.PP +.I +is the path of the main checkout with +.BR / , +.BR \e , +and +.B . +replaced by +.BR \- , +for example +.I \-Users\-me\-\-local\-share\-chezmoi +for +.IR /Users/me/.local/share/chezmoi . +The nono agent launchers grant the main checkout and that folder. .SH EXAMPLES .TP .B git wtclone git@github.com:acme/foo.git diff --git a/private_dot_local/share/man/man1/git-wtmigrate.1 b/private_dot_local/share/man/man1/git-wtmigrate.1 index 94aaa832..0f6e30e3 100644 --- a/private_dot_local/share/man/man1/git-wtmigrate.1 +++ b/private_dot_local/share/man/man1/git-wtmigrate.1 @@ -67,6 +67,10 @@ locations, so the pieces can be put back by hand. The command moves the repository folder, so it needs write access to the folder that contains it. Run it outside agent sandboxes. .PP +Repositories that cannot move do not need this command; see CLASSIC +REPOSITORIES in +.BR git\-wtclone (1). +.PP Folder names follow the same rule as .BR git\-wtclone (1): .B / diff --git a/run_after_ensure-worktrunk-config.sh b/run_after_ensure-worktrunk-config.sh index ef7d01c3..c2572af2 100644 --- a/run_after_ensure-worktrunk-config.sh +++ b/run_after_ensure-worktrunk-config.sh @@ -13,7 +13,10 @@ CONFIG_FILE="$CONFIG_DIR/config.toml" # Bare worktree layout (git wtclone / git wtmigrate): repo_path is /.git, # so every worktree lands in / next to the bare repository. -WORKTREE_PATH_LINE='worktree-path = "{{ repo_path }}/../{{ branch | sanitize }}"' +# Classic clones whose location is fixed get a central folder instead, keyed by +# the checkout path with / and . replaced by -. The nono launcher in +# ~/.agents/shell/nono.zsh derives the same key, so keep both in sync. +WORKTREE_PATH_LINE="worktree-path = \"{% if repo_path is endingwith('/.git') %}{{ repo_path }}/../{{ branch | sanitize }}{% else %}~/.local/share/worktrees/{{ repo_path | sanitize | replace('.', '-') }}/{{ branch | sanitize }}{% endif %}\"" HOOK_NAME="agents-settings" LEGACY_HOOK_NAME="claude-settings" From 64c3f2bca5ea5031c351d1caf8bfd1e3e92833fd Mon Sep 17 00:00:00 2001 From: Timor Gruber Date: Wed, 21 Oct 2026 18:32:38 +0300 Subject: [PATCH 3/3] check run scripts before treating a target as unmanaged chezmoi source-path only knows targets backed by a source file, so a config file that a run_* script edits, such as the worktrunk config, reports "not managed". Agents following the skill then edited the target directly, and the next apply would have reverted the change. Tell the skill to search the run_* scripts for the target path first and to change values in the owning script. --- .agents/skills/managing-chezmoi/SKILL.md | 2 +- .../managing-chezmoi/references/commands-and-workflows.md | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.agents/skills/managing-chezmoi/SKILL.md b/.agents/skills/managing-chezmoi/SKILL.md index b59682f2..03bfc972 100644 --- a/.agents/skills/managing-chezmoi/SKILL.md +++ b/.agents/skills/managing-chezmoi/SKILL.md @@ -18,7 +18,7 @@ This repo is edited from inside a nono sandbox. Treat chezmoi source files as th ## Default Workflow -1. **Confirm source ownership**: before editing a file under `~`, use `chezmoi source-path ` or inspect whether the source uses `dot_*`, `private_dot_*`, `dot_config/`, `private_dot_config/`, or a template source +1. **Confirm source ownership**: before editing a file under `~`, use `chezmoi source-path ` or inspect whether the source uses `dot_*`, `private_dot_*`, `dot_config/`, `private_dot_config/`, or a template source. When `source-path` reports "not managed", search the `run_*` scripts at the repo root for the target path before treating the file as unmanaged: some scripts own keys inside target config files, and the next apply overwrites manual edits to those keys 2. **Inspect state**: use `chezmoi status` and `chezmoi diff` for managed targets; use `git diff` for repo-only files that chezmoi does not manage as targets 3. **Edit source directly**: modify files in this repo (the source state) 4. **Validate safely**: run `chezmoi diff` and `chezmoi apply --dry-run -v` for managed targets; stop at source review and git diff for unmanaged repo files diff --git a/.agents/skills/managing-chezmoi/references/commands-and-workflows.md b/.agents/skills/managing-chezmoi/references/commands-and-workflows.md index 4959a106..63c56b00 100644 --- a/.agents/skills/managing-chezmoi/references/commands-and-workflows.md +++ b/.agents/skills/managing-chezmoi/references/commands-and-workflows.md @@ -47,7 +47,9 @@ This file is a *practical* quick reference for `chezmoi` operations in a dotfile ### 7) Repo-only files - Some files in this repo are not managed as chezmoi targets. -- If `chezmoi source-path ` reports "not managed", validate with source review and `git diff` instead of trying to apply. +- `chezmoi source-path ` only knows targets that map to a source file. A target that a `run_*` script writes or edits also reports "not managed". +- Before treating a target as unmanaged, search the `run_*` scripts at the repo root for its path, for example `grep -l 'worktrunk' run_*`. If a script owns the setting, change the value in the script, not in the target file: the script rewrites the target on every apply. +- For repo files that no target or script uses, validate with source review and `git diff` instead of trying to apply. ## Day-to-day commands