Skip to content

feat(cli): render generated commands as human tables, not raw JSON#46

Merged
ysyneu merged 1 commit into
feat/ai-srefrom
feat/generic-table-renderer
Jun 15, 2026
Merged

feat(cli): render generated commands as human tables, not raw JSON#46
ysyneu merged 1 commit into
feat/ai-srefrom
feat/generic-table-renderer

Conversation

@ysyneu

@ysyneu ysyneu commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

What

Generated commands carried no hand-written column set, so in human (table) mode they fell back to indented JSON. This adds a generic, reflection-based table renderer so every generated command renders an aligned table:

  • a paginated list envelope ({Items:[...], Total, ...}) or a top-level row array → aligned table
  • a single object → vertical FIELD/VALUE table
  • anything else → indented JSON (never empty)

Columns come from a display-only seed (display_columns.go), lifted from the curated commands' hand-written column sets, with a reflective heuristic (first N scalar/timestamp fields) for unmapped row types. The seed is display-only: it never touches flags or machine output, so a wrong entry degrades one column at worst — it can't cause a functional error. A reflection test (TestDisplayColumns_FieldsResolve) fails the build if any seeded field name doesn't resolve on its SDK type.

Machine modes (--output-format json/toon) are untouched — the renderer is reached only in table mode; structured output still marshals the full typed response (test asserts byte-identical to the direct printer).

Why

Keystone for converging the CLI on generated commands as the single source of truth: human tables no longer require hand-written commands, so curated "shadow" commands (which freeze a flag subset and hide server-side filters — root cause of the channel list --team-ids gap) can be removed later without losing readable output.

Test plan

  • go build/vet ./..., go test ./internal/... — green; gofumpt clean.
  • Unit: list-envelope / top-level-array / detail-vertical / empty / heuristic / structured-unchanged (json+toon) / display-columns field-resolution.
  • Live E2E against a real backend (fduty built from this branch):
    • role list (table) → aligned table from real rows (was raw JSON); --output-format json/toon → unchanged structured.
    • role info (table) → vertical FIELD/VALUE table.
    • Full e2e/ suite (-tags=e2e) → all pass except 4 pre-existing TestStatusPage* failures (unknown command "statuspage" — naming drift on the base branch, unrelated to rendering; confirmed identical on origin/feat/ai-sre).

Follow-ups (separate PRs)

  • Drop curated shadows group-by-group, migrating the agent skill-doc corpus + scan.sh in lockstep.
  • Unshadow insight team/channel/responder to expose their server-side filters (severities/*_ids/fields/labels).
  • Push value into the API so generated commands inherit it: team_name/creator_name on channel list; a short-id (num) param on incident info.

Generated commands carried no hand-written column set, so in human (table)
mode they fell back to indented JSON. Add a generic, reflection-based
renderer that gives every generated command an aligned table:

- a paginated list envelope ({Items:[...], Total, ...}) or a top-level
  row array prints as an aligned table;
- a single object prints as a vertical FIELD/VALUE table;
- anything else falls back to indented JSON, so output is never empty.

Columns come from a display-only seed (display_columns.go) lifted from the
curated commands' hand-written column sets, with a reflective heuristic
(first N scalar/timestamp fields) for unmapped row types. The seed is
display-only: it never affects flags or machine output, so a wrong entry
degrades one table column at worst — it cannot cause a functional error.

Machine modes (--output-format json/toon) are untouched: the renderer is
reached only in table mode; structured output still marshals the full
typed response (verified byte-identical to the direct printer).

This is the keystone for converging the CLI on generated commands as the
single source of truth: human tables no longer require hand-written
commands, so curated shadows can be removed later without losing readable
output. Follow-ups (separate PRs): drop curated shadows group-by-group with
skill-doc migration; unshadow insight to expose its server-side filters;
add team/creator names + incident short-id to the API so the generated
commands inherit them.
@ysyneu ysyneu merged commit b534516 into feat/ai-sre Jun 15, 2026
12 checks passed
ysyneu added a commit that referenced this pull request Jun 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant