mirror of
https://github.com/max-sixty/worktrunk.git
synced 2026-09-14 20:00:38 +08:00
main
12 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
89326f14af |
Publish shell integration, add a footer, dedupe global options (#4000)
Guides and site presentation: one home per topic, a shell-integration
page, a footer, and three rendering fixes.
**Global options were emitted once per command reference**
clap repeats the same ~20-line `Global Options:` block in every
reference it renders, so a page assembled from subdocs stacked 11 copies
on `/config/` and 13 on `/step/`. That padded the pages and gave site
search that many near-identical hits — "squash" returned both
`#command-reference` and `#command-reference-2`. `take_global_options`
cuts each reference at the heading as it is built, keeping only the
first; one `kept` flag threads through the subdoc expansion and the page
streams out rather than accumulating. Terminal `--help` renders through
clap directly and is unchanged.
The config page also carried colliding anchors — two "Hooks" (`#hooks`,
`#hooks-1`), two "Aliases", and seven "Examples" (`#examples` …
`#examples-6`) — now qualified at their source in `src/cli/config.rs`:
User/Project hooks, User/Project aliases, and
Approval/Alias/State/Cache/Log/Variable examples.
`/step/` still has its own set (eight "Examples", two "Options", two
"Arguments", plus "Staging" and "Dry run" pairs). Qualifying those moves
existing `/step/#examples-N` anchors, so it wants a pass of its own with
the inbound links audited; the deduplication above already removes 13
Global Options blocks from that page.
**Shell integration has a page**
Shell-integration debugging was skill-only: five named warning messages,
a PowerShell checklist, and the wrapper mechanism, with no site page —
while the FAQ's answer to "`wt switch` didn't cd" was to install the
Claude Code plugin. It is now `/shell-integration/`, offered first, with
the plugin as the second route. The `llms.txt` listing serves every page
as `/<slug>.md` from a hand-created symlink, so a new page was a 404 the
listing still advertised; the symlink is added and the sync now fails
when a listed page has none.
**Presentation**
- A site footer carries the version (read from `Cargo.toml` at build
time), releases, changelog and license. No page named any of them, and
`/code-signing/` was reachable only from inside a collapsed block on the
homepage. Starlight's `Footer` is wrapped rather than replaced.
- `wt list --full` renders 1157px inside an 800px content column, so 40%
of it sat behind a horizontal scrollbar with the pane beside the column
empty. A terminal frame now takes the whole pane where there is slack,
measured with a query container rather than recomputed from Starlight's
layout formula.
- The `wt-command-reference` frames offered a copy button for 3,877
characters of generated help text; they now expose no copy control. A
console block listing several commands is as often a menu of
alternatives as a recipe, and nothing in the markup tells them apart, so
every command line in such a block carries its own copy control
alongside the block's.
- The four command demos and the two hand-written figures get captions;
the 2.33 MB homepage GIF below the fold loads lazily.
**Sidebar order is pinned**
`site-navigation.mjs` told readers a
`test_sidebar_matches_frontmatter_order` would fail when the authored
sidebar and the pages' `sidebar.order` disagreed. No such test existed,
and the disagreement it describes is exactly what the survey found:
`remove` listed before `merge`, Agent integration ahead of
lower-numbered pages. The test is written, so the sidebar and the
`llms.txt` ordering derived from the frontmatter can't drift apart
again.
<details>
<summary>Guide corrections</summary>
- Tips & patterns was 26 flat H2 recipes in no order, all 26 in the
sidebar. They group under five H2s — setup and layout, aliases and
hooks, per-worktree services, working with agents, status/commits/logs —
with each recipe demoted to H3. Anchors are level-independent, so
existing `/tips-patterns/#…` fragments still resolve.
- `-x 'opencode run'` has been broken since 0.75.0 made `-x` a literal
program: it is `-x opencode -- run '<task>'`.
- The branch-summary preview moved from tab 5 to 6 when the unified-diff
tab landed; the recipe names the `summary` tab instead of a number.
- The Caddy recipe claimed `feature-auth` hashes to port 16460 — that is
`fix-auth`'s port. It is 18283.
- `_` in `wt list` is same-commit *and clean*; the
same-commit-with-changes glyph is `–`, which is not safe to delete.
- `wt step prune` removes branches with no worktree too, and the min-age
guard ages a worktree by its creation time and a bare branch by its
oldest reflog entry.
- `wt step eval -v` prints fifteen variables; the example showed two
under a lead calling them "the available template variables".
- A filter applied to `{{ vars.<key> }}` acts on the placeholder the
preview substitutes, so `{{ vars.port | default('8080') }}` previews as
`{{ vars.port }}`, filter gone.
- The `.git/wt/cache/` table was missing `picker-preview`, and `wt
config state clear` prompts unless `--yes`.
- `skills/worktrunk/reference/README.md` was a symlink to the repo
README that `SKILL.md` never referenced, and the plugin mirror
dereferenced it into a 262-line copy carrying the star-history token,
share links, and a logo path resolving nowhere. Nothing generated it, so
deleting the symlink is the whole fix.
- One home per topic: agent handoffs stay in tips-patterns, activity
markers in `claude-code.md`, alias-template deferral in `extending.md`,
and the `codename` filter's two `worktree-path` recipes give way to the
config page that owns path templates. The FAQ's "Running tests" and "How
can I contribute?" duplicated the README's Contributing block down to
the share URLs.
- The FAQ linked `/worktrunk/#install`, the `noindex` compatibility
route; the plugin hook shim's Windows Terminal hint pointed there too.
Both use `/#install`, where the new sidebar Install entry goes.
- Example names settle on `myproject` / `feature-auth`; "sibling to main
repo" becomes "sibling to the main worktree", and `wt remove`'s "target
worktree" becomes "the worktree being removed" per the project's own
terminology rule.
</details>
UX survey items: `#36`, `#37`, `#38`, `#39`, `#45`, `#47`, `#48`, `#49`,
`#50`, `#51`, `#52`, `#61`, `#94`, `#95`, `#96`, `#97`, `#99`, `#100`.
Reviewable files: the hand-written pages under `docs/src/content/docs/`
(notably the new `shell-integration.md`, `tips-patterns.md`, `faq.md`),
`docs/src/components/Footer.astro`,
`docs/src/plugins/worktrunk-terminal.mjs`,
`docs/src/site-navigation.mjs`, `docs/tests/*.mjs`, `src/help.rs`,
`plugins/worktrunk/hooks/wt.sh`. Generated mirrors and snapshots are
regenerated.
> _This was written by Claude Code on behalf of max-sixty_
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01XAUYWFN9d9oh6jyoQiouHb
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
|
||
|
|
c93b494bd0 |
test(configure_shell): pin the AUR PKGBUILD's fish completion to its generator (#4062)
The `worktrunk-bin` AUR package installs fish completions from a heredoc
in `.github/aur/PKGBUILD` that hand-copies what
`fish_completion_content("wt")` produces — makepkg runs no Rust, so
there is no way to call the generator from there. The PKGBUILD's own
`TODO` notes the duplication ("a bit hacky, but short enough that copy &
paste is OK for the moment"), but nothing checks it. The two copies are
identical today; if the generator changes, the next release ships a
stale completion to AUR users and the only symptom is someone's
tab-completion quietly producing nothing.
This adds a unit test next to the existing `fish_completion_content`
snapshots asserting the PKGBUILD still contains the generated string
verbatim, printing the replacement text on failure. It's a real surface:
the docs point at the official Arch package now, but `worktrunk-bin` was
deliberately kept publishing (#2052) and at least one user tracks it for
newer releases than the official package carries.
`.github/aur/PKGBUILD` is read at test time and so never appears in
coverage, which means a PKGBUILD-only edit maps to no test and would
slip through `cargo affected run`. It gets a
`workspace.metadata.affected.rule` for that — the same treatment
`readme_sync`'s runtime-read inputs already have.
`.gitattributes` pins the PKGBUILD to an LF checkout, because a byte
comparison against a `\n`-emitting generator is only meaningful if the
file on disk is LF everywhere. Git for Windows defaults to
`core.autocrlf=true` and the path carried no `text`/`eol` attribute, so
Windows checked it out CRLF and the assertion could never match — which
is how the first push went red on `affected tests (windows, advisory)`.
Pinning is right independently of the test (makepkg reads the file as a
shell script), it's the same mechanism `templates/* text eol=lf` already
uses, and unlike `readme_sync`'s `#![cfg(not(windows))]` it leaves
Windows actually running the drift check. The stored blob is already LF,
so nothing renormalizes; a Windows clone predating the attribute needs
the file re-checked out once (`git rm --cached .github/aur/PKGBUILD &&
git checkout -- .github/aur/PKGBUILD`), and CI checks out fresh.
**One thing I could not verify here:** `cargo-nextest` isn't reachable
from the CI agent sandbox (`tend: cargo-nextest came from the runner
home and is unavailable`), so I couldn't run `cargo nextest list -E
'test(/aur_pkgbuild/)'` to confirm the new filterset selects the test.
The expression mirrors the existing `test(/readme_sync/)` rule exactly,
and the repo's own nextest run will exercise it.
<details><summary>Verification</summary>
Run on Linux only — the sandbox has no Windows or macOS host, so the
cross-platform claim rests on CI (`test (windows)` and `full-tests
(windows-2025, windows)`), not on these:
```
cargo test --bin wt aur_pkgbuild # passes
cargo test --bin wt configure_shell # 33 passed
cargo fmt --check # clean
cargo clippy --bin wt --all-features # clean
```
Drift is actually caught: with `--exclusive` deleted from the PKGBUILD's
`complete` line, the test fails and prints the correct replacement body.
The CRLF mechanism behind the Windows failure, reproduced on Linux
against a scratch repo with `core.autocrlf=true`:
```
without .gitattributes entry: CR count = 29
with .gitattributes entry: CR count = 0
```
</details>
---------
Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
|
||
|
|
80e795b271 |
fix(plugin): run the Codex Windows hooks through Git Bash, not the WSL launcher (#4008)
## Problem Codex resolves a hook `command` through the platform shell — `/bin/sh -lc` on Unix, `cmd.exe /C` on Windows ([`default_shell_command`](https://github.com/openai/codex/blob/main/codex-rs/hooks/src/engine/command_runner.rs)). All four Codex hooks in the plugin manifest lead with a bare `bash`, and under `cmd.exe` that resolves through the Windows PATH to `System32\bash.exe` — the WSL launcher, not Git Bash. In a sandboxed session the launcher refuses to start (`Access is denied. Error code: Bash/Service/CreateInstance/E_ACCESSDENIED`), so every prompt, permission request, turn end, and session end raises a `Hook failed` banner (#4007). ## Solution Git for Windows stays a requirement; only the bare *name* `bash` is the problem, since `cmd.exe` resolves it through PATH. Each hook now also carries Codex's per-handler `commandWindows`, which **replaces** `command` on Windows (`command_windows.unwrap_or(command)` in `codex-rs/hooks/src/engine/discovery.rs`). It calls a new `cmd.exe` shim, `plugins/worktrunk/hooks/wt.cmd`, that locates `bash.exe` by path the way [`find_git_bash`](https://github.com/max-sixty/worktrunk/blob/2405b8b434cfa9d7a604405d3ace966ada60ff8a/src/shell_exec.rs#L481-L518) does in `src/shell_exec.rs` — derived from `git.exe`'s install directory, then the system-wide and per-user install defaults — and then runs the same `hooks/wt.sh` that Claude, Gemini, and Unix Codex already go through. Worktrunk's own binary resolution stays in that one script instead of being spelled a second time in cmd. Two details the shim inherits from the Rust resolver: `Git\bin\bash.exe` before `Git\usr\bin\bash.exe`, because the former is the wrapper that sets up the MSYS environment for a caller outside Git Bash (which is what puts `uname` within reach of `wt.sh`); and a PATH-scoped lookup, because an unscoped `where` searches the current directory first — for a hook that's the user's project, so a `git.exe` committed to a repo would otherwise choose the bash every event runs. The lookup is spelled `"%SystemRoot%\System32\where.exe" "$PATH:git.exe"` on both counts: the `$PATH:` prefix scopes what is searched, and the absolute path to `where.exe` closes the same surface one level up — `where` is `System32\where.exe` rather than a cmd built-in, so cmd resolves that bare name from the current directory too. `wt.sh` now clears `WT` before its branches. On Windows all of them can be skipped (neither `git-wt.exe` nor `wt` on PATH), and a hook is handed the caller's whole environment, so an inherited `WT` was what the final `command -v "$WT"` check accepted and ran. The Windows commands brace the plugin root as `${PLUGIN_ROOT}` because Codex substitutes only that form textually, before the shell runs; the unbraced `$PLUGIN_ROOT` the Unix commands use survives to `/bin/sh`, and `cmd.exe` would pass it through literally. The tail is `|| exit /b 0`, the cmd.exe spelling of the Unix `|| true`: a marker is decoration, and a nonzero exit is what raises the banner. `.gitattributes` pins `plugins/worktrunk/hooks/*.cmd` to a CRLF checkout, since cmd.exe resolves a `goto` label by seeking through the file and can fail that search on an LF-only batch file. Two doc changes ride along. `plugins/worktrunk/CLAUDE.md` records why every hook carries `commandWindows`, the shim's resolution order and the two lookup-scoping decisions, and why `SessionEnd` keeps `timeout: 3` (Codex's ceiling for that event, so the longer Windows chain has no more budget to ask for). And because the CHANGELOG entry opens an `## Unreleased` section, `.claude/skills/release/SKILL.md` step 9 now says to rename that heading at release time rather than insert a new one above it — otherwise the release ships a stale `## Unreleased`. ## Testing Six tests, all new: - `test_codex_hooks_carry_windows_commands` (all platforms) — pins that every Codex command hook has a `commandWindows`, that it names neither `bash` nor bare `wt`, that it calls the shim, and that the two `PLUGIN_ROOT` spellings stay on their respective sides. Written first: it failed on the manifest as shipped, with the exact hook command from the report. - `test_codex_windows_hook_commands_set_the_marker` (Windows leg of CI) — runs the real `commandWindows` the way Codex spawns it, reproducing both steps: the `${PLUGIN_ROOT}` substitution and `cmd.exe /C "<command>"` with the command line as a single quoted raw argument. It asserts `UserPromptSubmit` stores 🤖, `Stop` replaces it with 💬, `SessionEnd` clears it, and that a hook which cannot find worktrunk still exits 0. PATH is pinned to the shape a default Git for Windows install produces — `Git\cmd` and nothing else from the install — so the run covers cmd.exe's quote handling, the shim's search for bash, `wt.sh` running under the bash it picks, and the emoji surviving both hops. - `test_wt_sh_ignores_an_inherited_wt` (Windows leg) — points `WT` at a real worktrunk, spelled the way bash can run it, on a PATH where `wt.sh` finds none itself, and pins that the inherited value is not what runs. - `test_shim_ignores_a_git_in_the_current_directory` (Windows leg) — plants an unrunnable `git.exe` in the hook's current directory plus the `bash.exe` the shim would derive from it, puts a real Git and worktrunk on the pinned PATH, and asserts the shim prints a version line — which it could not do had it taken the decoy. - `test_shim_ignores_a_where_in_the_current_directory` (Windows leg) — the same question one level up: a `where.bat` in the hook's current directory names a decoy Git install whose `bin\bash.exe` exists, so an unscoped `where` would reach the derive branch and leave `BASH` set and unrunnable rather than falling through to a real install. A separate probe pins the premise — that the planted `where` really does shadow `System32\where.exe` — so the test cannot go green because the decoy was never consulted. - `test_shim_derives_bash_from_the_git_on_path` (Windows leg) — points `ProgramFiles` and `LOCALAPPDATA` at an empty directory, which leaves the derive branch as the only route to a bash. The two tests above go red only when the shim picks the *wrong* bash; this one goes red when the lookup finds nothing, so it is what observes that `"%SystemRoot%\System32\where.exe" "$PATH:git.exe"` survives the quoting `for /f` wraps it in. <details><summary>What this does not verify</summary> Nothing here drives a real Codex session on Windows, so the end-to-end claim — that Codex selects `commandWindows` and that the banner stops — rests on reading `codex-rs/hooks/src/engine/{discovery,command_runner}.rs` rather than on observation. What CI does exercise is the command string itself, executed the way that source says Codex executes it. The shim's two standard-install fallbacks (`%ProgramFiles%\Git`, `%LOCALAPPDATA%\Programs\Git`) are unexercised by CI. Every test that reaches a bash takes the derive branch above them, and the one test that touches the fallbacks empties them to force that branch rather than to exercise them; they share the whole tail with it. Two adjacent things are deliberately left alone, as separate concerns: the Gemini hooks at the repo-root `hooks/hooks.json` use the same bare `bash` (Gemini's Windows hook execution isn't established here), and Claude's `hooks/hooks.json` is unaffected because Claude Code runs hook commands through Git Bash on Windows. </details> --- Closes #4007 — automated triage --------- Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com> |
||
|
|
c63311db61 |
Collapse the remaining generated mirrors in PR diffs (#3990)
A help-text edit in `src/cli/mod.rs` regenerates three mirrors of the same paragraphs, and `.gitattributes` collapsed only one of them. So a PR touching help text — most of the docs-survey PRs — showed the text three times over. This marks the plugin skills tree and `dev/*.example.toml` generated as well, each with a comment naming what rewrites it. The command pages under `docs/src/content/docs/` deliberately stay visible: a reviewer should read the rendered help once, and the rest of that directory is hand-written primaries. Snapshots stay visible too — they are asserted content, not a mirror. Verified with `git check-attr linguist-generated` over a generated file and a primary in each group, including the plugin mirror's copies of the skill-only pages (`troubleshooting.md`, `shell-integration.md`), which collapse there while their primaries under `skills/worktrunk/reference/` stay exempt. <details> <summary>Considered and left alone: <code>docs/public/schema/list-v2.json</code></summary> It is generated by the same sync test, but `sync_json_schema`'s docstring says a schemars upgrade rewriting it "should be a reviewed diff" — collapsing it by default works against that. </details> > _This was written by Claude Code on behalf of max-sixty_ Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
e5f9589c5c |
Polish mobile docs terminals (#3936)
## Summary - re-record and publish both mobile homepage demos with restored ANSI colors - preserve exact ANSI roles in every snapshot-backed website terminal while keeping Markdown portable - wrap source and command snippets on mobile, contain fixed terminal tables, and add real WebKit layout and contrast regressions The corrected GIFs are already published in `max-sixty/worktrunk-assets` at `4de7bde`. ## Testing - `cargo run -- hook pre-merge --yes` (4,721 tests passed, 1 skipped) - `npm --prefix docs run check` - `npm --prefix docs test` - `npm --prefix docs run build` - `npm --prefix docs run test:site` - independent frame review of both mobile GIF themes - independent architecture, systematic, and adversarial reviews Closes #3930 Closes #3931 > _This was written by Codex on behalf of max-sixty._ |
||
|
|
8e405bced9 |
docs: rebuild the site with Astro and Starlight (#3866)
The docs now build with Astro and Starlight instead of Zola. This removes the Tera 2 migration blocker from #3827 while keeping the published routes, anchors, generated references, metadata, and crawler URLs stable. It replaces the approach closed in #3840. ## What changed - Move the site into Starlight, with a custom Worktrunk homepage and a shared copper, gold, paper, and ink design system. - Keep generated docs as portable Markdown. The Rust sync pipeline no longer needs Zola shortcodes, template escaping, ANSI-to-HTML conversion, or reverse transforms for skill and README output. - Preserve terminal semantics and command-only copying, add responsive handling for short wide tables, retain stable heading IDs, and give repeated command-reference headings useful search labels. - Move docs CI and publishing to Node, Astro, and built-site contract tests. The tests cover public routes, links, assets, metadata, navigation, tables, terminal frames, and compatibility aliases. - Record demo themes from isolated environments, add a mobile core demo, and test the recording contract. The matching mobile assets are published in `max-sixty/worktrunk-assets`. Closes #3827. ## Verification - `cargo run -- hook pre-merge --yes` (4,660 tests) - `npm --prefix docs run check` - `npm --prefix docs test` (9 tests) - `npm --prefix docs run build` (16 pages) - `npm --prefix docs run test:site` (12 tests) - `pytest docs/demos/tests/test_recording.py` (4 tests) > _This was written by Codex on behalf of max-sixty_ |
||
|
|
4c845597b2 |
fix(prune, remove, ci): count what a removal took, not what it selected (#3633)
Three follow-ups from the review of #3533, which landed shared-branch retention across the removal surface. Each is small and independent; they share a branch because they came out of the same pass. ## `wt step prune` counted kinds, not outcomes A candidate's kind is what the scan selected, not what the removal took. A branch a sibling worktree still has checked out is retained, so a worktree candidate can take the worktree and leave the branch standing. The summary counted the kind anyway: ```console ○ Worktree directory missing for feature; pruned ↳ branch checked out at ~/code/repo.feature ✓ Pruned 1 branch ← the branch is right there ``` `prune_summary` now counts the planned outcome, so that reads `✓ Pruned 1 worktree` — the stale entry, which is all that went. Both `--format=json` payloads gained `branch_deleted` for the same reason: a consumer reading `{"branch": "feature", "kind": "branch_only"}` would reasonably conclude the branch is gone. The predicate has one home now. `RemoveResult::to_json` already computed `branch_deleted` inline; it moved to `RemoveResult::deletes_branch()` and both callers share it. That incidentally fixes a detached worktree reporting `"branch_deleted": true` beside a null branch — it has no branch to delete. `wt step prune --dry-run` had the same defect and no per-item line to contradict it, so it now predicts retention too. A `Prunable` item has no plan until `try_remove` prunes its stale entry, so the dry run asks `live_sibling_checkout` — the same predicate the plan would. ## `RemoveTarget::Branch` now means "a branch with no worktree" All three callers resolve first and pass `Path` for anything with a worktree, so the arm handling "the branch turned out to have a worktree" was unreachable at selection time — codecov confirmed it never executed. Deleting it would have been enough, but the arm was a live hazard rather than dead weight: the picker builds a fresh `Repository` and re-lists worktrees between selecting a row and removing it, so a `wt switch` in another terminal can give a branch-only row a worktree mid-flight. The old code would then have removed that worktree, from a row that said "delete this branch" — the same defect class #3533 fixed. ```console ✗ Branch feature gained a worktree @ ~/code/repo.feature since it was selected; to remove that worktree, run wt remove ~/code/repo.feature ``` `test_prepare_removal_refuses_branch_that_gained_a_worktree` covers the picker path. I checked it fails when the guard is neutralized rather than passing trivially. ## `affected tests (windows, advisory)` was permanently red git calls a file binary only when it finds a NUL byte in the first 8000, and two of the committed loose objects under `tests/fixtures/*/_git/` are zlib streams small enough to have none. Those diff as text, so `git diff` writes raw deflate into its output, and `cargo affected` aborts decoding it as a string before running a test. Reproduced with `git show <commit> -- <object>` (invalid UTF-8, byte `0x95` at position 5403) and confirmed fixed — the same command now reports `Binary files … differ`. The rest of `_git/` stays text worth reading. ## Testing `prune_summary_counts_a_retained_branch_as_worktree_only` covers the counting; `test_prune_retains_branch_checked_out_in_another_worktree` gained an assertion on the summary line, and I verified it fails when the guard is removed. The six `.snap` changes are all the same added `branch_deleted` key. > _This was written by Claude Code on behalf of max_ --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
f2a8cb696d |
refactor(styling): end bash-gutter lines at their content, not a reopened dim (#3056)
Dispatched to fix a reported ANSI dim-bleed after bash-gutter blocks: in committed snapshots, multi-command hook announcements appear to inherit an unclosed `[2m` from the preceding gutter line (e.g. `post_start_named_commands.snap`, where the gutter line ends `'Installing deps'[0m[2m`). Diagnosis: the bleed doesn't exist in real output. Fresh `cat -v` captures of `wt hook pre-merge --yes` and `wt switch --create` with two-key hook tables show every gutter line closing with a full `[0m`, and later `◎ Running …` lines rendering un-dimmed. The snapshots are misleading by construction: the cross-platform filter in `tests/common/mod.rs` deletes every line-final `[0m` before snapshotting, and the formatter reopens dim after the last highlight token before its per-line reset, so filtered snapshots end `[0m[2m` and read exactly like a dangling dim. The change removes that misleading byte pattern at the source: phase 2 of `format_bash_with_gutter_impl` strips the no-op reopened dim (and the lone dim on blank lines) before appending each line's closing reset. Rendering is identical; lines now end at their content plus one reset. Also bundled: - A CAUTION comment on the reset-stripping snapshot filter, so future readers don't diagnose SGR bleed from `.snap` bytes. - `config_show_theme` now binds the standard env redactions; regenerating its snapshot leaked a host `LLVM_PROFILE_FILE` path and tripped `test_no_host_specific_paths_in_snapshots` (pre-existing gap, invisible until regeneration since insta never compares `info:` blocks). - 108 regenerated snapshots. Verified mechanically: ANSI-stripped bodies are byte-identical; raw diffs are confined to line-end SGR sequences plus stale `env:` header refreshes (`RUST_LOG: warn` from an older harness). Possible follow-up, not done here: the line-final-reset filter itself may be vestigial (anstream pass-through suggests piped output is identical across platforms now); removing it would make snapshots byte-truthful but churns nearly every snapshot and needs Windows CI to confirm. The first Windows CI run caught a real latent bug the strip exposed: askama strips a template's final newline, so on a CRLF checkout (Windows autocrlf) the fish wrapper render ends with a lone `\r` that the formatter's pair-wise CRLF normalization missed. The `\r` reached tree-sitter and came back as a trailing token after the highlight closed, defeating the end-of-line cleanup (and historically invisible because insta trims trailing whitespace when comparing). Fixed by trimming trailing `\r` in the formatter's normalization, plus `templates/* text eol=lf` in `.gitattributes` since CRLF templates embedded at compile time would leak `\r` into the shell code Windows-built binaries emit at runtime. 🤖 Generated with [Claude Code](https://claude.com/claude-code) > _This was written by Claude Code on behalf of max_ --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
76c8371b40 |
docs: fix stale test name in .gitattributes (#2434)
## Summary The header comment in `.gitattributes` referenced `test_command_pages_and_skill_files_are_in_sync`, but that test was renamed to `test_docs_are_in_sync` in #2419 when the three sync tests were consolidated. ## Test plan - [x] No code changes; comment-only fix - [x] `git grep` confirms no other stale references to the old test name Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
8b4e53f1b2 |
chore(tests): factor write_tracked helper; mark generated files (#2409)
## Summary Three small cleanups to the docs/skills sync pipeline (flagged by `/simplify` review on #2404 but deferred at the time): - **`readme_sync.rs`** — extract a `write_tracked(path, expected, rel_path, updated)` helper that handles `fs::create_dir_all` + `fs::write` + push-to-`updated_files`. Applied at five call sites: `sync_command_pages`, `convert_console_blocks_in_docs`, `sync_skill_files`, `sync_well_known_skills`, `sync_llms_txt`. Callers keep control of the "is it different?" check so each site can apply its own normalization (e.g., `trim_lines`) before comparing. - **`sync_llms_txt`** — merge the two parse/group loops. Use `let-else` to pull out `extra.group` when the frontmatter is first parsed, then push directly into the `BTreeMap`. Drops both the intermediate `Vec<(String, Frontmatter)>` and an unreachable `.expect("non-home pages must declare [extra] group")` that only existed because the group was looked up twice. - **`.gitattributes`** — new file. Marks the 14 auto-generated outputs as `linguist-generated=true` so GitHub collapses them in PR diffs. Targets `skills/worktrunk/reference/*.md`, `docs/static/*.md` (the `.md` symlinks), `docs/static/llms.txt`, and `docs/static/.well-known/agent-skills/index.json`. Primary sources (`docs/content/*.md`, `src/cli/mod.rs`) stay fully visible — `git check-attr` confirms. No behavior change; all 13 `readme_sync` tests pass, `cargo clippy --all-targets --all-features -- -D warnings` clean, `cargo fmt` clean. ## Test plan - [x] `cargo test --test integration readme_sync` — 13 passed - [x] `cargo clippy --all-targets --all-features -- -D warnings` — clean - [x] `cargo fmt --check` — clean - [x] `git check-attr linguist-generated` on a sample of generated + primary-source files — generated flip to `true`, primary sources stay `unspecified` > _This was written by Claude Code on behalf of @max-sixty_ --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com> |
||
|
|
2fb7f199cc |
Remove .gitattributes file
Co-authored-by: Claude <no-reply@anthropic.com> |
||
|
|
a16c6bb6cc |
Refactor: Introduce global output context for consistent messaging
This refactoring centralizes output logic into a global context, simplifying how messages, directory changes, and command executions are handled across different modes (interactive vs. internal).
Key changes:
- **New `output` module structure**:
- `output/global.rs`: Provides `initialize`, `success`, `change_directory`, `execute`, `flush`, `is_interactive` functions using thread-local storage for context-aware output.
- `output/interactive.rs`: Handles human-friendly output with colors, emojis, and direct command execution.
- `output/directive.rs`: Handles machine-readable output for shell integration using NUL-terminated directives.
- `output/handlers.rs`: Contains specific output formatting and handling for `switch` and `remove` commands, leveraging the global context.
- **Removed `src/output.rs`**: The old, monolithic output module is replaced by the new structured approach.
- **Updated `main.rs` and `merge.rs`**:
- Commands now initialize the global output context based on the `--internal` flag.
- Output calls are replaced with `output::success()`, `output::change_directory()`, `output::execute()`, and `output::flush()`.
- Removed `internal` parameters from output handler functions.
- **Snapshot test updates**: Adjusted expected output for various commands to reflect the new output formatting and directive structure.
- **Added `.gitattributes`**: Configures `*.snap` files to be treated as text for proper diffs.
This change improves maintainability, reduces code duplication, and provides a more flexible and consistent output experience.
Co-authored-by: Claude <no-reply@anthropic.com>
|