Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .claude/skills/running-tend/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,27 @@ git grep -niE "claude|codex"

Check the latest IDs at <https://docs.anthropic.com/en/docs/about-claude/models> and <https://developers.openai.com/codex/models>. The recommended commit-message commands should use the most recent fastest model from each vendor (Haiku for Anthropic, the smallest current Codex variant for OpenAI).

## Weekly Maintenance: Agent App Integration Surfaces

Worktrunk ships a plugin for each agent CLI it integrates with, and those CLIs
change their integration surfaces without notice. Each week, scan the upstream
changelogs and flag changes that affect what Worktrunk consumes or produces.

| App | Source to check | Integration surface |
|-----|-----------------|---------------------|
| Claude Code | `gh api repos/anthropics/claude-code/contents/CHANGELOG.md -H 'Accept: application/vnd.github.raw'`, plus `curl -sL https://code.claude.com/docs/en/statusline.md` for the statusline JSON schema | statusline stdin JSON, `WorktreeCreate`/`WorktreeRemove` hooks, plugin marketplace, `/wt-switch-create` |
| Codex | `gh release list -R openai/codex -L 10` | plugin marketplace |
| Gemini CLI | `gh release list -R google-gemini/gemini-cli -L 10` | native extension loading |
| OpenCode | `gh release list -R sst/opencode -L 10` | plugins API in `~/.config/opencode/plugins/` |

What to flag:

- **New statusline JSON fields** — `src/commands/statusline.rs` parses `workspace.current_dir`, `model.display_name`, and `context_window.used_percentage`. A newly added field (rate limits, session cost, PR review state) may be worth surfacing in `wt list statusline`.
- **Renamed or removed hook events** — `WorktreeCreate`/`WorktreeRemove` route agent worktree creation through `wt`; a renamed event silently disables isolation rather than erroring.
- **Changed plugin install mechanisms** — `wt config plugins {claude,codex,opencode} install` and the Gemini extension manifest break if the marketplace or plugins-directory contract changes.

Don't open a PR speculatively. File one issue per relevant change, linking the upstream entry and noting what Worktrunk would need to do. If nothing changed, say so and move on.

## README Date Check

The README blockquote opens with a month+year (e.g., "**April 2026**"). During daily
Expand Down
44 changes: 30 additions & 14 deletions src/styling/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@ pub fn terminal_width() -> usize {
/// stdout, and stderr — so [`terminal_width`] always falls through to its
/// `usize::MAX` sentinel, and the statusline output would overflow the bar.
/// As a last resort, this walks up to 10 parent processes looking for a TTY
/// and asks `stty size` for its dimensions, reserving 20% for Claude Code's
/// own UI messages.
/// and asks `stty size` for its dimensions, reserving 5 columns for Claude
/// Code's own UI messages.
///
/// Every other caller should use [`terminal_width`] — the parent-TTY walk is
/// a statusline-specific workaround, not a general fallback.
Expand All @@ -131,10 +131,7 @@ fn statusline_width_fallback(base: usize) -> usize {
///
/// This is a fallback for subprocesses (like Claude Code hooks) that don't have
/// direct TTY access. Walks up to 10 parent processes looking for one with a TTY,
/// then queries that TTY's size.
///
/// Returns 80% of the detected width to reserve space for Claude Code's UI messages
/// (like "Approaching context limit").
/// then queries that TTY's size via `stty` and [`statusline_width_from_stty_size`].
#[cfg(unix)]
fn detect_parent_tty_width() -> Option<usize> {
use crate::shell_exec::Cmd;
Expand All @@ -160,14 +157,7 @@ fn detect_parent_tty_width() -> Option<usize> {
.run()
.ok()?;

let cols = String::from_utf8_lossy(&size.stdout)
.split_whitespace()
.nth(1)?
.parse::<usize>()
.ok()?;

// Reserve 20% for Claude Code UI messages
return Some(cols * 80 / 100);
return statusline_width_from_stty_size(&String::from_utf8_lossy(&size.stdout));
}

if ppid == "1" || ppid == "0" {
Expand All @@ -179,6 +169,17 @@ fn detect_parent_tty_width() -> Option<usize> {
None
}

/// Convert `stty size` output (`"<rows> <cols>"`) into a statusline width.
///
/// Reserves 5 columns for Claude Code's UI messages (like "Approaching
/// context limit"). Returns `None` when the output has no parseable column
/// count.
#[cfg(unix)]
fn statusline_width_from_stty_size(stty_size: &str) -> Option<usize> {
let cols = stty_size.split_whitespace().nth(1)?.parse::<usize>().ok()?;
Some(cols.saturating_sub(5))
}

/// Calculate visual width of a string, ignoring ANSI escape codes
///
/// Uses unicode-width for proper handling of wide characters (CJK, emoji).
Expand Down Expand Up @@ -232,6 +233,21 @@ mod tests {
assert!(width > 0);
}

#[cfg(unix)]
#[test]
fn statusline_width_from_stty_size_reserves_five_columns() {
// `stty size` prints "<rows> <cols>"; the column count loses 5 to
// Claude Code's UI chrome.
assert_eq!(statusline_width_from_stty_size("24 200"), Some(195));
assert_eq!(statusline_width_from_stty_size("24 80"), Some(75));
// Saturates rather than underflowing on a terminal narrower than 5.
assert_eq!(statusline_width_from_stty_size("24 3"), Some(0));
// No parseable column count.
assert_eq!(statusline_width_from_stty_size(""), None);
assert_eq!(statusline_width_from_stty_size("24"), None);
assert_eq!(statusline_width_from_stty_size("24 wide"), None);
}

#[test]
fn test_toml_formatting() {
let toml_content = r#"worktree-path = "../{{ repo }}.{{ branch }}"
Expand Down
Loading