Skip to content

feat: add --format=json to config state subcommands#1969

Merged
max-sixty merged 14 commits intomainfrom
json-output
Apr 7, 2026
Merged

feat: add --format=json to config state subcommands#1969
max-sixty merged 14 commits intomainfrom
json-output

Conversation

@max-sixty
Copy link
Copy Markdown
Owner

@max-sixty max-sixty commented Apr 7, 2026

Add --format=json to 10 commands across two commits.

Config state subcommands (commit 1)

  • logs get{command_log: [...], hook_output: [...]} with file/size/modified_at per entry
  • ci-status get{status, source, stale, url} (richer than the text mode's bare status string)
  • marker get{branch, marker, set_at} or null
  • vars list{key: value, ...} object
  • hints get["hint-name", ...] array

Also extracts a shared log_entry_to_json helper, deduplicating the DirEntry→JSON conversion.

Top-level commands (commit 2)

  • config show — serialized user/project/system config with paths and existence flags
  • step prune --dry-run — array of candidates with branch, path, kind, reason, target
  • remove — array of removed worktrees with branch, path, deletion status
  • merge — summary with branch, target, committed, squashed, rebased, removed
  • step for-each — per-worktree results with branch, path, exit_code, success

For action commands (remove, merge, for-each), progress goes to stderr as usual; JSON summary goes to stdout at the end — same pattern as wt switch --format=json.

This was written by Claude Code on behalf of @max-sixty

Add structured JSON output to 5 config state subcommands:
- `logs get`: {command_log, hook_output} arrays with file/size/modified_at
- `ci-status get`: {status, source, stale, url}
- `marker get`: {branch, marker, set_at} or null
- `vars list`: {key: value} object
- `hints get`: string array

Extract shared log_entry_to_json helper, deduplicating DirEntry→JSON
conversion between handle_logs_get and handle_state_show_json.

Co-Authored-By: Claude <noreply@anthropic.com>
worktrunk-bot
worktrunk-bot previously approved these changes Apr 7, 2026
@worktrunk-bot worktrunk-bot dismissed their stale review April 7, 2026 04:10

CI passed but codecov/patch failed (90.98% vs 95.74% target) — dismissing to post coverage analysis

Copy link
Copy Markdown
Collaborator

@worktrunk-bot worktrunk-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

codecov/patch failed at 90.98% vs 95.74% target. The gap is the ci-status JSON Some branch — test_ci_status_get_json only exercises the None/no-ci path. The Some(ref s) arm (lines 458-462) and the refactored text-mode Some closure (lines 475-476) are uncovered.

A test can exercise this by injecting a cache file into .git/wt/cache/ci-status/main.json with a matching HEAD and recent checked_at. I can push a fix commit if you'd like.

Comment thread tests/integration_tests/config_state.rs
…r-each

JSON output for 5 more commands:
- `config show`: serialized user/project/system config with paths and existence
- `step prune --dry-run`: array of candidates with branch, path, kind, reason
- `remove`: array of removed worktrees with branch, path, deletion status
- `merge`: summary with branch, target, committed, squashed, rebased, removed
- `step for-each`: per-worktree results with branch, path, exit_code, success

For action commands (remove, merge, for-each), progress messages still go to
stderr; JSON summary goes to stdout at the end.

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator

@worktrunk-bot worktrunk-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

step prune --format=json only produces JSON output in --dry-run mode. Without --dry-run, the flag is silently ignored and text output is printed. Either wire up JSON for the non-dry-run path too, or add conflicts_with = "dry_run" (like logs does with --hook) so users get a clear error instead of unexpected output.

Comment thread src/commands/step_commands.rs
…-run

- Regenerate help snapshots for new --format flags
- Sync docs and skill reference files
- Add config/show.rs, for_each.rs, merge.rs to STDOUT_ALLOWED_PATHS
- Add JSON output to step prune non-dry-run path (was silently ignored)

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator

@worktrunk-bot worktrunk-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wt remove --format=json (no branch args, removes current) outputs a single object, while wt remove feature --format=json outputs an array. This means consumers need to check the shape before parsing. Consider always returning an array for consistency — even the single-remove path could wrap in [...]. Not blocking, just a design thought for API ergonomics.

Comment thread src/main.rs Outdated
- `wt remove --format=json` now always returns an array (even for single
  removal), matching the multi-branch path for consistent API shape
- Add test for config show JSON with project config to cover that branch

Co-Authored-By: Claude <noreply@anthropic.com>
worktrunk-bot
worktrunk-bot previously approved these changes Apr 7, 2026
- Reuse JsonCi::from for ci-status JSON (eliminates untestable Some branch)
- Add test for prune --format=json non-dry-run (actual removal)
- Add test for remove --format=json current worktree (single-target path)
- Update ci-status JSON test for new null output shape

Co-Authored-By: Claude <noreply@anthropic.com>
@worktrunk-bot worktrunk-bot dismissed their stale review April 7, 2026 17:17

codecov/patch failed (82.98% vs 95.74%) — multiple JSON format paths need test coverage

Copy link
Copy Markdown
Collaborator

@worktrunk-bot worktrunk-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

codecov/patch failed at 82.98% vs 95.74% target. The gap is across multiple --format=json code paths that lack test coverage:

Highest impact uncovered areas:

  1. src/commands/config/state.rshandle_state_get ci-status Some(ref s) branch (lines 458-462), handle_logs_get JSON None branch, handle_vars_list JSON branch
  2. src/commands/step_commands.rsstep_prune JSON output in both dry-run (lines 1649-1668) and non-dry-run (lines 1752-1770) paths
  3. src/commands/for_each.rsstep_for_each JSON mode across success/failure branches (lines 89-142)
  4. src/commands/merge.rs — merge JSON output block
  5. src/commands/config/show.rshandle_config_show_json (the new test covers one path but may not cover all branches)
  6. src/commands/config/hints.rshandle_hints_get JSON branch

The pattern is consistent: existing tests exercise text output but don't pair --format=json with the same scenarios. Adding JSON-mode variants to the existing integration tests would close most of the gap. I can push fix commits with tests if you'd like.

max-sixty and others added 2 commits April 7, 2026 10:18
Replace direct JSON value assertions with insta snapshot tests for
better structural visibility and regression detection. Dynamic values
(paths, timestamps, sizes) are redacted via insta filters.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
worktrunk-bot
worktrunk-bot previously approved these changes Apr 7, 2026
@worktrunk-bot worktrunk-bot dismissed their stale review April 7, 2026 17:34

CI failed — test_remove_json_current fails on Windows

Copy link
Copy Markdown
Collaborator

@worktrunk-bot worktrunk-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Windows CI failed: test_remove_json_current panics at assert!(output.status.success()). The test sets current_dir(&feature_wt) and uses --foreground, so the removal runs in-process while the subprocess's cwd is the directory being deleted — Windows locks directories in use. The existing test_remove_current_by_name passes because it uses background removal (no --foreground).

Simplest fix: #[cfg(not(target_os = "windows"))] on the test, since test_remove_json already covers the non-current path including --foreground.

Comment thread tests/integration_tests/remove.rs
max-sixty and others added 3 commits April 7, 2026 10:38
Foreground removal with cwd inside the worktree hits Windows directory
locking. Matches existing pattern for similar remove tests.

Co-Authored-By: Claude <noreply@anthropic.com>
Add tests for BranchOnly removal, orphan branch pruning,
current-worktree pruning, and bare subcommand dispatch paths.
Patch coverage: 96.7% (up from 92.6%).

Co-Authored-By: Claude <noreply@anthropic.com>
worktrunk-bot
worktrunk-bot previously approved these changes Apr 7, 2026
@worktrunk-bot worktrunk-bot dismissed their stale review April 7, 2026 20:21

codecov/patch failed — SpawnFailed JSON path in for_each.rs is uncovered

Copy link
Copy Markdown
Collaborator

@worktrunk-bot worktrunk-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

codecov/patch failed. Two uncovered changed-line regions:

  1. src/commands/for_each.rs:109-117 — the SpawnFailed JSON block. test_for_each_json_with_failure uses "false" (valid binary, nonzero exit) which hits ExitCode, not SpawnFailed. A test using a non-existent binary (e.g., "/nonexistent/binary") would cover this path.

  2. src/commands/config/state.rs:459 — ci-status JSON Some branch, already discussed in the PR description.

I can push a fix commit adding a SpawnFailed test if you'd like.

Comment thread src/commands/for_each.rs Outdated
"success": false,
"error": err,
}));
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This SpawnFailed + JSON block is uncovered — test_for_each_json_with_failure uses "false" which triggers ExitCode, not SpawnFailed. Adding a test with a non-existent binary would cover it:

