Skip to content

Add built-in --jq flag via gojq#286

Merged
jeremy merged 6 commits intomainfrom
rz/explore-jq-flag
Mar 13, 2026
Merged

Add built-in --jq flag via gojq#286
jeremy merged 6 commits intomainfrom
rz/explore-jq-flag

Conversation

@robzolkos
Copy link
Copy Markdown
Collaborator

@robzolkos robzolkos commented Mar 12, 2026

Summary

  • Adds a --jq persistent flag powered by gojq (pure Go jq implementation) that compiles into the binary — no external jq required
  • Users and agents can filter/extract JSON inline: basecamp todos list --jq '.data[].title'
  • --jq implies --json, works with --agent/--quiet, and string results print as plain text
  • Updates the agent skill to prefer --jq over piping to external jq

Addresses QA feedback about agents reaching for external jq when users don't have it installed. With this change, the CLI is self-sufficient for JSON filtering.

Examples

# Extract todo titles
basecamp todos list --in <project> --jq '.data[].title'

# Count results
basecamp todos list --in <project> --jq '.data | length'

# Filter completed items
basecamp todos list --in <project> --jq '[.data[] | select(.completed == true)]'

# Pick specific fields
basecamp people list --jq '[.data[] | {name, email: .email_address}]'

Summary by cubic

Adds a built-in --jq flag powered by gojq to filter JSON directly in the CLI; it implies --json and works with --agent/--quiet. Includes early validation, clear usage errors, compact output, and safer error handling for jq filtering.

  • New Features

    • Persistent --jq '<expr>' (built-in via gojq); implies --json.
    • Early validation: parse/compile before run; conflicts with --ids-only/--count are rejected.
    • Works with --agent/--quiet: filter runs on data-only; otherwise on the full envelope with normalized data.
    • Output: strings print as plain text; objects/arrays as compact single-line JSON; supports env access (env.VAR, $ENV.VAR).
    • Robust errors: jq validation/runtime/unsupported return usage errors; error rendering bypasses jq when needed; --jq counts as machine output.
    • Commands without structured output reject --jq with usage errors (version, login/setup, completion scripts, non-interactive skill); upgrade progress goes to stderr in machine mode; help lists --jq; Basecamp skill docs prefer --jq.
  • Dependencies

    • Added github.com/itchyny/gojq v0.12.18.
    • Added indirect github.com/itchyny/timefmt-go v0.1.7.

Written for commit 5e4ee59. Summary will update on new commits.

Users and agents can now filter JSON output without needing jq
installed: `basecamp todos list --jq '.data[].title'`

- Add gojq dependency (pure Go jq, compiles into binary)
- Add JQFilter to output.Options and GlobalFlags
- Wire --jq as a persistent flag on root command
- --jq implies --json format, works with --agent/--quiet too
- String results print as plain text, objects as formatted JSON
- Update agent skill to prefer --jq over piping to external jq
- Update .surface snapshot
@robzolkos robzolkos requested a review from a team as a code owner March 12, 2026 23:06
Copilot AI review requested due to automatic review settings March 12, 2026 23:06
@github-actions github-actions bot added tests Tests (unit and e2e) skills Agent skills output Output formatting and presentation deps labels Mar 12, 2026
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a built-in --jq global flag (powered by gojq) so the Basecamp CLI can filter/extract JSON output without requiring an external jq binary, and updates agent-facing documentation and metadata accordingly.

Changes:

  • Add a persistent --jq <expr> flag wired through global flags/app context into the output writer.
  • Implement jq filtering in the output layer, including plain-text printing for string results.
  • Update tests, agent skill docs, and .surface metadata; add gojq dependency.

Reviewed changes

Copilot reviewed 6 out of 8 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
internal/output/envelope.go Adds Options.JQFilter and writeJQ() to apply gojq filtering during output.
internal/appctx/context.go Threads JQFilter through GlobalFlags and ensures it triggers machine-output behavior and JSON output selection.
internal/cli/root.go Registers the new persistent --jq flag on the root command.
internal/output/output_test.go Adds coverage for jq filtering behavior (field extraction, string output, errors, etc.).
skills/basecamp/SKILL.md Updates agent guidance to prefer --jq and documents the new output mode.
go.mod / go.sum Adds github.com/itchyny/gojq (and indirect deps).
.surface Adds --jq to the surfaced flag inventory across commands.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 8 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="internal/appctx/context.go">

<violation number="1" location="internal/appctx/context.go:173">
P2: `Resolve()` doesn't propagate `--jq` as a machine-output signal. When `--jq` is used without `--json`, the resolver's `IsInteractive()` still returns true, so interactive prompts (project/account pickers) can fire even though output is JSON-filtered. Set `JSON: true` when a JQ filter is present to keep the resolver in sync with `ApplyFlags` (which already treats `--jq` as implying `--json`).</violation>
</file>

<file name="internal/output/envelope.go">

<violation number="1" location="internal/output/envelope.go:214">
P1: Missing `NormalizeData` call before marshaling. Every other output path normalizes `Response.Data` (to decode `json.RawMessage` etc.) before encoding, but `writeJQ` skips this step. This means jq filters will operate on a different data shape than `--json` output, breaking expressions users tested against `--json`.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

