feat: add --format=json to config state subcommands#1969
Conversation
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>
CI passed but codecov/patch failed (90.98% vs 95.74% target) — dismissing to post coverage analysis
worktrunk-bot
left a comment
There was a problem hiding this comment.
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.
…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>
worktrunk-bot
left a comment
There was a problem hiding this comment.
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.
…-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>
worktrunk-bot
left a comment
There was a problem hiding this comment.
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.
- `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>
- 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>
codecov/patch failed (82.98% vs 95.74%) — multiple JSON format paths need test coverage
worktrunk-bot
left a comment
There was a problem hiding this comment.
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:
src/commands/config/state.rs—handle_state_getci-statusSome(ref s)branch (lines 458-462),handle_logs_getJSONNonebranch,handle_vars_listJSON branchsrc/commands/step_commands.rs—step_pruneJSON output in both dry-run (lines 1649-1668) and non-dry-run (lines 1752-1770) pathssrc/commands/for_each.rs—step_for_eachJSON mode across success/failure branches (lines 89-142)src/commands/merge.rs— merge JSON output blocksrc/commands/config/show.rs—handle_config_show_json(the new test covers one path but may not cover all branches)src/commands/config/hints.rs—handle_hints_getJSON 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.
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>
CI failed — test_remove_json_current fails on Windows
worktrunk-bot
left a comment
There was a problem hiding this comment.
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.
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>
codecov/patch failed — SpawnFailed JSON path in for_each.rs is uncovered
worktrunk-bot
left a comment
There was a problem hiding this comment.
codecov/patch failed. Two uncovered changed-line regions:
-
src/commands/for_each.rs:109-117— theSpawnFailedJSON block.test_for_each_json_with_failureuses"false"(valid binary, nonzero exit) which hitsExitCode, notSpawnFailed. A test using a non-existent binary (e.g.,"/nonexistent/binary") would cover this path. -
src/commands/config/state.rs:459— ci-status JSONSomebranch, already discussed in the PR description.
I can push a fix commit adding a SpawnFailed test if you'd like.
| "success": false, | ||
| "error": err, | ||
| })); | ||
| } |
There was a problem hiding this comment.
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
codecov/patch failed — investigating coverage gap
worktrunk-bot
left a comment
There was a problem hiding this comment.
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:
-
src/commands/for_each.rs:106-117—SpawnFailedJSON block.test_for_each_json_with_failureuses"false"(valid binary, nonzero exit →ExitCode), not a missing binary (SpawnFailed). A test with a nonexistent binary would cover this. -
src/commands/config/state.rs:454-458— ci-status JSONSomebranch (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>
worktrunk-bot
left a comment
There was a problem hiding this comment.
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>
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>
worktrunk-bot
left a comment
There was a problem hiding this comment.
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).
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>
Add
--format=jsonto 10 commands across two commits.Config state subcommands (commit 1)
logs get—{command_log: [...], hook_output: [...]}with file/size/modified_at per entryci-status get—{status, source, stale, url}(richer than the text mode's bare status string)marker get—{branch, marker, set_at}ornullvars list—{key: value, ...}objecthints get—["hint-name", ...]arrayAlso extracts a shared
log_entry_to_jsonhelper, deduplicating the DirEntry→JSON conversion.Top-level commands (commit 2)
config show— serialized user/project/system config with paths and existence flagsstep prune --dry-run— array of candidates with branch, path, kind, reason, targetremove— array of removed worktrees with branch, path, deletion statusmerge— summary with branch, target, committed, squashed, rebased, removedstep for-each— per-worktree results with branch, path, exit_code, successFor 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.