#[rstest]
fn test_for_each_json_spawn_failed(repo: TestRepo) {
    repo.commit("initial");

    let output = repo
        .wt_command()
        .args(["step", "for-each", "--format=json", "--", "/nonexistent/binary"])
        .output()
        .unwrap();
    assert!(!output.status.success());
    let json: serde_json::Value =
        serde_json::from_str(&String::from_utf8_lossy(&output.stdout)).unwrap();
    let items = json.as_array().unwrap();
    assert!(!items.is_empty());
    for item in items {
        assert_eq!(item["success"], false);
        assert!(item["error"].as_str().is_some());
        assert_eq!(item["exit_code"], serde_json::Value::Null);
    }
}

# Conflicts:
#	src/commands/config/state.rs
worktrunk-bot
worktrunk-bot previously approved these changes Apr 7, 2026
@worktrunk-bot worktrunk-bot dismissed their stale review April 7, 2026 20:35

codecov/patch failed — investigating coverage gap

Copy link
Copy Markdown
Collaborator

@worktrunk-bot worktrunk-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

codecov/patch failed. The two uncovered regions are the same as flagged in previous reviews — the incremental changes since last review didn't introduce new gaps:

  1. src/commands/for_each.rs:106-117SpawnFailed JSON block. test_for_each_json_with_failure uses "false" (valid binary, nonzero exit → ExitCode), not a missing binary (SpawnFailed). A test with a nonexistent binary would cover this.

  2. src/commands/config/state.rs:454-458 — ci-status JSON Some branch (acknowledged in PR description as hard to test due to cache path canonicalization).

