From 45ba05d6aeeb10a79ebde54736dcb2a6112cd929 Mon Sep 17 00:00:00 2001 From: Caleb Cox Date: Wed, 5 Aug 2026 14:07:38 -0500 Subject: [PATCH 1/2] feat: add remote_repo variable --- CHANGELOG.md | 6 +++++ dev/config.example.toml | 1 + docs/content/config.md | 1 + docs/content/hook.md | 3 ++- .../skills/worktrunk/reference/config.md | 1 + .../skills/worktrunk/reference/hook.md | 3 ++- skills/worktrunk/reference/config.md | 1 + skills/worktrunk/reference/hook.md | 3 ++- src/cli/mod.rs | 4 ++- src/commands/command_executor.rs | 1 + src/config/expansion.rs | 16 ++++++++++++ src/config/user/accessors.rs | 12 ++++----- src/config/user/tests.rs | 23 ++++++++++++++++ tests/integration_tests/eval.rs | 26 +++++++++++++++++++ ...tests__eval__eval_format_json_verbose.snap | 1 + ...integration_tests__eval__eval_verbose.snap | 1 + ...ation_tests__help__help_config_create.snap | 1 + ...gration_tests__help__help_config_long.snap | 1 + ...t_commands__post_start_verbose_output.snap | 1 + ...post_start_verbose_template_expansion.snap | 1 + ...ias__alias_verbose_args_shell_escaped.snap | 1 + ...p_alias__alias_verbose_variable_table.snap | 1 + ...d_post_remove_and_post_switch_verbose.snap | 2 ++ ..._hooks__hook_verbose_background_dedup.snap | 1 + ...er_hooks__hook_verbose_variable_table.snap | 1 + 25 files changed, 103 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3edb33b405..6e1d82ad44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Improved + +- **`remote_repo` names the repository the way the remote spells it**: Templates had `owner` for the namespace in the primary remote URL but nothing for the repo name that follows it, so anything wanting the forge's name for the repository had to settle for `repo` — the directory on disk, which differs whenever a clone was renamed locally (`git clone git@github.com:company-org/project.git work` gives `repo = work`). `{{ remote_repo }}` resolves to the URL's repo component with the `.git` suffix stripped (`project`), and is available everywhere `owner` is: hook and alias bodies, `--execute` payloads, `wt step eval`, and the `worktree-path` template. Like `owner`, it is set only when a primary remote exists and its URL parses, so a template that must survive a remote-less repo guards it with `{% if remote_repo %}`. + ## 0.71.0 ### Improved diff --git a/dev/config.example.toml b/dev/config.example.toml index e31e2814cf..952b971155 100644 --- a/dev/config.example.toml +++ b/dev/config.example.toml @@ -16,6 +16,7 @@ # - `{{ repo_path }}` — absolute path to the repository root (e.g., `/Users/me/code/myproject`. Or for bare repos, the bare directory itself) # - `{{ repo }}` — repository directory name (e.g., `myproject`) # - `{{ owner }}` — primary remote owner path (may include subgroups like `group/subgroup`) +# - `{{ remote_repo }}` — repository name in the primary remote URL, without `.git` (e.g., `myproject`); differs from `{{ repo }}`, the directory on disk, when a clone was renamed # - `{{ branch }}` — raw branch name (e.g., `feature/auth`) # - `{{ branch | sanitize }}` — filesystem-safe: `/` and `\` become `-` (e.g., `feature-auth`) # - `{{ branch | sanitize_db }}` — database-safe: lowercase, underscores, hash suffix (e.g., `feature_auth_x7k`) diff --git a/docs/content/config.md b/docs/content/config.md index 0e554bcee0..7ff47a32ac 100644 --- a/docs/content/config.md +++ b/docs/content/config.md @@ -78,6 +78,7 @@ Controls where new worktrees are created. - `{{ repo_path }}` — absolute path to the repository root (e.g., `/Users/me/code/myproject`. Or for bare repos, the bare directory itself) - `{{ repo }}` — repository directory name (e.g., `myproject`) - `{{ owner }}` — primary remote owner path (may include subgroups like `group/subgroup`) +- `{{ remote_repo }}` — repository name in the primary remote URL, without `.git` (e.g., `myproject`); differs from `{{ repo }}`, the directory on disk, when a clone was renamed - `{{ branch }}` — raw branch name (e.g., `feature/auth`) - `{{ branch | sanitize }}` — filesystem-safe: `/` and `\` become `-` (e.g., `feature-auth`) - `{{ branch | sanitize_db }}` — database-safe: lowercase, underscores, hash suffix (e.g., `feature_auth_x7k`) diff --git a/docs/content/hook.md b/docs/content/hook.md index df00c75ce5..37cd2263e4 100644 --- a/docs/content/hook.md +++ b/docs/content/hook.md @@ -138,6 +138,7 @@ Hooks can use template variables that expand at runtime: | repo | `{{ repo }}` | Repository directory name | | | `{{ repo_path }}` | Absolute path to repository root | | | `{{ owner }}` | Primary remote owner path (may include subgroups) | +| | `{{ remote_repo }}` | Repository name from the primary remote URL, without `.git` | | | `{{ primary_worktree_path }}` | Primary worktree path | | | `{{ default_branch }}` | Default branch name | | | `{{ remote }}` | Primary remote name | @@ -148,7 +149,7 @@ Hooks can use template variables that expand at runtime: | | `{{ args }}` | Tokens forwarded from the CLI — see [Running Hooks Manually](#running-hooks-manually) | | user | `{{ vars. }}` | Per-branch variables from [`wt config state vars`](@/config.md#wt-config-state-vars) | -The `repo` variables (`repo`, `repo_path`, `owner`, `primary_worktree_path`, `default_branch`, `remote`, `remote_url`) are constant across the whole repository — `default_branch` is the same in every worktree. The `active` variables (`branch`, `worktree_path`, `worktree_name`, `commit`, `short_commit`, `upstream`) vary per worktree. +The `repo` variables (`repo`, `repo_path`, `owner`, `remote_repo`, `primary_worktree_path`, `default_branch`, `remote`, `remote_url`) are constant across the whole repository — `default_branch` is the same in every worktree. The `active` variables (`branch`, `worktree_path`, `worktree_name`, `commit`, `short_commit`, `upstream`) vary per worktree. Bare variables (`branch`, `worktree_path`, `commit`) refer to the branch the operation acts on: the destination for switch/create, the source for merge/remove. `base` and `target` give the other side: diff --git a/plugins/worktrunk/skills/worktrunk/reference/config.md b/plugins/worktrunk/skills/worktrunk/reference/config.md index 2f91dd0d93..0f1ea7e6b2 100644 --- a/plugins/worktrunk/skills/worktrunk/reference/config.md +++ b/plugins/worktrunk/skills/worktrunk/reference/config.md @@ -77,6 +77,7 @@ Controls where new worktrees are created. - `{{ repo_path }}` — absolute path to the repository root (e.g., `/Users/me/code/myproject`. Or for bare repos, the bare directory itself) - `{{ repo }}` — repository directory name (e.g., `myproject`) - `{{ owner }}` — primary remote owner path (may include subgroups like `group/subgroup`) +- `{{ remote_repo }}` — repository name in the primary remote URL, without `.git` (e.g., `myproject`); differs from `{{ repo }}`, the directory on disk, when a clone was renamed - `{{ branch }}` — raw branch name (e.g., `feature/auth`) - `{{ branch | sanitize }}` — filesystem-safe: `/` and `\` become `-` (e.g., `feature-auth`) - `{{ branch | sanitize_db }}` — database-safe: lowercase, underscores, hash suffix (e.g., `feature_auth_x7k`) diff --git a/plugins/worktrunk/skills/worktrunk/reference/hook.md b/plugins/worktrunk/skills/worktrunk/reference/hook.md index 9e4fc81435..82f2b53547 100644 --- a/plugins/worktrunk/skills/worktrunk/reference/hook.md +++ b/plugins/worktrunk/skills/worktrunk/reference/hook.md @@ -129,6 +129,7 @@ Hooks can use template variables that expand at runtime: | repo | `{{ repo }}` | Repository directory name | | | `{{ repo_path }}` | Absolute path to repository root | | | `{{ owner }}` | Primary remote owner path (may include subgroups) | +| | `{{ remote_repo }}` | Repository name from the primary remote URL, without `.git` | | | `{{ primary_worktree_path }}` | Primary worktree path | | | `{{ default_branch }}` | Default branch name | | | `{{ remote }}` | Primary remote name | @@ -139,7 +140,7 @@ Hooks can use template variables that expand at runtime: | | `{{ args }}` | Tokens forwarded from the CLI — see [Running Hooks Manually](#running-hooks-manually) | | user | `{{ vars. }}` | Per-branch variables from [`wt config state vars`](https://worktrunk.dev/config/#wt-config-state-vars) | -The `repo` variables (`repo`, `repo_path`, `owner`, `primary_worktree_path`, `default_branch`, `remote`, `remote_url`) are constant across the whole repository — `default_branch` is the same in every worktree. The `active` variables (`branch`, `worktree_path`, `worktree_name`, `commit`, `short_commit`, `upstream`) vary per worktree. +The `repo` variables (`repo`, `repo_path`, `owner`, `remote_repo`, `primary_worktree_path`, `default_branch`, `remote`, `remote_url`) are constant across the whole repository — `default_branch` is the same in every worktree. The `active` variables (`branch`, `worktree_path`, `worktree_name`, `commit`, `short_commit`, `upstream`) vary per worktree. Bare variables (`branch`, `worktree_path`, `commit`) refer to the branch the operation acts on: the destination for switch/create, the source for merge/remove. `base` and `target` give the other side: diff --git a/skills/worktrunk/reference/config.md b/skills/worktrunk/reference/config.md index 2f91dd0d93..0f1ea7e6b2 100644 --- a/skills/worktrunk/reference/config.md +++ b/skills/worktrunk/reference/config.md @@ -77,6 +77,7 @@ Controls where new worktrees are created. - `{{ repo_path }}` — absolute path to the repository root (e.g., `/Users/me/code/myproject`. Or for bare repos, the bare directory itself) - `{{ repo }}` — repository directory name (e.g., `myproject`) - `{{ owner }}` — primary remote owner path (may include subgroups like `group/subgroup`) +- `{{ remote_repo }}` — repository name in the primary remote URL, without `.git` (e.g., `myproject`); differs from `{{ repo }}`, the directory on disk, when a clone was renamed - `{{ branch }}` — raw branch name (e.g., `feature/auth`) - `{{ branch | sanitize }}` — filesystem-safe: `/` and `\` become `-` (e.g., `feature-auth`) - `{{ branch | sanitize_db }}` — database-safe: lowercase, underscores, hash suffix (e.g., `feature_auth_x7k`) diff --git a/skills/worktrunk/reference/hook.md b/skills/worktrunk/reference/hook.md index 9e4fc81435..82f2b53547 100644 --- a/skills/worktrunk/reference/hook.md +++ b/skills/worktrunk/reference/hook.md @@ -129,6 +129,7 @@ Hooks can use template variables that expand at runtime: | repo | `{{ repo }}` | Repository directory name | | | `{{ repo_path }}` | Absolute path to repository root | | | `{{ owner }}` | Primary remote owner path (may include subgroups) | +| | `{{ remote_repo }}` | Repository name from the primary remote URL, without `.git` | | | `{{ primary_worktree_path }}` | Primary worktree path | | | `{{ default_branch }}` | Default branch name | | | `{{ remote }}` | Primary remote name | @@ -139,7 +140,7 @@ Hooks can use template variables that expand at runtime: | | `{{ args }}` | Tokens forwarded from the CLI — see [Running Hooks Manually](#running-hooks-manually) | | user | `{{ vars. }}` | Per-branch variables from [`wt config state vars`](https://worktrunk.dev/config/#wt-config-state-vars) | -The `repo` variables (`repo`, `repo_path`, `owner`, `primary_worktree_path`, `default_branch`, `remote`, `remote_url`) are constant across the whole repository — `default_branch` is the same in every worktree. The `active` variables (`branch`, `worktree_path`, `worktree_name`, `commit`, `short_commit`, `upstream`) vary per worktree. +The `repo` variables (`repo`, `repo_path`, `owner`, `remote_repo`, `primary_worktree_path`, `default_branch`, `remote`, `remote_url`) are constant across the whole repository — `default_branch` is the same in every worktree. The `active` variables (`branch`, `worktree_path`, `worktree_name`, `commit`, `short_commit`, `upstream`) vary per worktree. Bare variables (`branch`, `worktree_path`, `commit`) refer to the branch the operation acts on: the destination for switch/create, the source for merge/remove. `base` and `target` give the other side: diff --git a/src/cli/mod.rs b/src/cli/mod.rs index c27d0effb9..6bf812f792 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1667,6 +1667,7 @@ Hooks can use template variables that expand at runtime: | repo | `{{ repo }}` | Repository directory name | | | `{{ repo_path }}` | Absolute path to repository root | | | `{{ owner }}` | Primary remote owner path (may include subgroups) | +| | `{{ remote_repo }}` | Repository name from the primary remote URL, without `.git` | | | `{{ primary_worktree_path }}` | Primary worktree path | | | `{{ default_branch }}` | Default branch name | | | `{{ remote }}` | Primary remote name | @@ -1677,7 +1678,7 @@ Hooks can use template variables that expand at runtime: | | `{{ args }}` | Tokens forwarded from the CLI — see [Running Hooks Manually](#running-hooks-manually) | | user | `{{ vars. }}` | Per-branch variables from [`wt config state vars`](@/config.md#wt-config-state-vars) | -The `repo` variables (`repo`, `repo_path`, `owner`, `primary_worktree_path`, `default_branch`, `remote`, `remote_url`) are constant across the whole repository — `default_branch` is the same in every worktree. The `active` variables (`branch`, `worktree_path`, `worktree_name`, `commit`, `short_commit`, `upstream`) vary per worktree. +The `repo` variables (`repo`, `repo_path`, `owner`, `remote_repo`, `primary_worktree_path`, `default_branch`, `remote`, `remote_url`) are constant across the whole repository — `default_branch` is the same in every worktree. The `active` variables (`branch`, `worktree_path`, `worktree_name`, `commit`, `short_commit`, `upstream`) vary per worktree. Bare variables (`branch`, `worktree_path`, `commit`) refer to the branch the operation acts on: the destination for switch/create, the source for merge/remove. `base` and `target` give the other side: @@ -1963,6 +1964,7 @@ Controls where new worktrees are created. - `{{ repo_path }}` — absolute path to the repository root (e.g., `/Users/me/code/myproject`. Or for bare repos, the bare directory itself) - `{{ repo }}` — repository directory name (e.g., `myproject`) - `{{ owner }}` — primary remote owner path (may include subgroups like `group/subgroup`) +- `{{ remote_repo }}` — repository name in the primary remote URL, without `.git` (e.g., `myproject`); differs from `{{ repo }}`, the directory on disk, when a clone was renamed - `{{ branch }}` — raw branch name (e.g., `feature/auth`) - `{{ branch | sanitize }}` — filesystem-safe: `/` and `\` become `-` (e.g., `feature-auth`) - `{{ branch | sanitize_db }}` — database-safe: lowercase, underscores, hash suffix (e.g., `feature_auth_x7k`) diff --git a/src/commands/command_executor.rs b/src/commands/command_executor.rs index 22d1ce2c05..ab1c5741b8 100644 --- a/src/commands/command_executor.rs +++ b/src/commands/command_executor.rs @@ -291,6 +291,7 @@ pub fn build_hook_context( if let Some(parsed_remote) = ctx.repo.primary_remote_parsed_url() { map.insert("owner".into(), parsed_remote.owner().to_string()); + map.insert("remote_repo".into(), parsed_remote.repo().to_string()); } // Default branch diff --git a/src/config/expansion.rs b/src/config/expansion.rs index 79dcbe62ca..60a21b6fa5 100644 --- a/src/config/expansion.rs +++ b/src/config/expansion.rs @@ -51,6 +51,7 @@ pub const REPO_VARS: &[&str] = &[ "repo", "repo_path", "owner", + "remote_repo", "primary_worktree_path", "default_branch", "remote", @@ -2931,6 +2932,20 @@ mod tests { .is_ok() ); + // Remote-derived repo vars validate in every scope + assert!( + validate_template("{{ owner }}/{{ remote_repo }}", hook, &test.repo, "test").is_ok() + ); + assert!( + validate_template( + "{{ remote_repo }}", + ValidationScope::Alias, + &test.repo, + "test" + ) + .is_ok() + ); + // Deprecated vars still valid in every scope assert!(validate_template("{{ main_worktree }}", hook, &test.repo, "test").is_ok()); @@ -3155,6 +3170,7 @@ mod tests { repo = demo repo_path = /tmp/demo owner = (unset) + remote_repo = (unset) primary_worktree_path = (unset) default_branch = (unset) remote = (unset) diff --git a/src/config/user/accessors.rs b/src/config/user/accessors.rs index 529a025114..5b0422d40d 100644 --- a/src/config/user/accessors.rs +++ b/src/config/user/accessors.rs @@ -232,7 +232,8 @@ impl UserConfig { /// * `main_worktree` - Main worktree directory name (replaces {{ main_worktree }} in template) /// * `branch` - Branch name (replaces {{ branch }} in template; use `{{ branch | sanitize }}` for paths) /// * `repo` - Repository for template function access - /// * remote owner/namespace is available as {{ owner }} + /// * remote owner/namespace is available as {{ owner }}, and the remote's + /// repository name as {{ remote_repo }} /// * `project` - Optional project identifier (e.g., "github.com/user/repo") to look up /// project-specific worktree-path template pub fn format_path( @@ -253,11 +254,10 @@ impl UserConfig { vars.insert("repo", main_worktree); vars.insert("branch", branch); vars.insert("repo_path", repo_path.as_str()); - let owner = repo - .primary_remote_parsed_url() - .map(|parsed_remote| parsed_remote.owner().to_string()); - if let Some(ref owner) = owner { - vars.insert("owner", owner.as_str()); + let parsed_remote = repo.primary_remote_parsed_url(); + if let Some(ref parsed_remote) = parsed_remote { + vars.insert("owner", parsed_remote.owner()); + vars.insert("remote_repo", parsed_remote.repo()); } Ok(expand_template( &template, diff --git a/src/config/user/tests.rs b/src/config/user/tests.rs index 1c4778bc12..86407910d8 100644 --- a/src/config/user/tests.rs +++ b/src/config/user/tests.rs @@ -447,6 +447,29 @@ fn test_worktrunk_config_format_path_owner_variable() { assert_eq!(path, "max-sixty/myrepo/feature/branch"); } +#[test] +fn test_worktrunk_config_format_path_remote_repo_variable() { + let mut test = TestRepo::with_initial_commit(); + test.setup_remote("main"); + test.run_git(&[ + "remote", + "set-url", + "origin", + "git@github.com:company-org/project.git", + ]); + + let config = UserConfig { + worktree_path: Some("{{ remote_repo }}/{{ repo }}/{{ branch }}".to_string()), + ..Default::default() + }; + + let path = config + .format_path("myrepo", "feature/branch", &test.repo, None) + .unwrap(); + + assert_eq!(path, "project/myrepo/feature/branch"); +} + #[test] fn test_worktrunk_config_format_path_owner_uses_full_namespace() { let mut test = TestRepo::with_initial_commit(); diff --git a/tests/integration_tests/eval.rs b/tests/integration_tests/eval.rs index 85815b321b..8544b8d2a1 100644 --- a/tests/integration_tests/eval.rs +++ b/tests/integration_tests/eval.rs @@ -117,6 +117,32 @@ fn test_eval_owner(repo: TestRepo) { ); } +#[rstest] +fn test_eval_remote_repo(repo: TestRepo) { + repo.run_git(&[ + "remote", + "set-url", + "origin", + "git@github.com:company-org/project.git", + ]); + + let output = repo + .wt_command() + .args(["step", "eval", "{{ remote_repo }}/{{ repo }}"]) + .output() + .expect("Failed to run wt step eval"); + + assert!( + output.status.success(), + "wt step eval should succeed, stderr: {}", + String::from_utf8_lossy(&output.stderr) + ); + assert_eq!( + String::from_utf8_lossy(&output.stdout).trim(), + "project/repo" + ); +} + #[rstest] fn test_eval_conditional(repo: TestRepo) { assert_cmd_snapshot!(make_snapshot_cmd( diff --git a/tests/snapshots/integration__integration_tests__eval__eval_format_json_verbose.snap b/tests/snapshots/integration__integration_tests__eval__eval_format_json_verbose.snap index 8603a78d84..fbe74292a6 100644 --- a/tests/snapshots/integration__integration_tests__eval__eval_format_json_verbose.snap +++ b/tests/snapshots/integration__integration_tests__eval__eval_format_json_verbose.snap @@ -66,6 +66,7 @@ exit_code: 0   repo = repo   repo_path = _REPO_   owner = (unset) +  remote_repo = (unset)   primary_worktree_path = _REPO_   default_branch = main   remote = origin diff --git a/tests/snapshots/integration__integration_tests__eval__eval_verbose.snap b/tests/snapshots/integration__integration_tests__eval__eval_verbose.snap index 9e2693dfcc..652030bfc3 100644 --- a/tests/snapshots/integration__integration_tests__eval__eval_verbose.snap +++ b/tests/snapshots/integration__integration_tests__eval__eval_verbose.snap @@ -61,6 +61,7 @@ exit_code: 0   repo = repo   repo_path = _REPO_   owner = (unset) +  remote_repo = (unset)   primary_worktree_path = _REPO_   default_branch = main   remote = origin diff --git a/tests/snapshots/integration__integration_tests__help__help_config_create.snap b/tests/snapshots/integration__integration_tests__help__help_config_create.snap index 77142de961..212014bf0d 100644 --- a/tests/snapshots/integration__integration_tests__help__help_config_create.snap +++ b/tests/snapshots/integration__integration_tests__help__help_config_create.snap @@ -91,6 +91,7 @@ Creates ~/.config/worktrunk/config.toml with the following content:   # - `{{ repo_path }}` — absolute path to the repository root (e.g., `/Users/me/code/myproject`. Or for bare repos, the bare directory itself)   # - `{{ repo }}` — repository directory name (e.g., `myproject`)   # - `{{ owner }}` — primary remote owner path (may include subgroups like `group/subgroup`) +  # - `{{ remote_repo }}` — repository name in the primary remote URL, without `.git` (e.g., `myproject`); differs from `{{ repo }}`, the directory on disk, when a clone was renamed   # - `{{ branch }}` — raw branch name (e.g., `feature/auth`)   # - `{{ branch | sanitize }}` — filesystem-safe: `/` and `\` become `-` (e.g., `feature-auth`)   # - `{{ branch | sanitize_db }}` — database-safe: lowercase, underscores, hash suffix (e.g., `feature_auth_x7k`) diff --git a/tests/snapshots/integration__integration_tests__help__help_config_long.snap b/tests/snapshots/integration__integration_tests__help__help_config_long.snap index c0fe08fef6..b257b8ed19 100644 --- a/tests/snapshots/integration__integration_tests__help__help_config_long.snap +++ b/tests/snapshots/integration__integration_tests__help__help_config_long.snap @@ -139,6 +139,7 @@ Controls where new worktrees are created. - {{ repo_path }} — absolute path to the repository root (e.g., /Users/me/code/myproject. Or for bare repos, the bare directory itself) - {{ repo }} — repository directory name (e.g., myproject) - {{ owner }} — primary remote owner path (may include subgroups like group/subgroup) +- {{ remote_repo }} — repository name in the primary remote URL, without .git (e.g., myproject); differs from {{ repo }}, the directory on disk, when a clone was renamed - {{ branch }} — raw branch name (e.g., feature/auth) - {{ branch | sanitize }} — filesystem-safe: / and \ become - (e.g., feature-auth) - {{ branch | sanitize_db }} — database-safe: lowercase, underscores, hash suffix (e.g., feature_auth_x7k) diff --git a/tests/snapshots/integration__integration_tests__post_start_commands__post_start_verbose_output.snap b/tests/snapshots/integration__integration_tests__post_start_commands__post_start_verbose_output.snap index 11544a3981..31263e3f3b 100644 --- a/tests/snapshots/integration__integration_tests__post_start_commands__post_start_verbose_output.snap +++ b/tests/snapshots/integration__integration_tests__post_start_commands__post_start_verbose_output.snap @@ -74,6 +74,7 @@ exit_code: 0   repo = repo   repo_path = _REPO_   owner = (unset) +  remote_repo = (unset)   primary_worktree_path = _REPO_   default_branch = main   remote = origin diff --git a/tests/snapshots/integration__integration_tests__post_start_commands__post_start_verbose_template_expansion.snap b/tests/snapshots/integration__integration_tests__post_start_commands__post_start_verbose_template_expansion.snap index 7c1cf69dc3..1322782ff2 100644 --- a/tests/snapshots/integration__integration_tests__post_start_commands__post_start_verbose_template_expansion.snap +++ b/tests/snapshots/integration__integration_tests__post_start_commands__post_start_verbose_template_expansion.snap @@ -74,6 +74,7 @@ exit_code: 0   repo = repo   repo_path = _REPO_   owner = (unset) +  remote_repo = (unset)   primary_worktree_path = _REPO_   default_branch = main   remote = origin diff --git a/tests/snapshots/integration__integration_tests__step_alias__alias_verbose_args_shell_escaped.snap b/tests/snapshots/integration__integration_tests__step_alias__alias_verbose_args_shell_escaped.snap index ad94886ec4..5085907055 100644 --- a/tests/snapshots/integration__integration_tests__step_alias__alias_verbose_args_shell_escaped.snap +++ b/tests/snapshots/integration__integration_tests__step_alias__alias_verbose_args_shell_escaped.snap @@ -61,6 +61,7 @@ hello foo bar baz   repo = repo   repo_path = _REPO_   owner = (unused) +  remote_repo = (unused)   primary_worktree_path = (unused)   default_branch = (unused)   remote = (unused) diff --git a/tests/snapshots/integration__integration_tests__step_alias__alias_verbose_variable_table.snap b/tests/snapshots/integration__integration_tests__step_alias__alias_verbose_variable_table.snap index 4a8c793ae9..f18903990a 100644 --- a/tests/snapshots/integration__integration_tests__step_alias__alias_verbose_variable_table.snap +++ b/tests/snapshots/integration__integration_tests__step_alias__alias_verbose_variable_table.snap @@ -60,6 +60,7 @@ hello world   repo = repo   repo_path = _REPO_   owner = (unused) +  remote_repo = (unused)   primary_worktree_path = (unused)   default_branch = (unused)   remote = (unused) diff --git a/tests/snapshots/integration__integration_tests__user_hooks__combined_post_remove_and_post_switch_verbose.snap b/tests/snapshots/integration__integration_tests__user_hooks__combined_post_remove_and_post_switch_verbose.snap index 8edb1454fb..df8f2a859c 100644 --- a/tests/snapshots/integration__integration_tests__user_hooks__combined_post_remove_and_post_switch_verbose.snap +++ b/tests/snapshots/integration__integration_tests__user_hooks__combined_post_remove_and_post_switch_verbose.snap @@ -65,6 +65,7 @@ exit_code: 0   repo = repo   repo_path = _REPO_   owner = (unset) +  remote_repo = (unset)   primary_worktree_path = _REPO_   default_branch = main   remote = origin @@ -88,6 +89,7 @@ exit_code: 0   repo = repo   repo_path = _REPO_   owner = (unset) +  remote_repo = (unset)   primary_worktree_path = _REPO_   default_branch = main   remote = origin diff --git a/tests/snapshots/integration__integration_tests__user_hooks__hook_verbose_background_dedup.snap b/tests/snapshots/integration__integration_tests__user_hooks__hook_verbose_background_dedup.snap index 15804a1054..55e6c99e3f 100644 --- a/tests/snapshots/integration__integration_tests__user_hooks__hook_verbose_background_dedup.snap +++ b/tests/snapshots/integration__integration_tests__user_hooks__hook_verbose_background_dedup.snap @@ -74,6 +74,7 @@ exit_code: 0   repo = repo   repo_path = _REPO_   owner = (unset) +  remote_repo = (unset)   primary_worktree_path = _REPO_   default_branch = main   remote = origin diff --git a/tests/snapshots/integration__integration_tests__user_hooks__hook_verbose_variable_table.snap b/tests/snapshots/integration__integration_tests__user_hooks__hook_verbose_variable_table.snap index 9490531a27..dfbd80f85c 100644 --- a/tests/snapshots/integration__integration_tests__user_hooks__hook_verbose_variable_table.snap +++ b/tests/snapshots/integration__integration_tests__user_hooks__hook_verbose_variable_table.snap @@ -69,6 +69,7 @@ exit_code: 0   repo = repo   repo_path = _REPO_   owner = (unset) +  remote_repo = (unset)   primary_worktree_path = _REPO_   default_branch = main   remote = origin From 62b11b237f859d45bbdd5f02bd3c3225ec3caaa2 Mon Sep 17 00:00:00 2001 From: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com> Date: Sat, 8 Aug 2026 21:25:39 +0000 Subject: [PATCH 2/2] docs(changelog): tighten the remote_repo entry Cut the entry from 131 words to ~47, within the release skill's 40-word ceiling for a non-headline bullet, and add the PR link and contributor credit the skill requires for external contributions. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 742ddaff6b..9b97525daa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Improved -- **`remote_repo` names the repository the way the remote spells it**: Templates had `owner` for the namespace in the primary remote URL but nothing for the repo name that follows it, so anything wanting the forge's name for the repository had to settle for `repo` — the directory on disk, which differs whenever a clone was renamed locally (`git clone git@github.com:company-org/project.git work` gives `repo = work`). `{{ remote_repo }}` resolves to the URL's repo component with the `.git` suffix stripped (`project`), and is available everywhere `owner` is: hook and alias bodies, `--execute` payloads, `wt step eval`, and the `worktree-path` template. Like `owner`, it is set only when a primary remote exists and its URL parses, so a template that must survive a remote-less repo guards it with `{% if remote_repo %}`. +- **`remote_repo` names the repository as the remote spells it**: `repo` is the directory on disk, so a renamed clone reports the new name. `{{ remote_repo }}` takes it from the primary remote's URL, available everywhere `owner` is and unset when no remote parses. ([#3745](https://github.com/max-sixty/worktrunk/pull/3745), thanks @canac) ## 0.72.0