Skip to content

Commit 2f52a7c

Browse files
author
jgstern-agent
committed
fix(cli): reject single-file input cleanly (WI-zujum / UAT BUG-04)
Pointing sketch or run at a file (rather than a directory) previously printed a helpful hint, then proceeded into generate_sketch and crashed inside _format_structure_tree_fallback with NotADirectoryError from Path.iterdir(). The user saw the hint AND a traceback — the opposite of the intent. Add an is_dir() guard after the existence check in both cmd_sketch and cmd_run: print an actionable error pointing at the file's parent directory, and sys.exit(1). Also fold the existence check into cmd_run (it was previously missing; cmd_run would crash later with a different traceback for nonexistent paths). Signed-off-by: jgstern-agent <josh-agent@iterabloom.com>
1 parent 5e6f8ab commit 2f52a7c

4 files changed

Lines changed: 92 additions & 2 deletions

File tree

.ci/affected-tests.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# Test selection manifest
2-
# Generated by smart-test at 2026-04-14T09:54:37-04:00
2+
# Generated by smart-test at 2026-04-14T11:17:40-04:00
33
# Mode: targeted
44
# Baseline: 3a502998615fa04cbe60be62b61dd38520b9750b
5-
# Changed files: 15
5+
# Changed files: 18
66
# Changed source files: 5
77
# Selected tests: 270
88
#

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ This changelog tracks the **tool version** (package releases). The **schema vers
1313
### Fixed
1414

1515
- **`-e/--exclude` glob normalization** (WI-zirik / UAT BUG-01): user patterns `ui/`, `ui/**`, `**/ui/**`, `**/ui` now behave consistently with the bare-name form `ui` (per-name fnmatch previously silently dropped any pattern containing `/`). Path-anchored patterns such as `cmd/server.go` are honored against the relative path. Affects both `run` and `sketch`; structure-tree code uses the same normalization.
16+
- **Single-file input exits cleanly** (WI-zujum / UAT BUG-04): `hypergumbo run` / `sketch` pointed at a file (not a directory) now print an actionable hint and `sys.exit(1)` instead of crashing with `NotADirectoryError` from `Path.iterdir()`. The hint points at the file's parent directory as a likely target.
1617

1718
### Performance
1819

packages/hypergumbo-core/src/hypergumbo_core/cli.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,20 @@ def cmd_sketch(args: argparse.Namespace) -> int:
573573
print(f"Error: path does not exist: {repo_root}", file=sys.stderr)
574574
return 1
575575

576+
# WI-zujum: a single-file argument crashes downstream in
577+
# _format_structure_tree_fallback (Path.iterdir() raises
578+
# NotADirectoryError). Reject early with a hint pointing at the
579+
# likely intent — analyse the parent directory.
580+
if not repo_root.is_dir():
581+
parent = repo_root.parent
582+
print(
583+
f"Error: {repo_root} is a file, not a directory.\n"
584+
f"hypergumbo analyses repositories. Try its parent directory:\n"
585+
f" hypergumbo {parent}",
586+
file=sys.stderr,
587+
)
588+
return 1
589+
576590
# Warn if analyzing a subdirectory of a git repo
577591
git_root = _find_git_root(repo_root)
578592
if git_root is not None and git_root.resolve() != repo_root.resolve():
@@ -866,6 +880,22 @@ def cmd_sketch(args: argparse.Namespace) -> int:
866880
def cmd_run(args: argparse.Namespace) -> int:
867881
# The positional argument for `run` is called `path` in the parser below.
868882
repo_root = Path(args.path).resolve()
883+
884+
# WI-zujum: same single-file guard as cmd_sketch — analysing a single
885+
# file is not a supported mode and crashes deeper in the pipeline.
886+
if not repo_root.exists():
887+
print(f"Error: path does not exist: {repo_root}", file=sys.stderr)
888+
return 1
889+
if not repo_root.is_dir():
890+
parent = repo_root.parent
891+
print(
892+
f"Error: {repo_root} is a file, not a directory.\n"
893+
f"hypergumbo analyses repositories. Try its parent directory:\n"
894+
f" hypergumbo run {parent}",
895+
file=sys.stderr,
896+
)
897+
return 1
898+
869899
out_path = Path(args.out) if args.out else None
870900
max_tier = getattr(args, "max_tier", None)
871901
max_files = getattr(args, "max_files", None)

packages/hypergumbo-core/tests/test_cli_commands.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2378,6 +2378,65 @@ def test_cmd_sketch_nonexistent_path(capsys) -> None:
23782378
assert "does not exist" in err
23792379

23802380

2381+
def test_cmd_sketch_single_file_input_exits_cleanly(tmp_path: Path, capsys) -> None:
2382+
"""Pointing sketch at a file (not a directory) exits cleanly with a hint.
2383+
2384+
Per WI-zujum (UAT 2026-04-13 BUG-04): the prior behaviour printed a
2385+
helpful hint, then proceeded into ``generate_sketch`` and crashed with
2386+
``NotADirectoryError`` from ``Path.iterdir()``. The hint must terminate
2387+
the command so users see the suggestion, not a traceback.
2388+
"""
2389+
sample = tmp_path / "thing.tla"
2390+
sample.write_text("---- MODULE Thing ----\n==== ")
2391+
2392+
args = FakeArgs()
2393+
args.path = str(sample)
2394+
args.tokens = None
2395+
args.exclude_tests = False
2396+
args.first_party_priority = True
2397+
args.extra_excludes = []
2398+
args.config_extraction_mode = "heuristic"
2399+
2400+
result = cmd_sketch(args)
2401+
2402+
assert result == 1
2403+
_, err = capsys.readouterr()
2404+
assert "directory" in err.lower()
2405+
assert "thing.tla" in err # mention what they pointed at
2406+
# Must not crash with traceback
2407+
assert "Traceback" not in err
2408+
2409+
2410+
def test_cmd_run_nonexistent_path_exits_cleanly(capsys) -> None:
2411+
"""``hypergumbo run /does/not/exist`` exits with a clean error, not a crash."""
2412+
args = FakeArgs()
2413+
args.path = "/nonexistent/path/that/does/not/exist"
2414+
args.out = None
2415+
2416+
result = cmd_run(args)
2417+
2418+
assert result == 1
2419+
_, err = capsys.readouterr()
2420+
assert "does not exist" in err
2421+
2422+
2423+
def test_cmd_run_single_file_input_exits_cleanly(tmp_path: Path, capsys) -> None:
2424+
"""``hypergumbo run /path/to/file`` should exit cleanly, not crash."""
2425+
sample = tmp_path / "thing.tla"
2426+
sample.write_text("---- MODULE Thing ----\n==== ")
2427+
2428+
args = FakeArgs()
2429+
args.path = str(sample)
2430+
args.out = None
2431+
2432+
result = cmd_run(args)
2433+
2434+
assert result == 1
2435+
_, err = capsys.readouterr()
2436+
assert "directory" in err.lower()
2437+
assert "Traceback" not in err
2438+
2439+
23812440
def test_cmd_sketch_warns_about_git_root(tmp_path: Path, capsys) -> None:
23822441
"""Test cmd_sketch warns when analyzing a subdirectory of a git repo."""
23832442
# Create a git repo structure

0 commit comments

Comments
 (0)