mirror of
https://github.com/github/spec-kit.git
synced 2026-08-03 06:26:30 +08:00
fix(workflows): escape step-graph brackets in workflow info so the type shows (#3690)
`workflow info` rendered each step as `→ <id> [<type>]`, but console.print has Rich markup enabled, so `[<type>]` was parsed as a style tag named after the step type (command/gate/prompt/…) and silently swallowed — every step printed as `→ <id> ` with the type gone. Escape the literal bracket with `\[` (and escape id/type via _escape_markup, as the sibling workflow_list does), so Rich renders `[<type>]` literally. Mirrors the in-file `\[disabled]` precedent. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2376,7 +2376,15 @@ def workflow_info(
|
||||
console.print(f"\n [bold]Steps ({len(definition.steps)}):[/bold]")
|
||||
for step in definition.steps:
|
||||
stype = step.get("type", "command")
|
||||
console.print(f" → {step.get('id', '?')} [{stype}]")
|
||||
# Escape the literal bracket (\[) so Rich renders `[<type>]`
|
||||
# instead of parsing it as a style tag named after the step
|
||||
# type (which it silently swallows); escape id/type too, as
|
||||
# the sibling workflow_list does. Mirrors the `\[disabled]`
|
||||
# precedent above.
|
||||
console.print(
|
||||
f" → {_escape_markup(str(step.get('id', '?')))} "
|
||||
f"\\[{_escape_markup(str(stype))}]"
|
||||
)
|
||||
return
|
||||
|
||||
# Try catalog
|
||||
|
||||
@@ -7882,6 +7882,37 @@ class TestWorkflowAddCaseInsensitiveSuffix:
|
||||
assert "installed" in result.output
|
||||
|
||||
|
||||
class TestWorkflowInfoStepGraph:
|
||||
"""`workflow info` must render each step as `→ <id> [<type>]` with LITERAL
|
||||
brackets. Rich parses an unescaped `[<type>]` as a style tag and silently
|
||||
swallows it, so the step type would vanish from the output."""
|
||||
|
||||
def test_step_type_rendered_in_literal_brackets(self, temp_dir, monkeypatch):
|
||||
import types
|
||||
|
||||
from typer.testing import CliRunner
|
||||
from specify_cli import app
|
||||
from specify_cli.workflows.engine import WorkflowEngine
|
||||
|
||||
(temp_dir / ".specify" / "workflows").mkdir(parents=True)
|
||||
|
||||
fake = types.SimpleNamespace(
|
||||
name="My WF", id="my-wf", version="1.0.0", author="", description="",
|
||||
default_integration=None, inputs={},
|
||||
steps=[{"id": "step-one", "type": "gate"}],
|
||||
)
|
||||
monkeypatch.setattr(WorkflowEngine, "load_workflow", lambda self, wid: fake)
|
||||
monkeypatch.chdir(temp_dir)
|
||||
|
||||
result = CliRunner().invoke(app, ["workflow", "info", "my-wf"])
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
assert "step-one" in result.output
|
||||
# The step type must survive as a literal bracketed token, not be eaten
|
||||
# by Rich as an unknown style tag.
|
||||
assert "[gate]" in result.output
|
||||
|
||||
|
||||
class TestWorkflowAddSymlinkGuard:
|
||||
def test_add_malformed_ipv6_url_exits_cleanly(self, temp_dir, monkeypatch):
|
||||
"""A malformed IPv6 URL must produce a clean error, not a ValueError traceback."""
|
||||
|
||||
Reference in New Issue
Block a user