feat: add --fields flag for context-window-friendly JSON output#373
feat: add --fields flag for context-window-friendly JSON output#373
Conversation
Semver Impact of This PR🟡 Minor (new features) 📋 Changelog PreviewThis is how your changes will appear in the changelog. New Features ✨
Bug Fixes 🐛
Internal Changes 🔧
🤖 This preview updates automatically when you update the PR. |
Codecov Results 📊✅ 104 passed | Total: 104 | Pass Rate: 100% | Execution Time: 0ms 📊 Comparison with Base Branch
✨ No test changes detected All tests are passing successfully. ✅ Patch coverage is 99.60%. Project has 900 uncovered lines. Files with missing lines (5)
Coverage diff@@ Coverage Diff @@
## main #PR +/-##
==========================================
+ Coverage 95.42% 95.45% +0.03%
==========================================
Files 141 141 —
Lines 19672 19768 +96
Branches 0 0 —
==========================================
+ Hits 18771 18868 +97
- Misses 901 900 -1
- Partials 0 0 —Generated by Codecov Action |
|
Re: BugBot — Fields filtering operates on wrapper, not nested data Fixed in the latest push. Created a For non-list wrappers like Also added 17 dedicated tests for |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
|
Re: BugBot — Truthy check drops falsy Fixed in this push. Changed |
|
Re: Seer — Not a bug. In multi-target mode ( The single-target |
|
Re: BugBot — Redundant fields parameter alongside flags.fields in option types (log/list.ts) Fixed. Removed the standalone |
Add a --fields flag to all 14 commands that support --json output, allowing agents (and humans) to request only specific fields. This reduces token consumption and keeps agent context windows focused. Features: - Comma-separated field selection: --fields id,title,status - Dot-notation for nested fields: --fields id,metadata.value - Array support: each element filtered independently - Silently ignores missing fields (no error for non-existent paths) Implementation: - filterFields() in src/lib/formatters/json.ts handles dot-notation traversal, nested object reconstruction, and array mapping - parseFieldsList() parses comma-separated input with dedup and trim - writeJson() accepts optional fields parameter for filtering - Shared FIELDS_FLAG constant in src/lib/list-command.ts - Threaded through all writeJson call sites in 14 command files Fixes #347
…dCommand
Lift --json and --fields flag definitions out of 17 individual commands
into buildCommand's output mode system. When a command declares
output: "json", the wrapper automatically:
- Injects --json (boolean) and --fields (parsed) flags
- Pre-parses --fields from comma-string to string[] before func runs
- Respects command-owned --json flags (skips injection, still adds --fields)
Also introduces writeJsonList() helper that applies --fields filtering
to each array element inside the {data, hasMore} wrapper, fixing the
BugBot-reported issue where filtering operated on the wrapper instead
of the nested data.
Changes:
- src/lib/command.ts: JSON_FLAG, FIELDS_FLAG constants, output mode logic
- src/lib/formatters/json.ts: writeJsonList() with extra? option
- src/lib/formatters/output.ts: fields? on WriteOutputOptions
- src/lib/list-command.ts: passes output through to buildCommand
- src/lib/org-list.ts: fields on BaseListFlags, writeJsonList at wrapper site
- 17 command files: migrated to output: "json", removed manual flag defs
- 3 commands gained --fields: auth/refresh, org/view, trace/logs
- test/lib/command.test.ts: 14 new tests for json injection + fields parsing
- test/lib/formatters/json.test.ts: 17 new tests for writeJsonList
## Phase 2: `writeOutput` convergence Builds on #373 (`output: "json"` centralization). Enhance `writeOutput` with two new options and migrate 3 Tier 1 commands to use it. ### New options on `writeOutput` - **`footer?: string`** — muted hint after human output, suppressed in JSON mode. Replaces separate `writeFooter()` calls. - **`jsonData?: J`** — separate data object for JSON when the serialized shape differs from the human formatter's input. ### Commands migrated | Command | What changed | |---------|-------------| | `auth/whoami` | `jsonData` narrows user to `{id, name, username, email}` for JSON | | `auth/refresh` | Inline `formatHuman` replaces 3-branch if/else | | `issue/explain` | `footer` replaces separate `writeFooter()` call | Commands with multi-part output or divergent data assembly (`issue/view`, `event/view`, `trace/view`, `project/create`, `log/view`) are intentionally left as-is — they don't fit the single-formatter pattern. ### Tests 16 new tests in `test/lib/formatters/output.test.ts` cover JSON mode, human mode, footer, detectedFrom, and jsonData divergence.
## Summary Phase 3 of the output convergence plan ([Phase 1: #373](#373), [Phase 2: #376](#376)). Commands can now declare an `OutputConfig` on `buildCommand` and return bare data from `func`. The wrapper intercepts the return value and renders automatically — selecting human vs JSON format based on `--json`, applying `--fields` filtering, and writing to stdout. Runtime hints like detection source or next-step footers use `[data, { hint }]` tuples. ### What changed **Core infrastructure** (`src/lib/formatters/output.ts`, `src/lib/command.ts`): - `OutputConfig<T>` — declared on `buildCommand` with `human` formatter - `OutputMeta` — runtime hints (`{ hint?: string }`) returned via tuple - `renderCommandOutput()` — renders data using config + merged runtime context - `buildCommand` `output` field accepts two forms: - `"json"` (string) — flag injection only (`--json`, `--fields`) - `{ json: true, human: fn }` — flag injection + auto-render - Detects bare data vs `[data, meta]` tuple returns; `void`/`Error` returns are ignored - Renamed `detectedFrom` → `hint` throughout the output layer (callers compose full message) **4 Category A commands migrated** to return-based pattern: - `auth/whoami` — `return user` - `auth/refresh` — `return payload` with `formatRefreshResult` in config - `issue/explain` — `return [causes, { hint }]` for next-step footer - `org/view` — `return [org, { hint }]` for detection source, or bare `org` **jsonData eliminated** — single canonical data object for both JSON and human renderers. The `--fields` flag handles field selection for context-window-friendly output. **JSON shapes normalized** for consistent `jq` ergonomics: - `issue/view` — always includes `event: null` instead of omitting the key - `log/view` — always emits array (was single object for single ID) - `project/view` — always emits array (was single object for single project) **Removed** (replaced by simpler pattern): - `OutputResult<T, J>` branded type - `output()` helper function - `isOutputResult()` / `renderOutputResult()` functions - `WriteOutputDivergentOptions<T, J>` type and `jsonData` from all output APIs **Tests**: All assertions updated for new shapes. Zero regressions (260 pre-existing failures on main, same on branch). ### Design decisions - **Two-form output**: `"json"` for flag-only injection (most commands), `OutputConfig` for full auto-render (4 migrated commands). No breaking changes to existing `output: "json"` commands. - **Bare return, not branded wrapper**: Commands return plain data instead of `output(data, opts)`. Simpler, less ceremony, no brand symbol machinery. - **`[data, { hint }]` for runtime context**: Hints depend on execution-time values (resolved issue arg, detection source). Declared at return time, not build time. - **`hint` replaces `detectedFrom` + `footer`**: Single generic field. Callers compose the full text. - **No jsonData**: One data shape for both human and JSON renderers. Use `--fields` to control what appears in JSON output. - **Always-array JSON**: Commands that return data return arrays even for single results, for consistent `jq` pipelines. - **Complex commands deferred**: event/view, trace/view, project/create, issue/plan have `--web`, polling, stderr progress, etc. These need a more advanced pattern (likely `buildStreamingCommand` variant) in follow-up PRs. ### Stats 14 files changed, 756 insertions, 305 deletions
Convert event view, issue view, issue plan, project create, and trace
view to the new return-based output pattern where commands return
{data} and the framework handles JSON serialization and human
formatting.
Each command now declares an output config with json: true and a human
formatter function, replacing manual stdout.write + writeJson calls.
Changes:
- event/view: extract buildEventData + formatEventOutput
- issue/view: extract buildIssueData + formatIssueOutput
- issue/plan: extract buildPlanData + formatPlanOutput, remove
outputSolution helper
- project/create: extract formatProjectCreateOutput
- trace/view: extract buildTraceData + formatTraceOutput
Infrastructure:
- Add OutputResult type export from lib/command.ts
- Add writeHuman helper to formatters/output.ts for consistent
newline-terminated human output
- Update tests to match new return signatures
Part of the output convergence plan (PR sequence #373→#376→#380).
) ## Summary Convert 5 remaining Tier 1 commands to the return-based `CommandOutput<T>` pattern introduced in #380. Each command now returns `{ data, hint?, footer? }` and declares an `OutputConfig` with `json: true` and a `human` formatter — the framework handles JSON serialization and human-readable rendering. ## Commands converted | Command | Human formatter | Return shape | |---------|----------------|-------------| | `event/view` | `formatEventView()` | `{ data: { event, trace, spanTreeLines }, hint }` | | `issue/view` | `formatIssueView()` | `{ data: { issue, event, trace, spanTreeLines }, footer }` | | `issue/plan` | `formatPlanOutput()` | `{ data: { run_id, status, solution } }` | | `project/create` | `formatProjectCreated()` | `{ data: ProjectCreatedResult }` | | `trace/view` | `formatTraceView()` | `{ data: { summary, spans, spanTreeLines }, footer }` | ## Infrastructure changes - Added `footer?: string` to `CommandOutput<T>` and `RenderContext` - `renderCommandOutput()` now renders footer via `writeFooter()` - `handleReturnValue` in `command.ts` passes `value.footer` through ## What was removed - `writeHumanOutput()` from trace/view (replaced by `formatTraceView`) - `outputSolution()` from issue/plan (replaced by `formatPlanOutput` + `buildPlanData`) - Manual `writeJson`/`writeFooter`/`stdout.write` calls from all 5 commands - `Writer` type imports (no longer needed) ## Test updates - `test/commands/trace/view.func.test.ts`: `writeHumanOutput` tests replaced with `formatTraceView` pure function tests - `test/commands/project/create.test.ts`: `parsed.slug` → `parsed.project.slug` (JSON shape now uses full `ProjectCreatedResult`) Net: **-33 lines** (188 insertions, 221 deletions) Part of the output convergence plan: #373 → #376 → #380 → this PR
Summary
Add a
--fieldsflag to all 14 commands that support--jsonoutput, allowing agents (and humans) to request only specific fields. This reduces token consumption and keeps agent context windows focused.Fixes #347
Motivation
From You Need to Rewrite Your CLI for AI Agents:
A full
sentry issue view --jsondumps the entire issue object, latest event, and span tree. An agent that just needsid,title, andstatusis forced to ingest thousands of tokens of irrelevant data.Usage
Design
Option A from the issue: Simple
--fieldsflag with comma-separated field paths. Covers the 90% case for agents, trivial to implement, no external dependencies.Core implementation (
src/lib/formatters/json.ts)filterFields(data, fields)— picks specified fields from objects/arrays, supports dot-notation for nested access, silently skips missing fieldsparseFieldsList(input)— parses comma-separated input with dedup, whitespace trimming, empty segment filteringwriteJson(stream, data, fields?)— optionalfieldsparam filters output before serializationShared flag (
src/lib/list-command.ts)FIELDS_FLAG— Stricli flag constant shared by all commands with--jsonCommands updated (14 total)
auth whoami,event view,issue explain,issue list,issue plan,issue view,log list,log view,org list,project create,project list,project view,trace list,trace viewEdge cases
--fieldswithout--json→ silently ignoredTesting