Skip to content

Commit a785d0b

Browse files
committed
Add JSON output support for erk current command
Extends the `erk current` command with `--json` flag to output worktree information in machine-readable format. Enables automation tools and scripts to reliably extract current worktree details (name, path, is_root status) with strict schema validation. ## Files Changed ### Added - `src/erk/cli/json_schemas.py` CurrentCommandResponse class ### Modified - `src/erk/cli/commands/current.py` - Added --json flag and JSON response handling - `tests/commands/display/test_current.py` - Added 3 new JSON output tests ## Key Changes - New `CurrentCommandResponse` Pydantic schema (name, path, is_root fields) - `--json` flag routes output through JSON serialization layer - Refactored output logic to support both text and JSON formats - Full test coverage: worktree JSON, root worktree JSON, and schema validation ## Test Results All 1575 tests passing. CI validation complete.
1 parent 7e8b970 commit a785d0b

File tree

3 files changed

+172
-4
lines changed

3 files changed

+172
-4
lines changed

src/erk/cli/commands/current.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,23 @@
33
import click
44

55
from erk.cli.core import discover_repo_context
6+
from erk.cli.json_output import emit_json
7+
from erk.cli.json_schemas import CurrentCommandResponse
68
from erk.cli.output import user_output
79
from erk.core.context import ErkContext
810
from erk.core.repo_discovery import RepoContext
911
from erk.core.worktree_utils import find_current_worktree, is_root_worktree
1012

1113

1214
@click.command("current", hidden=True)
15+
@click.option(
16+
"--json",
17+
"output_json",
18+
is_flag=True,
19+
help="Output JSON format",
20+
)
1321
@click.pass_obj
14-
def current_cmd(ctx: ErkContext) -> None:
22+
def current_cmd(ctx: ErkContext, output_json: bool) -> None:
1523
"""Show current erk name (hidden command for automation)."""
1624
# Use ctx.repo if it's a valid RepoContext, otherwise discover
1725
if isinstance(ctx.repo, RepoContext):
@@ -28,7 +36,17 @@ def current_cmd(ctx: ErkContext) -> None:
2836
if wt_info is None:
2937
raise SystemExit(1)
3038

31-
if is_root_worktree(wt_info.path, repo.root):
32-
user_output("root")
39+
# Determine name and is_root status
40+
is_root = is_root_worktree(wt_info.path, repo.root)
41+
name = "root" if is_root else wt_info.path.name
42+
43+
# Output based on format
44+
if output_json:
45+
response = CurrentCommandResponse(
46+
name=name,
47+
path=str(wt_info.path),
48+
is_root=is_root,
49+
)
50+
emit_json(response.model_dump(mode="json"))
3351
else:
34-
user_output(wt_info.path.name)
52+
user_output(name)

src/erk/cli/json_schemas.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,22 @@ class CreateCommandResponse(BaseModel):
3434
status: str = Field(..., pattern="^(created|exists)$")
3535

3636