I can push a fix commit adding a SpawnFailed JSON test to close gap #1. Gap #2 may need the cache-seeding approach from the earlier inline suggestion.

Seed CI cache with valid entry to cover the JSON output
path for ci-status get. Also fix write_ci_cache helper to
use sanitize_for_filename for correct cache filenames.

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator

@worktrunk-bot worktrunk-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

codecov/patch failed again. The new test_ci_status_get_json_with_cached_data test should cover the ci-status Some branch — if codecov still flags config/state.rs, it may be a stale report or a different uncovered line. The SpawnFailed JSON block in for_each.rs:106-117 remains the other known gap (see existing thread).

Consolidate the JSON accumulation for error cases in for-each
so the shared code path gets coverage from ExitCode tests.

Co-Authored-By: Claude <noreply@anthropic.com>
worktrunk-bot
worktrunk-bot previously approved these changes Apr 7, 2026
@worktrunk-bot worktrunk-bot dismissed their stale review April 7, 2026 21:24

CI failed — merge conflict with main: RemoveResult::BranchOnly gained target_branch and integration_reason fields in #1989, which remove_result_to_json in src/main.rs doesn't destructure.

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator

@worktrunk-bot worktrunk-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CI failed on all three platforms — compilation error after merging with main. RemoveResult::BranchOnly gained target_branch and integration_reason fields in #1989, which the remove_result_to_json pattern match at src/main.rs:741 doesn't destructure.

Fix: add .. to the pattern, or destructure the new fields and include them in the JSON output (probably the better choice since consumers may want them).

@max-sixty max-sixty merged commit 85c3dad into main Apr 7, 2026
27 checks passed
@max-sixty max-sixty deleted the json-output branch April 7, 2026 21:41
max-sixty added a commit that referenced this pull request Apr 8, 2026
Four targeted fixes identified as follow-ups from the `--format=json`
rollout (#1969, #1998):

1. **Sort stability in `partition_log_files_json`** — added filename
tiebreaker to match `render_log_table`'s sort. Without this, entries
with identical timestamps could appear in non-deterministic order in
JSON output.

2. **for-each `error` field always present on failure** — previously,
the `"error"` field only appeared for `SpawnFailed` errors, not
`ExitCode`. Consumers parsing the JSON had to handle two shapes. Now
both variants include `"error"`: `"exit code N"` for ExitCode, `"spawn
failed: ..."` for SpawnFailed, `"killed by signal"` for signal
termination. Error formatting lives in `CommandError::Display` impl with
a unit test covering all three variants.

3. **Test: `config show --format=json` outside a git repo** — verifies
that project path/config are `null` when not in a git repository.

4. **Test: multi-remove `--format=json` with current worktree** — covers
the `plans.current` code path where the current worktree appears in the
multi-remove target list (deferred to last in output).

> _This was written by Claude Code on behalf of Maximilian Roos_

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants