mirror of
https://github.com/max-sixty/worktrunk.git
synced 2026-09-14 20:00:38 +08:00
codex/remove-codex-cloud-specific-tests
39 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d44d68797a | docs(extending): add workz to custom-subcommand examples (#3513) | ||
|
|
568b6de85f |
docs: consolidate duplicated explanations and trim slop (#3494)
Sweep of the docs for repetition and filler, from an audit of the hand-authored pages, the command pages' source in `src/cli/mod.rs`, and the plugin skill. Net −574 lines; every cut either had a surviving canonical home or restated an adjacent sentence. **One home per mechanism** (other mentions now link to it): - `template-append` — the LLM commits guide owns the rendering mechanism; the user- and project-config sections keep the key, an example, and what is unique to them (the project approval gate and the only-`template-append`-from-project scoping). Was explained in full in three places. - `-vv` diagnostic files — `wt config state logs` owns the four file descriptions; the FAQ summarizes in one sentence and links. - fsmonitor/trash cleanup — the FAQ's two overlapping `wt remove` bullets merge into one that distinguishes own-daemon teardown from the orphan sweep; troubleshooting.md's restatement compresses to a pointer, keeping its unique wedged-daemon-on-live-worktree guidance. - copy-ignored built-in excludes — the `wt step copy-ignored` page owns the directory list (previously enumerated 4×); the config sections state the rule and link. - hook-types table — `wt hook` owns it; the extending guide replaces its verbatim copy with a sentence. - LLM tool commands — unchanged, deliberately: the apparent hand-synced duplication between llm-commits.md and the config example is already machine-pinned by `test_llm_docs_commands_match_config_example`, so it cannot drift. **Cut-over debt**: the deprecated `wt config state ci-status` section shrinks to a deprecation pointer — its status table, fetch order, and caching notes all duplicated the `wt list` CI-status section. **Structure**: `wt step copy-ignored`'s "Features" list dissolves into the sections that owned its facts (excludes → "What gets copied", reflink → "Performance"); the four trailing "Note: This command is experimental…" lines go (the `[experimental]` badge already appears twice per section); shell-integration.md described the directive-file mechanism twice and now describes it once. **SKILL.md** drops from 339 to 181 lines: the permission-model section duplicated the config-types bullets, the hook-type mapping appeared twice, and the "Determining Which Config to Use" / "Validation Before Adding Commands" scaffolding enumerated judgments an agent makes on its own. The approvals-escalation and agent-handoff sections are untouched. **Deliberately not addressed**: `wt list`'s schema 1 JSON tables (still the default schema; the wholesale deletion lands when the default flips) and corpus-wide em-dash density (a house-style decision, not a per-page fix). Generated mirrors (docs command pages, skill references, plugins mirror, `dev/*.example.toml`, help snapshots) regenerated via `test_docs_are_in_sync` and `cargo insta`. > _This was written by Claude Code on behalf of max_ Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
22484860e8 |
feat(completion): mirror wrapped built-in for alias arg completion (#3172)
An alias that forwards `{{ args }}` to a wt command — e.g. `co = "wt
switch {{ args }}"` or `cm = "wt step commit {{ args }}"` — now inherits
that command's argument and flag completion instead of the generic
`--dry-run`/`--yes`/`--var` stub. Alias name completion already worked;
this adds argument and flag completion.
Detection: the single command that forwards `{{ args }}` (via
`template_references_var` — minijinja, not a substring, and scoped per
command rather than the cross-command union), a leading `wt`, then a
tree-walk `find_subcommand` that lands on a leaf subcommand. The leaf
`Command` is cloned and renamed to the alias (clap `Command`/`Arg`/
`Extensions` and `ArgValueCompleter` are all `Clone`; the completer is
`Arc`-backed, so the live git query survives the clone). Bare
dispatchers (`wt step {{ args }}`), multiple `{{ args }}` forwarders,
and aliases that do not forward args fall back to the generic stub.
Mirroring runs at the top level only; `wt step <alias>` keeps the
generic stub.
|
||
|
|
5da0d2c3e4 |
docs: alias template-expansion timing; fix narrow help-test env redaction (#3006)
## Docs: alias template-expansion timing
The main addition documents a gotcha that's easy to hit and hard to
diagnose: an alias body renders its `{{ … }}` once at dispatch, in the
invoking worktree, so a per-worktree variable like `{{ branch }}` is
baked to a single value *before* a nested `wt step for-each` / `wt
switch --execute` iterates — printing the same value in every worktree.
The fix is `{% raw %}…{% endraw %}` deferral (plus a quoted `sh -c '…'`
for `for-each`, since the deferred `{{ branch }}` contains spaces).
- `extending.md` — rewrote "Deferring expansion to a nested `wt`
command" around the `for-each` symptom; improved the `up` rebase recipe.
- `faq.md`, `troubleshooting.md` — symptom-first entries with `wt config
alias dry-run` as the diagnostic.
- `hook.md` / `config.md` / `step.md` — distinguish repo-level
(constant) vs per-worktree (active) variables; note `{{ default_branch
}}` needs no deferral; cross-link the `{{ default_branch }}` variable vs
the `wt config state default-branch` shell command.
## Factual corrections
- `integration_reason` JSON values are hyphenated (`trees-match`,
`no-added-changes`, `merge-adds-nothing`) — the docs had underscores.
Verified against `src/commands/list/model/state.rs`.
- `SKILL.md`: 7 → 10 hook types (5 events × pre/post), added an
aliases/multi-worktree task section, fixed stale anchor links.
## Test fix: narrow help-test env redaction
`test_help_list_narrow_terminal` built its own `insta::Settings` but
skipped `add_standard_env_redactions` (every other help snapshot routes
through `snapshot_help`, which calls it). Its snapshot env block
therefore leaked host-specific paths (`LLVM_PROFILE_FILE` = the
machine's temp dir, plus the `WORKTRUNK_*` paths), which churn whenever
the snapshot is regenerated on a different machine. Adding the one call
mirrors `snapshot_help` and makes the snapshot reproducible.
Worth noting (and a candidate follow-up): this gap was masked under
`cargo test` (libtest) because the `repo` fixture's `mem::forget`'d
`bind_to_scope()` guard leaks redaction settings across the shared
process's reused threads. Under nextest (process-per-test, what the
pre-merge hook uses) there's no leak, so a test missing its own
redactions is exposed. A few other help tests (`test_help_md`,
`test_version`, `test_nested_subcommand_suggestion`) have the same gap
and could be consolidated through one settings helper — left out of this
PR to keep it focused.
> _This was written by Claude Code on behalf of max_
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
1efbcdd718 |
docs(extending): writing-prose cleanup (#2912)
Two-pass writing-prose cleanup of `docs/content/extending.md`. **First pass** (em-dashes, mostly mechanical): replaced 16 em-dashes with colons, parens, or sentence breaks; flattened a few nested asides; reworked the interface-differences table cells that relied on inner em-dashes. **Second pass** (concision and consistency): - Made the three intro paragraphs parallel — each says what the thing is and how it's defined. Hooks and aliases both end with "Defined in TOML"; custom subcommands describe the `wt-foo`-on-`PATH` mechanism. The previous text said TOML for hooks, omitted it for aliases, and called out "No configuration needed" for subcommands. - Dropped the `[[block]]` pipeline syntax mention from the shared-features paragraph. It's a sub-feature, not orientation-level content, and the reader hasn't seen a hook yet. Block pipelines are still covered in the Multi-step pipelines section where they're actually relevant. - Trimmed the Hooks section opener so it doesn't restate the intro's "Hooks are shell commands…". - Cut "TOML forms, template variables and filters" from the `wt hook` link blurb (covered elsewhere on this page and on the linked page). - Cut the four `same / same / same / same` repetitions from the Templates list. - Dropped the duplicate resolution-order rule and the template-variables sentence from the Custom subcommands section (both already in the comparison table). - Collapsed the Reference intro paragraph to "Aside from the differences below, hooks and aliases behave the same." The skill mirror (`skills/worktrunk/reference/extending.md`) regenerates from this via the existing sync test. --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
ec62580c29 |
revert(hooks): keep docs on pre-start/post-start; code accepts both (#2857)
Per @max-sixty's [direction in #2838](https://github.com/max-sixty/worktrunk/issues/2838#issuecomment-4509447593): revert the docs portion of #2840 and keep the code. Docs continue to recommend `pre-start`/`post-start`; both names work in code so anyone who already followed the briefly-changed docs (e.g. @EcksDy) isn't stranded once a release ships these aliases. ## User-visible — back to `pre-start`/`post-start` - README, docs site, skill mirrors, `dev/*.example.toml`, `plugins/worktrunk/README.md`, `flake.nix`, `.config/wt.toml` - `src/cli/mod.rs` / `src/cli/config.rs` / `src/cli/step.rs` / `src/help.rs` after_long_help and example snippets — and the auto-synced `docs/content/` and `skills/worktrunk/reference/` mirrors - `wt hook --help` canonical subcommand names; completion advertises `-start` only - `HookType` Display via strum, serde `rename`, and clap `ValueEnum` name — all `pre-start`/`post-start`. The Rust variant identifiers stay `PreCreate`/`PostCreate` (internal; we already paid for that rename in #2840, and now the eventual flip is a Display-only change) - `HooksConfig` serde canonical fields ## `*-create` still works (kept code) - `wt hook pre-create` / `post-create` — CLI alias on the canonical subcommand - `pre-create` / `post-create` in config: top-level, `[hooks.*]`, and per-project, in string, `[table]`, and `[[array-of-tables]]` form. Mechanism: serde `alias = ...` on the field, plus a silent in-memory rename in `migrate_content()` so the round-trip in `unknown_tree` doesn't flag table forms as schema-unknown. - The pre-0.32.0 `post-create` fatal-load-error machinery stays removed — the name is reclaimed, and both forms load without error. ## Smaller bits - `valid_user_config_keys()` / `valid_project_config_keys()` append `pre-create` / `post-create` so the unknown-field round-trip skips them. `test_valid_*_keys_all_deserialize` skips both aliases (they can't sit alongside the canonical without a duplicate-field error). - `DEPRECATED_SECTION_KEYS` drops the `pre-start`/`post-start` entries #2840 added — `pre-start`/`post-start` are canonical again. - `find_pre_start_from_doc` / `find_post_start_from_doc` / `find_renamed_hook_key` / `is_non_empty_item` / `migrate_start_hooks_doc` and their tests are removed; the migration direction flips via a new `migrate_create_hooks_doc` (silent, mirrors the prior shape). - Test files `e2e_shell_post_create.rs` and `post_create_commands.rs` rename back to `_post_start_` (via `git mv`, so the rename shows as a rename). ## Testing `cargo run -- hook pre-merge --yes` — 3806 tests pass; the 10 failures are all `case_4` of `shell_wrapper::unix_tests::*` (nu-shell case; `nu` isn't installed in this runner; same failures occur on `main`). Also manually verified that a fresh `wt switch --create` against a project config with `[post-create]` loads cleanly with no unknown-field warning and the hook fires as `post-start`. ## Follow-up Per @max-sixty: in a couple of weeks, once a release with both-names-work is out and users have had a chance to upgrade, the docs flip is straightforward (most of it is in `src/cli/mod.rs`'s `after_long_help` and the doc-sync test propagates). Re #2838. Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
d7e3f88422 |
feat(hooks): rename worktree-creation hooks to pre-create/post-create (#2840)
Phase 1 of the staged hook rename tracked in #2838: the worktree-creation hooks `pre-start`/`post-start` become `pre-create`/`post-create`. The old names keep working with no deprecation warning yet (Phase 2, months out, adds the warning). ## What changes - `pre-create`/`post-create` are canonical everywhere: the `HookType` enum, the `HooksConfig` serde fields, the `wt hook` CLI, completion, and all docs. - Old names keep working: `migrate_content()` rewrites `pre-start`/`post-start` config keys to `-create` before serde, and `parse_hook_type` accepts the old CLI names as silent aliases. `wt config update` rewrites them on disk; `wt config show` shows the migration diff. `wt hook <type>` execution and `wt hook show` both accept the old names; completion and `--help` advertise only the canonical names. - `detect_deprecations()` flags the old keys so `update`/`show` act on them, but `format_deprecation_warnings()` stays silent (Phase 2 adds the warning). A new empty-warnings guard in `check_and_migrate` keeps a `-start`-only config from emitting a stray hint. - The dead pre-0.32.0 `post-create` machinery is removed: the fatal `POST_CREATE_REMOVED_MSG` load error, the vestigial `HooksConfig.post_create` merge-fold, and `find_post_create_from_doc`. `post-create` is reclaimed as the canonical background creation hook. ## Semantic flip Before v0.32.0, the key `post-create` named a *blocking* hook. It now names the *background* one. Since v0.44.0, a pre-0.32.0 `post-create` config is a fatal load error on the `check_and_migrate` paths: `ProjectConfig::load` and user/system config loading, which fire on essentially every `wt` command. A repo carrying one has been unusable ever since. The one path that skips that check is `project_config_at_ref` (the base-ref read behind `wt switch --create`), which applies only structural migration. A pre-0.32.0 `post-create` surviving solely on a base ref, never checked out into a worktree, would now load as a background hook rather than folding into the blocking `pre-start`. That edge case is accepted: once `post-create` is valid again, reclaiming the name and detecting the dead key are mutually exclusive. ## Reviewing this diff 205 files, but the substance is ~36 files under `src/`. The rest is regenerated snapshots and auto-synced doc mirrors. Start with: - `src/config/deprecation.rs` — detection (`find_renamed_hook_key`), migration (`rename_hook_key`), removal of the fatal block, the empty-warnings guard, and the `DEPRECATED_SECTION_KEYS` entries that stop unknown-field detection from flagging the migrated keys. - `src/config/hooks.rs`, `src/git/mod.rs` — the serde field and enum renames. - `src/config/project.rs` — `ProjectConfig::load` deserializes `check_and_migrate`'s migrated content, so a current-worktree config using the old keys loads into the canonical fields. - `src/cli/hook.rs`, `src/commands/hook_commands.rs`, `src/completion.rs`, `src/main.rs` — the CLI alias layer; `wt hook show` accepts the old type names as hidden value-parser aliases. - `src/cli/mod.rs` — the `wt hook` docs, including the soft-deprecation note linking #2838. The ~93 modified snapshots also pick up deterministic env-block lines (`GIT_*: ""`, `LLVM_PROFILE_FILE`) that pre-existing snapshots already carry. That is stale-snapshot drift surfaced by the regeneration, not a behavior change. ## Testing Full suite green (3799 tests). New coverage: `snapshot_migrate_start_to_create` (migration preserves value shape and position), `test_deprecated_start_hook_key_runs_silently` and `test_standalone_hook_start_alias_runs_silently` (old config and CLI names run with no warning), `test_config_show_displays_start_hook_migration` (`config show` reveals the diff without an "unknown field" warning), and `test_hook_show_accepts_deprecated_start_hooks` (a current-worktree config using the old keys loads, and `wt hook show` takes both the canonical and the deprecated type arguments). Part of #2838. 🤖 Generated with [Claude Code](https://claude.com/claude-code) |
||
|
|
4cfc8e6acd |
docs(extending): note alias template timing and how to defer to nested wt (#2754)
Addresses [#2753](https://github.com/max-sixty/worktrunk/issues/2753) — adds a short subsection under Aliases documenting that templates render at alias dispatch (using the alias-invocation worktree's context), so a nested `wt` command's own template variables resolve against the outer worktree unless wrapped in `{% raw %}…{% endraw %}`. The example uses the reporter's pattern (`wt switch ... --execute 'echo {{ worktree_path }}'`) verified against the release binary in a scratch repo: - Direct: `wt switch other --no-cd --execute 'echo {{ worktree_path }}'` -> target path - Aliased without `{% raw %}`: -> outer worktree path (the bug) - Aliased with `{% raw %}{{ worktree_path }}{% endraw %}`: -> target path `test_docs_are_in_sync` propagates the change into `skills/worktrunk/reference/extending.md`. Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
b12ac078b3 |
docs(extending): add stdin row to hooks-vs-aliases comparison (#2529)
The comparison table in `extending.md` covered invocation, positional handling, approval flags, source filters, and template-context extras — but never said anything about stdin. Hooks have always received the template context as JSON on stdin (documented in `hook.md`), and since #2380 aliases inherit the parent's stdin so pipes pass through and interactive TUIs (`wt switch`) keep the tty. Adding a row makes the contract discoverable from the aliases page rather than only from the CHANGELOG. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
6eaf349711 |
refactor(for-each): exec argv directly, drop implicit shell (#2465)
## Problem
`wt step for-each` rebuilt the post-`--` command with `args.join(" ")`
before passing it to `sh -c`. That dropped the user's quoting and argv
boundaries, so anything with spaces, `;`, or shell metacharacters inside
an argv element broke. The reproduction from the issue:
```console
$ wt step for-each -- python3 -c 'import sys; print(sys.argv[1:])' 'a b'
sh: 1: Syntax error: word unexpected (expecting ")")
```
## Solution
Drop the implicit shell. The post-`--` argv is the program and its
arguments, exec'd directly via `Cmd::new(program).args(rest)`. Templates
expand into argv elements with `shell_escape=false`. Users wanting shell
features (pipes, redirects, `$VAR`, globs) write `sh -c '<snippet>'`
explicitly — the same pattern as `xargs`, `find -exec`, `kubectl exec
--`, and `docker run`.
This started as the smaller fix that landed on the previous commit
(shell-escape each argv element before joining, with an arity branch —
one arg meant "shell snippet," many args meant "argv to escape and
re-join"). On review the arity asymmetry felt wrong; the design
discussion concluded that direct exec is one rule with no quoting
recovery pipeline, and the #2461 bug becomes structurally impossible.
`for-each` is still tagged `[experimental]`, so the small breakage of
the documented snippet form is acceptable.
After the fix:
```console
$ wt step for-each -- python3 -c 'import sys; print(sys.argv[1:])' 'a b'
['a b']
$ wt step for-each -- sh -c 'git status | wc -l'
[per-worktree counts...]
```
### What changes for users
| Before | After |
|---|---|
| `wt step for-each -- 'git status \| wc -l'` | `wt step for-each -- sh
-c 'git status \| wc -l'` |
| `wt step for-each -- 'echo Branch: {{ branch }}'` | `wt step for-each
-- echo 'Branch: {{ branch }}'` (or wrap in `sh -c`) |
| `wt step for-each -- python3 -c '...' 'a b'` ✗ broken | `wt step
for-each -- python3 -c '...' 'a b'` ✓ works |
The `for-each` help/docs are rewritten around the simpler model (one
rule, two example blocks).
## Testing
- `test_for_each_preserves_argv_quoting` covers the exact reproduction
from the issue and is now exercising a load-bearing property of the
design rather than a join bug.
- `test_for_each_aborts_on_signal_exit` rewritten to use `sh -c
'<snippet>'` for the shell features it needs.
- `test_for_each_json_spawn_failure` simplified — direct exec means a
missing program is enough; the PATH/symlink dance for forcing sh-spawn
failure is no longer needed.
- `cargo test --test integration for_each` — 16 passed.
- `cargo run -- hook pre-merge --yes` — 3403 passed, 0 skipped, all
lints green.
---
Closes #2461 — automated triage
---------
Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Maximilian Roos <m@maxroos.com>
|
||
|
|
f0152a5a0d |
docs(hook): consolidate pipeline forms into one section (#2333)
The \`# Configuration\` intro introduced the string/table/pipeline forms, and a later \`# Pipeline Ordering [experimental]\` section re-explained the same three forms with different examples. Merge them under a single \`## Hook forms\` subsection inside \`# Configuration\`, keep the "when to use \`[[hook]]\`" guidance, and drop the stale \`[experimental]\` tag. Update the two \`#pipeline-ordering\` anchor links in \`extending.md\` to \`#hook-forms\`. Net: 6 files, -137 lines. No behavior change — help output unchanged (no snapshot updates needed). > _This was written by Claude Code on behalf of Maximilian Roos_ Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
1a0a3e2f83 |
Widen Zola link regex to tolerate code spans in link text (#2327)
`ZOLA_LINK_PATTERN`'s link-text class `[^\]]+` rejected any `]`, so Zola links whose text contained a code span with `]]` (e.g. `` `[[block]]` ``) never matched and shipped as dead `@/…md` references in skill files. #2321 worked around two such links by rewording them — this fixes the root cause and restores the original wording. ## The regex change Alternate "a balanced `` `…` `` code span" with "any non-`]`-non-backtick char" inside the link text: ``` \[((?:`[^`]*`|[^\]`])+)\]\(@/([^)#]+)\.md(#[^)]*)?\) ``` Forbidding bare backticks in the single-char branch is load-bearing: without it, the regex can bridge across two unrelated code spans on the same line. `docs/content/faq.md:63` is a live example — `` [`worktrunk-sync`](https://github.com/…) `` followed much later by `` [custom subcommands](@/extending.md#custom-subcommands) ``. With a permissive single-char branch, the engine pairs backtick 2 with backtick 3 (spanning the `]` after `worktrunk-sync`) and matches the whole chunk as one "link." ## Guardrail Added an `UNTRANSFORMED_ZOLA_LINK_PATTERN` check in `finalize_skill_content`. After the transform, any leftover `](@/…md)` panics the sync test with the offending line — so a future regex miss fails loudly instead of silently shipping a dead link. ## Doc restoration Reverts the two rewordings from #2321 in `docs/content/extending.md` (`[[block]]` now sits inside the link text again). ## Testing `cargo test --test integration readme_sync` (12 tests) passes. Full `cargo run -- hook pre-merge --yes` green locally (3291 tests, all lints). > _This was written by Claude Code on behalf of Maximilian Roos_ Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
d70e2f2e98 |
Clarify template engine scope in extending docs (#2321)
Tighten the extending docs around the template engine and pipeline syntax shared by hooks and aliases. Link to `wt hook`'s template-variables, filters, functions, pipeline-ordering, and passing-values sections so binding rules and `[[block]]` semantics are documented in one place rather than duplicated. Remove the redundant `--KEY=VALUE` / variable-override / hyphen-to-underscore explanations from the aliases section. --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
df448861bc |
docs(hook): link dev-server/database recipes out to tips-patterns (#2319)
The Dev servers and Databases sections in `hook.md` duplicated nearly identical TOML and narration from the corresponding recipes in `tips-patterns.md`. Replaces them with two bullets under a new "More recipes" section that link to the canonical recipes, and redirects inbound links (`README.md`, `worktrunk.md` feature list) from `/hook/#dev-servers` to `/tips-patterns/#dev-server-per-worktree`. The bullets use bare URLs in `cli.rs` so `wt hook --help` shows terminal-auto-linkable `https://...` rather than stripped markdown link text; `post_process_for_html` rewrites them to inline markdown links for the web docs. Same pattern as the existing "Open an issue at ..." transform. Also tightens the extending.md note on operation-context variables to "aren't auto-populated" — they can still be bound via `--KEY=VALUE` on the CLI. Net −156 lines. > _This was written by Claude Code on behalf of Maximilian_ Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
566f399331 |
docs: explain why aliases and hooks render --help differently (#2318)
Hooks inject stub subcommands into the Command tree so `wt hook --help`
and `wt hook <type> --help` render real clap output. Aliases don't —
their argument parsing is template-driven (smart routing based on which
vars the template references), `--dry-run` is rejected, and post-alias
`--yes` forwards to `{{ args }}`, so a clap stub would misrepresent
them. The help path text-splices an `Aliases:` block into `wt --help` /
`wt step --help` and redirects `wt <alias> --help` to `wt config alias
show` / `dry-run` instead.
Nothing actually changes. This PR surfaces the reason in the two doc
comments next to the relevant functions (`inject_alias_subcommands`,
`augment_help`), and rewrites the `--help` row of the `extending.md`
"hooks vs. aliases" reference table in user-facing terms — the previous
wording leaked "clap-rendered" as user-visible jargon.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>
|
||
|
|
86731e8cc2 |
docs(extending): refine alias recipes and template prose (#2317)
## Summary
- Tighten the **move-changes** and **hook-log** alias recipes; cut the
"actual branch doesn't move" aside on template variable overrides.
- `move-changes` now forwards tokens after `--` into the new worktree
via `--execute`, so `wt move-changes --to=feature-xyz -- claude` pops
the stash and opens Claude there.
- `hook-log` takes `--kind` so it isn't locked to `post-start`; prose
clarifies that `--name` is the TOML hook key and branch is pulled from
the current worktree.
- Reword the arg-escaping paragraph to explain *why* shell-escaping
matters (spaces don't split, `;` doesn't terminate), and clarify that
`--` forwards tokens literally into `{{ args }}`.
## Test plan
- [x] \`cargo run -- hook pre-merge --yes\` (3283 passed, lints clean)
- [x] \`cargo test --test integration
test_command_pages_and_skill_files_are_in_sync\`
- [x] Verified \`wt move-changes --to=foo -- claude\` renders
\`--execute="git stash pop --index; claude"\` via \`wt config alias
dry-run\`
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>
|
||
|
|
a77edaf0a8 |
docs(extending): consolidate overlap with hook.md and within aliases (#2315)
The Hooks section of \`extending.md\` largely restated \`hook.md\` — event table, pre-/post- rule, user/project split, three TOML forms, variable teaser, and common patterns all duplicated. The Hooks and Aliases sections within the page also repeated each other on the \`[[block]]\` pipeline syntax, approval model, and the core variable list. Cuts: - Added a one-paragraph shared-concepts note after the comparison table covering TOML config, the template engine link, \`[[block]]\` semantics, and the approval model. - Trimmed \`## Hooks\` to the event table, the pre-/post- rule, one flavor example, and a pointer to \`hook.md\`. - Trimmed \`### Templates\`, \`### Multi-step pipelines\`, and the former \`### Sources and approval\` in \`## Aliases\` to what's alias-specific. Renames / rewordings: - \`### Sources and approval\` → \`### Changing directory\`. Expanded to note that \`wt switch\`, \`wt merge\` (leaving the removed source), and \`wt remove\` of the current worktree all propagate cd via shell integration — but other shell state (\`cd\`, \`export\`) doesn't persist because the alias runs in a subshell. - Link text \"hook template engine\" → \"variable and filter reference\" so the template system reads as shared rather than hook-owned. Net: \`docs/content/extending.md\` 246 → 189 lines (−57). \`skills/worktrunk/reference/extending.md\` auto-synced. > _This was written by Claude Code on behalf of @max-sixty_ Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
e4fab6c791 |
feat(hook): unify hook argument syntax with alias smart routing (#2313)
## Summary
Hooks and aliases now share a single mental model for CLI variable
binding: `--KEY=VALUE` binds `{{ KEY }}` if the template references it,
else forwards to `{{ args }}`. Tokens after `--` forward
unconditionally. `{{ args }}` is now available in hook templates.
Internally, the 11 duplicated hook clap variants collapse into a single
`#[command(external_subcommand)]` mirroring the alias pattern, with
completion and help injection grafting hook-type stubs onto the
augmented `Command` tree.
- `--var KEY=VALUE` still works but emits a deprecation warning pointing
at `--KEY=VALUE`.
- `wt hook --help` lists all hook types (via help-tree injection).
- New `## Reference: hooks vs. aliases` section in
`docs/content/extending.md` documents remaining interface differences.
## Test plan
- [x] `cargo run -- hook pre-merge --yes` (all tests + lints, 3283 tests
passing)
- [x] `wt hook pre-merge --branch=foo --yes` — smart-binds when
referenced
- [x] `wt hook pre-merge --KEY=VALUE --yes` — forwards to `{{ args }}`
when not referenced
- [x] `wt hook pre-merge -- --extra --yes` — post-`--` tokens forward
unconditionally
- [x] `wt hook pre-merge --var branch=foo --yes` — deprecation warning +
force-bind
- [x] `wt hook --help` shows all 10 hook types
- [x] Shell completion: `wt hook <TAB>` lists hook types
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude <noreply@anthropic.com>
|
||
|
|
31b9b35103 |
docs(alias): clarify which template variables are available in aliases (#2314)
The aliases section of `extending.md` claimed "same template variables
as hooks" — that's wrong. Aliases get `BASE_VARS` + `args` +
`vars.<key>`, but not hook operation-context vars (`target`, `base`,
`pr_number`, `pr_url`, `target_worktree_path`, `base_worktree_path`) or
infrastructure vars (`hook_type`, `hook_name`). A user writing `{{
target }}` in an alias would hit an undefined-value error with no
pointer to what actually works.
Replaces the false equivalence with an explicit enumeration under a new
`### Templates` subsection — listing the variables that do work and
noting that operation-context vars aren't populated since there's no
operation in progress. Also renames the adjacent `### Forwarding
positional arguments` → `### Positional arguments` so the two headings
pair cleanly as siblings.
Docs-only. The synced skill mirror
(`skills/worktrunk/reference/extending.md`) picks up the same edits via
`test_command_pages_and_skill_files_are_in_sync`.
Co-authored-by: Claude <noreply@anthropic.com>
|
||
|
|
68229ce571 |
Smart routing for alias arguments + dry-run routing display (#2304)
Routes `--KEY=VALUE` tokens based on what the template references.
Tokens whose `KEY` appears as `{{ KEY }}` in the alias bind to that
variable; everything else joins `{{ args }}`. Space form `--KEY VALUE`
is equivalent.
Key changes:
- Parser uses template reference introspection (via
`referenced_vars_for_config`) to decide binding at parse time.
- Post-alias `--yes` retired — use `wt -y <alias>` (the global form)
instead. `--dry-run` on an alias invocation is also retired; `wt config
alias show/dry-run <name>` replaces it (landed separately in #2291).
- Hyphens in keys canonicalize to underscores, so `--my-var=x` binds `{{
my_var }}`.
- `--` is a literal-forward escape — everything after it forwards to `{{
args }}` regardless of bindings.
- `wt <alias> --help` / `-h` prints a hint pointing at `wt config alias
show/dry-run` rather than silently forwarding the flag into `{{ args
}}`. `wt <alias> -- --help` still forwards.
Advisory warnings (printed on stderr, don't affect execution):
- `--KEY VALUE` with `--`-prefixed VALUE: almost always a typo where
`--KEY=VALUE` was meant.
- `wt config alias show`/`dry-run` on a name that shadows a top-level
built-in (e.g. `list`): the alias is unreachable via `wt <name>`.
`wt config alias dry-run` now prints `# bound:` and `# args:` routing
comments above the rendered command so users can see how each token was
interpreted.
Docs rewrite of the aliases section: concrete fly preview-env example,
"Passing values" section (renamed from the opaque "How arguments are
routed"), routing mechanism moved above the introspection tools, `up`
rebase recipe restored, `since-main` alias added.
## Test plan
- [x] `cargo test --lib --bins commands::alias::tests` — unit tests
(new: duplicate-key precedence, footgun warning, multi-`=` value)
- [x] `cargo test --test integration step_alias` — integration tests
(new: shadow warning on `show` and `dry-run`, `--help` intercept,
retired `--dry-run` via `wt step`)
- [x] `cargo test --test integration
test_command_pages_and_skill_files_are_in_sync` — skill file sync
- [x] `cargo run -- hook pre-merge --yes` — full test suite + lints
green
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Worktrunk Bot <w@worktrunk.dev>
|
||
|
|
c7b94a7276 |
feat(config): add wt config alias show and dry-run subcommands (#2291)
## Summary Third PR in the series simplifying alias-control flags. Introduces `wt config alias` as the home for alias introspection and removes the `--dry-run` flag that lived on alias dispatch. - **`wt config alias show <name>`** — prints the configured template, source-labeled (user/project). Each step in a pipeline is rendered in a single gutter block with `# <step-name>` comment lines above named steps. - **`wt config alias dry-run <name> [-- args...]`** — parses the arguments with the same parser `wt <alias>` uses, renders templates against the execution-time context, and prints without running. Args after `--` are forwarded verbatim, so `wt config alias dry-run s -- target-branch` previews exactly what `wt s target-branch` would run. Same layout as `show`; only the header verb differs (`:` vs ` would run:`). - **`--dry-run` removed** from `AliasOptions` — both `wt <alias> --dry-run` and `wt step <alias> --dry-run` now return an actionable error pointing at the new subcommand. No deprecation period: top-level alias dispatch is recent, so cutover is acceptable. - **Tab-completion** completes alias names under `show` / `dry-run`. ## Output format ``` ○ Alias deploy (project) would run: ┃ # install ┃ npm install ┃ # build ┃ npm run build ``` The `○ Alias <name> (<source>)` header matches the existing `info_message` style. Bolding the name (what the user typed) and tagging the source in parens keeps the invocation identifier visually primary. When both user and project define the same alias, both entries print back-to-back (user first, matching runtime execution order). ## Design notes - The new subcommand reuses `AliasOptions::parse` as the source of truth for invocation parsing, so preview stays aligned with runtime. When user and project configs both define the alias, `referenced_vars` is unioned across entries so a flag binds if any template references it. - Templates referencing `vars.*` are shown unexpanded, mirroring the lazy execution path — those values are read from git config just before each step runs, potentially after earlier steps have set them. Syntax errors still surface up front. - `wt step <alias>` also breaks since both paths share `AliasOptions::parse`. That's fine — `wt step <alias>` has no forward-compat promise beyond the basic call. - `AliasSource` is promoted to `pub(crate)` with a `label()` helper so `wt config alias` and the alias runtime code read from one enum. ## Navigating the diff - `src/cli/config.rs` — adds `ConfigAliasCommand` + the `Alias` variant on `ConfigCommand`. - `src/commands/config/alias.rs` — new module. `handle_alias_show` and `handle_alias_dry_run` share a `format_entry` helper; the `verb: Option<&str>` parameter is the only difference between show and dry-run output. `render_preview` replaces the old `render_for_dry_run` in alias dispatch. - `src/commands/alias.rs` — the `--dry-run` flag and its rendering branch are gone. The parser bails on `--dry-run` with a migration message pointing at the new subcommand; the bail sits after the `--` literal-mode check, so `wt alias -- --dry-run` still forwards as positional. - `src/completion.rs` — `alias_name_completer()` mirrors the existing `hook_command_name_completer` pattern. - `tests/integration_tests/step_alias.rs` — existing `--dry-run` tests migrate to `wt config alias dry-run`, plus new coverage for `show`, unknown-alias suggestions, and the retired-flag error. - `docs/content/extending.md` — adds an "Inspecting and previewing" subsection. ## Test plan - [x] Full suite via `wt hook pre-merge --yes` (3271 tests, all passing) - [x] `cargo insta test --accept` — all snapshots up to date - [x] `cargo test --test integration test_command_pages_and_skill_files_are_in_sync` — docs synced - [x] Manual verification: `wt config alias show <name>` and `wt config alias dry-run <name> [-- args...]` in a repo with mixed user/project aliases > _This was written by Claude Code on behalf of Maximilian_ --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
8e3abf46db |
feat(alias): route --key=value via template refs, add -- escape, drop --var (#2287)
## Summary
Replace the alias-arg parser's "every `--KEY=VALUE` binds; bare `--KEY`
errors" grammar with template-driven routing. A token only binds when
the alias's pipeline references `{{ KEY }}`; otherwise it forwards into
`{{ args }}`. Adds `--` as a literal-forward escape and removes the
`--var KEY=VALUE` / `--var=KEY=VALUE` special cases.
The template already declares which variables it consumes — using that
as the routing signal removes both surprise binding (silent
`env=staging` for templates that never reference `env`) and the
unknown-flag error class. Every post-alias token now has a deterministic
destination.
## Grammar
Tokens after `wt <alias>` are walked left-to-right:
| Token shape | Routes to |
|---|---|
| `--KEY=VALUE` or `--KEY VALUE` where the template references `{{ KEY
}}` | Bound — `KEY` becomes the template value |
| `--KEY=VALUE` where the template doesn't reference `KEY` | Forwarded
literally to `{{ args }}` |
| `--KEY` followed by another `--…` (or end of args) | Forwarded
literally to `{{ args }}` |
| Bare positional | Forwarded to `{{ args }}` |
| Anything after `--` | Forwarded to `{{ args }}` regardless of shape |
`--dry-run` is the only post-alias built-in. `--yes`/`-y` is global only
(`wt -y <alias>`) per #2290 — post-alias `--yes` falls through the shape
rule and forwards as positional. Hyphens in the key canonicalize to
underscores before lookup, so `--my-var=value` binds to `{{ my_var }}`.
## Behavior changes
| Input | Before | After |
|---|---|---|
| `wt rm --force` (no `{{ force }}` ref) | Errored "Unknown flag" |
Forwards `--force` to `{{ args }}` |
| `wt deploy --env staging` | Errored "Unknown flag" | Binds
`env=staging` if referenced; else forwards both to `{{ args }}` |
| `wt deploy --env=staging` (no `{{ env }}` ref) | Silently bound
`env=staging` | Forwards `--env=staging` to `{{ args }}` |
| `wt foo --var x=1` | Bound `x=1` via the special `--var` arm | Same as
`--var=x=1`: binds `var="x=1"` if referenced, else forwards |
| `wt run -- --env=staging` | `--env=staging` consumed | `--env=staging`
forwarded literally |
| `wt show --branch=override` (with `{{ branch }}` ref) | Bound (worked,
undocumented) | Bound, now documented and tested |
Users who relied on `--var KEY=VALUE` should switch to `--KEY=VALUE`
directly.
## Navigating the diff
- `src/commands/alias.rs` — `AliasOptions::parse` rewritten as a
left-to-right walk against `referenced_vars: &BTreeSet<String>`.
`try_alias` and `step_alias` resolve the alias's `CommandConfig` first,
compute `referenced_vars`, then parse. `step_alias` now takes
`Vec<String>` (parses internally) because routing needs the resolved
alias.
- `src/config/expansion.rs` — new `referenced_vars_for_config` helper
unions `template.undeclared_variables(false)` across every command in a
pipeline.
- `src/main.rs` — call site updated for new `step_alias` signature.
- `tests/integration_tests/step_alias.rs` — six new integration tests
cover the grammar end-to-end (referenced bind, unreferenced forward,
`--` escape, multi-step binding, built-in overshadow, space-separated
bind). The `--var` tests are gone.
- `tests/integration_tests/approval_ui.rs` — updated
`test_post_alias_yes_does_not_skip_approval` (renamed from main's
`…_no_longer_supported`): under the new grammar `--yes` doesn't error,
it forwards as positional, but still doesn't skip approval.
- `docs/content/extending.md` (and auto-synced skill reference) — new
"How arguments are routed" section, `--` escape documented, built-in
overshadow caveat added.
## Test plan
- [x] `cargo run -- hook pre-merge --yes` (3255 tests, all pass)
- [x] Unit tests for parse grammar: `test_parse_built_in_flags`,
`test_parse_key_value_routing`, `test_parse_space_separated_routing`,
`test_parse_hyphen_canonicalization`,
`test_parse_literal_forward_escape`, `test_parse_mixed_pipeline`,
`test_parse_positionals` (covers post-alias `--yes`/`-y` forwarding),
`test_referenced_vars_for_config_unions_steps`
- [x] Integration tests for end-to-end routing
- [x] Doc sync test passes
> _This was written by Claude Code on behalf of Maximilian_
---------
Co-authored-by: Claude <noreply@anthropic.com>
|
||
|
|
64e1b4efb9 | feat(cli): move approvals subcommand from hook to config (#2282) | ||
|
|
4ec7023ee5 |
feat(alias): forward positional args to templates via {{ args }} (#2280)
## Summary
Non-flag tokens after an alias name are forwarded to the template as `{{
args }}` — a space-joined, shell-escaped sequence. `wt s some-branch`
with `s = "wt switch {{ args }}"` expands to `wt switch some-branch`.
Before: `wt s some-branch` errored with `Unexpected argument
'some-branch' for alias 's'`.
## Why
Users want to pass arguments through to aliased commands — `wt s foo` →
`wt switch foo` — without configuring `--var foo=…` for every possible
parameter. Positional args are the natural fit.
## Behavior
`{{ args }}` is a minijinja sequence. All four access patterns work:
| Template | Render |
|---|---|
| `{{ args }}` | space-joined, per-element shell-escaped |
| `{{ args[0] }}` | first arg (escaped) |
| `{{ args \| length }}` | count |
| `{% for a in args %}` | iteration |
Each element is individually shell-escaped, so `wt run 'a b' 'c;d'`
splices in as `'a b' 'c;d'` and can't inject shell syntax. Positionals
interleave freely with flags — `wt deploy foo --dry-run bar` collects
`["foo", "bar"]` into `args`.
`--dry-run`, `--yes`, `-y`, `--var KEY=VALUE`, and `--KEY=VALUE` parsing
are unchanged.
## Navigating the diff
- `src/config/expansion.rs` — new `ShellArgs` struct wraps `Vec<String>`
and implements minijinja's `Object` trait with `ObjectRepr::Seq`. Its
`render()` writes `shell_escape::unix::escape` of each element,
space-joined. The shell-escape formatter in `setup_template_env` detects
`ShellArgs` via `downcast_object_ref` and passes its `Display` through
unmodified — so bare `{{ args }}` isn't double-escaped by the generic
per-value escape. Iteration and indexing yield plain
`Value::from(String)` that still flow through the generic formatter. New
`ALIAS_ARGS_KEY` constant (`"args"`) is the reserved context key
carrying the JSON-encoded list.
- `src/commands/alias.rs` — `AliasOptions` gains a `positional_args:
Vec<String>` field. `AliasOptions::parse` collects non-flag tokens into
it instead of erroring. `run_alias` inserts the JSON-encoded list into
`context_map` under `ALIAS_ARGS_KEY` right after `build_hook_context`.
The sole special-case lives in `expand_template`, which rehydrates the
key as a `ShellArgs` object; all other call sites (hooks, for-each,
eval) are untouched and never see the key.
- `validate_template` — injects an empty `ShellArgs` so templates
referencing `{{ args }}` pass pre-flight validation. `args` is added to
`TEMPLATE_VARS`.
- `docs/content/extending.md` — new "Forwarding positional arguments"
subsection under Aliases with a shell-safety guarantee and
access-pattern examples.
## Tests
3233 tests pass, lints clean. New:
- `src/commands/alias.rs` — `test_parse` snapshots extended with
positional cases including interleaved flags and metacharacter-laden
args. `test_parse_errors` no longer expects "Unexpected argument".
- `src/config/expansion.rs` — `test_expand_template_args_sequence`
covers indexing, iteration, and length.
`test_expand_template_args_empty` confirms empty renders to empty
string. `test_expand_template_args_shell_metachar_safety` asserts the
exact output for `['; rm -rf /', '$(whoami)', "a'b"]`.
`test_validate_template_valid` now covers `{{ args }}`, `{{ args |
length }}`, and iteration.
- `tests/integration_tests/step_alias.rs` — end-to-end snapshots for `wt
step <alias> positionals`, `wt <alias> some-branch --dry-run`, empty
positionals, and sequence-style access.
## Do-nots
- No changes to `--dry-run`, `--yes`, `--var`, or `--KEY=VALUE` parsing
— the broader flag-handling question (whether alias-level flags should
move pre-name) is being designed separately.
- No `KEY=value` bare-token parsing for vars — deferred.
- No change to `wt step <alias>` semantics beyond inheriting positional
support via shared `AliasOptions::parse`.
> _This was written by Claude Code on behalf of Maximilian_
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
07db111340 |
docs: trim filler sentences from prose docs (#2271)
Remove sentences that restate obvious fallback/error behavior, duplicate nearby prose, or add visual weight without information. - `extending.md` — drop the "not a wt command" error note, the experimental badge on Aliases, and a duplicate recap of the `cd`-to-parent-shell behavior at the end of a recipe. - `faq.md` — drop "The result is cached for fast subsequent lookups" padding after the default-branch detection explanation. - `tips-patterns.md` — drop "Creates a worktree that builds on the current branch's changes" (restated by the section heading) and "Sessions are named after the branch for easy identification" (visible in the code). Skill reference files auto-synced via `test_command_pages_and_skill_files_are_in_sync`. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
016eb030e7 |
docs(extending): rename "external subcommand" to "custom subcommand" (#2270)
## Summary Renames the git-style `wt-<name>` dispatch feature to "custom subcommand" across user-facing docs and internal code. Two motivations: - **Avoid overloading "external."** The codebase already uses "external command" for the `shell_exec` concept (subprocesses like `git` and `gh`). Using the same word for the `wt-foo` dispatch feature is ambiguous. - **Cargo uses "custom command" / "external subcommand" interchangeably.** kubectl calls theirs "plugins," gh calls theirs "extensions"; git doesn't have a settled term. "Custom subcommand" reads naturally in prose and matches cargo's user-facing phrasing. ## Changes **User-facing docs** — `docs/content/extending.md` (section heading, description, comparison table) and `docs/content/faq.md` (link + anchor). Skill references auto-sync. **Internal code** — `src/commands/external.rs` → `custom.rs`, `Commands::External` → `Commands::Custom`, `handle_external_command` → `handle_custom_command`, plus matching renames in `src/completion.rs` (inject/discover/forward functions) and corresponding tests. Also renames `tests/integration_tests/external.rs` → `custom.rs`. **Kept intact** — clap's `#[command(external_subcommand)]` attribute and `.allow_external_subcommands(true)` are clap's own vocabulary, not ours. CHANGELOG is historical and left unchanged. ## Test plan - [x] `cargo build` clean - [x] `cargo clippy --all-targets --all-features` clean - [x] `cargo test --lib --bins` — all pass - [x] `cargo test --test integration` — all pass (1476) - [x] `pre-commit run --all-files` clean > _This was written by Claude Code on behalf of Maximilian_ --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
a1be5645c4 |
feat(alias): dispatch aliases from top-level wt <name> (#2266)
`wt deploy` now resolves `deploy` against configured aliases before falling through to a `wt-deploy` PATH binary. Built-ins still win (clap matches before alias dispatch ever runs), and `wt step <name>` keeps working at runtime — only the docs cut over to the new form. ## Why `wt deploy` reads better than `wt step deploy`, and aliases as first-class commands lower friction for using them as everyday shortcuts. ## Precedence built-in (clap) → alias (user/project config, merged) → `wt-<name>` PATH binary → "unrecognized subcommand" error. User config wins over PATH binaries because aliases are how users customize wt — same model as git, where `[alias]` entries shadow `git-foo` externals. ## Navigating the diff - `src/commands/alias.rs` — refactored `step_alias` to share `run_alias` with the new `try_alias(name, rest) -> Result<Option<()>>`. Returns `Ok(None)` when the name isn't a configured alias or when not in a git repo; propagates config-load errors so a broken `wt.toml` fails loudly instead of silently turning into "unrecognized subcommand". Argument parsing is gated on alias-membership, so unrelated args meant for an external binary don't surface as alias parse errors. New `alias_names_for_suggestions()` mixes alias names into "did you mean" hints. `HelpContext` enum lets the help splice annotate "(shadowed by built-in)" against the right level (top-level builtins for `wt --help`, step builtins for `wt step --help`). The user-facing "shadow warning" was removed entirely — under the new model an alias named `commit` runs fine via `wt commit`, only `wt step commit` is shadowed. - `src/commands/external.rs` — `handle_external_command` calls `try_alias` first, then PATH lookup, then unrecognized-subcommand error. Suggestions include alias names. Non-UTF-8 args bypass alias dispatch (alias parser requires UTF-8; binary subcommands get raw `OsStr`). - `src/help.rs` + `src/main.rs` — early-parse pass returns `Option<HelpContext>`; help splice fires for both `wt --help` and `wt step --help`. - `src/completion.rs` — aliases injected at the top level in addition to `step`. - `src/cli/mod.rs` — long Aliases section moved out of `Step::after_long_help` into hand-authored `docs/content/extending.md`. New sync test `test_top_level_builtins_match_clap` keeps the `TOP_LEVEL_BUILTINS` constant aligned with the `Cli` enum. ## Tests 3221 tests pass, lints clean. New integration tests: `test_top_level_alias_dispatch`, `test_top_level_alias_with_step_builtin_name`, `test_top_level_alias_did_you_mean`. Removed `test_step_alias_shadows_builtin_plural` (warning gone). Reframed `test_step_alias_shadows_builtin` to verify shadow filtering of typo suggestions instead. Completion tests now isolate user config via `WORKTRUNK_CONFIG_PATH=/dev/null` — project config isolation is a noted gap (commented inline). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
9884cdcf86 |
docs: link worktrunk-sync in Extending and FAQ (#2225)
## Summary Requested in max-sixty/worktrunk#2053 — link to [`worktrunk-sync`](https://github.com/pablospe/worktrunk-sync) from the docs. - **Extending Worktrunk** — adds an `### Examples` subsection under *External subcommands*, pointing to `worktrunk-sync` as a concrete real-world use of the `wt-<name>` dispatch mechanism. - **FAQ** — adds a "Does Worktrunk support stacked branches?" Q&A positioned after the tool-comparison section, explaining that stacked workflows are deliberately kept out of core and pointing readers at `worktrunk-sync`. Skill reference files under `skills/worktrunk/reference/` regenerated via `cargo test --test integration test_command_pages_and_skill_files_are_in_sync`. ## Test plan - [x] `cargo test --test integration test_command_pages_and_skill_files_are_in_sync` passes - [ ] Prose/links render as expected on the dev server Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
af1e0f5152 | docs(extending): lift hook-format descriptions out of code comments (#2191) | ||
|
|
5a02c1f916 | docs(extending): trim move/copy aliases recipe (#2190) | ||
|
|
d7680a27c6 |
feat(config): add sanitize_hash template filter (#2172)
## Summary
Adds a `sanitize_hash` minijinja filter — a pass-through wrapper for
`worktrunk::path::sanitize_for_filename`. Produces a filesystem-safe
name with a 3-char hash suffix so distinct originals never collide;
already-safe names pass through unchanged.
Uses it in a `hook-log` alias recipe in `docs/content/extending.md` so
`wt config state logs --format=json | jq ... select(.name == $name)`
matches the exact on-disk hook log filename even when the configured
branch or hook name contains characters like `/`.
## Test plan
- [x] New unit test `test_expand_template_sanitize_hash_filter` covers
safe passthrough, unsafe-char replacement + hash suffix, and empty input
- [x] `validate_template` test extended with `{{ branch | sanitize_hash
}}`
- [x] `test_command_pages_and_skill_files_are_in_sync` passes
(auto-syncs `docs/content/hook.md` and `skills/worktrunk/reference/*.md`
mirrors)
- [x] `pre-commit run --all-files` clean
- [x] Help snapshots regenerated
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
||
|
|
cec8d934ed |
docs(extending): simplify hook-log alias body; drop brittle hash match (#2169)
Two small polish changes to the `hook-log` alias recipe added in #2161: - **Multi-line the alias body** so the 217-char pipeline becomes readable in the rendered docs. The jq expression lives inside single quotes, so literal newlines are safe; the outer shell pipeline reads top-to-bottom without wrapping. - **Drop the `startswith($name + "-")` branch.** It was meant to also match the sanitized-with-hash form, but only covers the specific 3-char suffix shape and would misfire on hook names that naturally end in `-<3alnum>`. The limitation is documented instead: users with hook names that required sanitization should pass the on-disk sanitized form. A follow-up PR (dispatched separately) adds a `sanitize_hash` template filter so the alias can pre-sanitize its input and match reliably across all hook names. > _This was written by Claude Code on behalf of Maximilian Roos_ Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
50f9b82d2c |
refactor(config-state): structured JSON for logs; drop --hook/--branch filters (#2161)
Builds on [#2156](https://github.com/max-sixty/worktrunk/pull/2156) which hoisted `--format` and added absolute `path`. Two follow-on changes: ### 1. Structured fields on `hook_output` entries Hook-output log entries carried a slash-packed relative path in `file` — consumers had to split on `/` to filter by branch, source, or hook type. Expose first-class structured fields: ```json { "file": "main/user/post-start/server.log", "path": "/.../.git/wt/logs/main/user/post-start/server.log", "branch": "main", "source": "user", "hook_type": "post-start", "name": "server", "size": 1024, "modified_at": 1700000000 } ``` Internal-op entries (e.g., `feature/internal/remove.log`) normalize onto the same shape with `source: "internal"`, `hook_type: null`, and the op name in `name`. ### 2. Drop `--hook` / `--branch` filters; use jq With the structured fields, the old `--hook=SPEC` / `--branch=X` interface is a jq one-liner: ```bash wt config state logs --format=json \ | jq -r '.hook_output[] | select(.source == "user" and .hook_type == "post-start" and (.name | startswith("server"))) | .path' ``` That subsumes the filter flags without the CLI plumbing, the `HookLog::parse`/`to_spec` roundtrip, or the error-shape tests that locked in a fragile `source:hook-type:name` grammar. `logs` and `logs get` now always list — one code path, one shape. **Breaking change**: `wt config state logs get --hook=...` and `--branch=...` are removed. The jq pattern is documented in `wt config state logs --help`. > _This was written by Claude Code on behalf of Maximilian Roos_ --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
7e12435b33 |
docs(hook): teach pipelines as [[hook]] blocks; add TOML notes to config::commands (#2149)
Cut over hook pipeline docs from inline-list form (`hook = [{name =
"..."}, ...]`) to `[[hook]]` array-of-tables blocks. The deserializer
already accepts this shape — no code changes were needed; the change is
purely teaching. This mirrors what landed for aliases in #2144.
The summary in `Pipeline Ordering` now foregrounds the TOML shape
progression instead of a labeled taxonomy:
- `post-start = "npm install"` — one command
- `[post-start]` — one section of concurrent commands
- `[[post-start]]` — one of multiple sections, run in order
The bracket count itself tracks the structural escalation (value →
section → array-of-sections).
Also adds a module-level doccomment to `src/config/commands.rs`
describing the three primitive TOML shapes the hook deserializer accepts
(string / dict / list) and the asymmetry between dict-at-top (always
`Concurrent`) vs dict-in-list (1-entry → `Single(named)`). This is the
analysis that motivated the cutover and is retained so future readers
can orient on the deserialization rules without re-deriving them.
The Databases example in `hook.md` and `tips-patterns.md` got its first
pipeline step named (`set-vars`) since `[[post-start]]` blocks can't
hold anonymous bare strings the way inline lists could.
> _This was written by Claude Code on behalf of Maximilian_
---------
Co-authored-by: Claude <noreply@anthropic.com>
|
||
|
|
420cace354 |
docs(extending): use --key=value shorthand in move/copy alias examples (#2094)
PR #2091 added `--key=value` shorthand for alias variables. Update the three invocation comments in the move/copy-changes recipe to use the shorter form — `wt step move-changes --to=feature-xyz` instead of `--var to=feature-xyz`. Template bodies still reference `{{ to }}` internally, which remains the canonical variable name. > _This was written by Claude Code on behalf of max-sixty_ Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
5b89ef3b99 |
feat(alias): support --key=value shorthand for alias variables (#2091)
Aliases previously required `--var key=value` to pass template variables. Unknown `--key=value` flags are now treated as variable assignments, so `wt step deploy --env=staging` is equivalent to `--var env=staging`. The `=` is required to disambiguate from boolean flags — `--env value` would be ambiguous (is `value` a positional?), but `--env=value` is unambiguous. `--var` remains as the escape hatch for variable names that collide with built-in flags (`--dry-run`, `--yes`). Implementation is a single fallthrough match arm in `AliasOptions::parse()` (the alias parser is hand-rolled because aliases are clap external subcommands). Unit tests cover the shorthand alongside `--var`, equals-in-value, mixed forms, empty values, and bare `--key` errors. An integration test confirms end-to-end equivalence with `--var`. Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
61b0e5ae11 |
docs(extending): add recipe for carrying changes to a new worktree (#2083)
## Summary Adds a tested recipe to the Aliases section of `docs/content/extending.md` showing three `[aliases]` entries that compose `wt switch --create` with git's stash/diff plumbing to carry in-progress changes into a new worktree: - `move-changes` — stashes staged + unstaged + untracked, creates the new worktree, and pops the stash with `--execute`. Source becomes clean. - `copy-changes` — same push, plus `git stash apply --index --quiet` to restore the source before the new worktree pops. Both sides end up with identical state. - `copy-staged` — writes `git diff --cached` to a tempfile and applies it with `git apply --index` in the new worktree, so the source is untouched and staged/unstaged overlap is handled. Each alias takes the target branch via `--var to=feature-xyz`. Because an inner `wt switch --create` inside an alias body propagates its `cd` to the parent shell (#2077), they drop you in the new worktree directly. The content was worked out and tested end-to-end in the issue thread (`git status --porcelain` verified on both sides for clean / staged+unstaged+untracked / untracked-only / empty-staged scenarios). Closes #938. ## Test plan - [ ] `cargo test --test integration test_command_pages_and_skill_files_are_in_sync` — keep `skills/worktrunk/reference/extending.md` in sync - [ ] `pre-commit run --all-files` — lints - [ ] Visual check of the rendered Aliases section on the docs dev server Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
7e14f811e1 |
docs: cross-link Extending Worktrunk page (#2088)
Follow-up to #2079. Adds cross-links from the landing page and adds the `up` alias example to the extending page. - worktrunk.md "Next steps": link to extending.md - extending.md: add `wt step up` multi-line alias example, remove redundant TOML file-path comments > _This was written by Claude Code on behalf of @max-sixty_ Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
e2fff694a5 |
docs: add Extending Worktrunk page (#2079)
New Reference page covering the three extension mechanisms — hooks, aliases, and external subcommands — with a comparison table and enough detail to orient readers before linking to the full references (`hook.md`, `step.md#aliases`). Moves the external subcommands section out of tips-patterns.md into the new page as its canonical location. The aliases recipe in tips-patterns stays (it's a specific recipe, not a duplicate). > _This was written by Claude Code on behalf of @max-sixty_ Co-authored-by: Claude <noreply@anthropic.com> |