mirror of
https://github.com/max-sixty/worktrunk.git
synced 2026-09-14 20:00:38 +08:00
codex/remove-codex-cloud-specific-tests
154 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
aa9d8c43df |
feat: add remote_repo variable (#3745)
Add a `remote_repo` variable that returns the repo name from the remote URL. Unlike `repo`, it stays consistent even if the clone was renamed. Feel free to reject, or suggest other names for the variable. But this change would improve my workflow. I hope you don't mind my submitting a PR before opening an issue. Thanks for an amazing developer tool! AI Disclosure 🤖: I used Claude Code to generate the changes, but reviewed every line and made adjustments. --------- Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com> |
||
|
|
9e7ad0144d |
fix(hooks): expand everything but vars.* in a preview (#3638)
Follow-up to #3635, from two reviews that landed after it merged. ## The `vars.*` preview was worse than #3635 claimed `render_template_preview` short-circuits on `template_references_var(template, "vars")`, returning the raw template. So one `vars.` token disabled expansion for the entire command: ``` pre-commit = "deploy --branch={{ branch }} --repo={{ repo }} --env={{ vars.env }}" before: deploy --branch={{ branch }} --repo={{ repo }} --env={{ vars.env }} after: deploy --branch=main --repo=repo --env={{ vars.env }} ``` #3635 described this as "a `vars.*` template renders raw", which is true but understates it: `{{ branch }}` and `{{ repo }}` stopped expanding too, in a command whose whole job is to show the expansion. That short-circuit predates #3635 and has been degrading `wt hook <type> --dry-run` the same way; #3635 only extended it to `wt hook show --expanded`. The fix is at the source rather than at either caller. A preview now injects a stand-in object for `vars` that renders each reference back as itself, nested access included (`{{ vars.config.port }}` round-trips), while every other variable expands normally. `VarsMode::Resolve` keeps execution reading real values from git config; only previews pass `VarsMode::Literal`. A preview also no longer spawns the git read that resolving `vars` required. `vars.*` stays literal on purpose: those values are read when the step runs, after an earlier step in the pipeline may have written them, so a value resolved at preview time can differ from the one the run uses. Nothing covered this, which is why the suite stayed green through the regression. `test_hook_show_expanded_matches_dry_run` now sets a var and asserts the listing and the dry-run both leave it alone while expanding `{{ branch }}` beside it. ## The syntax gate is a type error now #3635 moved the template syntax check out of `prepare_steps` into a free `validate_pipeline_syntax` that both execution funnels had to remember to call. `prepare_steps` now returns a `PreparedPipeline` the caller must resolve: `.validated()` for the paths that run hooks, `.into_unvalidated()` for the listing, which annotates a broken template in place rather than blanking itself. Forgetting is a compile error, the same property `ApprovedHookPlan` gives hook approval. ## Smaller items Four cross-references went stale when the syntax check moved: `PreparedCommand.template`, `validate_template_syntax`, the `switch.rs` skip comment, and `HOOK_INFRASTRUCTURE_VARS` (which still named two deleted functions). The `--expanded` behavior is now documented in the sentence that already owns `{{ vars.<key> }}` semantics, with its three generated mirrors regenerated. `PreparedStep::commands()` replaces two hand-rolled matches in `hooks.rs`. `default_branch` moves inside `build_manual_hook_template_vars` — only the commit-hook arm reads it, and resolving it can cost a `git ls-remote` on a fresh clone, so the other eight hook types no longer pay for it. The listing carries its expansion state in an `Option<String>` instead of re-deriving "was this expanded?" from whether a context exists. > _This was written by Claude Code on behalf of max_ --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
c6eb148e62 | docs(hook): correct pre-switch {{ branch }} resolution note (#3516) | ||
|
|
67d2177029 | docs(hook): split cwd exception bullet to match 'three cases' count (#3367) | ||
|
|
3d0b99e072 |
Add WORKTRUNK_VERBOSE env var equivalent to -v/-vv (#3166)
Shell tab-completion runs the `wt` binary as its own subprocess (the shell sets `COMPLETE=<shell>`), and that path returns from `parse_cli` before `main` ever reaches `logging::init`. So when a tab-completion is slow, there's no flag that turns on logging for it — `-v`/`-vv` never run, and `RUST_LOG` only sets a level, not the `-vv` file sinks. There was no way to profile a slow completion. This adds `WORKTRUNK_VERBOSE=0|1|2` as the env-var equivalent of the `-v`/`-vv` flag count. It's read everywhere — including the completion path, which no flag can reach — and combined with the flag via `max`, so the env sets a baseline the flag can raise but never lower. Completion behaves *identically* to a flagged command at the same level: at level 2 it writes the same `trace.log`/`subprocess.log`/`diagnostic.md` under `.git/wt/logs/`, so a slow tab-completion can be profiled with: ```console $ WORKTRUNK_VERBOSE=2 COMPLETE=fish wt -- wt switch '' ``` then reading `trace.log`. (Set it inline like that, or as a one-off, rather than `export`-ing it — an exported value makes *every* TAB run as `-vv`, printing the "Writing to…" banner above your prompt and re-truncating the shared trace files on each keystroke. That's just normal `-vv` shared-sink behavior, but it's noisy interactively.) The one place completion deliberately diverges: it strips `WORKTRUNK_VERBOSE` from the environment of any forwarded `wt-*` custom-subcommand child, so the child doesn't re-run `logging::init` and clobber the trace files the parent completion just wrote (its stderr is discarded anyway). ### Testing Integration tests cover: `WORKTRUNK_VERBOSE=2` opens the trace files like `-vv` while `=1` does not; flag `-vv` combined with env `0` still writes (the `max`); and completion at level 2 writes `[wt-trace]`/`$ git` records to `trace.log` while candidates still go to stdout. A unit test pins the lossy parse (empty/garbage/out-of-range → `0`, never an error) so a stray value can't corrupt the completion candidate list. Docs (faq, config, the env-var table) and help snapshots are synced. > _This was written by Claude Code on behalf of max_ Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
a17fc22904 |
Add --config-set for inline TOML config overrides (#3138)
A global, repeatable `--config-set <toml>` flag that overrides any user-config key for a single invocation, layered above config files and `WORKTRUNK_*` env vars. The value is a real TOML fragment, so arrays and tables work natively — no bespoke `key=value` grammar. ## Behavior - **Precedence**: config files → `WORKTRUNK_*` env vars → `--config-set` (highest). - **Merge semantics**: a later override replaces an earlier one for the same key; scalars and arrays replace the lower-layer value; nested tables deep-merge, so `--config-set list.full=true` leaves sibling `list.*` keys untouched. - **Global**: works in any position (`wt --config-set … list` or `wt list --config-set …`), like `-v` / `-y` / `--config`. - **Graceful degradation**: a malformed, ill-typed, or invalid override drops the whole `--config-set` layer with an attributed warning (`▲ Ignoring --config-set overrides: …`) and preserves the lower layers — consistent with how the existing `WORKTRUNK_*` env overlay degrades. ## Why This is the foundation for a follow-up that parameterizes `wt list`'s column set without baking it into config: e.g. an alias `list-fast = "wt --config-set list.columns=[...] list"` renders a smaller view while plain `wt list` stays full. Doing it as a generic config-override lever (rather than a `list`-specific flag) keeps one canonical path and composes with aliases and any future config key. ## Implementation `Cli.config_override` (`--config-set`, global, `Vec<String>`) is stashed in a process-global `OnceLock` (mirroring `set_config_path`) and applied in `UserConfig::load_with_warnings` via `apply_cli_overrides` as the top layer. New `LoadError::CliOverride` variant carries the raw values for attribution; the warning is rendered in `emit_user_config_warnings`. ## Testing 8 unit tests on `apply_cli_overrides` (sets, deep-merge-preserves-siblings, last-wins, array-replace, malformed, type-mismatch, validation-failure, empty) and 3 integration tests (overrides-the-file, malformed-warns-attributed, works-after-subcommand). Full suite green: lib + 1828 integration, clippy clean, docs in sync. > _This was written by Claude Code on behalf of max_ --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
22768d425f |
feat(hooks)!: table-form pre-* hooks run concurrently — remove the serial path (#3052)
## Summary Lands the cutover announced in v0.37.0 (#2135): a multi-entry table hook now runs its commands concurrently for every hook type. `[pre-merge]` with several keys behaves like `[post-start]` with several keys and like a single `[[pre-merge]]` block — one execution semantic for `Concurrent` steps. The serial behavior was not implemented where one might expect. The executor's serial branch (`ForegroundStep.concurrent` / `SourcedStep.is_pipeline` / `CommandConfig::is_pipeline()`) was mostly dead: the deprecation shipped with a load-time TOML migration that rewrote multi-entry pre-* tables into serial pipeline form in memory on every config load, so affected configs never reached the executor as `Concurrent` steps. Removing that migration (now a `DEPRECATION_RULES` row, after #3045) is the actual behavior change; the executor flags come out with it. ## Why no replacement runtime warning Affected configs have printed a warning on every `wt` invocation for twenty minor releases (v0.37.0, April 12 → v0.57.0), with `wt config update` offering a one-command migration that preserved serial behavior explicitly ("migrate now to keep the current serial behavior once the table form is repurposed"). A post-cutover warning has no coherent shape: table form is now legitimate concurrent config, so it would nag users who want exactly that behavior with no way to silence it, and the `wt config update` rewrite would no longer be behavior-preserving. ## Parse normalization A one-entry top-level table previously parsed as a `Concurrent` group of one; with the serial branch gone it would have picked up `name │` output prefixes. One-entry maps now parse as `Single(named)` everywhere, matching one-entry maps inside pipeline lists (removing the documented dict-at-top vs dict-in-list asymmetry), and a new `Serialize` arm keeps one-step named configs round-tripping as named tables. Single-entry table hooks render exactly as before. ## Behavior changes 1. **Multi-entry table-form pre-* hooks: serial → concurrent.** The announced change. 2. **`wt hook <post-type>` foreground runs of multi-entry table hooks: serial → concurrent.** Previously an undocumented inconsistency — the same config already ran concurrently via the background path. 3. **Single-entry table aliases (`[aliases.x]` with one key): prefixed-stderr → stdout passthrough.** Now matches the string and `[[aliases.x]]` spellings, and makes `wt <alias> | …` work for this spelling too. (2) and (3) were never deprecation-warned; all three belong in the release notes. ## Tests - `test_pre_merge_deprecated_table_runs_serially` → rewritten as `test_pre_merge_table_form_runs_concurrently`; the single-`[[pre-merge]]`-block test is deleted (both forms now parse identically, so it duplicated the rewritten test). - Fixtures that genuinely need serial ordering (`>`/`>>` chains, the signal-abort "second must not run" assertion) converted to pipeline form. - `test_user_hooks_preserve_toml_order` (#737) and `test_post_start_named_commands` keep table form and pin ordering via `WORKTRUNK_TEST_SERIAL_CONCURRENT=1`, so insertion order is still asserted through the real concurrent input ordering. - New: one-entry-table parse/round-trip unit test; `test_alias_single_entry_table_writes_to_stdout` pinning change (3). - Regenerated snapshots drop a stale `RUST_LOG: warn` env line (the harness stopped setting it in #2901). ## Known follow-up Concurrent-group announcements expose a pre-existing ANSI dim-bleed: `format_bash_with_gutter` output ends without closing the dim attribute, so back-to-back `◎ Running …` lines render dim (visible today on pipeline-form hooks and `--execute` verbose). Now that this rendering is the table-form default it's worth fixing, but the fix lives in the gutter formatter and churns every bash-gutter snapshot — separate PR. > _This was written by Claude Code on behalf of max_ --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
371d286628 |
refactor(hooks): always-lazy template expansion for hooks and aliases (#3042)
Hook templates expanded eagerly at prep time unless they referenced `vars.*` (#1840); aliases were unconditionally lazy. The dual representation (`expanded` + `lazy_template` on `PreparedCommand`) put a mode choice in front of every consumer, and the seams produced real bugs (#1855, #1910). The background runner also re-rendered eagerly-expanded strings: a latent double-expansion whenever expansion output contained `{{`. This PR unifies on one rule: syntax is validated at preparation (before step 1), templates render when each step runs, and `vars.*` always reads fresh from git config. This is the direction #2128 recorded ("treat hooks as aliases bound to triggers"). **Navigating the diff:** - `src/commands/command_executor.rs`: `PreparedCommand` now carries the raw template plus the frozen context map; JSON is produced only at the process boundary. A `template_name` field keeps the moved errors' text identical to the old prep-time messages. `prepare_steps` validates syntax and freezes context; `resolve_command_str` renders at execution; `map_config_steps` is the structural walker shared with aliases. - `src/commands/alias.rs`: alias prep shares the walker. The residual fork is genuinely alias-specific: context construction (filtered to referenced vars, `{{ args }}` injection) and labels. A prep-time syntax check turned out to be dead code (the arg-routing parse already aborts on syntax errors), so `referenced_vars_for_config` now produces the rich `TemplateExpandError` instead. - `src/commands/hooks.rs`: the background spec always ships raw templates; the runner renders each step (it already did for vars steps). - Dry-run paths (`wt hook --dry-run`, `wt config alias dry-run`) share `render_template_preview`: vars-referencing templates shown raw after a syntax check, everything else rendered at display time. **Behavior changes** (each pinned by a test or snapshot): 1. A foreground pipeline whose step N has a semantic template error (undefined variable) runs steps 1..N-1 before failing, matching what vars-referencing steps already did. New test: `test_foreground_pipeline_undefined_var_runs_earlier_steps`. 2. A semantic error in a background hook template no longer fails the foreground command; it surfaces in the runner log. Syntax errors still abort at prep. New test: `test_background_hook_undefined_var_fails_in_runner`; `test_merge_drops_pending_hooks_when_post_merge_fails` switched to a syntax-error fixture since that is what now reaches its subject (the announcer Drop-flush). 3. `-v` no longer prints the parent-side rendered command for background hooks (rendering happens in the runner). The variables table remains and `wt hook <type> --dry-run` previews commands; help text updated. Three snapshots changed by exactly this. 4. Announce gutters for vars-referencing foreground steps show the rendered command instead of the raw template. 5. Syntax errors caught by the arg-routing parse (aliases, `wt hook` `--KEY=VALUE` routing) render as the rich template error with the source-line gutter, naming the alias or hook. `ApprovedHookPlan` freezing, approval semantics (keyed and displayed by template), config file format, and CLI flags are unchanged. **Testing:** full local gate green (3917 tests + lints). The deferral semantics in changes 1 and 2 previously had no coverage; both are now pinned. > _This was written by Claude Code on behalf of max_ Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
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>
|
||
|
|
3be40ad36f |
docs: writing-prose cleanup (claude-code, hook, llm-commits, tips-patterns) (#2922)
Targeted writing-prose cleanup across four docs pages. Per-page summary: **`claude-code.md`** — dropped the duplicate skill definition (the lead paragraph already covered it) and rewrote the `## Configuration skill` opener as "With the `/worktrunk` skill, the agent can help with:" so the section stands without depending on its heading. **`hook.md`** (edits in the `Hook` command's `after_long_help` in `src/cli/mod.rs`) — four small fixes: - Dropped "As usual, post-* hooks run in the background" (already established earlier on the page). - Split the perspective+cwd run-on paragraph; the three `cwd ≠ worktree_path` cases (`pre-switch`, `post-remove`, `post-merge` with removal) are now a bulleted list. - Folded the semicolon-spliced conditional-variables enumeration into the existing template-variables table descriptions (added "switch/create only" to `base`, "when target has a worktree" to `target_worktree_path`, hook-type qualifiers to `pr_number`/`pr_url`). The follow-on guidance about undefined variables and conditionals/defaults stays. - Trimmed the redundant `sanitize` sentence from the filters paragraph (the table above already says it). The two hook-types tables (event×pre/post matrix + per-hook purpose) are intentionally kept — they're complementary, not duplicate. **`llm-commits.md`** — dropped the "How it works" stub that mostly restated the lead, and the "There are sensible defaults, but templates are fully customizable" hedge. **`tips-patterns.md`** — three trims: - The `wt step tether` recipe's middle "This matters because…" sentence (covered by tether's own docs). - The ports-deterministic line tightened to lean on the concrete example rather than restate the abstract claim. - "in real-time" filler dropped from the Monitor hook logs section. Auto-synced skill mirrors (`skills/worktrunk/reference/*.md`) carry the same edits. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
21e0b27e49 |
refactor(verbose): rename output.log → subprocess.log; -vv keeps Info on stderr (#2913)
## Motivation The `-v` / `-vv` UX had three small issues that compounded: 1. **`output.log` is misnamed.** It holds the *uncapped raw stdout/stderr of every subprocess `wt` spawns* — multi-MB possible (`git log -p`, patch-id pipelines, etc.). "output" reads as "stuff `wt` printed" — the small thing — when it's actually the big thing. Easy to misread. 2. **`-vv` went fully dark on stderr.** PR #2892 moved the noisy debug pipeline to files at `-vv`; in the process, the stderr layer was disabled entirely. Users running `-vv` to see hook output (info-level, which `-v` shows on stderr) suddenly couldn't. 3. **`-v` help text was a 150-char one-liner** packed into a parenthetical, and the surrounding docs leaned on a "stderr stays readable / `log::*` pipeline" framing that was Rust-jargon-flavored and implied stderr-quiet at `-vv` — which is no longer true after change #2. ## Change - **Rename `output.log` → `subprocess.log`.** Filename now matches content. `OUTPUT` static → `SUBPROCESS`, plus the related `OutputMakeWriter` / `OutputFileFormat` / `build_output_layer` symbol renames. - **`-vv` keeps the Info baseline on stderr.** `build_stderr_layer` no longer returns `None` at `-vv`; debug-level records still route to file layers only, so the terminal stays readable while info-level status (hook output, template variables, the `Tracing to ...` pointer) shows the same as at `-v`. - **`-v` help text rewritten** to describe both levels cleanly without a wall of detail. - **`docs/content/faq.md` gets a "What does -v / -vv do?" section** with a three-level table. - **Docs cleanup**: drop "stderr stays readable" / `log::*` jargon / "but not subprocess.log" negative framing from user-facing prose. ## Notes for review - The only `log::info!` site in the codebase is `commands/picker/mod.rs:389` (a single picker error message), so making `-vv` show info-level on stderr doesn't add meaningful noise. - `test_vv_log_pipeline_silent_on_stderr` is renamed to `test_vv_debug_pipeline_silent_on_stderr` — its assertions only check debug-level records stay out of stderr (they do); the old name implied the whole `log::*` pipeline was silent, which was never quite true (direct `eprintln!` always showed) and is less true now (info-level routes to stderr). - 67 of the 69 changed files are snapshot updates (help text and one diagnostic snapshot) and auto-synced doc/skill mirrors. `git diff --stat -- 'tests/snapshots/*' 'docs/content/*' 'skills/worktrunk/reference/*' | tail -1` separates them. - CHANGELOG: not touched. The historical entry that introduced `output.log` (`#2201`) stays accurate for its release; this rename gets a new line in the next release. ## Tests 3870 tests pass. Re-snapshotted all `test_help_*` snapshots, three `step_alias` snapshots that quote the global help, and the diagnostic file format snapshot. > _This was written by Claude Code on behalf of max-sixty_ Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
1716ecab37 |
fix(hooks): resolve all hook config from the invoking worktree (#2873)
Worktrunk resolved each hook's `.config/wt.toml` from a different worktree depending on the hook — `post-merge` from the merge target, `post-switch` from the destination, `pre-remove`/`post-remove` from each removed worktree, `wt step prune` from each prunable worktree, and `wt switch --create` from the base ref's *committed* config via `git show`. That last one is the bug behind #2856 and #2818: an uncommitted or branch-local `.config/wt.toml` silently failed to fire creation hooks, and `wt config show` (which reads the working tree) disagreed with what actually ran. This replaces all of it with one rule: **every hook resolves its commands from the `.config/wt.toml` of the worktree `wt` ran in** — the invoking worktree, read from its working tree, the same file `wt config show` displays. ## Behavior changes - `wt switch --create` / `pr:` / `mr:` creation hooks read the invoking worktree's config, so an uncommitted `.config/wt.toml` fires them; the base ref's or PR's committed config is no longer consulted. - `post-merge` runs the feature worktree's config, not the merge target's. - `post-switch` into an existing worktree uses the source, not the destination. - `wt remove <other-branch>` and `wt step prune` use the invoking worktree's config, not each removed worktree's. In the common case — a committed, repo-wide `.config/wt.toml` — these are identical; they diverge only when a branch carries its own working-tree edits. ## For reviewers The module docstring in `src/commands/hooks.rs` is the spec — its per-hook config-source table collapsed to one rule. The change is concentrated in five approval gates that now call `repo.load_project_config()` once instead of `Repository::at(<other-worktree>)`: `merge::approve_merge_plan`, `main.rs`'s `approve_remove`, `step::prune::approve_prune_hooks`, `picker::approved_removal_plan`, and `worktree::switch`. The `switch_hook_project_config` helper and the `base_ref_for_create` / `project_config_at_ref` `git show` machinery are deleted. The *anchor* — the worktree a hook runs in, the executor's plan-lookup key — is unchanged; only the config *source* unifies. The frozen `ApprovedHookPlan` still closes the approval-boundary TOCTOU. ## Testing Hook config-resolution tests across `switch`, `merge`, `remove`, and `step_prune` were rewritten to assert the new rule, each also checking that the non-invoking worktree's config is ignored. `test_post_merge_hook_from_rebased_in_config_does_not_run` is the TOCTOU regression: a `post-merge` that enters the invoking worktree's config only via the rebase, after the gate froze the plan, must not run. Ref #2856, #2818. --------- 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) |
||
|
|
408f4f5bee |
Add wt step tether: kill a command's process group when its worktree is removed
`wt step tether -- CMD…` runs CMD in its own process group and tears the whole group down when CMD exits or its worktree is removed (a 250ms portable poll; killpg on Unix, taskkill /T /F on Windows). Replaces the leaked-dev-server / fseventsd-saturation failure mode with a fire-and-forget supervisor needing only a single post-start hook. No unsafe, no new deps. Shell handling matches `wt step for-each`. Windows taskkill has a documented self-exit-detach edge. Co-Authored-By: Claude <noreply@anthropic.com> |
||
|
|
7d09c5da58 |
fix(merge): gate commit-append separately so declining it keeps hooks (#2802)
## Summary `wt merge` bundled the project commit-append into the hook-approval batch and shadowed `verify = false` on *any* decline. When the project hooks were already approved (and thus filtered out of the prompt), declining the lone commit-append prompt skipped every pre-merge/post-merge hook for that run — even though the user only meant to skip the append. This was flagged as a follow-up during review of #2774. The append is now resolved through the shared `approve_commit_template_append` gate — the same path `wt step commit` / `wt step squash` already use — so its decline drops only the append and never touches hook approval. Hooks are approved on their own; only a hook decline skips hooks. Trade-off: when both project hooks *and* the append are unapproved on a fresh repo, the user now sees two prompts instead of one bundled prompt. This matches the standalone commit/squash flow and is the canonical behavior; the old single-prompt bundling was what introduced the conflation. Already-approved appends still don't re-prompt. ## Also in this PR - **Message canonicalization** — `merge`, `removal`, `prune`, and `switch` now print `Commands declined, … without hooks`, matching the wording `step squash` / `step commit` already used. `wt hook` and `wt config approvals add` stay bare (they end on decline rather than continuing an operation). - **Docs** — the hook Security section now states that declining skips every project command for that operation (already-approved ones included) and that saved approvals are unaffected. Synced to the skill reference. - **Doc-comment fixes** — `HookGate` / `PreApprovedGuidance` comments no longer describe the removed bundled-prompt flow. ## Testing - New regression test `test_merge_decline_append_keeps_approved_hooks`: with the hook pre-approved, declining the append still runs the pre-commit hook and the merge succeeds (fails on the old code, passes now). - `test_merge_bundles_append_into_hook_approval` renamed to `test_merge_prompts_hooks_and_append_separately`, updated for the two-prompt flow. - Four decline snapshots regenerated (message text only). - Full pre-merge gate green: 3736 tests, clippy, fmt, doc-sync. Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
a77c92e25b | docs(help): point banner at the actual cli source path (#2665) | ||
|
|
ecdb7bfc35 | feat(config): add codename template filter (#2641) | ||
|
|
c82236e034 |
refactor(config): rename path-traversal filters to dirname/basename (#2605)
\`name\` was too generic — likely to collide with future filters operating on domain objects (a Branch, a worktree, a remote). Switch to the POSIX/jinja-convention pair so both filters travel together: ansible ships \`dirname\`/\`basename\`, shell users already know them, and \`dirname\` is no more ambiguous than \`parent\` was. Follow-up to #2592, which landed ~30 minutes ago — no users to break yet. > _This was written by Claude Code on behalf of @max-sixty_ Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
670cb2a7b0 |
feat(config): add parent and name path-traversal template filters (#2592)
Two new filters expose `Path::parent` and `Path::file_name` to
templates, enabling path traversal that previous filters couldn't
express. They unblock the bare-repo-in-hidden-directory layout
(`myproject/.git`), where `{{ repo }}` resolves to `.git`: users who
want a richer worktree-path naming scheme can now write `{{ repo_path |
parent | name }}` to recover `myproject`.
The interactive bare-repo prompt still suggests the simpler `{{
repo_path }}/../{{ branch | sanitize }}` template, since the bare layout
already nests worktrees inside a repo-specific wrapper directory — no
prefix needed for disambiguation. The filters are there for users with
different layout preferences (#1281 discussion).
Thanks to @seakayone for reporting #1279 and @Xilis for raising the
`parent_dir` question that prompted this approach.
> _This was written by Claude Code on behalf of @max-sixty_
Co-authored-by: Claude <noreply@anthropic.com>
|
||
|
|
3593606283 |
refactor: route every short-SHA display through git's abbreviation logic (#2576)
Every site that abbreviated a commit SHA was either slicing `&sha[..7]` or running its own ad-hoc `git rev-parse --short` call. 7-char prefixes regularly collide in repos with many commits, and none of the slicing sites honored `core.abbrev`. Cut over to a single canonical helper. ## Single helper, every display site `Repository::short_sha(&str) -> Result<String>` wraps `git rev-parse --short`. Routes display through git's own abbreviation logic so `core.abbrev` is honored and prefixes auto-extend on collision. Used by: - `step commit` / `step squash` success lines - `step push --no-ff` `Merged to @ <hash>` line — the original bug. Flagged on #2560 as a pre-existing third instance of the same pattern fixed there in `commit.rs` and `step_commands.rs`. - `{{ short_commit }}` template var in hook contexts (`command_executor.rs`, `template_vars.rs`) - post-remove hook context for the removed worktree - safety-backup ref display (`create_safety_backup`) - `(detached <sha>)` label in the orphan-check loop All seven sites previously sliced `commit[..7]` or called their own `rev-parse`. Now they route through one helper. ## Batched form for `wt list --format=json` The JSON list path emits one `short_sha` per worktree row. Looping `short_sha` would fork a subprocess per row, so the short SHA is folded into the existing `commit_details_many` batch instead — `%h` is added to the `git log --no-walk --format=...` call that already fetches timestamp and subject. One subprocess for the whole list, same `core.abbrev` behavior as every other site. `CommitDetails` gains a `short_sha: String` field with the same provenance as the timestamp and subject. The JSON schema is unchanged (`commit.short_sha` was already a field) — only its length now varies by `core.abbrev` instead of being hard-coded to 7. ## API change `TemplateVars::with_active_commit(commit, short_commit)` now takes both forms. Previously it sliced `commit.get(..7)` internally. The sole caller (`worktree/finish.rs`) resolves the short form via `Repository::short_sha` and passes both. ## Docs `{{ short_commit }}` is no longer documented as "(7 chars)" — `src/cli/mod.rs` and `src/config/project.rs` now describe `core.abbrev` behavior. Doc-sync regenerated `docs/content/hook.md` and the skill mirror. ## Tests Full suite passes (3463/3463). `commit_details_many` tests updated for the new tuple shape; `CommitDetails` fixtures in `layout.rs` get a `short_sha` field; `template_vars` tests pass both forms explicitly. Two integration tests previously asserting `short_commit` was exactly 7 chars (`post_start_commands.rs`, `user_hooks.rs`) still pass — fresh test repos default to `core.abbrev = 7`. > _This was written by Claude Code on behalf of @max-sixty_ --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
5dae8d8e92 |
feat(config)!: cap sanitize_db output at 48 chars (#2467)
## Summary - Lowers `sanitize_db`'s total output cap from 63 (PostgreSQL's identifier limit) to 48, leaving headroom for users composing the output into longer paths or identifiers — e.g., Unix socket paths capped at 107 bytes (#2397). - The 3-char hash suffix is unchanged (still derived from the original input), so collision avoidance is preserved at the new budget; only the truncated base shrinks. - **Breaking change** for branches whose current `sanitize_db` output exceeds 48 chars: the truncated base shifts, so the final identifier changes. Most branch names are well under 48 chars and pass through unchanged. Users who relied on the previous output as a stable database identifier should be aware. Companion to #2453 (added the `hash` filter so users can compose their own truncate-with-collision-avoidance recipes when 48 still isn't tight enough). Refs #2397. ## Test plan - [x] `cargo test --lib --bins -- sanitize_db` — 5 unit tests pass (including updated `test_sanitize_db_truncation`) - [x] `cargo test --test integration -- sanitize_db` — 3 integration tests pass (including updated `test_doc_sanitize_db_truncation`) - [x] `cargo test --test integration test_docs_are_in_sync` — passes (docs/skills auto-synced) - [x] `cargo insta test --accept --test integration -- "test_help"` — help snapshots regenerated; no surprise diffs --------- Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
719a102409 |
feat(config): add hash template filter (#2453)
|
||
|
|
4cbc8ca5fd |
refactor(docs): drop mirrored help-page close; align convert_console_blocks_in_docs error channel (#2422)
## Summary Three follow-ups from #2419 review. - **Bare close everywhere.** With inner snapshot wrappers gone from command pages (#2419), no `AUTO-GENERATED` markers nest inside the help-page region anywhere in `docs/content/*.md`. The mirrored close (`<!-- END AUTO-GENERATED from \`wt cmd --help-page\` -->`) was the only remaining variant of the close form; collapsed to bare `MARKER_CLOSE`. - `src/help.rs::PageMode::emit_footer()` no longer takes `subcommand`. - The help-page regex in `readme_sync.rs` matches via non-greedy `.*?` to bare `MARKER_CLOSE`, with a comment pointing at the new invariant test. - `AUTO_GENERATED_MARKER_PATTERN` strip regex built from the constants. Added **`test_no_nested_auto_generated_markers`** — walks `docs/content/*.md` and fails if any `AUTO-GENERATED` open ever appears inside an already-open region. This is the explicit invariant that bare-close pairing depends on; if a future change tries to re-introduce nesting (e.g., restore an inner snapshot wrapper around terminal shortcodes), the test catches it before the subtle "regex chops region at first inner close" failure mode lands. - **Aligned error channel.** `convert_console_blocks_in_docs` now returns `(Vec<String>, Vec<String>)` like its sibling sync steps, with per-file error capture for `read_dir`, dir entries, and `read_to_string`. The caller passes errors through the same `tag()` aggregation as everything else, so a transient I/O failure on one file no longer aborts the whole pipeline silently. Also fixes the matching clippy warning (`is_some_and(|e| e == \"md\")` → `is_none_or(|e| e != \"md\")`). - **Docs alignment.** `docs/CLAUDE.md` updated in two places where the prose still documented the mirrored close as the canonical form. Visual check via curl across 12 dev-server pages confirmed no leakage of any prior marker form. Adversarial review (subagent /popper-style) caught the stale prose; otherwise no regressions found. Net diff: +113/-49 (the growth is the new invariant test and explicit per-file error handling; structural simplification shows as -49). ## Test plan - [x] `cargo test --test integration readme_sync` — 12 sync tests pass (was 11; +1 for the new invariant test) - [x] `cargo test --test integration` — full integration suite (1558 tests) pass - [x] `cargo test --test integration test_no_nested_auto_generated_markers` — new guard test passes; manually verified it fires on synthetic nesting - [x] Single-pass convergence verified: `git checkout docs/ && cargo test --test integration test_docs_are_in_sync && git diff --stat` produces an empty diff after the first run - [x] Visual check via local Zola dev server: 12 docs pages (\`/worktrunk/\`, \`/llm-commits/\`, \`/claude-code/\`, \`/tips-patterns/\`, \`/merge/\`, \`/step/\`, \`/remove/\`, \`/hook/\`, \`/list/\`, \`/switch/\`, \`/config/\`, \`/faq/\`) — only HTML comments contain marker text, no rendered leakage - [x] `cargo clippy --all-targets --all-features` — clean - [x] `cargo fmt --check` — clean 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
66a5becc25 |
refactor(tests): consolidate three sync tests; drop dead inner snapshot wrappers (#2419)
## Summary Two follow-ups from #2418 review. - **Test brittleness fix.** Three previously-separate sync tests had data dependencies that nextest could interleave: `test_readme_examples_are_in_sync` reads `docs/content/*.md`, which `test_docs_quickstart_examples_are_in_sync` generates from snapshots. Under parallelism, README sync could see stale docs and produce content that required a second run to converge. Collapsed both into the existing `test_command_pages_and_skill_files_are_in_sync` pipeline, renamed to `test_docs_are_in_sync`. Steps run sequentially in dependency order; single pass converges from a clean working tree. Each step's errors and updated-file list are tagged with the pipeline stage (`[command pages]`, `[standalone docs]`, `[README]`, etc.) so a failure tells a developer which stage broke without reading the test source. README failure also now reports `(N of M section(s) updated)`. - **Dead inner snapshot wrappers.** `expand_command_placeholders` wrapped each terminal shortcode in command pages with `<!-- ⚠️ AUTO-GENERATED from <snap> --> ... <!-- END AUTO-GENERATED -->` markers. Command pages regenerate wholesale from `--help-page` each sync, so the inner wrapper served no in-place-refresh purpose — it was dead weight nested inside the outer help-page region's markers. Stripped from `expand_command_placeholders`'s HTML branch; net -32 lines across six command-page docs files. The outer help-page region's mirrored close stays — it's load-bearing whenever any nested `AUTO-GENERATED` marker appears in the body. Comment in `src/help.rs:emit_footer` updated to explain the role. (My follow-up note that the mirrored close was redundant turned out to be a misanalysis — non-greedy `.*?` only safely pairs the open with the right close when there are no nested closes; once the inner snapshot wrappers were gone, the mirror became technically unnecessary, but keeping it survives any future reintroduction of nesting at no cost.) Renamed the test in `CLAUDE.md` (4 sites) and `docs/CLAUDE.md` (2 sites) so the documented `cargo test` invocations resolve. Net diff: −54 lines. ## Test plan - [x] `cargo test --test integration readme_sync` — 11 sync tests pass (was 13; minus the two collapsed) - [x] `cargo test --test integration` — full integration suite (1557 tests) pass - [x] **Single-pass convergence verified**: `git checkout docs/ README.md && cargo test --test integration test_docs_are_in_sync && git diff --stat` produces an empty diff after the first run - [x] Visual check via local Zola dev server: \`/worktrunk/\`, \`/llm-commits/\`, \`/claude-code/\`, \`/tips-patterns/\`, \`/merge/\`, \`/step/\`, \`/remove/\`, \`/hook/\`, \`/list/\` all render terminal blocks cleanly with no leaked marker strings - [x] Adversarial review of the consolidation (subagent /popper-style): all 4 actionable findings (stale doc references, lost README count, missing stage tags, etc.) addressed - [x] `cargo clippy --all-targets --all-features` — clean - [x] `cargo fmt --check` — clean 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
1e053d2d88 |
refactor(docs): fix blank-line cmd-stream corruption; share AUTO-GENERATED marker constants (#2417)
## Summary Three small follow-ups + a placeholder rename, all from #2405 review feedback. - **Root-cause fix for `|||` corruption.** `convert_dollar_console_to_terminal` was promoting blank lines inside mixed `\$ cmd + output` blocks to extra `cmd=` entries, dropping the blank from the rendered body and emitting a stray `\$` prompt (e.g. `cmd=\"wt list|||\"`). Now distinguishes command-only blocks (blanks are visual spacing in `cmd=`) from mixed blocks (blanks belong to body). Removes the matching `trim_end_matches('|')` bandage in `expand_command_placeholders`. New unit test in `src/docs.rs` covers the mixed-block case. - **Single-pass classification.** Folded the two filter chains over `block_lines` into one loop with one match — fewer branches, no duplicated predicates. - **AUTO-GENERATED marker consolidation.** Within `tests/integration_tests/readme_sync.rs`, the `<!-- ⚠️ AUTO-GENERATED ... -->` literal appeared in four producer / regex sites with subtly different shapes (HTML vs plain, ID format). Factored into `MARKER_OPEN_PREFIX` / `MARKER_OPEN_HTML_PREFIX` / `MARKER_CLOSE` constants plus a `wrap_in_marker()` helper, and threaded those constants into the regexes via `regex::escape`. Pure refactor — no docs/skill files regenerated. - **`__WT_OPEN2__` / `__WT_CLOSE2__` → `__WT_OPEN__` / `__WT_CLOSE__`.** The `2` was meant to distinguish doubled-brace placeholders from hypothetical single-brace ones, but Tera only treats `{{`/`}}` (not single braces) as template delimiters — there's no second variant to disambiguate from. Updated the producer (`src/docs.rs`), the test-side decoder (`tests/integration_tests/readme_sync.rs`), the Zola template (`docs/templates/shortcodes/terminal.html`), the docs-site `CLAUDE.md`, and the regenerated `docs/content/{step,hook}.md`. ## Test plan - [x] `cargo test --lib docs::tests::test_convert_dollar_console_to_terminal` — exercises new mixed-block case + existing command-only / multi-cmd / comment cases - [x] `cargo test --test integration readme_sync` — 13 sync tests still pass after the refactor and bandage removal - [x] `cargo test --test integration` — full integration suite (1558 tests) pass - [x] Visual check via local Zola dev server: `/merge/`, `/step/`, `/remove/`, `/hook/`, `/llm-commits/`, and `/list/` all render terminal blocks cleanly with no `|||` artifacts and no leaked placeholder strings (`__WT_OPEN__` etc. don't appear in served HTML). The multi-command jq recipes block on `/list/` continues to render comments as bash-styled section headers. - [x] `cargo clippy --all-targets --all-features` — clean - [x] `cargo fmt --check` — clean 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
5f126d21fe |
docs(pages): add static command output to sections dominated by GIFs (#2405)
## Summary Adds static command-output blocks to the docs pages dominated by GIFs (addresses #2403). The blocks are **driven from insta snapshots** so they stay in lockstep with what `wt` actually prints — one source flows to all three surfaces: - terminal `wt <cmd> --help` (plain text, gutter-formatted) - `docs/content/*.md` (colorized `{% terminal(cmd="...") %}` shortcode) - `skills/worktrunk/reference/*.md` (plain `$ cmd\noutput\n` block) ## What changed - **Six scripted snapshot tests** produce realistic output (`cargo nextest run`, `flyctl scale count 0`, LLM-generated commit messages): - `test_docs_merge_pre_merge_hook` — `wt merge` with pre-merge hook - `test_docs_step_commit_llm` — `wt step commit` with LLM - `test_docs_step_squash_llm` — three-commit squash with LLM - `test_docs_merge_squash_llm` — `wt merge` (squash + LLM + merge) for `llm-commits.md` - `test_docs_remove_pre_remove_hook` — `wt remove` with pre-remove hook - `test_docs_hook_pre_merge` — `wt hook pre-merge` direct invocation - **Sync pipeline extension** in `tests/integration_tests/readme_sync.rs`: - New write-back pass `sync_cli_mod_example_bodies` fills the ```console``` body in each `<!-- wt <cmd> (docs-example) -->` placeholder in `src/cli/mod.rs` from the registered snapshot. Runs before `--help-page` reads the file. - `COMMAND_PLACEHOLDER_PATTERN` extended to match three forms (```bash```, `{{ terminal() }}` self-closing, `{% terminal %} body {% end %}`). This **fixes a pre-existing bug** in `docs/content/list.md` where the HTML-mode expansion was silently broken. - Stripped trailing `|||` corruption that arises when blank lines in snapshot bodies are interpreted as empty commands by `convert_dollar_console_to_terminal`. - **Docs page migration**: - `src/cli/mod.rs` — replaced four hand-written ```console``` blocks (merge, step, remove, hook) with `<!-- wt <cmd> (docs-example) -->` markers. - `docs/content/llm-commits.md` — replaced three hand-crafted HTML blocks with `<!-- ⚠️ AUTO-GENERATED-HTML from X.snap -->` markers. - **Refactor follow-up** in a separate commit: - `BADGE_EXPERIMENTAL_HTML`, `SUBDOC_MARKER_PREFIX`, `DEMO_MARKER_PREFIX` hoisted to `worktrunk::docs` so producer and consumer stay in lockstep. - `normalize_clap_help_fences()` consolidates the `text→` + `console→bash` replacement pair shared by `--help-md` and `help_reference_inner`. - **CLAUDE.md note** documenting the `.gitattributes` `linguist-generated=false` exemption requirement when adding skill-only files (carryover from #2409). ## Test plan - [x] `cargo test --test integration readme_sync` — all 13 tests pass, idempotent - [x] `cargo test --test integration test_help` — help snapshots updated - [x] `cargo run -- {merge,remove,step,hook} --help` — clean gutter formatting, no visible HTML comments - [x] `cargo run -- hook pre-merge --yes` — full project gate green (3355 tests) - [ ] Visual check on dev site (maintainer — terminal shortcodes now render with colors via ANSI→HTML; dark/light variants both expected to look like the GIFs they replace) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: Maximilian Roos <m@maxroos.com> |
||
|
|
3a9d6402cb |
docs(hook): clarify when pre-start/post-start fire (#2360)
## Summary Refs #1571. Adjusts the existing `pre-start` and `post-start` rows in the hook types table to say when the hooks fire, rather than only what to put in them. Before: ``` | `pre-start` | Tasks that must complete before `post-start`/`--execute`: dependency install, env file generation | | `post-start` | Dev servers, long builds, file watchers, copying caches | ``` After: ``` | `pre-start` | Runs once when a new worktree is created, blocking `post-start`/`--execute` until complete: dependency install, env file generation | | `post-start` | Runs once when a new worktree is created, in the background: dev servers, long builds, file watchers, copying caches | ``` Matches the phrasing of neighbouring rows (e.g. `pre-switch` has \"Runs before...\", `post-merge` has \"Runs in the target...\"). No new section or explanation added — the adjustment is confined to the two rows whose definitions were silent on timing. Per @max-sixty's ask in https://github.com/max-sixty/worktrunk/issues/1571#issuecomment-4291227117, marked as draft for review. ## Test plan - [x] `cargo test --test integration test_command_pages_and_skill_files_are_in_sync` — passes after regenerating `docs/content/hook.md` and `skills/worktrunk/reference/hook.md` - [x] `cargo insta test --accept -- --test integration test_help` — no snapshot changes (the row is inside the table rendered from `after_long_help`; existing help snapshots already absorbed the old wording in their expected forms but don't anchor on it) Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
771918f29e |
docs(hook): promote Recipes to top-level H1 (#2351)
Follow-up to #2349. With the old "Designing Effective Hooks" umbrella heading removed, `## Recipes` was landing at H2 under `# Running Hooks Manually`, reading as a sub-topic of manual invocation — which it isn't. Promoting to `# Recipes` makes it a peer of the other top-level sections (`# Hook Types`, `# Security`, `# Configuration`, `# Running Hooks Manually`). Trailing `## See also` stays at H2 — consistent with how other command docs handle their "See also" footers. > _This was written by Claude Code on behalf of Maximilian_ Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
970375afd4 |
docs(hook): restructure recipes list and callout for copy-ignored (#2349)
Three tweaks to the hook docs: - Drop the "Designing Effective Hooks" heading and rename "More recipes" to "Recipes". - Make each recipe bullet lead with a specifically-named link to its Tips & Patterns section, so the list functions as a table of contents rather than a paragraph with a trailing URL. - Move "Copying untracked files" up next to the JSON context section and frame it as a specific command worth calling out, rather than leaving it as a lone sibling of "Recipes" under the old umbrella heading. Source is `after_long_help` in `src/cli/mod.rs`; `docs/content/hook.md` and `skills/worktrunk/reference/hook.md` are regenerated by the sync test. Dead post-processor entries in `src/help.rs` that used to convert the old bare-URL bullet format are removed. > _This was written by Claude Code on behalf of Maximilian_ --------- Co-authored-by: Claude <noreply@anthropic.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> |
||
|
|
18825c3e3c |
docs(hook): move progressive-validation and target-specific examples to tips-patterns (#2329)
Keeps "Copying untracked files" inline (the one example that materially uses worktree-specific mechanics) and moves the two plain-TOML patterns — progressive validation and target-specific `post-merge` — to `tips-patterns.md`. `hook.md` references them as `More recipes` bullets instead. > _This was written by Claude Code on behalf of Maximilian._ Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
0be0a28eae |
docs(hook): drop pre-start vs post-start section (#2326)
The section mostly restated content already covered by the "Hook Types"
table and the hook purpose table above it. Removing it and folding the
load-bearing guidance ("prefer `post-start` unless a later step needs
the work completed first") into the existing "most common starting
point" sentence near the top.
> _This was written by Claude Code on behalf of Maximilian_
---------
Co-authored-by: Claude <noreply@anthropic.com>
|
||
|
|
064f8940c0 |
docs(hook): consolidate copy-ignored recipe and trim redundant examples (#2323)
Docs consolidation sweep, same pattern as #2319. **Copy-ignored recipe** — collapses hook.md's "Copying untracked files" overlap with tips-patterns.md into a "More recipes" bullet pointing at the canonical recipe. Keeps a one-paragraph intro in hook.md (worktrees don't share untracked files → use `wt step copy-ignored`) since it's a fundamental worktree concept. Moves the concrete pnpm pipeline example into tips-patterns.md's canonical recipe, and switches it from `[[pre-start]]` to `[[post-start]]` — the pipeline form handles ordering without blocking worktree creation. `pre-start` is now called out only as the exception for when `--execute` needs the files immediately. **Trims** - Dropped hook.md's "Hook type examples" dump: most of its 10 entries duplicate existing recipes (dev server, database, cold starts) or the Progressive validation section. - Dropped tips-patterns.md's "Local CI gate": four-line recipe that duplicated hook.md's Progressive validation and merge.md's Local CI narrative. - Dropped hook.md's "Python virtual environments" snippet: step.md already owns language-specific notes. > _This was written by Claude Code on behalf of Maximilian Roos_ --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
0253260503 |
Extend -v variable dump to aliases + help-table drift test (#2324)
Follow-ups from #2316. ## What's in here 1. **Alias `-v` variable dump.** Added `format_alias_variables(ctx)` alongside the existing `format_hook_variables(hook_type, ctx)`, with a private `format_variables_table` helper sharing the alignment + `(unset)` logic. Wired into `run_alias` before the announcement, symmetric with the foreground hook path. 2. **Help-table drift test.** `test_template_variables_table_matches_constants` parses the `## Template variables` table out of `src/cli/mod.rs`, extracts `(kind, var_name)` pairs, and asserts presence + group placement against `ACTIVE_VARS` / `REPO_VARS` / `EXEC_BASE_VARS` / `ALIAS_ARGS_KEY` / union of `vars_available_in(Hook(*))`. Uses public API only — no leak of the private `hook_extras` helper. Adding a var to the constants without updating the table (or vice versa) fails the test. Descriptions stay free-form. 3. **Shorter `-v` help text.** `Verbose output (-v: info logs + hook/alias template variable & output; ...)`. ## Example ``` \$ wt -v greet world ○ template variables: branch = feature worktree_path = _REPO_.feature worktree_name = repo.feature … args = ["world"] repo = repo … cwd = _REPO_.feature ◎ Running alias greet ○ Expanding greet echo hello {{ args }} → echo hello world hello world ``` > _This was written by Claude Code on behalf of @max-sixty_ --------- Co-authored-by: Claude Opus 4.7 (1M context) <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> |
||
|
|
eec3b24883 |
Show resolved template variables under -v for hooks (#2316)
Closes #2309. When a hook fires under `-v`, `wt` now prints a `template variables:` block listing every variable in scope for that hook type and the value it resolved to for this specific invocation. Vars that are in scope but not populated render as `(unset)` — which is exactly how `target_worktree_path` surfaces during `wt switch -`, the thing the issue reporter hand-rolled an echo-hook to discover. The block prints *before* the `◎ Running …` announce line so it describes what the hook is about to see, not what already ran. Works in both paths: the foreground path (`announce_command`, one block per command) and the background path (`announce_and_spawn_background_hooks`, one block per distinct hook type in the pipeline batch). ## Key files - `src/config/expansion.rs` — `format_hook_variables(hook_type, ctx)` emits the aligned `name = value` block ordered per the docs table (active → operation → repo → exec). `BASE_VARS` is split into `ACTIVE_VARS` / `REPO_VARS` / `EXEC_BASE_VARS` with a `base_vars()` helper, so `vars_available_in` and the printer share one source of truth. - `src/commands/command_executor.rs` — foreground wiring in `announce_command`, gated on `verbosity() >= 1`. - `src/commands/hooks.rs` — background wiring in `announce_and_spawn_background_hooks`; prints one table per distinct hook type (since within a pipeline only `hook_name` varies). - `src/cli/mod.rs` — updates the global `-v` help text and adds a pointer under `## Template variables` in the hook help. ## Testing - Unit: `test_format_hook_variables_groups_and_unset` snapshots a pre-switch context with `target_worktree_path` omitted so `(unset)` is covered; `test_format_hook_variables_scope_filters_operation` confirms pre-commit's narrower operation scope. - Integration: `test_hook_verbose_prints_variable_table` covers the foreground path; the existing `test_post_start_verbose_shows_per_hook_output` now also exercises the background path. No new CLI surface, no new config keys — only behavior added behind the existing `-v` flag. --------- Co-authored-by: Claude Opus 4.7 (1M context) <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>
|
||
|
|
0639b6bf0e |
docs(hook): group template variables by kind and align ordering (#2303)
## Summary The hook template-variable surface had drifted: `BASE_VARS`, `hook_extras`, and the user-facing help table each used a different order. After #2300 added `pr_number`/`pr_url`, that drift got worse — the new vars landed at the bottom of the help table next to hook infrastructure, even though semantically they're operation context (they travel with `base`/`target`, populated by the same `pr:N`/`mr:N` code path). This PR reorganises around five semantic groups and applies the same ordering everywhere: | Kind | Vars | |------|------| | `active` | branch, worktree_path, worktree_name, commit, short_commit, upstream | | `operation` | base, base_worktree_path, target, target_worktree_path, pr_number, pr_url | | `repo` | repo, repo_path, owner, primary_worktree_path, default_branch, remote, remote_url | | `exec` | cwd, hook_type, hook_name | | `user` | vars.\<key\> | ## Changes - `BASE_VARS` reordered into active → repo/remote → exec(`cwd`), with a doc-comment pointing at the help table as the canonical order. - `hook_extras` doc-comment requires each arm to be a prefix-ordered subset of the operation-context block. - Help table gains a `Kind` column printed once per group (blank on continuation rows). `pr_number`/`pr_url` move up to `operation`; `cwd` moves down to `exec`; the word "Active" is dropped from the first six descriptions since `Kind` now carries that signal. - Auto-generated docs (`docs/content/hook.md`, `skills/worktrunk/reference/hook.md`) re-synced by `test_command_pages_and_skill_files_are_in_sync`. No behaviour change — ordering only, plus the new Kind column. ## Follow-ups (not in this PR) Add a test that enforces alignment across the three sites — the help-table row order, `BASE_VARS`, and each `hook_extras()` arm must all be prefix-subsets of one canonical ordered list. Would prevent the drift that #2300 introduced. ## Test plan - [x] `cargo run -- hook pre-merge --yes` — clippy + 3272 tests + doctests + `RUSTDOCFLAGS=-Dwarnings cargo doc` - [x] `test_command_pages_and_skill_files_are_in_sync` passes - [x] `test_help` rstest suite passes (no changes — it doesn't cover `wt hook --help`) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
68ee0a327b |
feat(hook): expose pr_number and pr_url to PR/MR worktree hooks (#2300)
## Summary `pr_number` and `pr_url` are now first-class template variables for hooks running on PR/MR-created worktrees (`wt switch pr:N` / `mr:N`). Previously the ForkRef code path injected `pr_*` (GitHub) or `mr_*` (GitLab) extras into the pre-start template context, but those names weren't in the validation allowlist — any user hook referencing them was rejected before the hook could run, so the feature was unreachable. This PR canonicalizes on a single pair (`pr_number`/`pr_url`) for both platforms, threads it through the validation scope, plumbs it into post-switch and post-start hooks via `SwitchResult::Created`, and documents it in CLI help (which auto-syncs to docs and skill reference). ## Notable decisions - **One canonical pair, not two.** GitHub and GitLab both populate `pr_number`/`pr_url`; no `mr_*` aliases. Keeps the template surface low-cardinality. - **Symmetric across pre/post.** The `hook_extras` table accepts `pr_number`/`pr_url` for `pre-switch`/`post-switch` and `pre-start`/`post-start`. Pre-switch never actually populates them (PR resolution hasn't happened yet at that point), but grouping pre/post pairs matches the existing `base`/`target` convention and avoids a one-off scope arm. - **Threaded through `SwitchResult::Created`.** Earlier drafts only wired pre-start; post-switch and post-start were silently dropping the data. The Option fields on `SwitchResult::Created` carry it forward to background hooks via `switch_extra_vars`. ## Test coverage - `test_validate_template_scope_rejects_out_of_scope_vars` — accepts `pr_number`/`pr_url` for pre-start, rejects for pre-merge. - `test_switch_pr_hooks_see_pr_vars` — fork-PR scenario with mocked `gh`; pre-start, post-start, and post-switch hooks each write a marker file and the test asserts all three observe `pr_number=42 pr_url=...`. ## Drive-by fix: stub cargo in --source flag test Last commit replaces `cargo run --bin wt` in `test_source_flag_forwards_errors` with a stub-cargo shell script that execs the existing wt binary directly. Real cargo unlinks and re-links `target/debug/wt` on every invocation (~3.9% non-existence window measured locally), racing against any concurrent test that `spawn(target/debug/wt)` and producing the long-standing ENOENT flake in `test_wrapper_switch_with_hooks` on Linux CI. Same end-to-end coverage of the `--source` branch; runs in 2s instead of 30–60s. > _This was written by Claude Code on behalf of Maximilian Roos_ --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
1fd6eaba20 |
fix(hook): inject target symmetrically on switch/create/start (#2295)
`pre-switch` already injected `target` (and conditionally
`target_worktree_path`), but `post-switch`, `pre-start`, and
`post-start` only injected `base`. A user writing `{{ target }}` in
`post-start` expecting the documented "target = bare vars" semantics
would hit an undefined-var error at runtime — runtime and docs
disagreed.
This injects `target` (skipped for detached HEAD) and
`target_worktree_path` (always present on switch/create since the
worktree just got created) in `handle_switch.rs` post-switch and
`worktree/switch.rs` pre-start (both `Regular` and `ForkRef` arms). The
commit hook injection (one-worktree, `target` only) is left untouched —
integration target may not have a worktree, and the existing behavior
already matches the design.
After merging main, the scope-aware validation refactor (#2288) had
landed with `PreStart`/`PostStart` whitelisting only
`base`/`base_worktree_path`. Adds `target`/`target_worktree_path` to
that whitelist so validation accepts the vars the runtime now injects,
and updates the stale `PreSwitch | PostSwitch` comment (post-switch now
injects target too). The
`test_validate_template_scope_rejects_out_of_scope_vars` case that
checked `{{ target }}` rejection in `PreStart` swaps to `{{ base }}` in
`PreMerge` — still proves scope rejection since `base` remains
switch/start-only.
Docs: adds a commit row to the `base`/`target` table scoped to
merge/squash context (standalone `wt step commit` doesn't inject
`target`), and rewrites the "only in two-worktree hooks" sentence to
describe each conditional var accurately.
Tests: adds one post-start case (create path) and one post-switch case
(existing-worktree switch path) that assert `{{ target }}` resolves to
the destination branch. The `post_create_upstream_template` snapshot
picks up `target` / `target_worktree_path` in its "Available variables"
error message — expected side effect of making them always-injected on
the switch path.
Out of scope: `pr_number`/`mr_number`/`pr_url`/`mr_url` (undocumented
ForkRef-only vars, separate concern).
> _This was written by Claude Code on behalf of max-sixty_
---------
Co-authored-by: Claude <noreply@anthropic.com>
|
||
|
|
1a4eea3c03 |
feat(cli): promote --yes to a global flag (#2279)
## Summary `-y, --yes` is now a top-level global clap flag. It moves off every subcommand's clap args and lives once on `Cli`, so `wt -y <anything>`, `wt <anything> --yes`, and `wt --yes <anything>` all skip any approval or confirmation prompt for that invocation. ## Why Follow-up to the top-level alias dispatch (#2266). The long-term plan is to unify approval-bypass behavior — `-y` should be a true global rather than duplicated across switch, remove, merge, commit, squash, prune, all ten hook subcommands, shell install/uninstall, plugin install/uninstall, and config update. A single canonical flag removes ~50 lines of duplicated clap definitions and one source of drift. ## Call-site survey Approval and confirmation prompt sources, as surfaced by `rg -l 'approve_|requires_approval|confirm_'`: - `approve_hooks` / `approve_hooks_filtered` — reads `ctx.yes`, already threaded via `CommandContext::new(..., yes)`. No change needed; the global now feeds those call sites from `handle_*_command` in `main.rs`. - `approve_command_batch` (merge, config `hook approvals add`) — takes `yes: bool`. Receives the global. - `approve_alias_commands` — takes `yes: bool`. Receives `global_yes || opts.yes` (see "Alias compat" below). - `handle_configure_shell` / `handle_unconfigure_shell` / `handle_config_update` / `handle_claude_install[_statusline]` / `handle_claude_uninstall` / `handle_opencode_install` / `handle_opencode_uninstall` — take `yes: bool` for confirmation-prompt bypass. All receive the global. ## Alias compat `AliasOptions` is hand-rolled (not clap) so it doesn't conflict with the clap global. Its post-alias `--yes` parsing is intentionally preserved here — `run_alias` does `let skip_approval = global_yes || opts.yes;` so both `wt -y deploy` and `wt deploy --yes` work unchanged. Removing the post-alias form is a separate cleanup, tracked by the user's long-term simplification plan. ## Navigating the diff - `src/cli/mod.rs` — new `yes: bool` on `Cli` with `global = true`, `short = 'y'`, `help_heading = "Global Options"`, `display_order = 103` (slots after `-v`). Removed the per-command `yes` field from `SwitchArgs`, `RemoveArgs`, `MergeArgs`. - `src/cli/step.rs` — removed `yes` from `CommitArgs`, `SquashArgs`, and `StepCommand::Prune`. Also dropped `help_heading = "Automation"` from the four step subcommands where the group was left with a single flag (`commit`, `squash`, `for-each`, `prune`); those flags now render under default Options instead of a single-item group. - `src/cli/hook.rs` — removed `yes` from all ten hook subcommands (`pre-switch`, `post-switch`, `pre-start`, `post-start`, `pre-commit`, `post-commit`, `pre-merge`, `post-merge`, `pre-remove`, `post-remove`). `--yes` stays in `KNOWN_HOOK_LONG_FLAGS` so the shorthand rewriter still recognizes it as a real flag rather than a template variable. - `src/cli/config.rs` — removed `yes` from `ConfigShellCommand::Install/Uninstall`, `ConfigCommand::Update` (and its now-redundant `conflicts_with = "yes"` on `--print`), `ConfigPluginsOpencodeCommand::Install/Uninstall`, `ConfigPluginsClaudeCommand::Install/Uninstall/InstallStatusline`. - `src/main.rs` — `dispatch_command` takes `yes: bool`; each `handle_*_command` accepts and threads it. `Cli` destructure adds `yes`. - `src/commands/alias.rs` — `try_alias`, `step_alias`, `run_alias` take `global_yes: bool`. `run_alias` OR's with `opts.yes` before calling `approve_alias_commands`. - `src/commands/custom.rs` — `handle_custom_command` accepts and passes the global to `try_alias`. - `docs/content/` + `skills/worktrunk/reference/` — auto-generated from `--help-page`; the global appears under "Global Options" on every subcommand. - `tests/integration_tests/approval_ui.rs` — six new tests: `test_global_yes_before_subcommand`, `test_global_yes_for_hook`, `test_global_yes_for_alias`, `test_post_alias_yes_still_works`, `test_global_yes_for_step_alias`, `test_global_yes_on_command_without_approval`. ## Notes - Does not remove `AliasOptions::yes` — deferred cleanup per the task brief. - Snapshot churn (~27 help files) is the expected fallout of adding a global flag; each subcommand's `--help` now shows `-y, --yes` under Global Options. ## Test plan - [x] 3242 tests pass (`cargo run -- hook pre-merge --yes`) - [x] Lints clean (clippy, cargo fmt, pre-commit) - [x] Doc sync test passes after regeneration - [x] New approval_ui tests cover before-subcommand, after-subcommand, alias, post-alias, step-alias, and no-approval positions > _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> Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com> |
||
|
|
64e1b4efb9 | feat(cli): move approvals subcommand from hook to config (#2282) | ||
|
|
ea9a52dfdc |
feat(hooks): enable concurrent execution in pre-* pipeline blocks (#2249)
Pipeline blocks (`[[pre-start]]`, `[[pre-merge]]`, etc.) now run their concurrent commands in parallel for foreground (pre-*) hooks, matching the existing behavior in post-* hooks and aliases. The deprecated single-table form (`[pre-start]`) remains serial. Moves the `concurrent` flag from a function parameter on `execute_pipeline_foreground` to a per-step field on `ForegroundStep`, so mixed configs (e.g., user config is deprecated table, project config is pipeline) get correct per-step behavior. `CommandConfig` now tracks whether it was deserialized from a pipeline form (seq visitor) vs a table form (map visitor), so even a single `[[hook]]` block is correctly identified as a pipeline. Also cleans up the "How it works" subsection from the hook docs — the content was either redundant with surrounding text or now incorrect (the pre-* serial restriction). > _This was written by Claude Code on behalf of Maximilian Roos_ Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
d4c80ca970 |
docs(hook): use concurrent form in multi-key hook examples (#2248)
The `wt hook` page and tips/merge examples paired two `[[pre-*]]` blocks with one command each, which runs them serially. For independent commands like `cargo fmt --check` + `cargo clippy`, that taught the wrong lesson. Collapsed each pair into a single `[[pre-*]]` block with both keys (runs concurrently). The multi-entry `[pre-*]` table form is deprecated (`src/config/deprecation.rs`), so the single-element array form is the non-deprecated way to express concurrent commands. Affected: `src/cli/mod.rs` (merge local-CI, hook progressive-validation, hook-type-examples), `docs/content/tips-patterns.md` (local CI gate). Generated docs, skill mirrors, and help snapshots synced. > _This was written by Claude Code on behalf of @max-sixty_ Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
3e1b351ac1 |
feat(log): split -vv output into trace.log + output.log, drop -vvv (#2201)
## Motivation At `-vv`, raw subprocess bodies were bounded (200 lines / 64 KB) on both stderr and `verbose.log`, with full output only available by rerunning at `-vvv`. Large captures (notably `git log -p` piped into `patch-id` during `wt list`) would routinely flood stderr with elision markers and force a second run just to see what was elided. ## Change The verbosity map is now `-v` (info) and `-vv` (debug); any `-v` count above 2 collapses to `-vv`. Captured subprocess stdout/stderr fan out through two log targets in `src/shell_exec.rs`: - `SUBPROCESS_TERMINAL_TARGET` → bounded preview on stderr, mirrored to `.git/wt/logs/trace.log` (new, replaces `verbose.log`) - `SUBPROCESS_FULL_TARGET` → uncapped body to `.git/wt/logs/output.log` (new), never stderr `src/log_files.rs` (renamed from `src/verbose_log.rs`) owns both file sinks behind a `LogSink` type and a `route(target)` helper that is the single source of truth for sink selection. `src/main.rs`'s env_logger format closure matches on the `Route` enum and emits once per sink. Diagnostic reports embed `trace.log` and reference `output.log` by path — multi-MB raw bodies would swamp a bug report. ## Fallback path When `RUST_LOG=debug` is set without `-vv`, neither sink is active. `FULL` records drop and the `TERMINAL` preview reaches stderr as before — preserving the bounded-stderr guarantee. The elision marker phrases its hint based on whether `output.log` was opened, so users in the fallback path see `rerun with -vv for full output` rather than a pointer to a file that doesn't exist. ## Key files - `src/log_files.rs` — new module; `LogSink`, `TRACE`, `OUTPUT`, `route`. - `src/shell_exec.rs` — two `pub const` targets, `log_output` emits on both, elision hint switches on `OUTPUT_LOG_AVAILABLE`. - `src/main.rs` — verbosity map + format closure. - `src/diagnostic.rs` — template splits inlined `trace.log` from referenced `output.log`. - `src/commands/config/state.rs` — diagnostic file recognition for the new names. ## Testing - `test_vv_splits_full_and_bounded_output` — `[wt-trace]` in `trace.log`, raw stdout in `output.log`, no trace records in `output.log`. - `test_vv_bounded_on_stderr_full_in_output_log` — 250-ref packed-refs trip the elision cap; asserts the marker on stderr + `trace.log`, full content in `output.log` without elision. - `test_rust_log_debug_fallback_without_vv` — no log files created at `-v 0 + RUST_LOG=debug`; bounded preview reaches stderr. > _This was written by Claude Code on behalf of max-sixty_ --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
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>
|
||
|
|
1e51526601 |
docs(hook): tighten pre-* table-form deprecation note (#2154)
Collapses the three-sentence deprecation note above Project vs user hooks into a single sentence. Now that the preceding section teaches `[[hook]]` blocks as the canonical pipeline form (PR #2149), the note can name them directly and drop the serial-vs-concurrent explanation — that mechanics detail is already covered in Pipeline Ordering further down. Before: > For pre-* hooks, prefer pipeline form over table form. Table form for pre-* hooks currently runs serially rather than concurrently — this inconsistency is deprecated and will change in a future version. Using pipeline form avoids the upcoming behavior change. After: > Table form for pre-* hooks is deprecated and its behavior will change in a future version — use `[[hook]]` blocks instead. > _This was written by Claude Code on behalf of Maximilian_ Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
2a6389a092 |
Centralise [wt-trace] emitter and fix -vv log verbosity (#2146)
## Summary
Three changes to worktrunk's logging conventions, all motivated by `wt
list -vv` dumping raw `git diff-tree -p` bodies into the log stream at
debug level:
**1. `src/trace/emit.rs` owns the `[wt-trace]` grammar.** Previously the
grammar was emitted via ad-hoc `log::debug!("[wt-trace] ...")` format
strings in `shell_exec.rs`, with `src/trace/parse.rs` silently defining
it by how it parsed. `trace_instant`, `log_command_result`, the
`TRACE_EPOCH` static, and `thread_id_number` moved into the new emitter
module so `parse.rs` and the producer share one source. Wire format
byte-identical; `wt-perf` parsing unchanged.
**2. Level discipline.** `-v` → Info, `-vv` → Debug, `-vvv` → Trace.
Previously `-v` didn't touch the `log` crate at all and `-vvv` didn't
exist. The LLM prompt dump (`src/llm.rs`) and captured subprocess
stdout/stderr (`log_output` in `shell_exec.rs`) moved to `log::trace!`,
so `-vv` stops spilling thousand-line diff bodies and full LLM prompts.
**3. Bounded `log_output`.** At Debug, each stream caps at 200 lines /
64 KB with `… (N more lines, M bytes elided — use -vvv for full
output)`. At Trace, uncapped.
## Reviewer navigation
- **New file**: `src/trace/emit.rs` — the single-source emitter.
`command_completed`, `command_errored`, `instant` plus `trace_epoch` /
`now_us` / `thread_id` helpers.
- **Delete/delegate**: `src/shell_exec.rs` loses its duplicate
`TRACE_EPOCH` + `trace_epoch` + `thread_id_number`, and the four
`log::debug!("[wt-trace] …")` branches collapse into two calls into
`trace::emit`. `log_output` gains `log_stream_full` (Trace) and
`log_stream_bounded` (Debug) helpers.
- **Level map**: `src/main.rs:init_logging` has the new match on
`verbose_level`.
- **Help + test snapshots**: the `--verbose` help text change in
`src/cli/mod.rs:249` drives the large auto-synced snapshot / docs /
skill-reference diff.
- **Test rename**: `tests/integration_tests/diagnostic.rs` —
`test_v_does_not_enable_logging` → `test_v_does_not_write_log_files`
(the old name became inaccurate now that `-v` enables Info logging on
stderr).
## Testing
- 969 library unit tests pass.
- Integration tests for `diagnostic`, `step_alias`, `test_help` pass (82
tests).
- Lints clean.
> _This was written by Claude Code on behalf of max-sixty_
---------
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>
|