Commit Graph

165 Commits

Author SHA1 Message Date
Maximilian Roos 491108a782 Document hooks in user config (#1845)
The user config (`~/.config/worktrunk/config.toml`) supports hooks via
`OverridableConfig.hooks`, but the user config documentation didn't
mention them. The project config docs had a hooks section with the full
TOML format reference — this adds a comparable section to user config
and deduplicates the project config side.

- Added `### Hooks` section to user config docs with the three formats
(string, named table, pipeline) and user-vs-project differentiation
- Replaced the duplicate TOML block in project config hooks with a
cross-reference to the user config section

> _This was written by Claude Code on behalf of @max-sixty_

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-31 18:33:45 -07:00
Maximilian Roos ca5980aee8 Remove double-commenting from project config example (#1836)
Same treatment as #1832: move prose descriptions out of TOML code blocks
so they don't produce `# #` lines in the generated `wt.example.toml`.
Also uncomments `hostname` field with an "Example:" note.

> _This was written by Claude Code on behalf of @max-sixty_

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-31 17:50:14 -07:00
Maximilian Roos 3292b4fe08 Flatten "Setting overrides" into parent section, remove experimental (#1839)
The "Setting overrides" subsection was the only child of "User
project-specific settings" — unnecessary nesting. Merged the override
description into the parent paragraph and dropped the `[experimental]`
tag.

> _This was written by Claude Code on behalf of @max-sixty_

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-31 00:44:08 -07:00
Maximilian Roos 75dd894284 Auto-generate wt.example.toml from mod.rs source (#1835)
The project config example file (`dev/wt.example.toml`) was
hand-maintained with no sync tests, while the user config example had a
robust auto-generation pipeline. This brings both to parity — `mod.rs`
is the single source of truth, sync tests generate both example files,
and CI catches drift.

Hooks documentation (~70 lines of formats, template variables, and
per-type examples) replaced with a pointer to `wt hook --help` plus a
quick-reference showing all three hook formats (string, named table,
pipeline). The example file now focuses on project-specific settings:
`list.url`, `forge`, `step.copy-ignored`, and aliases.

The sync test infrastructure is refactored from a user-config-specific
function into shared `extract_config_section` and
`assert_config_example_in_sync` helpers that both tests call.

> _This was written by Claude Code on behalf of @max-sixty_

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-31 00:09:02 -07:00
Maximilian Roos d0e3c2dee0 Show defaults uncommented in config example (#1832)
The example config had all TOML values double-commented (`# #`), making
them look disabled-within-disabled. Config code blocks in the source
markdown now show actual default values uncommented, producing clean
single-commented lines in the generated `config.example.toml`.

Other fixes: `switch.no-cd` default was shown as `true` but code
defaults to `false`; `step.copy-ignored.exclude` was shown as
`[".cache/", ".turbo/"]` but defaults to `[]`.

> _This was written by Claude Code on behalf of @max-sixty_

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-30 23:26:03 -07:00
Maximilian Roos b33cc8e9d6 Simplify forge detection with [forge] config section (#1826)
SSH host aliases (`git@work-ssh:owner/repo`) corrupt the hostname in
remote URLs but never the path. The previous forge detection code tried
to work around this by searching all remotes, auto-detecting GHE
hostnames, and maintaining multiple resolution strategies. This PR
replaces all of that with a simpler design: derive owner/repo from URL
paths, and let users set `forge.platform` / `forge.hostname` for corner
cases.

## Key changes

**New `[forge]` config section** with `platform` and `hostname` fields.
`ci.platform` is deprecated with migration support. Example configs and
inline help examples updated.

**`fetch_pr_info`** now builds literal API paths from the primary
remote's raw URL instead of relying on `gh`'s placeholder resolution.
`--hostname` only passed when explicitly configured.

**`platform_for_repo`** checks config > branch's remote > primary
remote. No longer searches all remotes. Loads config internally instead
of taking a `platform_override` parameter.

**`find_remote`** matches by owner/repo only (no host required),
handling SSH aliases where the local hostname differs from the API
hostname.

**`CiBranchName::from_branch_ref`** uses `split_once('/')` instead of
iterating `all_remote_urls()`, removing a git call per branch during `wt
list`.

## Deleted

- `resolve_gh_hostname` (GHE auto-detection heuristic)
- `find_forge_remote` (search all remotes by predicate)
- `all_remote_urls()` loop in branch name parsing
- `platform_override` parameter threading through callers

The spec in `src/git/repository/remotes.rs` documents the full design.

> _This was written by Claude Code on behalf of max-sixty_

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-30 21:10:32 -07:00
Maximilian Roos 4047079fb2 Wrap prompt openers in <task> tags and reword squash template (#1794)
Wraps the opening instruction line in all three LLM prompt templates
(commit, squash, summary) in `<task>` XML tags — the only content that
wasn't already in XML tags. Also rewords the squash template opener from
"Combine these commits into a single commit message" to "Write a commit
message for the combined effect of these commits", shifting the framing
from mechanical concatenation to synthesis.

> _This was written by Claude Code on behalf of @max-sixty_

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-29 16:01:12 -07:00
Maximilian Roos 5c1672932f docs: remove deprecated post-create from documentation (#1776)
Replace all `post-create` references with `pre-start` across
documentation, skills, example config, and test names. The Rust
deprecation handling code (migration, alias, config parsing) remains
intact for users with existing configs.

Also removes `post-create` from the `wt hook show` value_parser — it was
listed as a valid hook type for display even though it's been
deprecated.

> _This was written by Claude Code on behalf of @max-sixty_

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-27 17:38:57 -07:00
Shun Kakinoki 439d79b614 feat(config): add copy-ignored exclude patterns (#1667)
Add configurable exclusions to `wt step copy-ignored`, and make the
command safer by skipping known tool-state directories by default.

- `wt step copy-ignored` always skips built-in excluded directories (VCS
metadata like `.jj/`, `.hg/` and tool-state like `.conductor/`,
`.worktrees/`)
- Users can add more excludes with `[step.copy-ignored] exclude = [...]`
in user config, per-project user overrides, or project config
(`.config/wt.toml`)
- All exclusion sources are combined; scalar config values replace,
everything else appends (global first)

Closes #1653

> _This was written by Claude Code on behalf of maximilian_

---------

Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Maximilian Roos <m@maxroos.com>
2026-03-25 14:30:10 -07:00
Maximilian Roos 9ad642f874 feat: use append semantics for alias merging (#1724)
Per-project alias merging changes from "override on collision" to
"append" semantics, matching how hooks merge. When both global and
per-project user configs define the same alias name, both commands run
in sequence (global first, then per-project).

The alias value type changes from `String` to `CommandConfig` — the same
type hooks use. This stores each command separately and enables the
named-table TOML format for multi-command aliases. Key changes:

- `merge_alias_maps()` and `UserConfig::aliases()` use
`CommandConfig::merge_append()` on collision
- `step_alias()` iterates over `CommandConfig.commands()`, running each
command separately (fail-fast on first failure)
- `approve_alias_commands()` approves each project-config command
individually via the existing batch approval system
- Project-vs-user merge stays as override (user wins) for security —
only the global-vs-per-project *user* merge uses append

> _This was written by Claude Code on behalf of @max-sixty_

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-25 11:34:57 -07:00
Maximilian Roos efad9312db feat!: rationalize hooks — rename post-create, add post-commit, background post-merge (#1679)
Implements the hook rationalization from #1670, establishing a symmetric
`pre-` (blocking) / `post-` (background) pattern for every lifecycle
event.

## Changes

**Rename `post-create` → `pre-start`**: The old name suggested it ran
*after* creation, but it actually runs *before* `post-start` as a
blocking dependency step. Both names accepted for one release cycle —
config deprecation detection, migration file generation, `wt config
update` support, and CLI alias all in place. `merge_with` folds old-name
hooks into new-name so cross-config combinations don't silently drop
hooks.

**Add `post-commit` hook**: New background hook firing after successful
commits (including squash commits), completing the commit lifecycle
pair. Included in approval batches for `wt step commit`, `wt step
squash`, and `wt merge`.

**Change `post-merge` to background**: Was blocking with `Warn`
strategy, now runs in background like all other `post-` hooks.
`--foreground` flag available for debugging.

The hook table is now a clean symmetric grid:

| Event | `pre-` (blocking) | `post-` (background) |
|-------|-------------------|---------------------|
| start | `pre-start` | `post-start` |
| switch | `pre-switch` | `post-switch` |
| commit | `pre-commit` | `post-commit` |
| merge | `pre-merge` | `post-merge` |
| remove | `pre-remove` | `post-remove` |

## Testing

868 lib + 483 bin + 1227 integration tests pass, all 13 lint checks
pass. The deprecation has 23 dedicated unit tests covering detection at
all three config scopes, migration, empty table filtering, cross-config
merge safety, and integration with `wt config show` / `wt config
update`.

Closes #1670

> _This was written by Claude Code on behalf of max-sixty_

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-23 00:33:30 -07:00
Maximilian Roos 7339e4b283 docs: add bare repository worktree-path example and layout guide (#1664)
## Summary

- Add bare repo `worktree-path` example (`{{ repo_path }}/../{{ branch |
sanitize }}`) to config docs alongside existing examples
- Update `repo_path` variable description to note bare repo behavior
- Expand "Bare repository layout" section in tips-patterns: explains why
bare repos suit worktree workflows and documents the `git clone --bare
<url> project/.git` setup

## Test plan

- [x] `cargo run -- hook pre-merge --yes` passes
- [x] Doc sync tests pass
(`test_command_pages_and_skill_files_are_in_sync`,
`test_config_source_generates_example_toml`)
- [x] Help snapshots updated and accepted

> _This was written by Claude Code on behalf of @max-sixty_

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-22 16:07:41 -07:00
worktrunk-bot ce9ea1a6b6 refactor: consolidate .git/wt-* paths under .git/wt/ and stage removed worktrees in trash (#1583)
## Summary

- Moves the rename-based staging directory from a visible sibling path
(`project.wt-removing-<timestamp>`) into `.git/wt/trash/`, hiding it
from the user's workspace
- Consolidates all worktrunk-managed `.git/wt-*` directories under a
single `.git/wt/` parent
- Adds `Repository::wt_dir()` accessor as the single root for all
worktrunk state
- Renames `wt-relocate-tmp` to `wt/relocate-staging` for consistency
with `wt/promote-staging`
- Falls back to legacy `git worktree remove` if the trash directory
can't be created
- The `.git/` directory is always on the same filesystem as worktrees,
so the instant rename guarantee is preserved

## Path migration

| Before | After |
|--------|-------|
| `.git/wt-logs/` | `.git/wt/logs/` |
| `.git/wt-cache/summaries/` | `.git/wt/cache/summaries/` |
| `.git/wt-cache/ci-status/` | `.git/wt/cache/ci-status/` |
| `.git/wt-promote-staging/` | `.git/wt/promote-staging/` |
| `.git/wt-relocate-tmp/` | `.git/wt/relocate-staging/` |
| (new) `.git/wt/trash/` | Staging for background removal |

## Context

Users reported confusion when seeing `.wt-removing-*` directories in
their workspace after `wt remove` (#1572). By staging in
`.git/wt/trash/` instead, the directory is completely hidden — even if
the background `rm -rf` is slow or gets interrupted.

The `.git/wt-*` sibling directories were also consolidated into
`.git/wt/` for tidiness per review feedback.

## Test plan

- [x] Unit tests for `generate_removing_path` and
`build_remove_command_staged` updated and passing
- [x] All 105 remove-related integration tests passing
- [x] `test_remove_background_path_gone_immediately` — verifies instant
removal still works
- [x] `test_remove_background_fallback_on_rename_failure` — verifies
fallback when staging path is blocked
- [x] `test_remove_stale_staging_dir_from_crashed_removal` — verifies
stale dirs land inside `.git/`
- [x] All 2496 tests pass (lib + bin + integration)
- [x] Help snapshots, doc sync, and lint checks all pass

Closes #1572

🤖 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.6 <noreply@anthropic.com>
Co-authored-by: Maximilian Roos <5635139+max-sixty@users.noreply.github.com>
2026-03-17 13:30:49 -07:00
Sirio Balmelli 799aab0eb9 merge: add --no-ff flag for merge commit (semi-linear history) (#1438) 2026-03-16 18:15:23 -07:00
Maximilian Roos 7512d8695e Add opencode, llm, and aichat to example config (#1533)
Follow-up to #1531. These three tools were only documented in
`llm-commits.md` — adding them as double-commented entries in the config
example makes them discoverable via `wt config create` and keeps all
tool commands in one place, verified by the existing sync test.

> _This was written by Claude Code on behalf of @max-sixty_

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-14 17:41:29 -07:00
Maximilian Roos 5a2895cd03 Add LLM tool commands to example config as single source of truth (#1531)
The Claude/Codex recommended commands were duplicated across 4 locations
(Rust code, llm-commits.md, config.md, Taskfile) with no automated sync
— they were already drifting (missing `CLAUDECODE=` prefix, missing `-c
system_prompt=''`).

Now the double-commented entries in `config.example.toml` are the single
source of truth. `recommended_config()` parses them at runtime via
`include_str!` + `LazyLock`, and a new sync test verifies
`llm-commits.md` matches. Also fixes the existing drift in all copies.

> _This was written by Claude Code on behalf of @max-sixty_

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-14 16:08:53 -07:00
Maximilian Roos 7834271528 Unify timeout model for list and switch picker (#1515)
The picker had its own hardcoded 200ms per-command timeout default plus
a `WORKTRUNK_PICKER_BUDGET_MS` env var escape hatch, while `wt list` had
a separate experimental `[list] timeout-ms` for per-task timeouts. This
consolidates into a shared config model with clear separation between
per-task and wall-clock timeouts.

**New config model:**
- `[list] task-timeout-ms` — per-task command timeout, shared by both
list and picker (default: disabled)
- `[list] timeout-ms` — wall-clock budget for list's collect phase
(default: disabled)
- `[switch.picker] timeout-ms` — wall-clock budget for picker (default:
500ms, was 200ms per-command)

The picker's old per-command timeout goes away — the wall-clock budget
is sufficient. `collect_deadline` moves from a `collect()` parameter
into `ShowConfig::Resolved` where it belongs. Removes the
`WORKTRUNK_PICKER_BUDGET_MS` env var and a dead benchmark that tested a
non-functional env var.

Note: `[list] timeout-ms` changes semantics from per-task to wall-clock
budget. The old field was marked "(Experimental)" and not shown in
user-facing docs.

> _This was written by Claude Code on behalf of max-sixty_

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-14 01:08:36 -07:00
Maximilian Roos 0760817af7 feat(docs): style experimental markers as badge pills (#1499)
Replaces plain `[experimental]` text in web docs with styled pill badges
— small uppercase labels with a subtle border that read as metadata
rather than emphasis.

Canonicalizes all experimental markers to `[experimental]` (was a mix of
`[experimental]`, `(experimental)`, `(Experimental)`). One marker format
in cli.rs, one `.replace()` in the post-processing.

Also renames `colorize_ci_status_for_html` → `post_process_for_html` (it
handles badges and URLs too), adds a module docstring documenting the
full `--help-page` pipeline, and fixes a latent double-application of
the post-processor on subdoc content.

> _This was written by Claude Code on behalf of @max-sixty_

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-14 00:12:22 -07:00
Jarryd Tilbrook 00eb04a042 feat(switch): add no-cd config option (#1401) 2026-03-13 07:42:40 -07:00
Maximilian Roos 55bc9188b4 feat(step): add alias command for user-defined command templates (#1348)
## Summary

- Adds `wt step <alias-name>` for running user-defined command templates
configured in `[aliases]` sections of user or project config
- Aliases support the same template variables as hooks (`{{ branch }}`,
`{{ worktree }}`, etc.) plus custom `--var KEY=VALUE` variables
- Project-config aliases require the same command approval flow as
project hooks; user-config aliases are trusted. `--dry-run` skips
approval since it's a read-only preview
- Alias names matching built-in step commands are filtered from the
"available" list in error messages (shadowed by the built-in)
- Introduces `Phase` enum (`Hook(HookType)` | `Alias`) replacing the old
`hook_type` + `phase_override` pattern in the approval system
- Renames `HookCommand` to `ApprovableCommand` since it now covers both
hooks and aliases

## Test plan

- [x] Unit tests for `AliasOptions::parse` (name-only, --dry-run, --yes,
--var, empty key rejection, positional arg rejection)
- [x] Integration tests for alias execution, dry-run (without --yes),
exit code propagation
- [x] Integration tests for shadowing (filtered from available list)
- [x] Integration tests for user/project config merging
- [x] Integration tests for approval flow (project prompts, user skips,
override skips, already-approved, --yes bypass, decline)
- [x] Unit tests for `merge_alias_maps` coverage
- [x] Sync test: `BUILTIN_STEP_COMMANDS` matches actual `StepCommand`
clap variants

> _This was written by Claude Code on behalf of max-sixty_

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-07 21:46:41 -08:00
Maximilian Roos 8587b66080 docs: update stale documentation and fix version snapshot filter (#1113)
## Summary

- Remove stale "Approved hook commands" subsection from config docs —
approvals moved to `approvals.toml` and no longer belong in the user
config section
- Fix version regex filters in test infra that failed to handle `-dirty`
suffix on bare hash versions (when `git describe` has no reachable
tags), making snapshots non-deterministic
- Add `commands.jsonl` documentation to `wt config state logs` help text
- Add "Documentation accuracy" checklist to PR review skill
- Remove deleted `test_clone_bare_repo_list_no_status_errors` (covered
by #1112's `BareRepoTest`)
- Minor cleanup: remove "select" from CLAUDE.md command list, remove
stale pre-release-replacements, update skill version marker

## Test plan

- [x] All 1084 tests pass locally
- [x] Pre-commit lints pass
- [x] Snapshot tests reviewed — version snap now shows `wt [VERSION]`
(not `wt [VERSION]-dirty`), config help snaps reflect removed approvals
section
- [x] Doc sync test passes
(`test_command_pages_and_skill_files_are_in_sync`)

> _This was written by Claude Code on behalf of @max-sixty_

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-19 10:02:40 -08:00
Maximilian Roos 91a505603a feat: add Summary column to wt list --full (#1100)
## Summary

- Adds an LLM-generated Summary column to `wt list --full`, showing a
one-line description of each branch's changes
- Extracts shared summary module (`src/summary.rs`) from picker-specific
code for reuse across `wt list` and `wt switch`
- Summary and Message are both flexible columns — Summary expands first
(10→70 chars), then Message gets remaining space (10→100 chars)
- Gated on `[list] summary = true` config (disabled by default — each
branch's diff is sent to the configured LLM) plus `[commit.generation]`
and `--full`
- Labeled as experimental throughout docs

## Test plan

- [x] 539 unit tests pass
- [x] 1072 integration tests pass (including 9 new layout tests for
flexible Summary/Message allocation)
- [x] Pre-commit lints pass
- [x] Manual: `wt list --full` with LLM configured + `summary = true` —
Summary column appears
- [x] Manual: `wt list --full` without `summary = true` — no Summary
column
- [x] Manual: `wt list` (no `--full`) — no Summary column
- [x] Manual: `wt switch` picker — summary tab still works

> _This was written by Claude Code on behalf of max-sixty_

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-18 16:25:46 -08:00
Maximilian Roos 41eaf85aad feat: add [switch.picker] config with configurable timeout (#1087)
## Summary

- Migrate `[select]` config → `[switch.picker]` with backward compat and
deprecation guidance
- Add `timeout-ms` to `[switch.picker]` (default: 200ms, `0` = no
timeout) replacing the hardcoded 500ms picker command timeout
- Add `test_config_docs_include_all_sections` to catch undocumented
config sections

## Test plan

- [x] 530 unit tests pass
- [x] 1058 integration tests pass
- [x] All lints pass (pre-commit)
- [x] Help text snapshots updated
- [x] Docs, example config, and skills auto-synced
- [x] Deprecation detection and TOML migration tested (including
snapshot)

> _This was written by Claude Code on behalf of @max-sixty_

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-17 14:59:29 -08:00
Maximilian Roos 74e3d6baf8 feat(switch): add AI summary preview tab (#1049)
* feat(switch): add AI summary preview tab (tab 5)

Add a fifth preview mode to `wt switch` that shows AI-generated branch
summaries using the configured [commit.generation] LLM command. Summaries
use commit-message format (imperative subject + body) and render through
the standard markdown help renderer for consistent styling.

- Background thread generates summaries in parallel for all branches
- Disk cache in .git/wt-cache/summaries/ with hash-based invalidation
- Graceful fallback: config hint when LLM not configured, dim "no changes"
  for default branch
- Shortened tab labels to fit 5 tabs: 1:diff | 2:log | 3:main | 4:upstream | 5:summary

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: resolve merge conflicts from jj revert

After merging main (which reverted jj support), fix two issues:
- Resolve config to access commit_generation (handle_select no longer
  receives resolved config directly)
- Restore pub(crate) visibility on execute_llm_command

Co-Authored-By: Claude <noreply@anthropic.com>

* test: add cache and rendering tests for summary module

Add tests for cache round-trip, hash invalidation, file path
sanitization, directory structure, and pre-styled text rendering
to improve codecov/patch coverage.

Co-Authored-By: Claude <noreply@anthropic.com>

* test: add integration test for summary tab and expand cache tests

- Add PTY test for tab 5 showing config hint when LLM not configured
- Add cache round-trip, invalidation, sanitized path, and dir tests
- Add pre-styled text rendering test for dim "no changes" messages

Co-Authored-By: Claude <noreply@anthropic.com>

* test(summary): add coverage for diff computation and LLM generation

Add 10 new unit tests covering `compute_combined_diff`,
`generate_summary`, `generate_all_summaries`, and the single-line
`render_summary` path. Uses real temp git repos with shell-stub LLM
commands (following existing patterns from merge integration tests).

Also refactors test helpers to share git command setup and repo
initialization, eliminating duplication between test cases.

Co-authored-by: Claude <noreply@anthropic.com>

* fix(summary): handle missing default branch + add coverage tests

- compute_combined_diff no longer bails when default_branch() returns
  None — wraps branch diff in if-let, preserving working tree diff
- Fix test to use exotic branch name so default_branch() actually
  returns None (infer_default_branch_locally checks "main"/"master"/etc)
- Add unit tests for items.rs Summary tab paths (main worktree, feature
  branch, cache hit/miss, compute_preview delegation)
- Add error path tests for write_cache (unwritable path, permission
  failure)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(summary): address PR review feedback

- Remove is_main() shortcut from compute_summary_preview — was checking
  main worktree (git concept) not default branch (different concept)
- Add unicode visual cues to tab labels: 1:diff±, 3:main↕, 4:upstream⇅
- Move summary_items clone closer to its consumer in mod.rs

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(ci): revert unicode tab cues — ambiguous East Asian Width

Characters ±, ↕, ⇅ have East Asian Width "Ambiguous" which skim
renders as double-width on CI, shifting [N/M] alignment by 3 chars.
Revert to plain labels for cross-platform consistency.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(switch): restore unicode symbols in tab labels

Restore ±, ↕, ⇅ symbols to tab labels (1:diff±, 3:main↕, 4:upstream⇅).
These characters are used throughout the codebase and haven't shown
width issues in practice.

Co-authored-by: Maximilian Roos <max-sixty@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(summary): bound concurrent LLM calls with Semaphore

Use the project's existing Semaphore (from src/sync.rs) to limit
concurrent LLM calls to 8 — same pattern as HEAVY_OPS_SEMAPHORE
and CMD_SEMAPHORE.

Co-authored-by: Maximilian Roos <max-sixty@users.noreply.github.com>

* fix(switch): adjust snapshot spacing for unicode tab symbols

skim-tuikit uses width_cjk() for header layout, which treats
East Asian Width "Ambiguous" characters (±, ↕) as double-width.
This shifts [N/M] left by 3 columns. Update snapshots to match.

Co-authored-by: Maximilian Roos <max-sixty@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(test): normalize tab bar padding for cross-platform unicode widths

Skim right-aligns the [N/M] count indicator with padding that varies
depending on whether unicode chars (±, ↕, ⇅) are rendered as single
or double width. Normalize this padding in the snapshot filter so
tests pass regardless of the terminal's East Asian Width handling.

Co-authored-by: Maximilian Roos <max-sixty@users.noreply.github.com>

* fix(switch): restore original tab titles, add 5th summary tab

Revert tab labels 1-4 to their original format ("1: HEAD±", "2: log",
"3: main…±", "4: remote⇅") and add "5: summary" as a new 5th tab.

Co-authored-by: Maximilian Roos <max-sixty@users.noreply.github.com>

* fix(test): handle skim count overlap with summary tab label

When the 5 restored tab labels use ambiguous-width unicode symbols (±, …, ⇅),
skim's width_cjk() treats them as double-width, leaving insufficient space for
the count indicator. This causes the count to overlap with "summary" (e.g.,
"summary1/4") or truncate it ("summar1/28"). Add a targeted regex filter that
normalizes this overlap before the generic whitespace-padded count filter runs.

Co-authored-by: Maximilian Roos <max-sixty@users.noreply.github.com>

* fix(test): avoid typos lint on truncated word in snapshot regex

Use `summary?` (optional `y`) instead of `summar` to avoid the typos
spell checker flagging the partial word.

Co-authored-by: Maximilian Roos <max-sixty@users.noreply.github.com>

* fix(ci): restructure review skill workflow and fix dead sticky comment (#1056)

- Reorder as explicit numbered workflow: pre-flight checks before
  expensive diff analysis to avoid redundant work
- Remove redundant "read CLAUDE.md" (already in system prompt)
- Filter dedup check by bot identity so human approvals don't
  cause the bot to skip its review
- Accept brief approval bodies (matches actual bot behavior)
- Replace {owner}/{repo} placeholders with derived $REPO variable
- Add --paginate on comment fetching for large PRs
- Remove sticky comment references from skill (bot puts feedback
  in review bodies, not stdout — sticky comment stopped working
  after PAT switch in #1052)
- Add TODO on use_sticky_comment in workflow
- Restore gh pr comment prohibition with correct justification

Co-authored-by: Claude <noreply@anthropic.com>

* fix(shell): harden nushell wrapper and improve diagnostics (#1059)

* fix(shell): harden nushell wrapper and improve diagnostics

- Move LAST_EXIT_CODE capture inside do{} block so it reflects the
  actual command exit code, not a subsequent operation
- Wrap directive processing in try/catch to ensure temp file cleanup
  on error
- Include nushell vendor autoload paths in scan_for_detection_details
  so `wt config show` reports nushell integration status
- Add "nu" to the supported shells hint shown on unsupported shells
- Fix detection tests to use actual nushell config line patterns
- Document why non-cd directives delegate to sh -c

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor(shell): remove try/catch from nushell directive cleanup

Drop error-path cleanup for the temp directive file. On error, the file
persists in /tmp as a useful debugging artifact (the OS cleans it up).
This matches bash and fish which already use a single rm on the happy
path with no error wrapping.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>

* fix(shell): PowerShell wrapper swallows -D flag as -Debug (#1057)

* fix(shell): PowerShell wrapper swallows -D flag as -Debug (#885)

The `[Parameter(ValueFromRemainingArguments)]` attribute promoted the
wrapper to an "advanced function", which adds common parameters like
-Debug and -Verbose. PowerShell then consumed `-D` as `-Debug` instead
of passing it to wt.exe — so `wt remove -D` silently lost the flag.

Replace with `$args` (automatic variable for simple functions) which
passes all arguments through unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(test): use .ps1 mock for cross-platform PowerShell test

The shell script mock (#!/bin/sh) doesn't work on Windows. Use a .ps1
script instead — pwsh can invoke it directly with &, and pwsh is already
required for this test.

Co-Authored-By: Claude <noreply@anthropic.com>

* style: apply cargo fmt to PowerShell test

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>

* fix(ci): use empty body for LGTM approvals instead of fluff (#1060)

The review bot was generating summary prose like "Clean hardening of
the nushell wrapper..." when it had no issues to raise. An empty
approval is less noisy — the thumbs-up reaction is sufficient signal.

Co-authored-by: Claude <noreply@anthropic.com>

* fix(list): handle empty repos (no commits) gracefully (#1058)

* fix(list): handle empty repos (no commits) gracefully

Skip commit-dependent tasks for unborn branches (null OID) using a
COMMIT_TASKS constant, following the existing EXPENSIVE_TASKS pattern.
Filter null OIDs from timestamp batching, accept unborn default branch
in validation, and render empty commit/age cells instead of garbage.

Closes #885

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: address codecov/patch coverage gaps

- Remove unreachable COMMIT_TASKS check from work_items_for_branch (null
  OIDs only appear in worktree HEAD, never in git for-each-ref)
- Restructure json_output null OID handling to eliminate dead branch
- Pre-set default branch config in test to exercise is_unborn_head_branch path

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: remove unused BranchRef::has_commits() (dead code)

Only WorktreeInfo::has_commits() is called in the dispatch code.
BranchRef::has_commits() was never referenced, causing a codecov gap.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>

* refactor(switch): unify preview mode handling

All 5 preview modes now follow the same cache → compute → post-process
path in preview_for_mode, eliminating the Summary early-return special
case. Summary precomputation uses rayon (queued after tabs 1-4) instead
of a separate std::thread::spawn + thread::scope wrapper.

Threading simplified from:
  rayon::spawn × (N × 4)      ← tabs 1-4
  std::thread::spawn           ← wrapper
    └── std::thread::scope     ← N scoped threads
        └── LLM_SEMAPHORE      ← rate limit

To:
  rayon::spawn × (N × 4)      ← tabs 1-4 (queued first)
  rayon::spawn × N             ← summaries (queued last, semaphore inside)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(switch): gate summary tab behind [list] summary config

Summary generation is opt-in via `[list] summary = true` (default: false)
to avoid surprise LLM calls for users who have `[commit.generation]`
configured. Both settings are required for summaries to fire.

Adds documentation for the feature in switch help, FAQ (commands we run),
and llm-commits page (new "Picker summaries" section).

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: use ResolvedConfig directly after main merge

The merge with main changed handle_select to receive ResolvedConfig
instead of UserConfig, so the .resolved() call is no longer needed.

Co-Authored-By: Claude <noreply@anthropic.com>

* test: update summary preview snapshot for config hint

The hint text now includes [list] summary = true in addition to the
[commit.generation] example.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: replace missed shlex::try_quote with shell_escape

The shlex removal in #1065 missed one call site in the switch suggestion
context builder. Replace with shell_escape::escape to match the rest of
the codebase.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Maximilian Roos <max-sixty@users.noreply.github.com>
2026-02-16 15:15:59 -08:00
Maximilian Roos 600da7101b Refactor approvals into separate file with deprecation migration (#1042)
* Refactor approvals into separate file

Approved commands are now stored in `~/.config/worktrunk/approvals.toml`,
independent of user config. This enables dotfile management of
`config.toml` without exposing machine-local trust state.

The `Approvals` type provides the same mutation interface as
`UserConfig` (approve/revoke commands, file locking) but stores
exclusively approval data. For backward compatibility, approvals
are silently read from `config.toml` if `approvals.toml` doesn't exist.

All command approval code now uses `Approvals::load()` and passes
`&approvals` to `approve_command_batch()`. Tests use isolated
`test_approvals_path()` to prevent pollution of user approvals.

* Add deprecation warning for approved-commands in config.toml

Approved commands moved to approvals.toml in the previous commit but
stale entries in config.toml were silently ignored. Now `wt config show`
detects them, generates a migration file that removes the entries, and
shows a diff + mv command. Other commands show a brief warning.

Also updates all CLI help text, docs, and FAQ to reference
approvals.toml as the storage location, and migrates all test configs
to use write_test_approvals() for approved-commands content.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: document fallback path asymmetry and add config.toml fallback tests

Address PR review feedback:
- Document why `load_from_config_fallback()` uses `get_config_path()` while
  `reload_from()` uses sibling derivation, and why both converge in production
- Add `test_load_from_config_file` testing the extraction logic
- Add `test_mutation_picks_up_config_toml_fallback` testing the full mutation
  path with config.toml fallback (no approvals.toml → reads config.toml →
  preserves existing commands alongside new one)
- Inline `get_config_path` import (only used in `#[cfg(not(test))]` block)

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: single fallback path for config.toml approvals

Replace two separate fallback implementations (load_from_config_fallback
via get_config_path, and reload_from via sibling derivation) with a
single load_with_fallback(path) that both load() and reload_from() use.

The shared helper derives config.toml as a sibling of the approvals
file, which is always correct since get_approvals_path() derives
approvals.toml as a sibling of config.toml.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(tests): pass WORKTRUNK_APPROVALS_PATH in shell wrapper tests

Shell wrapper tests only passed WORKTRUNK_CONFIG_PATH to the binary,
so it derived the approvals path from config and found approved-commands
in config.toml, triggering the new deprecation warnings. This broke
4 snapshot tests and likely caused 6 timeouts on CI.

Fix: pass WORKTRUNK_APPROVALS_PATH alongside WORKTRUNK_CONFIG_PATH in
all shell wrapper test environments (build_shell_script, build_test_env_vars,
and custom script builders for all shell types). Migrate all approved-commands
writes from test_config_path() to write_test_approvals().

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(docs): escape brackets in doc comments for rustdoc

Rustdoc interprets `[projects]` as a broken intra-doc link.
Escape with `\[...\]` to treat as literal text.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(tests): migrate approvals in e2e_shell_post_start and approval_pty tests

- e2e_shell_post_start: write approved-commands to approvals.toml
  instead of config.toml (fixes 5 timeout failures from unanswered
  approval prompts)
- approval_pty permission test: use a subdirectory for the read-only
  approvals path so the temp root stays writable for .zshrc and other
  test files

Co-Authored-By: Claude <noreply@anthropic.com>

* test: improve coverage for approvals and deprecation modules

Add tests covering error paths, edge cases, and new deprecation features:
- approvals.rs: load_from_file parse errors, load_from_config_file parse
  errors, load_from_path nonexistent, revoke on nonexistent projects,
  clear_all when empty, save_to with empty project, revoke_project with
  empty commands
- deprecation.rs: remove_approved_commands with invalid TOML,
  format_deprecation_details with approved_commands flag,
  write_migration_file with approved_commands

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: remove unused revoke_command from Approvals

Adversarial review found a normalization bug: revoke_command used exact
string matching while is_command_approved normalizes template variables.
Rather than fix a method with no production callers, remove it entirely.
Production code uses revoke_project (removes all approvals for a project)
and clear_all.

Rewrote test_concurrent_revoke_preserves_all_changes to use revoke_project.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-16 14:54:42 -08:00
Maximilian Roos 0c9e0205a3 feat(switch): integrate interactive picker into wt switch (#894)
* feat(switch): integrate interactive picker into `wt switch` (#890)

`wt switch` without arguments now opens the interactive picker (previously
`wt select`). This simplifies the mental model: one command for all switching.

- `wt switch` → opens interactive picker (Unix) or shows error (Windows)
- `wt switch --branches/--remotes` → customizes picker
- `wt select` → deprecated hidden alias with warning

Removes the separate `wt select` docs page since the functionality is now
documented under `wt switch`. The `[select]` config section is preserved
for backward compatibility with a TODO to rename once migration is confirmed.

Closes #890

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(test): make switch TTY test Unix-only

The test expects the Unix error message ("Interactive picker requires
an interactive terminal") but Windows shows a different message
("Interactive picker is not available on Windows").

Co-Authored-By: Claude <noreply@anthropic.com>

* chore: trigger CI

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(switch): require branch when using --create, --base, --execute, or --clobber

These flags only make sense when switching to a specific branch, not when
opening the interactive picker. Previously, running `wt switch --create`
would silently ignore the flag and open the picker instead.

Now clap properly enforces that these flags require a branch argument:

    $ wt switch --create
    error: the following required arguments were not provided:
      <BRANCH>

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(test): update snapshot for interactive picker error message

The snapshot was stale after integrating `wt select` into `wt switch`.
Now `wt switch` without args shows the picker error, not clap's missing
argument message.

Co-Authored-By: Claude <noreply@anthropic.com>

* docs(switch): move interactive picker section after shortcuts

The picker is a navigation method (like shortcuts), not an afterthought.
New order: Shortcuts → Interactive picker → GitHub/GitLab PRs → Troubleshooting

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(test): update help snapshot after doc reorg

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-02 06:05:39 -08:00
Maximilian Roos 4bf73ae6fb feat(config): support absolute paths in worktree-path template (#904)
* feat(config): support absolute paths in worktree-path template

Add {{ repo_path }} variable to worktree-path templates, enabling
absolute path configurations. The new default template uses this
variable to produce canonical absolute paths.

Changes:
- Remove validation that rejected absolute paths
- Add repo_path variable to format_path() template expansion
- Change default from "../{{ repo }}.{{ branch | sanitize }}" to
  "{{ repo_path }}/../{{ repo }}.{{ branch | sanitize }}"
- Update documentation and tests

Existing relative paths continue to work unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(config): use native path format for repo_path template variable

Address review feedback from Codex:

- Use native path format instead of POSIX for repo_path variable, since
  it's used for filesystem operations, not shell commands
- Rename misleading `relative_path` to `expanded_path` in resolve.rs
- Add .trim() to worktree-path empty validation to catch whitespace-only
- Make tests cross-platform by using std::path::MAIN_SEPARATOR

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(tests): make format_path tests cross-platform

Don't rely on platform-specific path separators in test assertions.
The template uses forward slashes which work on all platforms, but
mixing with native paths creates inconsistent results.

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: lead with absolute paths in worktree-path examples

Remove /tmp example and make relative paths a minor detail at the end.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: expand ~ to home directory in worktree-path templates

Enables patterns like:
  worktree-path = "~/worktrees/{{ repo }}/{{ branch | sanitize }}"

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: simplify relative path note

Co-Authored-By: Claude <noreply@anthropic.com>

* test: verify PathBuf::join replaces base with absolute path

Documents the behavior we rely on for absolute worktree-path templates.

Co-Authored-By: Claude <noreply@anthropic.com>

* revert: remove std behavior test

The template tests already verify absolute paths work correctly.

Co-Authored-By: Claude <noreply@anthropic.com>

* test(bare): add test for {{ repo_path }} variable in bare repos

Verifies that absolute path templates using {{ repo_path }} work
correctly with bare repositories. For bare repos, repo_path resolves
to the bare repo directory itself.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-29 16:48:45 -08:00
Maximilian Roos 3d69cec1a7 fix(docs): correct broken worktrunk.dev URLs in templates (#882)
- fish_wrapper.fish: /docs/shell-integration → /config/#shell-integration
- config.example.toml: /config.md#project-configuration/ → /config/#project-configuration
- Remove worktrunk.dev from lychee exclusions so future broken links are caught

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-26 18:17:05 -08:00
Maximilian Roos e154b9ee50 Add LLM setup prompt for first-time commit configuration (#867)
* Add LLM setup prompt for first-time commit configuration

Implement one-time interactive prompt when users attempt commit/merge/squash without LLM configuration. Detects available tools (claude, codex) and offers auto-configuration with preview on `?`. Adds `skip-commit-generation-prompt` flag to suppress re-prompting, `set_commit_generation_command()` config method, and reusable `prompt_yes_no_preview()` utility. Updates commit message templates to clarify format requirements, fixes Claude Code command syntax (spaces to equals in flags), and adds visual styling to section headers in documentation.

* Update snapshots for template format changes

Co-Authored-By: Claude <noreply@anthropic.com>

* Simplify commit template format guidance

Remove redundant trivial-changes line from template format section.
Fix duplicate "# Other" header from merge. Document skip-commit-generation-prompt
in first-run prompts section alongside skip-shell-integration-prompt.

Co-Authored-By: Claude <noreply@anthropic.com>

* Add unit tests for command detection functions

Cover command_exists() and detect_llm_tool() with basic unit tests
to improve test coverage on the commit generation module.

Co-Authored-By: Claude <noreply@anthropic.com>

* Add PTY tests for commit generation prompt

Test the interactive prompt flow for LLM commit configuration:
- No tool found → sets skip flag
- User declines → sets skip flag
- User accepts → saves config
- User requests preview → shows preview

Uses fake claude script to test the prompt path that requires
a detected LLM tool.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-26 00:26:38 -08:00
Maximilian Roos d9ced70893 docs(config): reapply H1 major sections with hero fix (#862)
* docs(config): add H1 major sections with TOC support (#857)

* docs(config): add H1 major sections with TOC support

Add H1 headings for major sections (User Configuration, Project
Configuration, Shell Integration, Environment Variables) to improve
navigation and visual hierarchy.

Theme changes:
- Include H1s in sidebar TOC with divider lines
- Fix H1 link color (was orange due to missing zola-anchor rule)
- Style H1 headings consistently with H2 (underline, size)

Co-Authored-By: Claude <noreply@anthropic.com>

* docs(config): improve TOC structure and auto-generate Subcommands header

- Auto-insert "# Subcommands" in help.rs before subdoc expansions
- Add "# Other" section containing Environment Variables and Command reference
- Rename "Commands" to "Command config" for clarity
- Fix TOC styling: align H1/H2 text, reduce vertical spacing, use sentence case
- Template: show H2s nested under H1s at same indent as standalone H2s

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: address codex review feedback

- Add default guard for h.children in TOC template
- Remove ineffective :first-of-type selector

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: use sentence case for Environment variables

Co-Authored-By: Claude <noreply@anthropic.com>

* test: update snapshots for config section header changes

Co-Authored-By: Claude <noreply@anthropic.com>

* test: update help_config_create snapshot

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>

* fix(docs): prevent h1 styling from breaking hero layout

The H1 major sections commit added `width: fit-content` and underline
styling to all h1 elements. This broke the hero because:
- fit-content shrinks the element, breaking text-align: center
- The underline appeared on the hero title

Fix by overriding these in .heading-text (used for page titles/hero):
- width: auto to restore centering
- > a::after { display: none } to hide underline

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(docs): align H1 TOC entries with H2s

Remove left border and custom padding from H1 entries in the sidebar
TOC. H1s are now distinguished only by bolder weight and extra top
margin, creating a cleaner visual hierarchy.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-25 22:05:30 -08:00
Maximilian Roos 400f22d2c0 Revert "docs(config): add H1 major sections with TOC support (#857)"
This reverts commit 5d831694dd.
2026-01-25 21:10:28 -08:00
Maximilian Roos 5d831694dd docs(config): add H1 major sections with TOC support (#857)
* docs(config): add H1 major sections with TOC support

Add H1 headings for major sections (User Configuration, Project
Configuration, Shell Integration, Environment Variables) to improve
navigation and visual hierarchy.

Theme changes:
- Include H1s in sidebar TOC with divider lines
- Fix H1 link color (was orange due to missing zola-anchor rule)
- Style H1 headings consistently with H2 (underline, size)

Co-Authored-By: Claude <noreply@anthropic.com>

* docs(config): improve TOC structure and auto-generate Subcommands header

- Auto-insert "# Subcommands" in help.rs before subdoc expansions
- Add "# Other" section containing Environment Variables and Command reference
- Rename "Commands" to "Command config" for clarity
- Fix TOC styling: align H1/H2 text, reduce vertical spacing, use sentence case
- Template: show H2s nested under H1s at same indent as standalone H2s

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: address codex review feedback

- Add default guard for h.children in TOC template
- Remove ineffective :first-of-type selector

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: use sentence case for Environment variables

Co-Authored-By: Claude <noreply@anthropic.com>

* test: update snapshots for config section header changes

Co-Authored-By: Claude <noreply@anthropic.com>

* test: update help_config_create snapshot

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-25 20:59:44 -08:00
Maximilian Roos 16c29b48bf docs(config): fix project config anchor links
Update anchor links from "worktrunk-project-configuration" to
"project-configuration" across documentation and code files.
2026-01-25 18:47:39 -08:00
Maximilian Roos 79fe892bfa feat: add per-project hooks with append semantics (#842)
* feat: add per-project hooks with append semantics

Per-project hooks in user config now append to global hooks rather than
replacing them. Execution order: global → per-project → project.

Also:
- Move hooks into OverridableConfig for consistency
- Rename UserConfig.overrides to UserConfig.configs
- Add schemars for deriving valid config keys from JsonSchema
- Replace manual VALID_USER_CONFIG_KEYS with schema-derived function

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: unify config validation on JsonSchema

Both UserConfig and ProjectConfig now use JsonSchema-derived keys for
validation instead of serde HashMap catchall. This:

- Removes `unknown` field from ProjectConfig
- Removes `unknown()` method from WorktrunkConfig trait
- Simplifies `is_valid_key` to not need value parameter
- Adds JsonSchema to JSON output types for future schema generation

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: document per-project hooks in setting overrides section

- Add per-project hooks to Setting overrides (Experimental) section
- Update hook.md table to show execution order with per-project hooks
- Replace JSON context example with one showing complex conditionals
- Keep documentation DRY by linking from hook.md to config.md

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: use consistent terminology (user config, not settings)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-24 18:32:17 -08:00
Maximilian Roos 3dd7657880 docs: clarify user project-specific settings section (#835)
- Restructure intro paragraph as bullet list for clearer context
- Fix casing: "worktrunk" → "Worktrunk"
- Fix command: "wt hook approve --clear" → "wt hook approvals clear"

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-24 12:23:54 -08:00
Maximilian Roos ee029d19f1 docs: clarify user project-specific settings section (#830)
Restructure the per-project settings documentation to better distinguish
between user config and project config. The section now clearly explains
the two-config system before introducing per-project user settings as a
third category.

Key changes:
- Rename section from "Per-project settings" to "User project-specific settings"
- Add intro establishing user/project config dichotomy
- Split into subsections: "Approved hook commands" and "Setting overrides"
- Remove misplaced reference to project config hooks
- Link to project config documentation for clarity

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-23 23:38:59 -08:00
Maximilian Roos f8aaead5b3 refactor: restructure commit-generation config to [commit.generation] (#809)
* refactor: restructure commit-generation config to [commit.generation]

- Move `[commit-generation]` to `[commit.generation]` (nested under commit)
- Replace `command` + `args` with single `command` string (shell execution)
- Add deprecation warning for old format with backward compatibility
- Update docs to present Claude CLI and llm as equal options

The new format uses shell execution (`sh -c`) so environment variables
can be set inline in the command string.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: update tests and docs for new commit.generation config format

Fix test regressions identified by Codex review where tests using
WORKTRUNK_COMMIT_GENERATION__ARGS were silently failing because
args is now ignored with the new shell-based command format.

Tests now use the correct pattern:
  WORKTRUNK_COMMIT_GENERATION__COMMAND="cat >/dev/null && echo 'message'"

Also update CLI docs to show:
- New config path: commit.generation.command (not commit-generation.command)
- New env var format: WORKTRUNK_COMMIT__GENERATION__COMMAND
- Remove args documentation (no longer applicable)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: update doc comments and add validation for new config path

Address Codex review feedback:
- Update per-project doc example to show new format
  (commit.generation instead of commit-generation)
- Update env var comment to show new format
  (COMMIT__GENERATION__COMMAND instead of COMMIT_GENERATION__COMMAND)
- Add validation for projects.*.commit.generation path
  (was only validating deprecated commit-generation path)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: address remaining Codex review issues

- Use ShellConfig for LLM command execution (Windows compatibility)
- Improve error handling when LLM stderr is empty
- Format reproduction commands with conditional sh -c wrapping
- Update tests to use new env var format (WORKTRUNK_COMMIT__GENERATION__)
- Make test assertion more specific ([commit] not just "commit")
- Document cformat safety for shell commands in output guidelines

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: use shell_escape crate for reproduction command escaping

Replace manual single-quote escaping with the shell_escape crate
which is already a dependency used elsewhere in the codebase.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: unify deprecation handling with single .new migration file

Consolidate all deprecation handling into deprecation.rs:
- Add [commit-generation] → [commit.generation] section migration
- Merge deprecated `args` field into `command` string
- Generate single .new file with all migrations (template vars + sections)
- Skip migration if new section already exists (don't overwrite)
- Skip warning for empty deprecated sections
- Use shell_escape for proper quoting when merging args
- Rename hint from deprecated-project-config to deprecated-config

Remove separate warn_deprecated_commit_generation() from user.rs.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: harden commit-generation migration against edge cases

Fixes found during adversarial testing:

1. Args data loss prevention: merge_args_into_command() now validates
   preconditions (args is array, command is string) BEFORE removing
   the args field. Previously, table.remove("args") happened
   unconditionally, losing data if command was missing or non-string.

2. Malformed generation detection: When checking if new [commit.generation]
   exists, now verify it's actually a table (is_table || is_inline_table).
   A malformed value like `generation = "string"` no longer suppresses
   deprecation warnings for the old section.

3. Inline table support: Both detection and migration now handle inline
   table format (e.g., `commit-generation = { command = "..." }`).
   Detection uses as_inline_table() in addition to as_table().
   Migration converts InlineTable to Table via into_table().

Added comprehensive tests for all edge cases.

Co-Authored-By: Claude <noreply@anthropic.com>

* test: add coverage for project-level inline table migration

Add tests for:
- Project-level inline table migration
- Preserving existing [commit] fields during migration
- Empty inline table detection (should not flag as deprecated)

Co-Authored-By: Claude <noreply@anthropic.com>

* test: add integration test for commit-generation deprecation warning

Add test that exercises the full deprecation path through check_and_migrate()
for commit-generation section rename and args merge. This improves coverage
on the warning output and migration file generation paths.

Co-Authored-By: Claude <noreply@anthropic.com>

* test: add integration test for project-level commit-generation deprecation

Add test that exercises the project_keys iteration path in check_and_migrate()
for project-level commit-generation deprecation warnings. This covers the
warning message format that includes project identifiers.

Co-Authored-By: Claude <noreply@anthropic.com>

* test: add edge case tests for merge_args_into_command

Cover the early-return paths in merge_args_into_command():
- Args without command: args preserved (no command to merge into)
- Non-string command: args preserved (can't merge into non-string)
- Command-only: clean migration without args field

These tests exercise the !can_merge validation path.

Co-Authored-By: Claude <noreply@anthropic.com>

* test: cover malformed config fallback branches in migration

Add tests for the `_ => None` fallback branches that handle unusual
cases where commit-generation is neither a table nor inline table
(e.g., a string value like `commit-generation = "not a table"`).

These tests exercise lines 264 and 305 in deprecation.rs.

Co-Authored-By: Claude <noreply@anthropic.com>

* test: add coverage for edge cases in deprecation migration

Add unit tests for:
- Empty command with args: args become the command when command=""
- Invalid TOML parsing: returns content unchanged
- Deduplication path: second call with same path hits early return

These tests cover previously uncovered code paths in deprecation.rs.

Co-Authored-By: Claude <noreply@anthropic.com>

* test: add unit tests for llm helper functions

Add tests for format_reproduction_command and is_lock_file to improve
code coverage.

Co-Authored-By: Claude <noreply@anthropic.com>

* test: add unit tests for config merge logic

Add tests for:
- CommitConfig merge when only base/override has generation
- CommitConfig merge when both have generation
- CommitGenerationConfig merge for squash_template fields

These cover previously uncovered branches in the Merge implementations.

Co-Authored-By: Claude <noreply@anthropic.com>

* test: add validation tests for new commit.generation format

Add tests for validation error paths in the new [commit.generation]
config format (both top-level and per-project).

Co-Authored-By: Claude <noreply@anthropic.com>

* test: add save_to() tests for commit.generation serialization

Cover the "create from scratch" branch in save_to() which serializes
commit.generation and deprecated commit-generation sections to new files.
Previously only the "update existing file" branch was tested.

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: fix "Claude CLI" to "Claude Code"

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: deduplicate LLM setup from config page

Remove Claude Code/llm setup examples from config docs since they
duplicate llm-commits.md. Config page now links to llm-commits.md
for setup and keeps only the custom prompt templates section.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: trigger deprecation warnings in `wt config show`

Previously `config show` read files directly without calling the config
loaders, so deprecation warnings weren't shown. Now we call
UserConfig::load() and repo.load_project_config() to trigger warnings
while still displaying invalid configs (the deprecation check runs on
raw content before parsing).

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: move aichat into Setup section with other options

Move aichat example from "Alternative tools" into the Setup section as
"Option 3: aichat" alongside Claude CLI and llm. Add "Custom scripts"
subsection for the generic example. Remove the now-redundant
"Alternative tools" section.

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: reframe LLM setup as examples, not numbered options

Change from "Option 1/2/3" to just showing examples of tools that work.
Lead with the concept: any command that reads stdin and outputs a commit
message. Simplify Claude Code example and condense install instructions.

Co-Authored-By: Claude <noreply@anthropic.com>

* Simplify LLM commit generation setup documentation

Remove redundant setup instructions and tool-specific examples from config templates and documentation. Consolidate guidance to reference the main LLM commits docs page for setup details.

* Skip deprecated section key in unknown fields warning

Skip the "commit-generation" key when warning about unknown fields, since
it's already handled by the deprecated sections warning and this prevents
duplicate warnings in config output.

* Extract DEPRECATED_SECTION_KEYS constant for reuse

Instead of hardcoding "commit-generation" in the skip check, define a
constant that documents which section keys are deprecated. This makes
it easier to add future deprecated sections without forgetting to
update the skip logic.

Co-Authored-By: Claude <noreply@anthropic.com>

* Remove unused has_args field from CommitGenerationDeprecations

The has_args field was tracked but never used in production code — the
migration logic runs unconditionally and handles both cases (with or
without args). This simplifies the struct and removes a redundant test.

Co-Authored-By: Claude <noreply@anthropic.com>

* Move deprecated section filtering to callers of warn_unknown_fields

Instead of having warn_unknown_fields know about deprecated sections
and skip them internally, make the constant public and have callers
filter before calling. This is cleaner because:

1. warn_unknown_fields doesn't need to know about the deprecation system
2. Callers already interact with deprecation, so filtering there is natural
3. The coupling is explicit rather than hidden inside the function

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: use Unix shell escaping for migration hint command

The `mv` command in the migration hint is a Unix command, so use
`shell_escape::unix::escape` instead of the platform-specific `escape`
to get consistent output on all platforms.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: normalize path separators before shell escaping

Convert backslashes to forward slashes before shell escaping so the
migration hint command is consistent across platforms. Forward slashes
work in Git Bash on Windows.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: remove shell escaping from migration hint paths

Shell escaping was producing different output on Windows vs Unix even
with unix::escape, likely due to some invisible path characteristic.
Since config paths rarely contain special characters, just output the
paths directly with forward slashes.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 17:04:32 -08:00
Maximilian Roos 41b241162b feat: add per-project config overrides (#749)
* feat: add worktree-path support to per-project user config

Allow users to specify different worktree path templates for different
projects in their user config, enabling portable dotfiles across
machines with different project structures.

Config format:
```toml
[projects."github.com/user/repo"]
worktree-path = ".worktrees/{{ branch | sanitize }}"
approved-commands = ["npm ci"]
```

Per-project worktree-path takes precedence over global, which falls
back to the default template.

Closes #596

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: add per-project config for commit-generation, list, commit, and merge

Extend the per-project user config to support overriding more settings:
- [projects."...".commit-generation] - LLM model, args, template
- [projects."...".list] - branches, remotes, full defaults
- [projects."...".commit] - stage mode
- [projects."...".merge] - squash, commit, rebase, remove, verify

Config precedence: CLI arg > project config > global config > default

Implementation:
- Add Merge trait for field-wise config merging (other takes precedence)
- Add effective_* methods to WorktrunkConfig for merged config access
- Add helper methods on CommandEnv/CommandContext for easy access
- Update all commands to use effective configs instead of direct access
- Fix approval clearing to preserve other per-project settings

The approval clearing fix prevents data loss: previously clearing approvals
would wipe all per-project settings (worktree-path, commit-generation, etc).
Now it only clears approved-commands and removes project entries only if
completely empty.

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: extract merge_optional helper to reduce duplication

DRY improvement: the effective_list, effective_commit, and effective_merge
methods all had identical 4-way match logic. Extract to a generic helper
function that handles the merge pattern.

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: simplify per-project config documentation

- Clarify that per-project settings contain approvals + optional overrides
- Put approved-commands first (primary use case)
- Use TOML dotted keys for compact examples (list.full, merge.squash)
- Remove verbose examples that repeated the section header

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: drop `effective_` prefix from config accessor methods

The `project: Option<&str>` parameter already indicates merging behavior.
Renamed methods:
- WorktrunkConfig: commit_generation(), list(), commit(), merge()
- CommandEnv/CommandContext: commit_generation(), commit(), merge()

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: escape brackets in doc comments to avoid rustdoc link errors

Rustdoc interprets `[text]` as intra-doc links. Use backticks to make
them code spans instead.

Co-Authored-By: Claude <noreply@anthropic.com>

* test: update help snapshots after merge

Updated config help snapshots to reflect per-project config documentation.

Co-Authored-By: Claude <noreply@anthropic.com>

* test: add validation tests for config edge cases

Add tests for config validation:
- Empty worktree-path (global and per-project)
- Absolute worktree-path (global and per-project)
- Mutual exclusivity of template/template-file options

Extracted validation into a separate `validate()` method to avoid
code duplication between `load()` and test helper.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: use platform-appropriate absolute paths in validation tests

On Windows, `/absolute/path` is not considered absolute - Windows uses
`C:\path` style. Use cfg!(windows) to conditionally set the test path.

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: fix project_id() doc comments to describe path fallback

The doc comments incorrectly stated that project_id() returns None when
there's no remote URL. The actual implementation falls back to the
canonical repository path, only returning None for non-UTF-8 paths.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: mark per-project setting overrides as experimental

- Add "(Experimental)" to per-project settings section heading
- Clarify that setting overrides (worktree-path, list, commit, merge) are
  new while approved-commands is stable
- Replace manual PartialEq impl with derive for CommitGenerationConfig

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: minor code simplifications

- Replace .to_string().as_str() with &.to_string()
- Replace .is_some() + .unwrap() with tuple pattern match

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: fix if-let formatting

Move opening brace to same line as if-let condition to match code style.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-20 05:57:32 +00:00
Maximilian Roos bada0d298d fix: convert bare URLs to proper markdown links in docs (#664)
Replace angle-bracket URLs (<https://...>) with proper markdown links
that render nicely on web while showing descriptive text in CLI help.

- src/cli/mod.rs: Use Zola @/ syntax for internal links
- dev/config.example.toml: Remove angle brackets (plain URLs for TOML)
- .claude-plugin/CLAUDE.md: Use GitHub shorthand for issue link

CLI now shows "LLM commits docs" and "`wt hook` docs" instead of raw URLs.
Web docs render as clickable links with descriptive text.

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-15 12:34:22 -08:00
Maximilian Roos 93acd3531c Restore visual hierarchy for help headers (#638)
* style: improve help hierarchy and add visible dividers

- Render `---` as visible dimmed line (────────────────) instead of blank
- Restructure user config: promote "LLM commit messages" to H2
- Group command defaults under "Command defaults" H2 with H3 subsections
- Remove "defaults" from titles: "List command defaults" → "List"
- Convert "Commit template"/"Squash template" to bold text (parent is now H3)

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: add TODO explaining H1/divider tradeoff

Document why we use `---` dividers instead of H1 headers and list
alternative approaches considered.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-14 16:43:19 -08:00
Maximilian Roos 46827d2d4e feat: restore visual hierarchy and add section dividers 2026-01-14 15:46:03 -08:00
Maximilian Roos 20a5b97546 style: use sentence case for CLI help headings (#620)
* Refactor config documentation into single source file

- Create `dev/config.source.md` as canonical markdown source for user config documentation
- Extract config content from CLI help text into sourced file via `include_str!`
- Generate `dev/config.example.toml` from source by commenting all lines
- Sync default LLM templates between `src/llm.rs` and source markdown
- Update help snapshots to reflect restructured content
- Simplify test logic: split template sync into separate source test
- Remove manual template commenting logic in favor of transform function

* refactor: move user config docs from dev/config.source.md into cli/mod.rs

The user config documentation is now embedded directly in src/cli/mod.rs
between USER_CONFIG_START/END markers, eliminating the separate markdown
file. The sync tests extract this section for transforming to
dev/config.example.toml.

Co-Authored-By: Claude <noreply@anthropic.com>

* Refactor config documentation formatting

Reorganize and clarify configuration documentation across multiple files
by improving readability of location information, worktree path examples,
template variable descriptions, and removing obsolete template-file
configuration options.

* refactor: inline user config docs into CLI help

Move user config documentation from separate `dev/config.source.md` into
`src/cli/mod.rs` using marker comments. This makes the CLI the single
source of truth for config documentation.

Changes:
- Add USER_CONFIG_START/END markers in mod.rs for content extraction
- Update readme_sync.rs to extract from mod.rs instead of separate file
- Delete dev/config.source.md (content now inline)
- Remove `wt config create` subdoc expansion (redundant with main help)
- Restore User Hooks section and approvals management tip
- Reorganize help: Examples first, then Configuration files table
- Fix various formatting issues (lists, line wrapping, TOML comments)
- Remove obsolete template-file documentation (feature removed)

Co-Authored-By: Claude <noreply@anthropic.com>

* style: use sentence case for CLI help headings

Standardize all help text headings to sentence case (only first word
capitalized, except for proper nouns and acronyms like CLI, JSON, LLM).

This follows common CLI conventions (git, cargo) where headings read
more naturally as prose.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-14 01:28:13 -08:00
Maximilian Roos fe12a688fb refactor: inline user config docs into CLI help (#618)
* Refactor config documentation into single source file

- Create `dev/config.source.md` as canonical markdown source for user config documentation
- Extract config content from CLI help text into sourced file via `include_str!`
- Generate `dev/config.example.toml` from source by commenting all lines
- Sync default LLM templates between `src/llm.rs` and source markdown
- Update help snapshots to reflect restructured content
- Simplify test logic: split template sync into separate source test
- Remove manual template commenting logic in favor of transform function

* refactor: move user config docs from dev/config.source.md into cli/mod.rs

The user config documentation is now embedded directly in src/cli/mod.rs
between USER_CONFIG_START/END markers, eliminating the separate markdown
file. The sync tests extract this section for transforming to
dev/config.example.toml.

Co-Authored-By: Claude <noreply@anthropic.com>

* Refactor config documentation formatting

Reorganize and clarify configuration documentation across multiple files
by improving readability of location information, worktree path examples,
template variable descriptions, and removing obsolete template-file
configuration options.

* refactor: inline user config docs into CLI help

Move user config documentation from separate `dev/config.source.md` into
`src/cli/mod.rs` using marker comments. This makes the CLI the single
source of truth for config documentation.

Changes:
- Add USER_CONFIG_START/END markers in mod.rs for content extraction
- Update readme_sync.rs to extract from mod.rs instead of separate file
- Delete dev/config.source.md (content now inline)
- Remove `wt config create` subdoc expansion (redundant with main help)
- Restore User Hooks section and approvals management tip
- Reorganize help: Examples first, then Configuration files table
- Fix various formatting issues (lists, line wrapping, TOML comments)
- Remove obsolete template-file documentation (feature removed)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-14 01:16:19 -08:00
Maximilian Roos a9ed5344ed refactor: rename main_worktree_path template var to primary_worktree_path (#613)
* refactor: rename main_worktree_path template var to primary_worktree_path

The old name was confusing because "main worktree" has a specific meaning
in git (the original clone directory), but the template variable actually
provided the "primary" worktree — where established files live.

- Rename template var: main_worktree_path → primary_worktree_path
- Remove default_branch_worktree() method, inline into primary_worktree()
- Add deprecation mapping so existing configs continue to work
- Update all documentation and examples

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: improve primary_worktree_path description in examples

Update the template variable description from "Where established files
live" to "Main worktree (or default branch worktree for bare repos)" for
clarity.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-13 22:34:05 -08:00
Maximilian Roos e551c4d333 feat(list): add experimental per-task timeout config (#601)
* feat(list): add experimental per-task timeout config

Add configurable timeout for `wt list` git operations via
`[list] timeout-ms` in user config.

- Replace `timed_out: bool` with `ErrorCause` enum for extensibility
- Show timeout count in footer: "3 tasks timed out (run with `-v` for details)"
- `0` means no timeout (explicit disable)
- `--full` always disables timeout for complete data collection
- Add comprehensive unit and integration tests

Co-Authored-By: Claude <noreply@anthropic.com>

* Address Codex review feedback for timeout feature

- Make `cause` field private, access via `is_timeout()` method
- Fix SHA slicing with safe `sha[..sha.len().min(8)]`
- Update "killed gracefully" to "terminated" in docs
- Add timeout-ms = 0 to config defaults
- Single constructor for TaskError (takes cause parameter)
- Show all errors in footer with timeout breakout:
  - "3 tasks timed out" (all timeouts)
  - "5 tasks failed (3 timed out)" (mixed)
  - "2 tasks failed" (no timeouts)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-13 19:12:21 -08:00
Maximilian Roos 6a4aa35e7e docs: consolidate template variables reference into hook.md (#544)
- Remove deprecated variable mentions from all docs
- Replace duplicate table in step.md with link to hook.md#template-variables
- Add URL to canonical reference in project config template
- All filters (sanitize, sanitize_db, hash_port) documented in hook.md

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-11 17:20:38 -08:00
Maximilian Roos 56c26f289d refactor: replace dev scripts with Taskfile (#542)
Consolidate 7 shell scripts into a single Taskfile.yml using go-task:
- coverage: run tests with coverage
- setup-web: setup Claude Code web environment
- fetch-assets: download assets from worktrunk-assets
- publish-assets: publish assets to worktrunk-assets
- build-social-cards: generate social card PNGs
- generate-logo: generate logo with Gemini AI

Remove update-homebrew task (now handled by CI workflow).

Update CLAUDE.md files to reference `task X` commands and add
task runner installation instructions for web environments.

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-11 17:04:33 -08:00
Maximilian Roos 75c39245d9 refactor: simplify dev/coverage.sh (#541)
Reduce from 38 lines to 2. Removed:
- Toolchain workaround (rust-toolchain.toml handles this)
- llvm-tools component check (cargo-llvm-cov handles this)
- LCOV generation (CI uses cobertura, not LCOV)
- Separate no-report + report passes (--html does both)

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-11 16:34:49 -08:00
Maximilian Roos bf575aaaee Redesign social cards with horizontal layout (#522)
* Redesign social cards with horizontal layout

- New horizontal layout: logo left, text right
- Warm radial glow emanating from logo
- Refined typography: 88px title with tight tracking, 28px tagline
- Darker title color (#1a1817) for better hierarchy
- Move generated PNGs to assets repo (same pattern as demos)
- Add build script with automatic font downloading

Co-Authored-By: Claude <noreply@anthropic.com>

* Fix deploy workflow to include social cards

The assets repo now has both demos/ and social/ directories.

Co-Authored-By: Claude <noreply@anthropic.com>

* Simplify assets structure: single path copy

Restructured worktrunk-assets repo to have all content under assets/
so deploy workflow can use a single `cp -r` command.

Co-Authored-By: Claude <noreply@anthropic.com>

* Address review feedback

- Fix typo: "ONGs" → "PNGs"
- Fix stale comment: "Google Fonts" → "GitHub releases"
- Add -f flag to curl for better error handling
- Document purpose of each social card size

Co-Authored-By: Claude <noreply@anthropic.com>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Fix asset URL in README for restructured repo

The worktrunk-assets repo was restructured from demos/ to assets/.
Update the URL transform in readme_sync.rs to match.

Co-Authored-By: Claude <noreply@anthropic.com>

* Fix README demo links after assets repo restructure

- Update wt-core.gif URL from demos/ to assets/ path
- Add PNGs exception to typos config (was incorrectly flagged)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-01-10 17:55:35 -08:00
Maximilian Roos 962b548bbf feat: add sanitize_db filter for database-safe identifiers (#498)
* feat: add sanitize_db filter for database-safe identifiers

Add a new template filter `sanitize_db` that transforms strings into
database-safe identifiers compatible with PostgreSQL, MySQL, and SQL Server.

Transformation rules:
- Convert to lowercase
- Replace non-alphanumeric characters with underscore
- Collapse consecutive underscores
- Prefix with underscore if starts with digit
- Truncate to 63 characters (PostgreSQL limit)

Examples:
- feature/auth-oauth2 → feature_auth_oauth2
- 123-bug-fix → _123_bug_fix
- UPPERCASE.Branch → uppercase_branch

Closes #474

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: add sanitize_db mention to database per worktree section

Add example showing how to use sanitize_db for branch-based database names
in the tips-patterns.md guide.

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: add sanitize_db to example configs and tips-patterns

- Add sanitize_db to filter list in wt.example.toml
- Add sanitize_db to variables section in config.example.toml
- Add usage example in tips-patterns.md database section

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: simplify URL template description

Remove filter list from URL template description - the example already
shows hash_port usage and other filters aren't relevant in this context.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(sanitize_db): add 3-char hash suffix for collision/keyword safety

Appends a deterministic 3-character base36 hash suffix to sanitize_db
output. This ensures:

- SQL reserved words are avoided (e.g., `user` → `user_abc`)
- Different inputs don't collide (e.g., `a-b` and `a_b` get different
  suffixes since hash is computed from original input)

The suffix uses 46,656 unique values (36^3), providing good uniqueness
while keeping identifiers readable.

Updated all documentation examples to use sanitize_db for database names
(POSTGRES_DB, DATABASE_URL) instead of repo name.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-09 18:29:44 -08:00