From 3174017beae353baed7c0b0747fe66cf8c9de801 Mon Sep 17 00:00:00 2001 From: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com> Date: Wed, 12 Aug 2026 23:35:30 +0000 Subject: [PATCH 1/4] fix(list): keep `[list] columns` out of the `--format json` plan MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `wt list --format json` planned `all_columns` unioned with the selection's forced-on columns, so a user's `[list] columns = […, "ci"]` turned a machine-readable call into a forge fetch — `--full` is meant to be the switch for that, and it's the one a caller controls. JSON now plans `all_columns` alone; the table and the picker are unchanged. Reported in #3787 by a Neovim plugin shelling out to `wt`, where the user's display config decided whether the editor talked to GitHub. --- CLAUDE.md | 2 +- dev/config.example.toml | 10 ++-- docs/content/config.md | 10 ++-- docs/content/list.md | 2 +- .../skills/worktrunk/reference/config.md | 10 ++-- .../skills/worktrunk/reference/list.md | 2 +- skills/worktrunk/reference/config.md | 10 ++-- skills/worktrunk/reference/list.md | 2 +- src/cli/mod.rs | 12 ++-- src/commands/list/collect/mod.rs | 29 +++++---- src/commands/list/mod.rs | 5 +- tests/integration_tests/list_config.rs | 59 +++++++++++++++++++ ...ation_tests__help__help_config_create.snap | 10 ++-- ...gration_tests__help__help_config_long.snap | 10 ++-- ...tegration_tests__help__help_list_long.snap | 2 +- ...tion_tests__help__help_list_narrow_80.snap | 6 +- 16 files changed, 130 insertions(+), 51 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 94656e51f4..82ff48091d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -117,7 +117,7 @@ Why: silent "lookup" paths that walk to the wire (alias dispatch, hook context b What currently reaches the wire: -- `wt list --full`, `wt list statusline` — CI status; also plain `wt list` (any format) when `[list] columns` names `ci`, which forces the column (and its fetch) on without `--full` +- `wt list --full`, `wt list statusline` — CI status; also plain `wt list` (table) when `[list] columns` names `ci`, which forces the column (and its fetch) on without `--full`. `--format json` is exempt: it plans off `--full` alone, so a display setting can't send a machine-readable call to a forge - `wt switch` (interactive picker, no target) — per-row CI status, primed from the local cache then fetched live and streamed into the rows; once a row's CI fetch surfaces an open PR/MR, a per-row background `gh pr view --json comments` (`glab api …/notes` on GitLab) fills that row's `comments` preview tab — the same fetch a `--prs` row makes, spawned once per row from `progressive_handler` (see `picker::prs::spawn_comments_fetch`). The `comments` tab is the only PR data fetched lazily here; `pr` rides the CI call and `log` is the local `git log` - generating a branch summary with a `commit.generation` command - generating a commit message with a `commit.generation` command diff --git a/dev/config.example.toml b/dev/config.example.toml index e31e2814cf..fd73b30801 100644 --- a/dev/config.example.toml +++ b/dev/config.example.toml @@ -139,10 +139,12 @@ # needs an LLM command (`[commit.generation]`), `url` needs a `[list] url` # template — since listing can't supply the data. # -# The selection drives the table and the `wt switch` picker. `wt list --format -# json` always emits every field, but a listed gated column (`ci`, `summary`) -# still forces its data collection on, so the JSON carries the same data the -# table shows. +# The selection drives the table and the `wt switch` picker; `wt list --format +# json` ignores it in both directions. JSON always emits every field, and a +# listed `ci` or `summary` does not force its collection on there — that stays +# with `--full` and `[list] summary`, so a display setting never decides whether +# a machine-readable call reaches a forge. Which data a run gathered is reported +# in schema 2's `collected`. # # #### Custom columns [experimental] # diff --git a/docs/content/config.md b/docs/content/config.md index 0e554bcee0..807fbcb60f 100644 --- a/docs/content/config.md +++ b/docs/content/config.md @@ -227,10 +227,12 @@ named one. A column whose data source is missing still stays hidden — `summary needs an LLM command (`[commit.generation]`), `url` needs a `[list] url` template — since listing can't supply the data. -The selection drives the table and the `wt switch` picker. `wt list --format -json` always emits every field, but a listed gated column (`ci`, `summary`) -still forces its data collection on, so the JSON carries the same data the -table shows. +The selection drives the table and the `wt switch` picker; `wt list --format +json` ignores it in both directions. JSON always emits every field, and a +listed `ci` or `summary` does not force its collection on there — that stays +with `--full` and `[list] summary`, so a display setting never decides whether +a machine-readable call reaches a forge. Which data a run gathered is reported +in schema 2's `collected`. #### Custom columns diff --git a/docs/content/list.md b/docs/content/list.md index e89abb5348..f9dceb48f6 100644 --- a/docs/content/list.md +++ b/docs/content/list.md @@ -279,7 +279,7 @@ Item fields: | `worktree` | `{path, main, current, previous, detached, locked, prunable, branch_mismatch, duplicate_branch, operation, changes}`; absent on branch-only rows. `locked`/`prunable` are `{reason}` objects and can co-occur; `operation` is `"rebase"` or `"merge"`; `changes` holds the five working-tree flags plus `conflicted` and `diff {added, deleted}` | | `default_branch` | Relation to the default branch: `{ahead, behind, diff, orphan, integration, merge_conflicts}`; absent on the default branch itself. `integration.reason` is one of `same_commit`, `ancestor`, `no_added_changes`, `trees_match`, `merge_adds_nothing`, `patch_id_match`; a dirty tree skips the checks, leaving `integration` null | | `upstream` | Tracking branch: `{remote, branch, ahead, behind}`; absent when none is configured | -| `pr` | Open PR/MR: `{number, url, review, mergeable, repo}`; collected with `--full` or a listed `ci` column. `review` uses the schema 1 `ci.review_state` vocabulary; `mergeable` is false when the forge reports conflicts, null otherwise | +| `pr` | Open PR/MR: `{number, url, review, mergeable, repo}`; collected with `--full` (`[list] columns` doesn't reach JSON). `review` uses the schema 1 `ci.review_state` vocabulary; `mergeable` is false when the forge reports conflicts, null otherwise | | `checks` | CI pipeline: `{status, source, stale}`; `status` is `passed`, `running`, or `failed` — null when a conflicts report masks it | | `dev_server` | `{url, listening}` from the project's `list.url` template | | `summary` | LLM branch summary (requires `[list] summary = true`) | diff --git a/plugins/worktrunk/skills/worktrunk/reference/config.md b/plugins/worktrunk/skills/worktrunk/reference/config.md index 2f91dd0d93..e912e55c99 100644 --- a/plugins/worktrunk/skills/worktrunk/reference/config.md +++ b/plugins/worktrunk/skills/worktrunk/reference/config.md @@ -226,10 +226,12 @@ named one. A column whose data source is missing still stays hidden — `summary needs an LLM command (`[commit.generation]`), `url` needs a `[list] url` template — since listing can't supply the data. -The selection drives the table and the `wt switch` picker. `wt list --format -json` always emits every field, but a listed gated column (`ci`, `summary`) -still forces its data collection on, so the JSON carries the same data the -table shows. +The selection drives the table and the `wt switch` picker; `wt list --format +json` ignores it in both directions. JSON always emits every field, and a +listed `ci` or `summary` does not force its collection on there — that stays +with `--full` and `[list] summary`, so a display setting never decides whether +a machine-readable call reaches a forge. Which data a run gathered is reported +in schema 2's `collected`. #### Custom columns [experimental] diff --git a/plugins/worktrunk/skills/worktrunk/reference/list.md b/plugins/worktrunk/skills/worktrunk/reference/list.md index a9fb36ce1b..e755da0a96 100644 --- a/plugins/worktrunk/skills/worktrunk/reference/list.md +++ b/plugins/worktrunk/skills/worktrunk/reference/list.md @@ -264,7 +264,7 @@ Item fields: | `worktree` | `{path, main, current, previous, detached, locked, prunable, branch_mismatch, duplicate_branch, operation, changes}`; absent on branch-only rows. `locked`/`prunable` are `{reason}` objects and can co-occur; `operation` is `"rebase"` or `"merge"`; `changes` holds the five working-tree flags plus `conflicted` and `diff {added, deleted}` | | `default_branch` | Relation to the default branch: `{ahead, behind, diff, orphan, integration, merge_conflicts}`; absent on the default branch itself. `integration.reason` is one of `same_commit`, `ancestor`, `no_added_changes`, `trees_match`, `merge_adds_nothing`, `patch_id_match`; a dirty tree skips the checks, leaving `integration` null | | `upstream` | Tracking branch: `{remote, branch, ahead, behind}`; absent when none is configured | -| `pr` | Open PR/MR: `{number, url, review, mergeable, repo}`; collected with `--full` or a listed `ci` column. `review` uses the schema 1 `ci.review_state` vocabulary; `mergeable` is false when the forge reports conflicts, null otherwise | +| `pr` | Open PR/MR: `{number, url, review, mergeable, repo}`; collected with `--full` (`[list] columns` doesn't reach JSON). `review` uses the schema 1 `ci.review_state` vocabulary; `mergeable` is false when the forge reports conflicts, null otherwise | | `checks` | CI pipeline: `{status, source, stale}`; `status` is `passed`, `running`, or `failed` — null when a conflicts report masks it | | `dev_server` | `{url, listening}` from the project's `list.url` template | | `summary` | LLM branch summary (requires `[list] summary = true`) | diff --git a/skills/worktrunk/reference/config.md b/skills/worktrunk/reference/config.md index 2f91dd0d93..e912e55c99 100644 --- a/skills/worktrunk/reference/config.md +++ b/skills/worktrunk/reference/config.md @@ -226,10 +226,12 @@ named one. A column whose data source is missing still stays hidden — `summary needs an LLM command (`[commit.generation]`), `url` needs a `[list] url` template — since listing can't supply the data. -The selection drives the table and the `wt switch` picker. `wt list --format -json` always emits every field, but a listed gated column (`ci`, `summary`) -still forces its data collection on, so the JSON carries the same data the -table shows. +The selection drives the table and the `wt switch` picker; `wt list --format +json` ignores it in both directions. JSON always emits every field, and a +listed `ci` or `summary` does not force its collection on there — that stays +with `--full` and `[list] summary`, so a display setting never decides whether +a machine-readable call reaches a forge. Which data a run gathered is reported +in schema 2's `collected`. #### Custom columns [experimental] diff --git a/skills/worktrunk/reference/list.md b/skills/worktrunk/reference/list.md index a9fb36ce1b..e755da0a96 100644 --- a/skills/worktrunk/reference/list.md +++ b/skills/worktrunk/reference/list.md @@ -264,7 +264,7 @@ Item fields: | `worktree` | `{path, main, current, previous, detached, locked, prunable, branch_mismatch, duplicate_branch, operation, changes}`; absent on branch-only rows. `locked`/`prunable` are `{reason}` objects and can co-occur; `operation` is `"rebase"` or `"merge"`; `changes` holds the five working-tree flags plus `conflicted` and `diff {added, deleted}` | | `default_branch` | Relation to the default branch: `{ahead, behind, diff, orphan, integration, merge_conflicts}`; absent on the default branch itself. `integration.reason` is one of `same_commit`, `ancestor`, `no_added_changes`, `trees_match`, `merge_adds_nothing`, `patch_id_match`; a dirty tree skips the checks, leaving `integration` null | | `upstream` | Tracking branch: `{remote, branch, ahead, behind}`; absent when none is configured | -| `pr` | Open PR/MR: `{number, url, review, mergeable, repo}`; collected with `--full` or a listed `ci` column. `review` uses the schema 1 `ci.review_state` vocabulary; `mergeable` is false when the forge reports conflicts, null otherwise | +| `pr` | Open PR/MR: `{number, url, review, mergeable, repo}`; collected with `--full` (`[list] columns` doesn't reach JSON). `review` uses the schema 1 `ci.review_state` vocabulary; `mergeable` is false when the forge reports conflicts, null otherwise | | `checks` | CI pipeline: `{status, source, stale}`; `status` is `passed`, `running`, or `failed` — null when a conflicts report masks it | | `dev_server` | `{url, listening}` from the project's `list.url` template | | `summary` | LLM branch summary (requires `[list] summary = true`) | diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 3412f03c3d..9fd28f758d 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1026,7 +1026,7 @@ Item fields: | `worktree` | `{path, main, current, previous, detached, locked, prunable, branch_mismatch, duplicate_branch, operation, changes}`; absent on branch-only rows. `locked`/`prunable` are `{reason}` objects and can co-occur; `operation` is `"rebase"` or `"merge"`; `changes` holds the five working-tree flags plus `conflicted` and `diff {added, deleted}` | | `default_branch` | Relation to the default branch: `{ahead, behind, diff, orphan, integration, merge_conflicts}`; absent on the default branch itself. `integration.reason` is one of `same_commit`, `ancestor`, `no_added_changes`, `trees_match`, `merge_adds_nothing`, `patch_id_match`; a dirty tree skips the checks, leaving `integration` null | | `upstream` | Tracking branch: `{remote, branch, ahead, behind}`; absent when none is configured | -| `pr` | Open PR/MR: `{number, url, review, mergeable, repo}`; collected with `--full` or a listed `ci` column. `review` uses the schema 1 `ci.review_state` vocabulary; `mergeable` is false when the forge reports conflicts, null otherwise | +| `pr` | Open PR/MR: `{number, url, review, mergeable, repo}`; collected with `--full` (`[list] columns` doesn't reach JSON). `review` uses the schema 1 `ci.review_state` vocabulary; `mergeable` is false when the forge reports conflicts, null otherwise | | `checks` | CI pipeline: `{status, source, stale}`; `status` is `passed`, `running`, or `failed` — null when a conflicts report masks it | | `dev_server` | `{url, listening}` from the project's `list.url` template | | `summary` | LLM branch summary (requires `[list] summary = true`) | @@ -2117,10 +2117,12 @@ named one. A column whose data source is missing still stays hidden — `summary needs an LLM command (`[commit.generation]`), `url` needs a `[list] url` template — since listing can't supply the data. -The selection drives the table and the `wt switch` picker. `wt list --format -json` always emits every field, but a listed gated column (`ci`, `summary`) -still forces its data collection on, so the JSON carries the same data the -table shows. +The selection drives the table and the `wt switch` picker; `wt list --format +json` ignores it in both directions. JSON always emits every field, and a +listed `ci` or `summary` does not force its collection on there — that stays +with `--full` and `[list] summary`, so a display setting never decides whether +a machine-readable call reaches a forge. Which data a run gathered is reported +in schema 2's `collected`. #### Custom columns [experimental] diff --git a/src/commands/list/collect/mod.rs b/src/commands/list/collect/mod.rs index 353aff5f6e..ebb073f916 100644 --- a/src/commands/list/collect/mod.rs +++ b/src/commands/list/collect/mod.rs @@ -1286,11 +1286,13 @@ pub fn collect( // a listed `summary` that `Default` alone wouldn't plan (LLM command set, // `[list] summary` off): without it the picker would hide a column `wt list` // shows. CI is already covered — the picker is always `show_full`. - // - `--format json` → `all_columns` unioned with the selection's forced-on - // columns: the every-field contract (`src/cli/mod.rs`) needs the full - // set, never a narrowing — but a listed `ci` forces the fetch on, so - // JSON reports the same data the table shows (and `collected.ci` says - // so). + // - `--format json` → `all_columns` alone (source `Default`), so the + // selection reaches it in neither direction. The every-field contract + // (`src/cli/mod.rs`) already rules out the narrowing half; the forcing + // half is out too, because a presentation setting shouldn't decide + // whether a machine-readable call fetches from a forge — `--full` is + // the switch for that, and it's the one a caller controls (#3787). + // `collected` reports what the run actually gathered either way. // // So a branch/path `ls` alias over many dirty worktrees runs no `git status` // / diffs / ahead-behind walks (#3133), while a default column gated off by @@ -1315,16 +1317,19 @@ pub fn collect( &gates, ) }; - let prune_to_selection = - render_table && progressive_handler.is_none() && !selected_columns.is_empty(); - let tasks = if prune_to_selection { - listed_plan() - } else { - // Picker and JSON: the full set plus the selection's forced-on - // columns (a no-op when nothing is selected). + // The picker runs `collect` with a handler and no table render; JSON is the + // remaining handler-less non-table shape. + let tasks = if progressive_handler.is_some() { + // Picker: the full set plus the selection's forced-on columns (a no-op + // when nothing is selected). let mut tasks = full_plan(); tasks.extend(listed_plan()); tasks + } else if render_table && !selected_columns.is_empty() { + listed_plan() + } else { + // `wt list` with no selection, and every `--format json` run. + full_plan() }; // The picker primes its CI cells from the local cache so the column paints diff --git a/src/commands/list/mod.rs b/src/commands/list/mod.rs index d8592c2cf2..879299d06f 100644 --- a/src/commands/list/mod.rs +++ b/src/commands/list/mod.rs @@ -46,8 +46,9 @@ //! First run in a repo without cached default branch adds ~100-300ms for network lookup. //! //! After the skeleton appears, cells fill in progressively as git operations complete. -//! The slowest operation (CI status) runs with `--full`, or when `[list] columns` -//! names `ci` (which forces the column and its fetch on without `--full`). +//! The slowest operation (CI status) runs with `--full`, or — for the table and +//! the picker only — when `[list] columns` names `ci`, which forces the column +//! and its fetch on without `--full`. `--format json` plans off `--full` alone. //! //! ## Git Commands Per Worktree //! diff --git a/tests/integration_tests/list_config.rs b/tests/integration_tests/list_config.rs index 325e05abf2..876cf2ba8a 100644 --- a/tests/integration_tests/list_config.rs +++ b/tests/integration_tests/list_config.rs @@ -1108,6 +1108,65 @@ columns = ["branch", "age"] ); } +/// The selection doesn't reach JSON in the forcing direction either. A listed +/// `ci` renders the CI column in the table without `--full` (asserted in +/// `test_list_config_listed_column_overrides_full_gate`), but `--format json` +/// plans off `--full` alone: a display setting must not decide whether a +/// machine-readable call reaches a forge (#3787). Asserted on schema 2's +/// `collected.ci`, which records what the plan requested rather than what a +/// fetch returned — so this needs no forge and no `gh` on PATH. +#[rstest] +fn test_list_json_columns_selection_does_not_force_ci(repo: TestRepo) { + let collected = |config: &str, full: bool| -> serde_json::Value { + fs::write(repo.test_config_path(), config).unwrap(); + let mut cmd = wt_command(); + repo.configure_wt_cmd(&mut cmd); + cmd.args(["list", "--format=json"]); + if full { + cmd.arg("--full"); + } + cmd.current_dir(repo.root_path()); + let output = cmd.output().unwrap(); + assert!( + output.status.success(), + "exit code should be 0 for config {config:?} (full={full}): {}", + String::from_utf8_lossy(&output.stderr) + ); + let json: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap(); + json["collected"].clone() + }; + + // Control: no selection, no `--full` — nothing gated was requested. + assert_eq!( + collected(SCHEMA_2_ONLY, false)["ci"], + serde_json::Value::Bool(false), + "plain `--format json` should not request CI data" + ); + + // Listing `ci` shows the column in the table, but leaves JSON's plan alone. + assert_eq!( + collected(SCHEMA_2_WITH_CI_COLUMN, false)["ci"], + serde_json::Value::Bool(false), + "a listed `ci` column must not force the forge fetch on for `--format json`" + ); + + // `--full` is the switch that does turn it on, listed or not. + assert_eq!( + collected(SCHEMA_2_WITH_CI_COLUMN, true)["ci"], + serde_json::Value::Bool(true), + "`--full` should still request CI data for `--format json`" + ); +} + +const SCHEMA_2_ONLY: &str = r#"[list] +json-schema = 2 +"#; + +const SCHEMA_2_WITH_CI_COLUMN: &str = r#"[list] +json-schema = 2 +columns = ["branch", "ci"] +"#; + /// TODO(list-columns-env): `WORKTRUNK__LIST__COLUMNS` is not wired up yet. The /// env overlay can only deliver a scalar, which the `Vec` field rejects, /// so the override is dropped — but with a warning naming the var, not silently diff --git a/tests/snapshots/integration__integration_tests__help__help_config_create.snap b/tests/snapshots/integration__integration_tests__help__help_config_create.snap index 441405d991..3e6fcb5f6d 100644 --- a/tests/snapshots/integration__integration_tests__help__help_config_create.snap +++ b/tests/snapshots/integration__integration_tests__help__help_config_create.snap @@ -214,10 +214,12 @@ Creates ~/.config/worktrunk/config.toml with the following content:   # needs an LLM command (`[commit.generation]`), `url` needs a `[list] url`   # template — since listing can't supply the data.   # -  # The selection drives the table and the `wt switch` picker. `wt list --format -  # json` always emits every field, but a listed gated column (`ci`, `summary`) -  # still forces its data collection on, so the JSON carries the same data the -  # table shows. +  # The selection drives the table and the `wt switch` picker; `wt list --format +  # json` ignores it in both directions. JSON always emits every field, and a +  # listed `ci` or `summary` does not force its collection on there — that stays +  # with `--full` and `[list] summary`, so a display setting never decides whether +  # a machine-readable call reaches a forge. Which data a run gathered is reported +  # in schema 2's `collected`.   #   # #### Custom columns [experimental]   # diff --git a/tests/snapshots/integration__integration_tests__help__help_config_long.snap b/tests/snapshots/integration__integration_tests__help__help_config_long.snap index c0fe08fef6..ff3302719c 100644 --- a/tests/snapshots/integration__integration_tests__help__help_config_long.snap +++ b/tests/snapshots/integration__integration_tests__help__help_config_long.snap @@ -262,10 +262,12 @@ named one. A column whose data source is missing still stays hidden — summ needs an LLM command ([commit.generation]), url needs a [list] url template — since listing can't supply the data. -The selection drives the table and the wt switch picker. wt list --format -json always emits every field, but a listed gated column (ci, summary) -still forces its data collection on, so the JSON carries the same data the -table shows. +The selection drives the table and the wt switch picker; wt list --format +json ignores it in both directions. JSON always emits every field, and a +listed ci or summary does not force its collection on there — that stays +with --full and [list] summary, so a display setting never decides whether +a machine-readable call reaches a forge. Which data a run gathered is reported +in schema 2's collected. Custom columns [experimental] diff --git a/tests/snapshots/integration__integration_tests__help__help_list_long.snap b/tests/snapshots/integration__integration_tests__help__help_list_long.snap index c136ef9430..d36cbe33fb 100644 --- a/tests/snapshots/integration__integration_tests__help__help_list_long.snap +++ b/tests/snapshots/integration__integration_tests__help__help_list_long.snap @@ -340,7 +340,7 @@ Item fields: worktree {path, main, current, previous, detached, locked, prunable, branch_mismatch, duplicate_branch, operation, changes}; absent on branch-only rows. locked/prunable are {reason} objects and can co-occur; operation is "rebase" or "merge"; changes holds the five working-tree flags plus conflicted and diff {added, deleted} default_branch Relation to the default branch: {ahead, behind, diff, orphan, integration, merge_conflicts}; absent on the default branch itself. integration.reason is one of same_commit, ancestor, no_added_changes, trees_match, merge_adds_nothing, patch_id_match; a dirty tree skips the checks, leaving integration null upstream Tracking branch: {remote, branch, ahead, behind}; absent when none is configured - pr Open PR/MR: {number, url, review, mergeable, repo}; collected with --full or a listed ci column. review uses the schema 1 ci.review_state vocabulary; mergeable is false when the forge reports conflicts, null otherwise + pr Open PR/MR: {number, url, review, mergeable, repo}; collected with --full ([list] columns doesn't reach JSON). review uses the schema 1 ci.review_state vocabulary; mergeable is false when the forge reports conflicts, null otherwise checks CI pipeline: {status, source, stale}; status is passed, running, or failed — null when a conflicts report masks it dev_server {url, listening} from the project's list.url template summary LLM branch summary (requires [list] summary = true) diff --git a/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap b/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap index 7499e2ad47..8276346e7d 100644 --- a/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap +++ b/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap @@ -428,9 +428,9 @@ Item fields: upstrea Tracking branch: {remote, branch, ahead, behind}; absent when none is m configured pr Open PR/MR: {number, url, review, mergeable, repo}; collected with - --full or a listed ci column. review uses the schema 1 ci.review_state - vocabulary; mergeable is false when the forge reports conflicts, null - otherwise + --full ([list] columns doesn't reach JSON). review uses the schema 1 + ci.review_state vocabulary; mergeable is false when the forge reports + conflicts, null otherwise checks CI pipeline: {status, source, stale}; status is passed, running, or failed — null when a conflicts report masks it dev_ser {url, listening} from the project's list.url template From df5c2387431d0d9b26577ec770e78e0a9ca0e702 Mon Sep 17 00:00:00 2001 From: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com> Date: Wed, 12 Aug 2026 23:49:38 +0000 Subject: [PATCH 2/4] docs(list): name `--full` in the schema-2 `summary` and `checks` rows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both rows described a gate the JSON plan no longer honours in full. With `[list] columns` out of the JSON plan, `summary` resolves under `ColumnSource::Default`, so it needs `--full` *and* `[list] summary = true` (plus a `[commit.generation]` command) — the listed-column escape hatch that made `[list] summary = true` alone sufficient is what this PR removes. `checks` rides the same `CiStatus` task as `pr` and stated no gate at all. --- docs/content/list.md | 4 ++-- plugins/worktrunk/skills/worktrunk/reference/list.md | 4 ++-- skills/worktrunk/reference/list.md | 4 ++-- src/cli/mod.rs | 4 ++-- ...ation__integration_tests__help__help_list_long.snap | 5 +++-- ...__integration_tests__help__help_list_narrow_80.snap | 10 +++++++--- 6 files changed, 18 insertions(+), 13 deletions(-) diff --git a/docs/content/list.md b/docs/content/list.md index f9dceb48f6..92984fa4c3 100644 --- a/docs/content/list.md +++ b/docs/content/list.md @@ -280,9 +280,9 @@ Item fields: | `default_branch` | Relation to the default branch: `{ahead, behind, diff, orphan, integration, merge_conflicts}`; absent on the default branch itself. `integration.reason` is one of `same_commit`, `ancestor`, `no_added_changes`, `trees_match`, `merge_adds_nothing`, `patch_id_match`; a dirty tree skips the checks, leaving `integration` null | | `upstream` | Tracking branch: `{remote, branch, ahead, behind}`; absent when none is configured | | `pr` | Open PR/MR: `{number, url, review, mergeable, repo}`; collected with `--full` (`[list] columns` doesn't reach JSON). `review` uses the schema 1 `ci.review_state` vocabulary; `mergeable` is false when the forge reports conflicts, null otherwise | -| `checks` | CI pipeline: `{status, source, stale}`; `status` is `passed`, `running`, or `failed` — null when a conflicts report masks it | +| `checks` | CI pipeline: `{status, source, stale}`; collected with `--full`, on the same task as `pr`. `status` is `passed`, `running`, or `failed` — null when a conflicts report masks it | | `dev_server` | `{url, listening}` from the project's `list.url` template | -| `summary` | LLM branch summary (requires `[list] summary = true`) | +| `summary` | LLM branch summary; collected with `--full` plus `[list] summary = true` (`[list] columns` doesn't reach JSON), and needs a `[commit.generation]` command | | `vars` | Per-branch variables from [`wt config state vars`](@/config.md#wt-config-state-vars) | | `display` | Rendered strings: `state` (schema 1's `main_state` vocabulary), `symbols`, `statusline` (with ANSI colors and OSC 8 hyperlinks), `columns` (custom-column cells keyed by header) | diff --git a/plugins/worktrunk/skills/worktrunk/reference/list.md b/plugins/worktrunk/skills/worktrunk/reference/list.md index e755da0a96..d08bd2ab2a 100644 --- a/plugins/worktrunk/skills/worktrunk/reference/list.md +++ b/plugins/worktrunk/skills/worktrunk/reference/list.md @@ -265,9 +265,9 @@ Item fields: | `default_branch` | Relation to the default branch: `{ahead, behind, diff, orphan, integration, merge_conflicts}`; absent on the default branch itself. `integration.reason` is one of `same_commit`, `ancestor`, `no_added_changes`, `trees_match`, `merge_adds_nothing`, `patch_id_match`; a dirty tree skips the checks, leaving `integration` null | | `upstream` | Tracking branch: `{remote, branch, ahead, behind}`; absent when none is configured | | `pr` | Open PR/MR: `{number, url, review, mergeable, repo}`; collected with `--full` (`[list] columns` doesn't reach JSON). `review` uses the schema 1 `ci.review_state` vocabulary; `mergeable` is false when the forge reports conflicts, null otherwise | -| `checks` | CI pipeline: `{status, source, stale}`; `status` is `passed`, `running`, or `failed` — null when a conflicts report masks it | +| `checks` | CI pipeline: `{status, source, stale}`; collected with `--full`, on the same task as `pr`. `status` is `passed`, `running`, or `failed` — null when a conflicts report masks it | | `dev_server` | `{url, listening}` from the project's `list.url` template | -| `summary` | LLM branch summary (requires `[list] summary = true`) | +| `summary` | LLM branch summary; collected with `--full` plus `[list] summary = true` (`[list] columns` doesn't reach JSON), and needs a `[commit.generation]` command | | `vars` | Per-branch variables from [`wt config state vars`](https://worktrunk.dev/config/#wt-config-state-vars) | | `display` | Rendered strings: `state` (schema 1's `main_state` vocabulary), `symbols`, `statusline` (with ANSI colors and OSC 8 hyperlinks), `columns` (custom-column cells keyed by header) | diff --git a/skills/worktrunk/reference/list.md b/skills/worktrunk/reference/list.md index e755da0a96..d08bd2ab2a 100644 --- a/skills/worktrunk/reference/list.md +++ b/skills/worktrunk/reference/list.md @@ -265,9 +265,9 @@ Item fields: | `default_branch` | Relation to the default branch: `{ahead, behind, diff, orphan, integration, merge_conflicts}`; absent on the default branch itself. `integration.reason` is one of `same_commit`, `ancestor`, `no_added_changes`, `trees_match`, `merge_adds_nothing`, `patch_id_match`; a dirty tree skips the checks, leaving `integration` null | | `upstream` | Tracking branch: `{remote, branch, ahead, behind}`; absent when none is configured | | `pr` | Open PR/MR: `{number, url, review, mergeable, repo}`; collected with `--full` (`[list] columns` doesn't reach JSON). `review` uses the schema 1 `ci.review_state` vocabulary; `mergeable` is false when the forge reports conflicts, null otherwise | -| `checks` | CI pipeline: `{status, source, stale}`; `status` is `passed`, `running`, or `failed` — null when a conflicts report masks it | +| `checks` | CI pipeline: `{status, source, stale}`; collected with `--full`, on the same task as `pr`. `status` is `passed`, `running`, or `failed` — null when a conflicts report masks it | | `dev_server` | `{url, listening}` from the project's `list.url` template | -| `summary` | LLM branch summary (requires `[list] summary = true`) | +| `summary` | LLM branch summary; collected with `--full` plus `[list] summary = true` (`[list] columns` doesn't reach JSON), and needs a `[commit.generation]` command | | `vars` | Per-branch variables from [`wt config state vars`](https://worktrunk.dev/config/#wt-config-state-vars) | | `display` | Rendered strings: `state` (schema 1's `main_state` vocabulary), `symbols`, `statusline` (with ANSI colors and OSC 8 hyperlinks), `columns` (custom-column cells keyed by header) | diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 9fd28f758d..cabe3e0384 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1027,9 +1027,9 @@ Item fields: | `default_branch` | Relation to the default branch: `{ahead, behind, diff, orphan, integration, merge_conflicts}`; absent on the default branch itself. `integration.reason` is one of `same_commit`, `ancestor`, `no_added_changes`, `trees_match`, `merge_adds_nothing`, `patch_id_match`; a dirty tree skips the checks, leaving `integration` null | | `upstream` | Tracking branch: `{remote, branch, ahead, behind}`; absent when none is configured | | `pr` | Open PR/MR: `{number, url, review, mergeable, repo}`; collected with `--full` (`[list] columns` doesn't reach JSON). `review` uses the schema 1 `ci.review_state` vocabulary; `mergeable` is false when the forge reports conflicts, null otherwise | -| `checks` | CI pipeline: `{status, source, stale}`; `status` is `passed`, `running`, or `failed` — null when a conflicts report masks it | +| `checks` | CI pipeline: `{status, source, stale}`; collected with `--full`, on the same task as `pr`. `status` is `passed`, `running`, or `failed` — null when a conflicts report masks it | | `dev_server` | `{url, listening}` from the project's `list.url` template | -| `summary` | LLM branch summary (requires `[list] summary = true`) | +| `summary` | LLM branch summary; collected with `--full` plus `[list] summary = true` (`[list] columns` doesn't reach JSON), and needs a `[commit.generation]` command | | `vars` | Per-branch variables from [`wt config state vars`](@/config.md#wt-config-state-vars) | | `display` | Rendered strings: `state` (schema 1's `main_state` vocabulary), `symbols`, `statusline` (with ANSI colors and OSC 8 hyperlinks), `columns` (custom-column cells keyed by header) | diff --git a/tests/snapshots/integration__integration_tests__help__help_list_long.snap b/tests/snapshots/integration__integration_tests__help__help_list_long.snap index d36cbe33fb..3d24106c28 100644 --- a/tests/snapshots/integration__integration_tests__help__help_list_long.snap +++ b/tests/snapshots/integration__integration_tests__help__help_list_long.snap @@ -35,6 +35,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" --- @@ -341,9 +342,9 @@ Item fields: default_branch Relation to the default branch: {ahead, behind, diff, orphan, integration, merge_conflicts}; absent on the default branch itself. integration.reason is one of same_commit, ancestor, no_added_changes, trees_match, merge_adds_nothing, patch_id_match; a dirty tree skips the checks, leaving integration null upstream Tracking branch: {remote, branch, ahead, behind}; absent when none is configured pr Open PR/MR: {number, url, review, mergeable, repo}; collected with --full ([list] columns doesn't reach JSON). review uses the schema 1 ci.review_state vocabulary; mergeable is false when the forge reports conflicts, null otherwise - checks CI pipeline: {status, source, stale}; status is passed, running, or failed — null when a conflicts report masks it + checks CI pipeline: {status, source, stale}; collected with --full, on the same task as pr. status is passed, running, or failed — null when a conflicts report masks it dev_server {url, listening} from the project's list.url template - summary LLM branch summary (requires [list] summary = true) + summary LLM branch summary; collected with --full plus [list] summary = true ([list] columns doesn't reach JSON), and needs a [commit.generation] command vars Per-branch variables from wt config state vars display Rendered strings: state (schema 1's main_state vocabulary), symbols, statusline (with ANSI colors and OSC 8 hyperlinks), columns (custom-column cells keyed by header) diff --git a/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap b/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap index 8276346e7d..889fe668ee 100644 --- a/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap +++ b/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap @@ -35,6 +35,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" --- @@ -431,11 +432,14 @@ Item fields: --full ([list] columns doesn't reach JSON). review uses the schema 1 ci.review_state vocabulary; mergeable is false when the forge reports conflicts, null otherwise - checks CI pipeline: {status, source, stale}; status is passed, running, or - failed — null when a conflicts report masks it + checks CI pipeline: {status, source, stale}; collected with --full, on the + same task as pr. status is passed, running, or failed — null when a + conflicts report masks it dev_ser {url, listening} from the project's list.url template ver - summary LLM branch summary (requires [list] summary = true) + summary LLM branch summary; collected with --full plus [list] summary = true ( + [list] columns doesn't reach JSON), and needs a [commit.generation] + command vars Per-branch variables from wt config state vars display Rendered strings: state (schema 1's main_state vocabulary), symbols, statusline (with ANSI colors and OSC 8 hyperlinks), columns From 51c812110da8f567802a3f656e08cde744f375eb Mon Sep 17 00:00:00 2001 From: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com> Date: Thu, 13 Aug 2026 23:54:16 +0000 Subject: [PATCH 3/4] docs(list): state the `columns` scope once, in its own definition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The JSON caveat was repeated across the `columns` prose, three item-field rows, and two code comments. Say it once where `columns` is defined — the selection drives the `wt list` table and the picker, `--format json` ignores it — and drop the per-row annotations, which now just name `--full` as the gate. --- CLAUDE.md | 2 +- dev/config.example.toml | 19 ++++++-------- docs/content/config.md | 19 ++++++-------- docs/content/list.md | 6 ++--- .../skills/worktrunk/reference/config.md | 19 ++++++-------- .../skills/worktrunk/reference/list.md | 6 ++--- skills/worktrunk/reference/config.md | 19 ++++++-------- skills/worktrunk/reference/list.md | 6 ++--- src/cli/mod.rs | 25 ++++++++----------- src/commands/list/collect/mod.rs | 10 +++----- src/commands/list/mod.rs | 2 +- tests/integration_tests/list_config.rs | 8 +++--- ...ation_tests__help__help_config_create.snap | 20 ++++++--------- ...gration_tests__help__help_config_long.snap | 20 ++++++--------- ...tegration_tests__help__help_list_long.snap | 6 ++--- ...tion_tests__help__help_list_narrow_80.snap | 15 +++++------ 16 files changed, 81 insertions(+), 121 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 82ff48091d..7485d09002 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -117,7 +117,7 @@ Why: silent "lookup" paths that walk to the wire (alias dispatch, hook context b What currently reaches the wire: -- `wt list --full`, `wt list statusline` — CI status; also plain `wt list` (table) when `[list] columns` names `ci`, which forces the column (and its fetch) on without `--full`. `--format json` is exempt: it plans off `--full` alone, so a display setting can't send a machine-readable call to a forge +- `wt list --full`, `wt list statusline` — CI status; also plain `wt list` (table) when `[list] columns` names `ci`, which forces the column (and its fetch) on without `--full`. `--format json` plans off `--full` alone, so a display setting can't send a machine-readable call to a forge - `wt switch` (interactive picker, no target) — per-row CI status, primed from the local cache then fetched live and streamed into the rows; once a row's CI fetch surfaces an open PR/MR, a per-row background `gh pr view --json comments` (`glab api …/notes` on GitLab) fills that row's `comments` preview tab — the same fetch a `--prs` row makes, spawned once per row from `progressive_handler` (see `picker::prs::spawn_comments_fetch`). The `comments` tab is the only PR data fetched lazily here; `pr` rides the CI call and `log` is the local `git log` - generating a branch summary with a `commit.generation` command - generating a commit message with a `commit.generation` command diff --git a/dev/config.example.toml b/dev/config.example.toml index fd73b30801..d91383263c 100644 --- a/dev/config.example.toml +++ b/dev/config.example.toml @@ -105,11 +105,13 @@ # # timeout-ms = 0 # Wall-clock budget for the entire collect phase; 0 disables # -# `columns` selects and orders the columns to render; omit it for the default set. -# It is meant to drive a per-invocation alias (https://worktrunk.dev/extending/#aliases) -# (`wt --config-set 'list.columns=[…]' list`), giving a named view without -# disturbing the default `wt list`. A static setting works but pins one layout -# over a table that otherwise adapts to `--full` and terminal width. +# `columns` selects and orders the columns the `wt list` table and the `wt switch` +# picker render; `--format json` ignores it and always emits every field. Omit it +# for the default set. It is meant to drive a per-invocation +# alias (https://worktrunk.dev/extending/#aliases) (`wt --config-set 'list.columns=[…]' list`), +# giving a named view without disturbing the default `wt list`. A static setting +# works but pins one layout over a table that otherwise adapts to `--full` and +# terminal width. # # Valid built-in names: # @@ -139,13 +141,6 @@ # needs an LLM command (`[commit.generation]`), `url` needs a `[list] url` # template — since listing can't supply the data. # -# The selection drives the table and the `wt switch` picker; `wt list --format -# json` ignores it in both directions. JSON always emits every field, and a -# listed `ci` or `summary` does not force its collection on there — that stays -# with `--full` and `[list] summary`, so a display setting never decides whether -# a machine-readable call reaches a forge. Which data a run gathered is reported -# in schema 2's `collected`. -# # #### Custom columns [experimental] # # Custom columns add per-branch context to the `wt list` table. Each diff --git a/docs/content/config.md b/docs/content/config.md index 807fbcb60f..9c36ba1f17 100644 --- a/docs/content/config.md +++ b/docs/content/config.md @@ -193,11 +193,13 @@ columns = ["branch", "status", "ci", "path"] # Columns to show, in order — b timeout-ms = 0 # Wall-clock budget for the entire collect phase; 0 disables ``` -`columns` selects and orders the columns to render; omit it for the default set. -It is meant to drive a per-invocation [alias](@/extending.md#aliases) -(`wt --config-set 'list.columns=[…]' list`), giving a named view without -disturbing the default `wt list`. A static setting works but pins one layout -over a table that otherwise adapts to `--full` and terminal width. +`columns` selects and orders the columns the `wt list` table and the `wt switch` +picker render; `--format json` ignores it and always emits every field. Omit it +for the default set. It is meant to drive a per-invocation +[alias](@/extending.md#aliases) (`wt --config-set 'list.columns=[…]' list`), +giving a named view without disturbing the default `wt list`. A static setting +works but pins one layout over a table that otherwise adapts to `--full` and +terminal width. Valid built-in names: @@ -227,13 +229,6 @@ named one. A column whose data source is missing still stays hidden — `summary needs an LLM command (`[commit.generation]`), `url` needs a `[list] url` template — since listing can't supply the data. -The selection drives the table and the `wt switch` picker; `wt list --format -json` ignores it in both directions. JSON always emits every field, and a -listed `ci` or `summary` does not force its collection on there — that stays -with `--full` and `[list] summary`, so a display setting never decides whether -a machine-readable call reaches a forge. Which data a run gathered is reported -in schema 2's `collected`. - #### Custom columns diff --git a/docs/content/list.md b/docs/content/list.md index 92984fa4c3..d3948f0984 100644 --- a/docs/content/list.md +++ b/docs/content/list.md @@ -279,10 +279,10 @@ Item fields: | `worktree` | `{path, main, current, previous, detached, locked, prunable, branch_mismatch, duplicate_branch, operation, changes}`; absent on branch-only rows. `locked`/`prunable` are `{reason}` objects and can co-occur; `operation` is `"rebase"` or `"merge"`; `changes` holds the five working-tree flags plus `conflicted` and `diff {added, deleted}` | | `default_branch` | Relation to the default branch: `{ahead, behind, diff, orphan, integration, merge_conflicts}`; absent on the default branch itself. `integration.reason` is one of `same_commit`, `ancestor`, `no_added_changes`, `trees_match`, `merge_adds_nothing`, `patch_id_match`; a dirty tree skips the checks, leaving `integration` null | | `upstream` | Tracking branch: `{remote, branch, ahead, behind}`; absent when none is configured | -| `pr` | Open PR/MR: `{number, url, review, mergeable, repo}`; collected with `--full` (`[list] columns` doesn't reach JSON). `review` uses the schema 1 `ci.review_state` vocabulary; `mergeable` is false when the forge reports conflicts, null otherwise | -| `checks` | CI pipeline: `{status, source, stale}`; collected with `--full`, on the same task as `pr`. `status` is `passed`, `running`, or `failed` — null when a conflicts report masks it | +| `pr` | Open PR/MR: `{number, url, review, mergeable, repo}`; collected with `--full`. `review` uses the schema 1 `ci.review_state` vocabulary; `mergeable` is false when the forge reports conflicts, null otherwise | +| `checks` | CI pipeline: `{status, source, stale}`; collected with `--full`. `status` is `passed`, `running`, or `failed` — null when a conflicts report masks it | | `dev_server` | `{url, listening}` from the project's `list.url` template | -| `summary` | LLM branch summary; collected with `--full` plus `[list] summary = true` (`[list] columns` doesn't reach JSON), and needs a `[commit.generation]` command | +| `summary` | LLM branch summary; needs `--full`, `[list] summary = true`, and a `[commit.generation]` command | | `vars` | Per-branch variables from [`wt config state vars`](@/config.md#wt-config-state-vars) | | `display` | Rendered strings: `state` (schema 1's `main_state` vocabulary), `symbols`, `statusline` (with ANSI colors and OSC 8 hyperlinks), `columns` (custom-column cells keyed by header) | diff --git a/plugins/worktrunk/skills/worktrunk/reference/config.md b/plugins/worktrunk/skills/worktrunk/reference/config.md index e912e55c99..5bdf118408 100644 --- a/plugins/worktrunk/skills/worktrunk/reference/config.md +++ b/plugins/worktrunk/skills/worktrunk/reference/config.md @@ -192,11 +192,13 @@ columns = ["branch", "status", "ci", "path"] # Columns to show, in order — b timeout-ms = 0 # Wall-clock budget for the entire collect phase; 0 disables ``` -`columns` selects and orders the columns to render; omit it for the default set. -It is meant to drive a per-invocation [alias](https://worktrunk.dev/extending/#aliases) -(`wt --config-set 'list.columns=[…]' list`), giving a named view without -disturbing the default `wt list`. A static setting works but pins one layout -over a table that otherwise adapts to `--full` and terminal width. +`columns` selects and orders the columns the `wt list` table and the `wt switch` +picker render; `--format json` ignores it and always emits every field. Omit it +for the default set. It is meant to drive a per-invocation +[alias](https://worktrunk.dev/extending/#aliases) (`wt --config-set 'list.columns=[…]' list`), +giving a named view without disturbing the default `wt list`. A static setting +works but pins one layout over a table that otherwise adapts to `--full` and +terminal width. Valid built-in names: @@ -226,13 +228,6 @@ named one. A column whose data source is missing still stays hidden — `summary needs an LLM command (`[commit.generation]`), `url` needs a `[list] url` template — since listing can't supply the data. -The selection drives the table and the `wt switch` picker; `wt list --format -json` ignores it in both directions. JSON always emits every field, and a -listed `ci` or `summary` does not force its collection on there — that stays -with `--full` and `[list] summary`, so a display setting never decides whether -a machine-readable call reaches a forge. Which data a run gathered is reported -in schema 2's `collected`. - #### Custom columns [experimental] Custom columns add per-branch context to the `wt list` table. Each diff --git a/plugins/worktrunk/skills/worktrunk/reference/list.md b/plugins/worktrunk/skills/worktrunk/reference/list.md index d08bd2ab2a..643e897209 100644 --- a/plugins/worktrunk/skills/worktrunk/reference/list.md +++ b/plugins/worktrunk/skills/worktrunk/reference/list.md @@ -264,10 +264,10 @@ Item fields: | `worktree` | `{path, main, current, previous, detached, locked, prunable, branch_mismatch, duplicate_branch, operation, changes}`; absent on branch-only rows. `locked`/`prunable` are `{reason}` objects and can co-occur; `operation` is `"rebase"` or `"merge"`; `changes` holds the five working-tree flags plus `conflicted` and `diff {added, deleted}` | | `default_branch` | Relation to the default branch: `{ahead, behind, diff, orphan, integration, merge_conflicts}`; absent on the default branch itself. `integration.reason` is one of `same_commit`, `ancestor`, `no_added_changes`, `trees_match`, `merge_adds_nothing`, `patch_id_match`; a dirty tree skips the checks, leaving `integration` null | | `upstream` | Tracking branch: `{remote, branch, ahead, behind}`; absent when none is configured | -| `pr` | Open PR/MR: `{number, url, review, mergeable, repo}`; collected with `--full` (`[list] columns` doesn't reach JSON). `review` uses the schema 1 `ci.review_state` vocabulary; `mergeable` is false when the forge reports conflicts, null otherwise | -| `checks` | CI pipeline: `{status, source, stale}`; collected with `--full`, on the same task as `pr`. `status` is `passed`, `running`, or `failed` — null when a conflicts report masks it | +| `pr` | Open PR/MR: `{number, url, review, mergeable, repo}`; collected with `--full`. `review` uses the schema 1 `ci.review_state` vocabulary; `mergeable` is false when the forge reports conflicts, null otherwise | +| `checks` | CI pipeline: `{status, source, stale}`; collected with `--full`. `status` is `passed`, `running`, or `failed` — null when a conflicts report masks it | | `dev_server` | `{url, listening}` from the project's `list.url` template | -| `summary` | LLM branch summary; collected with `--full` plus `[list] summary = true` (`[list] columns` doesn't reach JSON), and needs a `[commit.generation]` command | +| `summary` | LLM branch summary; needs `--full`, `[list] summary = true`, and a `[commit.generation]` command | | `vars` | Per-branch variables from [`wt config state vars`](https://worktrunk.dev/config/#wt-config-state-vars) | | `display` | Rendered strings: `state` (schema 1's `main_state` vocabulary), `symbols`, `statusline` (with ANSI colors and OSC 8 hyperlinks), `columns` (custom-column cells keyed by header) | diff --git a/skills/worktrunk/reference/config.md b/skills/worktrunk/reference/config.md index e912e55c99..5bdf118408 100644 --- a/skills/worktrunk/reference/config.md +++ b/skills/worktrunk/reference/config.md @@ -192,11 +192,13 @@ columns = ["branch", "status", "ci", "path"] # Columns to show, in order — b timeout-ms = 0 # Wall-clock budget for the entire collect phase; 0 disables ``` -`columns` selects and orders the columns to render; omit it for the default set. -It is meant to drive a per-invocation [alias](https://worktrunk.dev/extending/#aliases) -(`wt --config-set 'list.columns=[…]' list`), giving a named view without -disturbing the default `wt list`. A static setting works but pins one layout -over a table that otherwise adapts to `--full` and terminal width. +`columns` selects and orders the columns the `wt list` table and the `wt switch` +picker render; `--format json` ignores it and always emits every field. Omit it +for the default set. It is meant to drive a per-invocation +[alias](https://worktrunk.dev/extending/#aliases) (`wt --config-set 'list.columns=[…]' list`), +giving a named view without disturbing the default `wt list`. A static setting +works but pins one layout over a table that otherwise adapts to `--full` and +terminal width. Valid built-in names: @@ -226,13 +228,6 @@ named one. A column whose data source is missing still stays hidden — `summary needs an LLM command (`[commit.generation]`), `url` needs a `[list] url` template — since listing can't supply the data. -The selection drives the table and the `wt switch` picker; `wt list --format -json` ignores it in both directions. JSON always emits every field, and a -listed `ci` or `summary` does not force its collection on there — that stays -with `--full` and `[list] summary`, so a display setting never decides whether -a machine-readable call reaches a forge. Which data a run gathered is reported -in schema 2's `collected`. - #### Custom columns [experimental] Custom columns add per-branch context to the `wt list` table. Each diff --git a/skills/worktrunk/reference/list.md b/skills/worktrunk/reference/list.md index d08bd2ab2a..643e897209 100644 --- a/skills/worktrunk/reference/list.md +++ b/skills/worktrunk/reference/list.md @@ -264,10 +264,10 @@ Item fields: | `worktree` | `{path, main, current, previous, detached, locked, prunable, branch_mismatch, duplicate_branch, operation, changes}`; absent on branch-only rows. `locked`/`prunable` are `{reason}` objects and can co-occur; `operation` is `"rebase"` or `"merge"`; `changes` holds the five working-tree flags plus `conflicted` and `diff {added, deleted}` | | `default_branch` | Relation to the default branch: `{ahead, behind, diff, orphan, integration, merge_conflicts}`; absent on the default branch itself. `integration.reason` is one of `same_commit`, `ancestor`, `no_added_changes`, `trees_match`, `merge_adds_nothing`, `patch_id_match`; a dirty tree skips the checks, leaving `integration` null | | `upstream` | Tracking branch: `{remote, branch, ahead, behind}`; absent when none is configured | -| `pr` | Open PR/MR: `{number, url, review, mergeable, repo}`; collected with `--full` (`[list] columns` doesn't reach JSON). `review` uses the schema 1 `ci.review_state` vocabulary; `mergeable` is false when the forge reports conflicts, null otherwise | -| `checks` | CI pipeline: `{status, source, stale}`; collected with `--full`, on the same task as `pr`. `status` is `passed`, `running`, or `failed` — null when a conflicts report masks it | +| `pr` | Open PR/MR: `{number, url, review, mergeable, repo}`; collected with `--full`. `review` uses the schema 1 `ci.review_state` vocabulary; `mergeable` is false when the forge reports conflicts, null otherwise | +| `checks` | CI pipeline: `{status, source, stale}`; collected with `--full`. `status` is `passed`, `running`, or `failed` — null when a conflicts report masks it | | `dev_server` | `{url, listening}` from the project's `list.url` template | -| `summary` | LLM branch summary; collected with `--full` plus `[list] summary = true` (`[list] columns` doesn't reach JSON), and needs a `[commit.generation]` command | +| `summary` | LLM branch summary; needs `--full`, `[list] summary = true`, and a `[commit.generation]` command | | `vars` | Per-branch variables from [`wt config state vars`](https://worktrunk.dev/config/#wt-config-state-vars) | | `display` | Rendered strings: `state` (schema 1's `main_state` vocabulary), `symbols`, `statusline` (with ANSI colors and OSC 8 hyperlinks), `columns` (custom-column cells keyed by header) | diff --git a/src/cli/mod.rs b/src/cli/mod.rs index cabe3e0384..186edb1efc 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1026,10 +1026,10 @@ Item fields: | `worktree` | `{path, main, current, previous, detached, locked, prunable, branch_mismatch, duplicate_branch, operation, changes}`; absent on branch-only rows. `locked`/`prunable` are `{reason}` objects and can co-occur; `operation` is `"rebase"` or `"merge"`; `changes` holds the five working-tree flags plus `conflicted` and `diff {added, deleted}` | | `default_branch` | Relation to the default branch: `{ahead, behind, diff, orphan, integration, merge_conflicts}`; absent on the default branch itself. `integration.reason` is one of `same_commit`, `ancestor`, `no_added_changes`, `trees_match`, `merge_adds_nothing`, `patch_id_match`; a dirty tree skips the checks, leaving `integration` null | | `upstream` | Tracking branch: `{remote, branch, ahead, behind}`; absent when none is configured | -| `pr` | Open PR/MR: `{number, url, review, mergeable, repo}`; collected with `--full` (`[list] columns` doesn't reach JSON). `review` uses the schema 1 `ci.review_state` vocabulary; `mergeable` is false when the forge reports conflicts, null otherwise | -| `checks` | CI pipeline: `{status, source, stale}`; collected with `--full`, on the same task as `pr`. `status` is `passed`, `running`, or `failed` — null when a conflicts report masks it | +| `pr` | Open PR/MR: `{number, url, review, mergeable, repo}`; collected with `--full`. `review` uses the schema 1 `ci.review_state` vocabulary; `mergeable` is false when the forge reports conflicts, null otherwise | +| `checks` | CI pipeline: `{status, source, stale}`; collected with `--full`. `status` is `passed`, `running`, or `failed` — null when a conflicts report masks it | | `dev_server` | `{url, listening}` from the project's `list.url` template | -| `summary` | LLM branch summary; collected with `--full` plus `[list] summary = true` (`[list] columns` doesn't reach JSON), and needs a `[commit.generation]` command | +| `summary` | LLM branch summary; needs `--full`, `[list] summary = true`, and a `[commit.generation]` command | | `vars` | Per-branch variables from [`wt config state vars`](@/config.md#wt-config-state-vars) | | `display` | Rendered strings: `state` (schema 1's `main_state` vocabulary), `symbols`, `statusline` (with ANSI colors and OSC 8 hyperlinks), `columns` (custom-column cells keyed by header) | @@ -2083,11 +2083,13 @@ columns = ["branch", "status", "ci", "path"] # Columns to show, in order — b timeout-ms = 0 # Wall-clock budget for the entire collect phase; 0 disables ``` -`columns` selects and orders the columns to render; omit it for the default set. -It is meant to drive a per-invocation [alias](@/extending.md#aliases) -(`wt --config-set 'list.columns=[…]' list`), giving a named view without -disturbing the default `wt list`. A static setting works but pins one layout -over a table that otherwise adapts to `--full` and terminal width. +`columns` selects and orders the columns the `wt list` table and the `wt switch` +picker render; `--format json` ignores it and always emits every field. Omit it +for the default set. It is meant to drive a per-invocation +[alias](@/extending.md#aliases) (`wt --config-set 'list.columns=[…]' list`), +giving a named view without disturbing the default `wt list`. A static setting +works but pins one layout over a table that otherwise adapts to `--full` and +terminal width. Valid built-in names: @@ -2117,13 +2119,6 @@ named one. A column whose data source is missing still stays hidden — `summary needs an LLM command (`[commit.generation]`), `url` needs a `[list] url` template — since listing can't supply the data. -The selection drives the table and the `wt switch` picker; `wt list --format -json` ignores it in both directions. JSON always emits every field, and a -listed `ci` or `summary` does not force its collection on there — that stays -with `--full` and `[list] summary`, so a display setting never decides whether -a machine-readable call reaches a forge. Which data a run gathered is reported -in schema 2's `collected`. - #### Custom columns [experimental] Custom columns add per-branch context to the `wt list` table. Each diff --git a/src/commands/list/collect/mod.rs b/src/commands/list/collect/mod.rs index ebb073f916..1e732452ca 100644 --- a/src/commands/list/collect/mod.rs +++ b/src/commands/list/collect/mod.rs @@ -1287,12 +1287,10 @@ pub fn collect( // `[list] summary` off): without it the picker would hide a column `wt list` // shows. CI is already covered — the picker is always `show_full`. // - `--format json` → `all_columns` alone (source `Default`), so the - // selection reaches it in neither direction. The every-field contract - // (`src/cli/mod.rs`) already rules out the narrowing half; the forcing - // half is out too, because a presentation setting shouldn't decide - // whether a machine-readable call fetches from a forge — `--full` is - // the switch for that, and it's the one a caller controls (#3787). - // `collected` reports what the run actually gathered either way. + // selection reaches it in neither direction: the every-field contract + // (`src/cli/mod.rs`) rules out narrowing, and a display setting must not + // decide whether a machine-readable call reaches a forge — `--full` is + // the switch for that (#3787). // // So a branch/path `ls` alias over many dirty worktrees runs no `git status` // / diffs / ahead-behind walks (#3133), while a default column gated off by diff --git a/src/commands/list/mod.rs b/src/commands/list/mod.rs index 879299d06f..c00f8d6176 100644 --- a/src/commands/list/mod.rs +++ b/src/commands/list/mod.rs @@ -48,7 +48,7 @@ //! After the skeleton appears, cells fill in progressively as git operations complete. //! The slowest operation (CI status) runs with `--full`, or — for the table and //! the picker only — when `[list] columns` names `ci`, which forces the column -//! and its fetch on without `--full`. `--format json` plans off `--full` alone. +//! and its fetch on without `--full`. //! //! ## Git Commands Per Worktree //! diff --git a/tests/integration_tests/list_config.rs b/tests/integration_tests/list_config.rs index 876cf2ba8a..f5cac7d2b4 100644 --- a/tests/integration_tests/list_config.rs +++ b/tests/integration_tests/list_config.rs @@ -1108,11 +1108,9 @@ columns = ["branch", "age"] ); } -/// The selection doesn't reach JSON in the forcing direction either. A listed -/// `ci` renders the CI column in the table without `--full` (asserted in -/// `test_list_config_listed_column_overrides_full_gate`), but `--format json` -/// plans off `--full` alone: a display setting must not decide whether a -/// machine-readable call reaches a forge (#3787). Asserted on schema 2's +/// A listed `ci` renders the CI column in the table without `--full` (asserted +/// in `test_list_config_listed_column_overrides_full_gate`), but `--format +/// json` plans off `--full` alone (#3787). Asserted on schema 2's /// `collected.ci`, which records what the plan requested rather than what a /// fetch returned — so this needs no forge and no `gh` on PATH. #[rstest] diff --git a/tests/snapshots/integration__integration_tests__help__help_config_create.snap b/tests/snapshots/integration__integration_tests__help__help_config_create.snap index 3e6fcb5f6d..65b5e915ab 100644 --- a/tests/snapshots/integration__integration_tests__help__help_config_create.snap +++ b/tests/snapshots/integration__integration_tests__help__help_config_create.snap @@ -36,6 +36,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" --- @@ -180,11 +181,13 @@ Creates ~/.config/worktrunk/config.toml with the following content:   #   # timeout-ms = 0 # Wall-clock budget for the entire collect phase; 0 disables   # -  # `columns` selects and orders the columns to render; omit it for the default set. -  # It is meant to drive a per-invocation alias (https://worktrunk.dev/extending/#aliases) -  # (`wt --config-set 'list.columns=[…]' list`), giving a named view without -  # disturbing the default `wt list`. A static setting works but pins one layout -  # over a table that otherwise adapts to `--full` and terminal width. +  # `columns` selects and orders the columns the `wt list` table and the `wt switch` +  # picker render; `--format json` ignores it and always emits every field. Omit it +  # for the default set. It is meant to drive a per-invocation +  # alias (https://worktrunk.dev/extending/#aliases) (`wt --config-set 'list.columns=[…]' list`), +  # giving a named view without disturbing the default `wt list`. A static setting +  # works but pins one layout over a table that otherwise adapts to `--full` and +  # terminal width.   #   # Valid built-in names:   # @@ -214,13 +217,6 @@ Creates ~/.config/worktrunk/config.toml with the following content:   # needs an LLM command (`[commit.generation]`), `url` needs a `[list] url`   # template — since listing can't supply the data.   # -  # The selection drives the table and the `wt switch` picker; `wt list --format -  # json` ignores it in both directions. JSON always emits every field, and a -  # listed `ci` or `summary` does not force its collection on there — that stays -  # with `--full` and `[list] summary`, so a display setting never decides whether -  # a machine-readable call reaches a forge. Which data a run gathered is reported -  # in schema 2's `collected`. -  #   # #### Custom columns [experimental]   #   # Custom columns add per-branch context to the `wt list` table. Each diff --git a/tests/snapshots/integration__integration_tests__help__help_config_long.snap b/tests/snapshots/integration__integration_tests__help__help_config_long.snap index ff3302719c..2f68636c0e 100644 --- a/tests/snapshots/integration__integration_tests__help__help_config_long.snap +++ b/tests/snapshots/integration__integration_tests__help__help_config_long.snap @@ -35,6 +35,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" --- @@ -228,11 +229,13 @@ Persistent flag values for wt list. Override on command line as needed.     timeout-ms = 0 # Wall-clock budget for the entire collect phase; 0 disables -columns selects and orders the columns to render; omit it for the default set. -It is meant to drive a per-invocation alias -(wt --config-set 'list.columns=[…]' list), giving a named view without -disturbing the default wt list. A static setting works but pins one layout -over a table that otherwise adapts to --full and terminal width. +columns selects and orders the columns the wt list table and the wt switch +picker render; --format json ignores it and always emits every field. Omit it +for the default set. It is meant to drive a per-invocation +alias (wt --config-set 'list.columns=[…]' list), +giving a named view without disturbing the default wt list. A static setting +works but pins one layout over a table that otherwise adapts to --full and +terminal width. Valid built-in names: @@ -262,13 +265,6 @@ named one. A column whose data source is missing still stays hidden — summ needs an LLM command ([commit.generation]), url needs a [list] url template — since listing can't supply the data. -The selection drives the table and the wt switch picker; wt list --format -json ignores it in both directions. JSON always emits every field, and a -listed ci or summary does not force its collection on there — that stays -with --full and [list] summary, so a display setting never decides whether -a machine-readable call reaches a forge. Which data a run gathered is reported -in schema 2's collected. - Custom columns [experimental] Custom columns add per-branch context to the wt list table. Each diff --git a/tests/snapshots/integration__integration_tests__help__help_list_long.snap b/tests/snapshots/integration__integration_tests__help__help_list_long.snap index 3d24106c28..ec7162c177 100644 --- a/tests/snapshots/integration__integration_tests__help__help_list_long.snap +++ b/tests/snapshots/integration__integration_tests__help__help_list_long.snap @@ -341,10 +341,10 @@ Item fields: worktree {path, main, current, previous, detached, locked, prunable, branch_mismatch, duplicate_branch, operation, changes}; absent on branch-only rows. locked/prunable are {reason} objects and can co-occur; operation is "rebase" or "merge"; changes holds the five working-tree flags plus conflicted and diff {added, deleted} default_branch Relation to the default branch: {ahead, behind, diff, orphan, integration, merge_conflicts}; absent on the default branch itself. integration.reason is one of same_commit, ancestor, no_added_changes, trees_match, merge_adds_nothing, patch_id_match; a dirty tree skips the checks, leaving integration null upstream Tracking branch: {remote, branch, ahead, behind}; absent when none is configured - pr Open PR/MR: {number, url, review, mergeable, repo}; collected with --full ([list] columns doesn't reach JSON). review uses the schema 1 ci.review_state vocabulary; mergeable is false when the forge reports conflicts, null otherwise - checks CI pipeline: {status, source, stale}; collected with --full, on the same task as pr. status is passed, running, or failed — null when a conflicts report masks it + pr Open PR/MR: {number, url, review, mergeable, repo}; collected with --full. review uses the schema 1 ci.review_state vocabulary; mergeable is false when the forge reports conflicts, null otherwise + checks CI pipeline: {status, source, stale}; collected with --full. status is passed, running, or failed — null when a conflicts report masks it dev_server {url, listening} from the project's list.url template - summary LLM branch summary; collected with --full plus [list] summary = true ([list] columns doesn't reach JSON), and needs a [commit.generation] command + summary LLM branch summary; needs --full, [list] summary = true, and a [commit.generation] command vars Per-branch variables from wt config state vars display Rendered strings: state (schema 1's main_state vocabulary), symbols, statusline (with ANSI colors and OSC 8 hyperlinks), columns (custom-column cells keyed by header) diff --git a/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap b/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap index 889fe668ee..aa39654895 100644 --- a/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap +++ b/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap @@ -429,17 +429,14 @@ Item fields: upstrea Tracking branch: {remote, branch, ahead, behind}; absent when none is m configured pr Open PR/MR: {number, url, review, mergeable, repo}; collected with - --full ([list] columns doesn't reach JSON). review uses the schema 1 - ci.review_state vocabulary; mergeable is false when the forge reports - conflicts, null otherwise - checks CI pipeline: {status, source, stale}; collected with --full, on the - same task as pr. status is passed, running, or failed — null when a - conflicts report masks it + --full. review uses the schema 1 ci.review_state vocabulary; mergeable + is false when the forge reports conflicts, null otherwise + checks CI pipeline: {status, source, stale}; collected with --full. status is + passed, running, or failed — null when a conflicts report masks it dev_ser {url, listening} from the project's list.url template ver - summary LLM branch summary; collected with --full plus [list] summary = true ( - [list] columns doesn't reach JSON), and needs a [commit.generation] - command + summary LLM branch summary; needs --full, [list] summary = true, and a + [commit.generation] command vars Per-branch variables from wt config state vars display Rendered strings: state (schema 1's main_state vocabulary), symbols, statusline (with ANSI colors and OSC 8 hyperlinks), columns From 2fad4b020297ee3881ad7ebca05e1b77ad487603 Mon Sep 17 00:00:00 2001 From: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com> Date: Fri, 14 Aug 2026 00:10:01 +0000 Subject: [PATCH 4/4] docs(list): drop the listed-column gate from the published schema MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `Collected`'s doc comment ships verbatim into docs/static/schema/list-v2.json, and it still named "a listed `ci`/`summary` column" as a gate on `collected`. The JSON plan is now `full_plan()` alone, which passes `ColumnSource::Default`, so a listed column can never set `collected.ci`/`collected.summary` — the published schema was advertising the exact forcing path this PR removes, to the machine consumers it removes it for. Schema regenerated by `test_docs_are_in_sync`. --- docs/static/schema/list-v2.json | 2 +- src/commands/list/model/item.rs | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/static/schema/list-v2.json b/docs/static/schema/list-v2.json index 4003140b67..51bf15bdb6 100644 --- a/docs/static/schema/list-v2.json +++ b/docs/static/schema/list-v2.json @@ -16,7 +16,7 @@ ] }, "Collected": { - "description": "Fact families whose collection is gated (`--full`, `[list] summary`,\na listed `ci`/`summary` column). Ungated families (working tree, counts,\ndiffs) are always requested. Serialized as-is into the schema-2 JSON\nenvelope's `collected` field, disambiguating \"absent because not\nrequested\".", + "description": "Fact families whose collection is gated (`--full`, `[list] summary`).\nUngated families (working tree, counts, diffs) are always requested.\nSerialized as-is into the schema-2 JSON envelope's `collected` field,\ndisambiguating \"absent because not requested\".", "properties": { "ci": { "description": "Forge CI/PR data was fetched.", diff --git a/src/commands/list/model/item.rs b/src/commands/list/model/item.rs index d6d73fef53..183f61225b 100644 --- a/src/commands/list/model/item.rs +++ b/src/commands/list/model/item.rs @@ -359,11 +359,10 @@ pub struct ListData { pub collected: Collected, } -/// Fact families whose collection is gated (`--full`, `[list] summary`, -/// a listed `ci`/`summary` column). Ungated families (working tree, counts, -/// diffs) are always requested. Serialized as-is into the schema-2 JSON -/// envelope's `collected` field, disambiguating "absent because not -/// requested". +/// Fact families whose collection is gated (`--full`, `[list] summary`). +/// Ungated families (working tree, counts, diffs) are always requested. +/// Serialized as-is into the schema-2 JSON envelope's `collected` field, +/// disambiguating "absent because not requested". #[derive(Debug, Clone, Copy, Default, serde::Serialize, schemars::JsonSchema)] pub struct Collected { /// Forge CI/PR data was fetched.