- Normalize Response.Data before jq filtering (match --json shape)
- Respect --agent/--quiet: jq filter runs on data-only payload
- Compile jq expression once in New(), reuse across writes
- Propagate --jq to resolver as machine-output signal
- Update flag help text to mention implies --json
- Clarify --agent + --jq interaction in skill docs
@github-actions github-actions bot added the enhancement New feature or request label Mar 12, 2026
@robzolkos robzolkos requested a review from jeremy March 12, 2026 23:17
jeremy added 3 commits March 12, 2026 23:09
Sentinel error infrastructure:
- errJQUnsupported sentinel with ErrJQValidation, ErrJQNotSupported,
  ErrJQConflict, ErrJQRuntime constructors and IsJQError predicate

Early validation in PersistentPreRunE (root.go):
- Parse + Compile before RunE so invalid expressions are rejected with
  no side effects
- Mutual exclusion: --jq with --ids-only or --count returns ErrJQConflict

Error handler hardening (root.go Execute):
- disableJQ flag set by IsJQError OR app.Err() write failure
- hadJQ preserves --jq-implies-JSON format decision after zeroing filter
- Fallback writer built with JQFilter from parsed flags, zeroed when
  disabled

isMachineConsumer (root.go):
- Now checks --jq flag so early errors honor machine output

Envelope changes (envelope.go):
- Store *gojq.Code instead of *gojq.Query type alias
- Compile with gojq.WithEnvironLoader(os.Environ) for env.VAR / $ENV.VAR
- Compact single-line JSON via json.Marshal (no indented encoder)
- All writeJQ errors return structured *Error with CodeUsage and the
  errJQUnsupported sentinel, ensuring correct exit code 1 and proper
  disableJQ handling in root.go
Coverage fixes from full command audit:

auth token: route through app.OK when --jq is set (was only
checking --json/--agent)

auth login, setup: reject --jq with ErrJQNotSupported (interactive
prose, no structured output)

version: check --jq in RunE since PersistentPreRunE skips for
version and app is never initialized

completion bash/zsh/fish/powershell + parent: reject --jq in all
five shell-generation entry points (completion refresh and status
already use app.OK and honor --jq)

skill (non-interactive): reject --jq before dumping raw SKILL.md

upgrade: redirect progress writer to stderr when IsMachineOutput
so only the structured app.OK result appears on stdout

help: add --jq to curated flags table in root help for
discoverability
output_test.go (12 tests):
- env.VAR and $ENV.VAR access via gojq environment loader
- Compact single-line JSON output (no indent)
- jq runtime error on error response returns error with CodeUsage
- Non-serializable results (nan, infinite) return error with CodeUsage
- Empty result produces no output, exit 0
- Null result produces literal "null"
- Multi-result renders one per line, each per type
- ErrJQValidation, ErrJQNotSupported, ErrJQConflict constructors
- IsJQError false for non-jq errors
- All jq errors verified as IsJQError with correct exit code

root_test.go (7 tests):
- Invalid --jq rejected before RunE (parse error)
- Invalid --jq rejected (compile error, e.g. $__loc__)
- --jq --ids-only mutual exclusion
- --jq --count mutual exclusion
- isMachineConsumer returns true with --jq
- version --jq returns usage error

help_test.go (1 test):
- --jq visible in root help flags
Copilot AI review requested due to automatic review settings March 13, 2026 06:21
@github-actions github-actions bot added the commands CLI command implementations label Mar 13, 2026
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 13 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="internal/commands/skill.go">

<violation number="1" location="internal/commands/skill.go:53">
P2: Misleading error message and inconsistent `ErrJQNotSupported` argument. All other callers pass a short command description (e.g. `"the version command"`), but this embeds a parenthetical hint in the `command` string. More importantly, suggesting `basecamp skill install --jq` is unhelpful — `skill install` writes files and returns metadata, not skill content.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 18 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- Fix misleading ErrJQNotSupported argument in skill.go (consistent
  short description like other callers)
- Clarify envelope.go New() comment: early validation happens in
  PersistentPreRunE, compilation here is best-effort to avoid re-parsing
@jeremy jeremy merged commit 9edead6 into main Mar 13, 2026
26 checks passed
@jeremy jeremy deleted the rz/explore-jq-flag branch March 13, 2026 06:42
jeremy added a commit that referenced this pull request Mar 15, 2026
Lock in the behavior added in #286: --jq implies --json on success,
extracts scalars cleanly, rejects invalid expressions, and conflicts
with --ids-only.
@jeremy jeremy mentioned this pull request Mar 15, 2026
2 tasks
jeremy added a commit that referenced this pull request Mar 16, 2026
* Add e2e test coverage for --jq flag

Lock in the behavior added in #286: --jq implies --json on success,
extracts scalars cleanly, rejects invalid expressions, and conflicts
with --ids-only.

* Tighten jq e2e assertions per review feedback

- Exact-match scalar output instead of substring contains
- Use assert_json_value for conflict error (known exact string)
- Add create_credentials to error tests for convention consistency
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

commands CLI command implementations deps enhancement New feature or request output Output formatting and presentation skills Agent skills tests Tests (unit and e2e)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants