[PR #2333] [MERGED] fix(cli)!: keep a selected JSON null in --filter-output output #5169

Closed
opened 2026-10-31 01:04:30 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/2333
Author: @Jackmeson1
Created: 10/12/2026
Status: ✅ Merged
Merged: 10/13/2026
Merged by: @morluto

Base: main ← Head: fix/cli-filter-output-null


📝 Commits (1)

  • ab96bc8 fix(cli)!: keep a selected JSON null in --filter-output output

📊 Changes

2 files changed (+35 additions, -2 deletions)

View changed files

📝 src/cli/cliResultOutput.ts (+3 -2)
📝 tests/boundary/cli/jsonOutput.test.ts (+32 -0)

📄 Description

Summary

The streamed JSON/JSONL path printed {} when --filter-output selected an explicit null, which is the same output
as a selection that does not exist. This keeps a selected null as null on that path, without --full-output or
--token-count. No separate issue was filed; this PR is the report.

Problem and expected behavior

Smallest trigger: in a fresh invocation without a bound analysis provider, node scripts/rea.mjs capabilities --json
returns "analysis_provider_binding": null. From a checkout of fd95880, run npm ci && npm run build:cached, then:

$ node scripts/rea.mjs capabilities --format json --filter-output analysis_provider_binding
{}
$ node scripts/rea.mjs capabilities --format json --filter-output no_such_field
{}

Violated invariant: AGENTS.md asks that omission and uncertainty survive boundaries, and the JSON writer already
preserves null (tests/boundary/cli/jsonOutput.test.ts). Incur's filter also preserves a selected null; the streamed
renderer then collapsed it to {}. renderEmptyFilteredCliOutput in src/cliOutput.ts is a separate fallback for an
Incur invocation that emitted no output; it does not inspect the selected value.

Change and scope

In src/cli/cliResultOutput.ts (createCliResultOutput), the non-envelope branch no longer maps null to
{}. This also preserves an unfiltered top-level null. undefined, functions and symbols still fall back to {}.
Not changed:

  • --full-output without --token-count, which already writes {"ok":true,"data":null,...} for a selected null;
  • --token-count, which still hands results that fit the formatted-string limit back to Incur. Incur 0.6.1 counts
    selected null as empty, so --token-count --filter-output analysis_provider_binding still prints 0;
  • other formats: the capabilities selection above still produces {} in YAML and no output in TOON/Markdown;
  • Incur-owned paths, including sentinel and generator results, and createCli calls without the streamed output
    surface. A selected null in a plain success sentinel still reaches the dispatcher's {} fallback in JSON/JSONL.

The fix uses the existing filter and JSON writer; it removes the intervening null-to-empty conversion. Changing
Incur's formatter, token counting, or sentinel/generator protocols is outside this streamed JSON/JSONL fix.

Contribution scope: an ordinary fix within the existing CLI output boundary. Not applicable.

Contract and boundary impact

  • Semantic owner and earliest changed stage: CLI JSON/JSONL rendering in createCliResultOutput, after
    optional --filter-output selection.
  • Contribution scope fit and linked maintainer decision: in scope as a fix. Not applicable.
  • CLI and MCP/tool-catalog contract: CLI only. A top-level null now prints null instead of {} on the streamed
    JSON/JSONL path described above. MCP is unchanged.
  • Provider, bridge, target-format, or platform compatibility: none.
  • Evidence, artifact, provenance, or reconstruction contract: none. Stored data is unchanged; only the CLI projection
    changes.
  • Target selection, authority, upstream behavior, and cleanup details: not applicable.
  • Generated metadata, package, or installation impact: none.

Expected effects:

  • Read existing files or captured data.
  • Launch or attach to a process.
  • Inject instrumentation or run scripts/code.
  • Change process memory, registers, files, or configuration.
  • Contact network services.
  • Recover protected code or work around protections.
  • Documentation or usability only; no target interaction.
  • Not sure yet; investigation or maintainer guidance needed.

Evidence and regression coverage

  • Tests added or updated: tests/boundary/cli/jsonOutput.test.ts, new describe block "streamed CLI filtered
    selections". It is a table over createCliResultOutput with:

    • a selected null in JSON and JSONL;
    • a nested selection containing null ({"nested":{"value":null}}, unchanged);
    • a missing selection ({});
    • 0 and false.
  • Base reproduction, executed outside the sandbox and repeated in the sandbox: with only the test applied on
    fd95880, the two selected-null rows fail with
    expected '{}\n' to be 'null\n'. The other four pass.

  • User-visible CLI output, after npm run build:cached with this change:

    $ node scripts/rea.mjs capabilities --format json --filter-output analysis_provider_binding
    null
    $ node scripts/rea.mjs capabilities --format jsonl --filter-output analysis_provider_binding
    null
    $ node scripts/rea.mjs capabilities --format json --filter-output no_such_field
    {}
    
  • Remaining proof gaps: the Incur-owned paths above retain their existing behavior. The sandbox could not complete
    artifact preparation or the full static gate; related compiled tests failed on both revisions as recorded below.
    Validation with the repository-pinned Node/npm versions remains outstanding.

  • Evidence-bearing changes: stored Evidence and provenance are unchanged; only the CLI projection changes.

Validation performed

Outside-sandbox validation on WSL Ubuntu 22.04 with the repository-pinned Node 24.18.0 and npm 11.16.0. Rebased
commit on base fd95880, which renamed streamedJsonOutput.ts to cliResultOutput.ts:

  • npm run check — passed (typecheck, lint, format:check, knip, metadata:check and the architecture guards)
  • npx vitest run tests/boundary/cli/jsonOutput.test.ts — 41 passed. On fd95880 with only the new test applied,
    the two selected-null rows fail as described above.
  • npm run test:prepare && npx vitest run --project cli-boundary — 316 passed, 21 skipped, 2 failed. Both failures
    are in tests/boundary/cli/referenceSourceImportStatus.test.ts (the --full-output preflight cases, which expect
    ok: true and get ok: false), and they fail identically on fd95880 without this change.
  • npx vitest run tests/acceptance/managedCliOutput.test.ts — 7 passed
  • node scripts/rea.mjs capabilities ... --filter-output ... with the built CLI on fd95880 and on this branch —
    output shown above

Before the rebase, on base a97b713: the same npm run check passed, and cliOutput, dispatcher,
javascriptApplicationJsonOutput, managedCliOutput and jsonOutput passed together (61 tests). Independent
checks in a disposable sandbox copy (Node 24.21.0, npm 11.19.0) also passed there: tsc --noEmit, oxlint and
oxfmt --check on the changed files, npm run verify:test-discovery, and the built-CLI comparison across JSON/JSONL,
missing field, --token-count, --full-output, YAML and TOON/Markdown. In that sandbox, npm run check and the
compiled CLI tests could not run (spawnSync ... EPERM); the outside-sandbox runs cover them.

Compatibility, safety, and release

  • Breaking changes or migration steps: capabilities --format json --filter-output analysis_provider_binding
    previously returned {} when the field was null; the streamed JSON/JSONL path now returns null. Consumers must
    accept JSON null for a present null and distinguish it from a missing selection. Use --full-output if an object
    envelope is required. Keeping the old shared {} representation would continue hiding this distinction.
  • Real Hopper/Ghidra, browser, or OS coverage: CLI validation was on WSL/Linux only; real providers were not
    exercised and are not required for this formatting change.
  • Package or release metadata impact: no manual version or CHANGELOG edit. The ! title marks the compatibility
    change for Release Please; the migration is described above.
  • Security, privacy, process, or containment review: not applicable.

AI disclosure: prepared with AI assistance (Claude Code); the checks above were run before opening the PR.

Before merge

  • This PR addresses a concrete problem or an agreed enhancement.
  • Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff.
  • The PR has one focused outcome and the title follows type(scope): outcome.
  • Related issue is linked, or the reason for not linking one is stated above.
  • Tests cover changed observable behavior and meaningful failure paths.
  • Owning docs, contracts, and generated metadata are updated where needed.
  • User-visible CLI/MCP changes include representative output.
  • I checked the final diff for secrets, unrelated cleanup, and unsupported claims.
  • Complete npm run check with the pinned Node 24.18.0/npm 11.16.0 and rerun the relevant compiled tests outside
    the restricted sandbox.

🤖 Generated with Claude Code

https://claude.ai/code/session_019TTGAv6fe859ebbCLLfLHP


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/2333 **Author:** [@Jackmeson1](https://github.com/Jackmeson1) **Created:** 10/12/2026 **Status:** ✅ Merged **Merged:** 10/13/2026 **Merged by:** [@morluto](https://github.com/morluto) **Base:** `main` ← **Head:** `fix/cli-filter-output-null` --- ### 📝 Commits (1) - [`ab96bc8`](https://github.com/morluto/rea/commit/ab96bc82bb7586ca98d522f82406fc37eadc8551) fix(cli)!: keep a selected JSON null in --filter-output output ### 📊 Changes **2 files changed** (+35 additions, -2 deletions) <details> <summary>View changed files</summary> 📝 `src/cli/cliResultOutput.ts` (+3 -2) 📝 `tests/boundary/cli/jsonOutput.test.ts` (+32 -0) </details> ### 📄 Description ## Summary The streamed JSON/JSONL path printed `{}` when `--filter-output` selected an explicit `null`, which is the same output as a selection that does not exist. This keeps a selected null as `null` on that path, without `--full-output` or `--token-count`. No separate issue was filed; this PR is the report. ## Problem and expected behavior Smallest trigger: in a fresh invocation without a bound analysis provider, `node scripts/rea.mjs capabilities --json` returns `"analysis_provider_binding": null`. From a checkout of `fd95880`, run `npm ci && npm run build:cached`, then: ```text $ node scripts/rea.mjs capabilities --format json --filter-output analysis_provider_binding {} $ node scripts/rea.mjs capabilities --format json --filter-output no_such_field {} ``` Violated invariant: AGENTS.md asks that omission and uncertainty survive boundaries, and the JSON writer already preserves `null` (`tests/boundary/cli/jsonOutput.test.ts`). Incur's filter also preserves a selected null; the streamed renderer then collapsed it to `{}`. `renderEmptyFilteredCliOutput` in `src/cliOutput.ts` is a separate fallback for an Incur invocation that emitted no output; it does not inspect the selected value. ## Change and scope In `src/cli/cliResultOutput.ts` (`createCliResultOutput`), the non-envelope branch no longer maps `null` to `{}`. This also preserves an unfiltered top-level null. `undefined`, functions and symbols still fall back to `{}`. Not changed: - `--full-output` without `--token-count`, which already writes `{"ok":true,"data":null,...}` for a selected null; - `--token-count`, which still hands results that fit the formatted-string limit back to Incur. Incur 0.6.1 counts selected null as empty, so `--token-count --filter-output analysis_provider_binding` still prints `0`; - other formats: the capabilities selection above still produces `{}` in YAML and no output in TOON/Markdown; - Incur-owned paths, including sentinel and generator results, and `createCli` calls without the streamed output surface. A selected null in a plain success sentinel still reaches the dispatcher's `{}` fallback in JSON/JSONL. The fix uses the existing filter and JSON writer; it removes the intervening null-to-empty conversion. Changing Incur's formatter, token counting, or sentinel/generator protocols is outside this streamed JSON/JSONL fix. Contribution scope: an ordinary fix within the existing CLI output boundary. Not applicable. ## Contract and boundary impact - Semantic owner and earliest changed stage: CLI JSON/JSONL rendering in `createCliResultOutput`, after optional `--filter-output` selection. - Contribution scope fit and linked maintainer decision: in scope as a fix. Not applicable. - CLI and MCP/tool-catalog contract: CLI only. A top-level `null` now prints `null` instead of `{}` on the streamed JSON/JSONL path described above. MCP is unchanged. - Provider, bridge, target-format, or platform compatibility: none. - Evidence, artifact, provenance, or reconstruction contract: none. Stored data is unchanged; only the CLI projection changes. - Target selection, authority, upstream behavior, and cleanup details: not applicable. - Generated metadata, package, or installation impact: none. Expected effects: - [ ] Read existing files or captured data. - [ ] Launch or attach to a process. - [ ] Inject instrumentation or run scripts/code. - [ ] Change process memory, registers, files, or configuration. - [ ] Contact network services. - [ ] Recover protected code or work around protections. - [x] Documentation or usability only; no target interaction. - [ ] Not sure yet; investigation or maintainer guidance needed. ## Evidence and regression coverage - Tests added or updated: `tests/boundary/cli/jsonOutput.test.ts`, new describe block "streamed CLI filtered selections". It is a table over `createCliResultOutput` with: - a selected null in JSON and JSONL; - a nested selection containing null (`{"nested":{"value":null}}`, unchanged); - a missing selection (`{}`); - `0` and `false`. - Base reproduction, executed outside the sandbox and repeated in the sandbox: with only the test applied on `fd95880`, the two selected-null rows fail with `expected '{}\n' to be 'null\n'`. The other four pass. - User-visible CLI output, after `npm run build:cached` with this change: ```text $ node scripts/rea.mjs capabilities --format json --filter-output analysis_provider_binding null $ node scripts/rea.mjs capabilities --format jsonl --filter-output analysis_provider_binding null $ node scripts/rea.mjs capabilities --format json --filter-output no_such_field {} ``` - Remaining proof gaps: the Incur-owned paths above retain their existing behavior. The sandbox could not complete artifact preparation or the full static gate; related compiled tests failed on both revisions as recorded below. Validation with the repository-pinned Node/npm versions remains outstanding. - Evidence-bearing changes: stored Evidence and provenance are unchanged; only the CLI projection changes. ## Validation performed Outside-sandbox validation on WSL Ubuntu 22.04 with the repository-pinned Node 24.18.0 and npm 11.16.0. Rebased commit on base `fd95880`, which renamed `streamedJsonOutput.ts` to `cliResultOutput.ts`: - `npm run check` — passed (typecheck, lint, format:check, knip, metadata:check and the architecture guards) - `npx vitest run tests/boundary/cli/jsonOutput.test.ts` — 41 passed. On `fd95880` with only the new test applied, the two selected-null rows fail as described above. - `npm run test:prepare && npx vitest run --project cli-boundary` — 316 passed, 21 skipped, 2 failed. Both failures are in `tests/boundary/cli/referenceSourceImportStatus.test.ts` (the `--full-output` preflight cases, which expect `ok: true` and get `ok: false`), and they fail identically on `fd95880` without this change. - `npx vitest run tests/acceptance/managedCliOutput.test.ts` — 7 passed - `node scripts/rea.mjs capabilities ... --filter-output ...` with the built CLI on `fd95880` and on this branch — output shown above Before the rebase, on base `a97b713`: the same `npm run check` passed, and `cliOutput`, `dispatcher`, `javascriptApplicationJsonOutput`, `managedCliOutput` and `jsonOutput` passed together (61 tests). Independent checks in a disposable sandbox copy (Node 24.21.0, npm 11.19.0) also passed there: `tsc --noEmit`, `oxlint` and `oxfmt --check` on the changed files, `npm run verify:test-discovery`, and the built-CLI comparison across JSON/JSONL, missing field, `--token-count`, `--full-output`, YAML and TOON/Markdown. In that sandbox, `npm run check` and the compiled CLI tests could not run (`spawnSync ... EPERM`); the outside-sandbox runs cover them. ## Compatibility, safety, and release - Breaking changes or migration steps: `capabilities --format json --filter-output analysis_provider_binding` previously returned `{}` when the field was null; the streamed JSON/JSONL path now returns `null`. Consumers must accept JSON null for a present null and distinguish it from a missing selection. Use `--full-output` if an object envelope is required. Keeping the old shared `{}` representation would continue hiding this distinction. - Real Hopper/Ghidra, browser, or OS coverage: CLI validation was on WSL/Linux only; real providers were not exercised and are not required for this formatting change. - Package or release metadata impact: no manual version or CHANGELOG edit. The `!` title marks the compatibility change for Release Please; the migration is described above. - Security, privacy, process, or containment review: not applicable. AI disclosure: prepared with AI assistance (Claude Code); the checks above were run before opening the PR. ## Before merge - [x] This PR addresses a concrete problem or an agreed enhancement. - [x] Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff. - [x] The PR has one focused outcome and the title follows `type(scope): outcome`. - [x] Related issue is linked, or the reason for not linking one is stated above. - [x] Tests cover changed observable behavior and meaningful failure paths. - [x] Owning docs, contracts, and generated metadata are updated where needed. - [x] User-visible CLI/MCP changes include representative output. - [x] I checked the final diff for secrets, unrelated cleanup, and unsupported claims. - [x] Complete `npm run check` with the pinned Node 24.18.0/npm 11.16.0 and rerun the relevant compiled tests outside the restricted sandbox. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_019TTGAv6fe859ebbCLLfLHP --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-31 01:04:30 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#5169
No description provided.