refactor(cli): drop --format=json end-to-end
test / unit (pull_request) Successful in 18s
test / integration (pull_request) Successful in 1m2s

Companion to the compact preflight in #31 — the JSON format was
the structured alternative to the verbose text summary. With the
new compact text already on screen, no consumer was using the
JSON shape, and the abstract `BottlePlan.to_dict` was the
biggest piece of API surface no one is implementing against.

Removed:
  - `--format` CLI flag from `start` and `resume`.
  - `output_format` kwarg from `_launch_bottle`.
  - `BottlePlan.to_dict` abstract method.
  - `DockerBottlePlan.to_dict` (60-line dict builder).
  - The `_PlanView` dataclass — `print` was the only remaining
    caller, so the env-name computation is inlined.
  - `tests/integration/test_dry_run_plan.py` (JSON-shape
    integration test).
  - `tests/unit/test_cli_start_format.py` (flag-conflict unit).

Plan-introspection is still possible by reading the
`DockerBottlePlan` dataclass directly — fields like `image`,
`container_name`, `stage_dir`, `use_runsc` are all there. Tooling
that needs a stable wire shape can JSON-serialize the dataclass
themselves.

411 unit + integration tests pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-25 20:54:51 -04:00
parent 5d5f118fb4
commit 572106d98f
6 changed files with 17 additions and 334 deletions
-54
View File
@@ -1,54 +0,0 @@
"""Unit: argparse-level CLI checks for `start --format`.
Lives in tests/unit/ because nothing here touches Docker — the CLI
exits at argument-validation time before any backend code runs.
"""
import json
import os
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent.parent
class TestStartFormatFlag(unittest.TestCase):
def test_json_format_requires_dry_run(self):
"""Emitting JSON in a real run would race the y/N prompt; the
CLI must reject the combination with a message that names the
offending flag."""
work_dir = Path(tempfile.mkdtemp())
try:
(work_dir / "claude-bottle.json").write_text(json.dumps({
"bottles": {"dev": {}},
"agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}},
}))
env = os.environ.copy()
env["HOME"] = str(work_dir)
env.pop("CLAUDE_BOTTLE_DRY_RUN", None)
result = subprocess.run(
[
sys.executable, str(REPO_ROOT / "cli.py"),
"start", "--format", "json", "demo",
],
cwd=work_dir,
env=env,
capture_output=True,
text=True,
check=False,
)
self.assertNotEqual(0, result.returncode)
self.assertIn(
"--format=json requires --dry-run", result.stderr,
f"expected the flag-conflict message; got stderr={result.stderr!r}",
)
finally:
import shutil
shutil.rmtree(work_dir, ignore_errors=True)
if __name__ == "__main__":
unittest.main()