Skip to content

Make --fields preserve output format, empty-result cardinality, and body recovery metadata #5190

Description

@Widthdom

Summary

Three current --fields failures arise in the same bounded-response projection pipeline:

  1. an empty graph result becomes one phantom {} row;
  2. --fields silently overrides an explicit non-JSON format;
  3. selecting body_content drops the metadata needed to know it was truncated and how to continue.

These belong in one PR because --fields activates JsonEnvelopeWrapper, which owns primary collection extraction, output-mode wrapping, and projected companion fields.

Environment

  • origin/main: c642bc4c3dda4ef659df8e6b699e10f61da3c836
  • cdidx v1.44.3, locally built
  • fresh and complete repository index
  • full net8.0 and net9.0 suites pass

Reproduction

A. Empty graph projection fabricates a row

dotnet ./src/CodeIndex/bin/Debug/net8.0/cdidx.dll callees \
  DefinitelyMissingSymbol_42 --exact-name \
  --db .cdidx/codeindex.db --json \
  --fields path,caller_name,callee_name,first_line

Actual:

{
  "metadata": {
    "result_count": 1,
    "returned_count": 1,
    "total_count": 1
  },
  "results": [{}]
}

Without --fields, the same query correctly returns zero callees. With --strict-not-found, the command exits 2 but still reports the phantom result.

B. Explicit CSV is silently replaced by JSON

dotnet ./src/CodeIndex/bin/Debug/net8.0/cdidx.dll definition \
  GetCompletionScript --exact-name \
  --path src/CodeIndex/Cli/ConsoleCompletionRenderer.cs \
  --db .cdidx/codeindex.db --format csv --limit 1 \
  --fields path,name

The command exits 0 and emits a JSON envelope. In contrast, --json --format csv correctly returns a usage error.

C. Body projection loses truncation/recovery facts

dotnet ./src/CodeIndex/bin/Debug/net8.0/cdidx.dll definition \
  ConsoleCompletionRenderer --exact-name --body --limit 1 \
  --db .cdidx/codeindex.db --json \
  --fields path,name,kind,line,body_content

body_content ends mid-expression at .Append("bat"), but the row omits body_content_truncated, next-start/range data, truncation reasons, and recovery command. Without --fields, the response correctly reports body_content_truncated: true and how to continue.

Expected behavior

  • Empty source collections remain empty after projection.
  • Explicit incompatible output selectors fail with E010_USAGE_ERROR; --fields does not silently replace --format csv.
  • Selecting bounded body content automatically retains mandatory truncation, range, omission, and recovery metadata.
  • Counts remain derived from the real pre-projection collection.

Root cause and evidence

  • src/CodeIndex/Cli/JsonEnvelopeWrapper.Bounded.cs:87-106 auto-wraps whenever --fields is present before preserving or rejecting an explicit non-JSON formatter.
  • JsonEnvelopeWrapper.Bounded.cs:1043-1120 does not authoritatively unwrap the empty callers/callees collection, so the whole zero-result payload falls through as one row and projects to {}.
  • JsonEnvelopeWrapper.Bounded.cs:1298-1324 copies only directly requested fields and aliases, with no dependency closure for bounded body metadata.

Focused PR scope

One projection-contract PR should:

  1. resolve and validate output mode before auto-wrapping;
  2. reject --fields with incompatible non-JSON formats unless formally supported;
  3. authoritatively extract empty and non-empty references/callers/callees collections;
  4. define central companion-field groups for bounded bodies;
  5. derive counts from the real source collection;
  6. reject unmappable projections instead of fabricating {};
  7. add a bilingual changelog fragment.

Non-goals

  • Do not add CSV column projection in this PR.
  • Do not redesign every JSON output shape.
  • Do not change body caps or recovery paging semantics.
  • Do not broadly rewrite unrelated serializers.

Implementation guidance and cautions

  • Model projection dependencies centrally rather than copying body field lists per command.
  • Distinguish an empty collection, a non-empty row lacking one field, and an envelope that was not unwrapped.
  • Preserve trust/completeness/recovery fields required to interpret selected data.
  • Keep byte-budget accounting truthful after adding mandatory companions.
  • If the minimum safe projection cannot fit a byte cap, return the bounded-response error rather than removing recovery metadata.
  • Apply equivalent rules to definition, references, callers, callees, inspect, and impact body rows where applicable.

Required tests

Use a matrix covering:

  • empty/non-empty references, callers, and callees;
  • projected/unprojected envelope, compact, and array/stream forms where supported;
  • explicit CSV/TSV/qf/LSP/SARIF conflicts;
  • full, line-truncated, byte-truncated, and unavailable bodies;
  • body_content alone, --fields all, aliases, and unknown fields;
  • byte budgets, strict-not-found behavior, counts, and cursors.

Acceptance criteria

  • A missing graph target produces results: [], returned count 0, and total count 0.
  • --format csv --fields ... never emits JSON; the current preferred contract is a clear usage error.
  • Truncated body_content always includes a truthful truncation flag, next range, and recovery instruction.
  • No accepted projection fabricates {} from an empty envelope.
  • Counts, cursors, and byte-limit metadata remain truthful.

Regression history

No current issue covers these three failures in their shared --fields pipeline.

Metadata

Metadata

Assignees

No one assigned

    Labels

    .NETPull requests that update .NET codebugSomething isn't workingdocumentationImprovements or additions to documentationtests

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions