|
| 1 | +# ADR-33276: Adopt Lipgloss Tree Rendering for CLI Hierarchies |
| 2 | + |
| 3 | +**Date**: 2026-05-19 |
| 4 | +**Status**: Draft |
| 5 | +**Deciders**: Unknown |
| 6 | + |
| 7 | +--- |
| 8 | + |
| 9 | +## Part 1 — Narrative (Human-Friendly) |
| 10 | + |
| 11 | +### Context |
| 12 | + |
| 13 | +CLI output for `mcp inspect` and `status` previously mixed ad-hoc hierarchy formatting (manual indentation, custom prefixes) with table-only views. Nested relationships — a workflow's engine and MCP servers, or a workflow's dependency graph — were hard to read because they were either flattened into tables or rendered with bespoke string-building logic in each command. The codebase already standardized terminal styling under `pkg/styles` (including `TreeEnumerator` and `TreeNode`) and has adopted `charm.land/lipgloss/v2` as the terminal rendering library, so a shared tree primitive was available but not yet used for hierarchical command output. |
| 14 | + |
| 15 | +### Decision |
| 16 | + |
| 17 | +We will adopt `charm.land/lipgloss/v2/tree` as the canonical primitive for rendering hierarchical CLI output in `pkg/cli`, starting with `mcp inspect` (workflow → engine + MCP servers) and the verbose mode of `status` (workflow → dependencies). All such trees will use `tree.RoundedEnumerator` together with the existing `styles.TreeEnumerator` and `styles.TreeNode` styles, so hierarchical output is visually consistent across commands. |
| 18 | + |
| 19 | +### Alternatives Considered |
| 20 | + |
| 21 | +#### Alternative 1: Keep manual hierarchy formatting per command |
| 22 | + |
| 23 | +Each command would continue building its own indented strings (or rely on table-only views) to convey hierarchy. Rejected because it duplicates formatting logic across commands, drifts visually as each command evolves independently, and makes nested relationships (a tree with three or more levels) hard to express without reinventing tree primitives. |
| 24 | + |
| 25 | +#### Alternative 2: Write a small in-repo tree printer |
| 26 | + |
| 27 | +We could add a focused helper under `pkg/console` or `pkg/styles` that prints trees with the project's style tokens, avoiding a new dependency surface. Rejected because `lipgloss/v2/tree` is already a transitive dependency (the project uses `lipgloss/v2` extensively for terminal styling), and reimplementing tree layout, enumerator handling, and styling would duplicate well-tested upstream code with no clear benefit. |
| 28 | + |
| 29 | +### Consequences |
| 30 | + |
| 31 | +#### Positive |
| 32 | +- Hierarchical CLI output is rendered consistently across commands using one library and one set of styles. |
| 33 | +- Adding a new hierarchical view in another command becomes a small, declarative composition of `tree.Root(...).Child(...)` calls instead of bespoke string formatting. |
| 34 | +- The tree rendering is testable as a pure function (see the new `*_tree_test.go` files), which is harder to do for ad-hoc string concatenation. |
| 35 | + |
| 36 | +#### Negative |
| 37 | +- Adds a concrete coupling between `pkg/cli` command code and the `charm.land/lipgloss/v2/tree` API; replacing the renderer in the future would require touching every site that builds a tree. |
| 38 | +- Tree output is written to `stderr` alongside existing info messages, which can be noisier in `mcp inspect` and in verbose `status` runs that pipe through tooling expecting a clean stderr. |
| 39 | + |
| 40 | +#### Neutral |
| 41 | +- The shape of `WorkflowStatus` grows a new `Dependencies []string` field (JSON-tagged `omitempty`, console-tagged `-`), so JSON consumers may now see a `dependencies` key for workflows that import or include other files. |
| 42 | +- Dependency extraction logic now lives in `pkg/cli/status_command.go` (frontmatter `imports` in string/list/object forms plus inline `@include`/`@import` directives, with `#section` stripping, dedup, and sort); future changes to import/include syntax must update this extractor. |
| 43 | + |
| 44 | +--- |
| 45 | + |
| 46 | +## Part 2 — Normative Specification (RFC 2119) |
| 47 | + |
| 48 | +> The key words **MUST**, **MUST NOT**, **REQUIRED**, **SHALL**, **SHALL NOT**, **SHOULD**, **SHOULD NOT**, **RECOMMENDED**, **MAY**, and **OPTIONAL** in this section are to be interpreted as described in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119). |
| 49 | +
|
| 50 | +### Tree Rendering Library |
| 51 | + |
| 52 | +1. Hierarchical CLI output in `pkg/cli` **MUST** be rendered using `charm.land/lipgloss/v2/tree`. |
| 53 | +2. Implementations **MUST NOT** introduce a second tree-rendering library or hand-rolled ASCII tree printer in `pkg/cli` while this ADR is in effect. |
| 54 | +3. Tree composition **SHOULD** use `tree.Root(...).Child(...)` chains rather than mutating tree nodes after construction. |
| 55 | + |
| 56 | +### Styling Consistency |
| 57 | + |
| 58 | +1. Tree renderers in `pkg/cli` **MUST** apply `tree.RoundedEnumerator` as the enumerator. |
| 59 | +2. Tree renderers **MUST** apply `styles.TreeEnumerator` as the `EnumeratorStyle` and `styles.TreeNode` as the `ItemStyle`. |
| 60 | +3. Renderers **SHOULD NOT** override these styles inline with per-call style values; new style variants **SHOULD** be added to `pkg/styles` first. |
| 61 | + |
| 62 | +### Output Channel and Triggering |
| 63 | + |
| 64 | +1. Tree output for `mcp inspect` and `status` **MUST** be written to `stderr`, alongside existing informational messages, and **MUST NOT** be written to `stdout` when `stdout` is reserved for machine-readable output (e.g., JSON). |
| 65 | +2. The dependency tree in `status` **MUST** only be rendered when verbose text output is requested; it **MUST NOT** be rendered in JSON mode or in non-verbose runs. |
| 66 | +3. Renderers **MUST** return an empty string when there is nothing to display (e.g., zero MCP servers, zero dependencies) so the caller can skip the surrounding section header. |
| 67 | + |
| 68 | +### Dependency Extraction (Status Command) |
| 69 | + |
| 70 | +1. The status command **MUST** extract workflow dependencies from both frontmatter `imports` (accepting string, list, and object-with-`uses` forms) and inline `@include` / `@import` directives in the workflow body. |
| 71 | +2. Extracted dependencies **MUST** be normalized by stripping any `#section` suffix, deduplicated, and sorted before rendering. |
| 72 | +3. The `Dependencies` field on `WorkflowStatus` **MUST** be JSON-tagged `omitempty` so workflows without dependencies do not emit an empty list in JSON output. |
| 73 | + |
| 74 | +### Conformance |
| 75 | + |
| 76 | +An implementation is considered conformant with this ADR if it satisfies all **MUST** and **MUST NOT** requirements above. Failure to meet any **MUST** or **MUST NOT** requirement constitutes non-conformance. |
| 77 | + |
| 78 | +--- |
| 79 | + |
| 80 | +*This is a DRAFT ADR generated by the [Design Decision Gate](https://github.com/github/gh-aw/actions/runs/26092090247) workflow. The PR author must review, complete, and finalize this document before the PR can merge.* |
0 commit comments