fix(cli): write machine-readable formats directly to stdout bypassing Rich line-wrapping

The format_output() function returned a string that callers passed to
Rich console.print(), which wraps long lines at terminal width.  This
injected literal newline characters into JSON string values (e.g. in
definition_of_done fields), producing invalid JSON that downstream
parsers could not decode (JSONDecodeError: Invalid control character).

For machine-readable formats (json, yaml, plain), format_output() now
writes the rendered output directly to sys.stdout and returns an empty
string.  This preserves the exact serialization from json.dumps/
yaml.dump without Rich text processing artifacts.

Refs: #746
This commit is contained in:
Luis Mendes
2026-03-13 23:03:43 +00:00
parent 2acb957d83
commit ab911dbdc4
22 changed files with 298 additions and 52 deletions
+29 -10
View File
@@ -210,17 +210,23 @@ def step_request_diff_rich(context: Context) -> None:
@when("I request the diff in plain format")
def step_request_diff_plain(context: Context) -> None:
context.diff_output = context.apply_service.diff(context.plan_id, fmt="plain")
context.diff_output = _capture_service_output(
context.apply_service.diff, context.plan_id, fmt="plain"
)
@when("I request the diff in JSON format")
def step_request_diff_json(context: Context) -> None:
context.diff_output = context.apply_service.diff(context.plan_id, fmt="json")
context.diff_output = _capture_service_output(
context.apply_service.diff, context.plan_id, fmt="json"
)
@when("I request the diff in YAML format")
def step_request_diff_yaml(context: Context) -> None:
context.diff_output = context.apply_service.diff(context.plan_id, fmt="yaml")
context.diff_output = _capture_service_output(
context.apply_service.diff, context.plan_id, fmt="yaml"
)
@when("I try to get the diff")
@@ -234,27 +240,40 @@ def step_try_get_diff(context: Context) -> None:
@when("I request the artifacts")
def step_request_artifacts(context: Context) -> None:
context.artifacts_output = context.apply_service.artifacts(context.plan_id)
context.artifacts_output = _capture_service_output(
context.apply_service.artifacts, context.plan_id
)
def _capture_service_output(func, *args, **kwargs):
"""Call a service method capturing stdout (format_output writes there)."""
from contextlib import redirect_stdout
from io import StringIO
buf = StringIO()
with redirect_stdout(buf):
result = func(*args, **kwargs)
return result or buf.getvalue().rstrip("\n")
@when("I request the artifacts in JSON format")
def step_request_artifacts_json(context: Context) -> None:
context.artifacts_output = context.apply_service.artifacts(
context.plan_id, fmt="json"
context.artifacts_output = _capture_service_output(
context.apply_service.artifacts, context.plan_id, fmt="json"
)
@when("I request the artifacts in plain format")
def step_request_artifacts_plain(context: Context) -> None:
context.artifacts_output = context.apply_service.artifacts(
context.plan_id, fmt="plain"
context.artifacts_output = _capture_service_output(
context.apply_service.artifacts, context.plan_id, fmt="plain"
)
@when("I request the artifacts in YAML format")
def step_request_artifacts_yaml(context: Context) -> None:
context.artifacts_output = context.apply_service.artifacts(
context.plan_id, fmt="yaml"
context.artifacts_output = _capture_service_output(
context.apply_service.artifacts, context.plan_id, fmt="yaml"
)