Conversation
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
There was a problem hiding this comment.
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
.surfacemetadata; addgojqdependency.
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.
There was a problem hiding this comment.
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
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
Lock in the behavior added in #286: --jq implies --json on success, extracts scalars cleanly, rejects invalid expressions, and conflicts with --ids-only.
* 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
Summary
--jqpersistent flag powered by gojq (pure Go jq implementation) that compiles into the binary — no externaljqrequiredbasecamp todos list --jq '.data[].title'--jqimplies--json, works with--agent/--quiet, and string results print as plain text--jqover piping to externaljqAddresses QA feedback about agents reaching for external
jqwhen users don't have it installed. With this change, the CLI is self-sufficient for JSON filtering.Examples
Summary by cubic
Adds a built-in
--jqflag powered bygojqto filter JSON directly in the CLI; it implies--jsonand works with--agent/--quiet. Includes early validation, clear usage errors, compact output, and safer error handling for jq filtering.New Features
--jq '<expr>'(built-in viagojq); implies--json.--ids-only/--countare rejected.--agent/--quiet: filter runs on data-only; otherwise on the full envelope with normalized data.env.VAR,$ENV.VAR).--jqcounts as machine output.--jqwith 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
github.com/itchyny/gojq v0.12.18.github.com/itchyny/timefmt-go v0.1.7.Written for commit 5e4ee59. Summary will update on new commits.