37+
class CurrentCommandResponse(BaseModel):
38+
"""JSON response schema for the `erk current` command.
39+
40+
Attributes:
41+
name: Name of the worktree (or "root" if in root worktree)
42+
path: Absolute path to the worktree directory
43+
is_root: Whether this is the root worktree
44+
"""
45+
46+
model_config = ConfigDict(strict=True)
47+
48+
name: str
49+
path: str
50+
is_root: bool
51+
52+
3753
class StatusWorktreeInfo(BaseModel):
3854
"""Worktree information in status command output.
3955

tests/commands/display/test_current.py

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,3 +230,137 @@ def test_current_handles_nested_worktrees(tmp_path: Path) -> None:
230230
# Should return the deepest (most specific) worktree
231231
assert result.exit_code == 0
232232
assert result.output.strip() == "nested"
233+
234+
235+
def test_current_json_output_for_worktree() -> None:
236+
"""Test that current --json outputs correct JSON structure in named worktree."""
237+
import json
238+
239+
runner = CliRunner()
240+
with erk_inmem_env(runner) as env:
241+
# Construct sentinel paths (no filesystem operations needed)
242+
work_dir = env.erk_root / env.cwd.name
243+
feature_x_path = work_dir / "feature-x"
244+
245+
# Configure FakeGitOps with worktrees - feature-x is current
246+
git_ops = FakeGitOps(
247+
worktrees={
248+
env.cwd: [
249+
WorktreeInfo(path=env.cwd, branch="main", is_root=True),
250+
WorktreeInfo(path=feature_x_path, branch="feature-x", is_root=False),
251+
]
252+
},
253+
current_branches={
254+
env.cwd: "main",
255+
feature_x_path: "feature-x",
256+
},
257+
git_common_dirs={
258+
env.cwd: env.git_dir,
259+
feature_x_path: env.git_dir,
260+
},
261+
default_branches={env.cwd: "main"},
262+
)
263+
264+
# Use env.build_context() helper to eliminate boilerplate
265+
test_ctx = env.build_context(git_ops=git_ops, cwd=feature_x_path, repo=env.repo)
266+
267+
# Run current command with --json flag
268+
result = runner.invoke(cli, ["current", "--json"], obj=test_ctx)
269+
270+
assert result.exit_code == 0
271+
272+
# Parse JSON output
273+
data = json.loads(result.output)
274+
275+
# Verify JSON structure
276+
assert data["name"] == "feature-x"
277+
assert data["path"] == str(feature_x_path)
278+
assert data["is_root"] is False
279+
280+
281+
def test_current_json_output_for_root() -> None:
282+
"""Test that current --json outputs correct JSON for root worktree."""
283+
import json
284+
285+
runner = CliRunner()
286+
with erk_inmem_env(runner) as env:
287+
# Configure FakeGitOps with just root worktree
288+
git_ops = FakeGitOps(
289+
worktrees={
290+
env.cwd: [
291+
WorktreeInfo(path=env.cwd, branch="main", is_root=True),
292+
]
293+
},
294+
current_branches={env.cwd: "main"},
295+
git_common_dirs={env.cwd: env.git_dir},
296+
default_branches={env.cwd: "main"},
297+
)
298+
299+
# Use env.build_context() helper to eliminate boilerplate
300+
test_ctx = env.build_context(git_ops=git_ops, cwd=env.cwd, repo=env.repo)
301+
302+
# Run current command with --json flag
303+
result = runner.invoke(cli, ["current", "--json"], obj=test_ctx)
304+
305+
assert result.exit_code == 0
306+
307+
# Parse JSON output
308+
data = json.loads(result.output)
309+
310+
# Verify JSON structure for root worktree
311+
assert data["name"] == "root"
312+
assert data["path"] == str(env.cwd)
313+
assert data["is_root"] is True
314+
315+
316+
def test_current_json_validates_schema() -> None:
317+
"""Test that current --json output validates against expected schema."""
318+
import json
319+
320+
runner = CliRunner()
321+
with erk_inmem_env(runner) as env:
322+
# Construct sentinel paths
323+
work_dir = env.erk_root / env.cwd.name
324+
test_path = work_dir / "test-worktree"
325+
326+
# Configure FakeGitOps
327+
git_ops = FakeGitOps(
328+
worktrees={
329+
env.cwd: [
330+
WorktreeInfo(path=env.cwd, branch="main", is_root=True),
331+
WorktreeInfo(path=test_path, branch="test", is_root=False),
332+
]
333+
},
334+
current_branches={
335+
env.cwd: "main",
336+
test_path: "test",
337+
},
338+
git_common_dirs={
339+
env.cwd: env.git_dir,
340+
test_path: env.git_dir,
341+
},
342+
default_branches={env.cwd: "main"},
343+
)
344+
345+
test_ctx = env.build_context(git_ops=git_ops, cwd=test_path, repo=env.repo)
346+
347+
# Run current command with --json flag
348+
result = runner.invoke(cli, ["current", "--json"], obj=test_ctx)
349+
350+
assert result.exit_code == 0
351+
352+
# Parse JSON output
353+
data = json.loads(result.output)
354+
355+
# Validate all expected keys are present
356+
assert "name" in data
357+
assert "path" in data
358+
assert "is_root" in data
359+
360+
# Validate types
361+
assert isinstance(data["name"], str)
362+
assert isinstance(data["path"], str)
363+
assert isinstance(data["is_root"], bool)
364+
365+
# Validate path is absolute
366+
assert Path(data["path"]).is_absolute()

0 commit comments

Comments
 (0)