Skip to content

refactor: converge success output through a single Emitter that owns the write#1899

Merged
sang-neo03 merged 9 commits into
mainfrom
feat/output-emitter-convergence
Jul 21, 2026
Merged

refactor: converge success output through a single Emitter that owns the write#1899
sang-neo03 merged 9 commits into
mainfrom
feat/output-emitter-convergence

Conversation

@sang-neo03

@sang-neo03 sang-neo03 commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Why

The success-output path (content-safety scan → envelope → jq → format render → write) had no single owner: it was assembled at two envelope points (RuntimeContext.emit, WriteSuccessEnvelope) and re-implemented across several format-routing functions, which had drifted (raw/Meta support, MarkRaw, the jq safety-warning). Write failures were silently swallowed on most paths, and _notice injection mutated the caller's map.

What this PR does (now)

Converges all success output onto a single command-scoped internal/output.Emitter and makes that Emitter own the write — proven byte-for-byte on the success path by a differential golden harness driven from the real legacy entry points.

Scope note. An earlier revision of this description called the PR "deliberately unwired / no production caller migrated / six production files empty diff / skip acceptance." That is no longer accurate — all success-output callers are migrated and the Emitter now owns write-failure semantics. The description below reflects HEAD.

Migrated onto the Emitter (byte-for-byte)

  • RuntimeContext.Out / OutRaw / OutFormat / OutFormatRaw / OutPartialFailure — signatures unchanged; emit / outFormat deleted.
  • WriteSuccessEnvelope — thin delegate to Emitter.Success.
  • internal/client.HandleResponse — the non-JSON structured-response presentation branch.
  • api / service pagination — per-page output via Emitter.StreamPage.

Write-ownership contract (this PR)

  • Leaf writers gained error-returning Write* cores (WriteJSON, WriteNDJSON, WriteTable, WriteCSV, WriteAlertWarning, PaginatedFormatter.WritePage); legacy Print* / FormatValue wrappers keep their exact behavior for unmigrated callers.
  • The Emitter renders to a buffer then io.Copys to stdout, so a marshal/render failure leaves stdout empty and returns a typed internal error (with cause via errors.Unwrap/errors.Is); a stdout write failure is propagated, not swallowed.
  • handleEmitterError now captures every error (not only the jq/safety branches).
  • _notice is injected into a maps.Clone of the payload — the caller's map is never mutated and an existing _notice is never overwritten.
  • jq's own typed error (validation for a bad expression, api for a runtime failure) is preserved; only genuine stdout write failures are wrapped.

Contract table (per format)

Format stdout shape envelope jq content-safety writer failure
"" / json standard Envelope yes alert in envelope; block → typed policy error, no stdout typed internal error, stdout empty
--jq filtered Envelope yes jq's own typed error preserved alert also warned to stderr (when JQSafetyWarning) typed internal error
pretty naked business data (command renderer) no alert warned to stderr typed internal error
table / csv / ndjson naked business data no alert warned to stderr typed internal error, stdout empty

Behavior changes

  • Intended fixes: write/marshal failures now propagate as typed errors with cause (was: swallowed / os.Stderr only); _notice no longer mutates the caller's map; struct/named-map payloads keep _notice on the unknown-format → JSON fallback; jq's typed error is preserved through the buffer.
  • Strictly preserved (byte-for-byte): all success-path stdout/stderr bytes across raw / ok / meta / jq / notice, every format, content-safety fail-open/fail-closed, and pagination — locked by the golden double-diff.
  • Known residuals (deferred, see Follow-ups): unknown --format still falls back to JSON instead of a typed validation error; the pretty renderer does not yet receive the scanned data; the streaming formatter advances column state before confirming the write (write failures are propagated, but the poisoned-state machine is follow-up); notice still flows via the global PendingNotice provider and JQSafetyWarning is still caller-selected.

Oracle provenance

  • Legacy base: 4a56748bfa941ff0ee0bfec92e65acac427732b0.
  • Golden fixtures are frozen from the legacy entry points; the differential harness runs a double diff (Emitter vs golden AND the migrated RuntimeContext.Out* vs golden) so it cannot self-reference.
  • The golden self-update env vars were removed; regeneration is allowed only from the fixed base, never from the current SUT.

Test Plan

  • go build ./... / go vet ./... clean; gofmt clean
  • GOTOOLCHAIN=go1.25.4 go test -race -count=1 ./internal/output ./shortcuts/common ./cmd/api ./cmd/service
  • golden double-diff byte-for-byte (success path frozen)
  • normative contract suite: err==nil ⇒ fully written, marshal failure → typed internal error + empty stdout, writer failure preserves errors.Is cause, Emitter does not mutate the caller map, existing _notice not overwritten, notice read at most once, jq runtime error preserves its typed error
  • write-failure matrix: OutRaw propagates (flipped from the old ignore test), runShortcut non-zero exit on a failing writer, api/service mid-page write failure stops further pages
  • acceptance-reviewer — running (owed now that production callers are migrated)

Follow-ups (out of scope; separate PRs, one verifiable invariant each)

  1. Strict typed Format: unknown format → typed --format validation error, reject illegal combos (jq+table, raw+csv). (user-visible; needs changelog)
  2. PrettyRenderer(w, data, color) receives the scanned data.
  3. Streaming: commit formatter state only after a successful page write; poison the stream on failure; unify the format-lock.
  4. Remove the global PendingNotice; notice only in Envelope.Notice; drop caller-selected JQSafetyWarning (always warn).
  5. Consolidate the duplicated api/service pagination; migrate remaining direct-PrintJson paths (binary/download metadata, event consume) and the pretty dry-run safety bypass; remove the WriteSuccessEnvelope delegate once callers are migrated.

Reviewer guide

contract/golden (emitter_contract_test.go, emitter_legacy_compat_test.go) → Emitter (emitter.go) → RuntimeContext adapter + handleEmitterError → api/service pagination.

Related Issues

N/A (follow-ups tracked out-of-band, listed above).

@sang-neo03
sang-neo03 requested a review from liangshuo-1 as a code owner July 15, 2026 07:05
@github-actions github-actions Bot added the size/L Large or sensitive change across domains or core paths label Jul 15, 2026
@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Introduces output.Emitter to centralize success, partial-failure, and paginated output across JSON, jq, pretty, formatted, safety, notices, and output validation. Integrates it into runtime, client, API, and service paths with legacy-parity and behavior tests.

Changes

Output emission

Layer / File(s) Summary
Emitter contracts and dispatch
internal/output/emitter.go
Defines emitter configuration and options, wires dependencies, validates output configuration, and dispatches success and partial-failure output by format.
Emission and pagination paths
internal/output/emitter.go
Implements envelope, pretty, formatted, notice-aware, and paginated emission with safety scanning, warnings, jq restrictions, and stable page formats.
Emitter integration
cmd/api/api.go, cmd/service/service.go, internal/client/response.go, internal/output/envelope_success.go, shortcuts/common/runner.go
Routes streaming, response, envelope, and runtime-context output through the centralized emitter.
Legacy parity and error validation
internal/output/emitter_legacy_diff_test.go, internal/output/testdata/*
Compares emitter output and typed errors with legacy paths across formats, safety modes, notices, pagination, pretty rendering, and writer failures.
Command and response validation
cmd/api/api_paginate_test.go, cmd/service/service_paginate_test.go, internal/client/response_test.go, shortcuts/common/*_test.go
Tests pagination behavior, exact response formatting, safety errors, jq validation, and writer-error handling.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Suggested reviewers: liangshuo-1

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.23% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: consolidating success output through a single Emitter that owns writes.
Description check ✅ Passed Covers motivation, scope, test plan, and related issues with clear change details; headings differ from the template but required info is present.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/output-emitter-convergence

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jul 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 69.00270% with 115 lines in your changes missing coverage. Please review.
✅ Project coverage is 75.03%. Comparing base (4a56748) to head (11d4560).
⚠️ Report is 15 commits behind head on main.

Files with missing lines Patch % Lines
internal/output/emitter.go 76.51% 23 Missing and 12 partials ⚠️
internal/output/format.go 51.35% 15 Missing and 3 partials ⚠️
internal/output/print.go 45.16% 15 Missing and 2 partials ⚠️
internal/output/csv.go 40.74% 10 Missing and 6 partials ⚠️
internal/output/table.go 48.38% 10 Missing and 6 partials ⚠️
shortcuts/common/runner.go 78.18% 10 Missing and 2 partials ⚠️
internal/output/emit.go 75.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1899      +/-   ##
==========================================
+ Coverage   75.02%   75.03%   +0.01%     
==========================================
  Files         894      901       +7     
  Lines       94294    95321    +1027     
==========================================
+ Hits        70746    71528     +782     
- Misses      18137    18300     +163     
- Partials     5411     5493      +82     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown

🚀 PR Preview Install Guide

🧰 CLI update

npm i -g https://pkg.pr.new/larksuite/cli/@larksuite/cli@11d456096041344c1951c9f1d08ecdcfe7ebb8ea

🧩 Skill update

npx skills add larksuite/cli#feat/output-emitter-convergence -y -g

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
internal/output/emitter.go (2)

203-222: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate "unknown format → warn & fall back to json" logic; also silently coerces an unsupported option.

The block at Lines 212-215 is identical to StreamPage's block at Lines 130-133 — worth extracting into a shared helper (e.g., resolveFormat(errOut io.Writer, raw string) Format) to avoid drift between the two paths.

Separately, per the coding guideline for **/*.go ("never silently fall back, coerce unsupported values... Return a typed validation error when a requested option cannot be honored"), silently rewriting an unrecognized Format to JSON (rather than a typed validation error) is exactly the pattern the guideline calls out. This appears intentional here for byte-identical legacy parity (per the PR's stated goal) and the emitter isn't wired to any production caller yet, so I'm not blocking on it — but this should be revisited (return a typed error instead of silently falling back) before this path becomes the production code path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/output/emitter.go` around lines 203 - 222, Extract the duplicated
unknown-format warning and JSON fallback used by emitFormatted and StreamPage
into a shared resolveFormat helper, preserving both paths’ current behavior and
warning text. Leave the unsupported-format fallback unchanged for now, but keep
the helper boundary suitable for later replacement with a typed validation
error.

Source: Coding guidelines


224-241: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

New loose-map type + in-place mutation of caller-supplied data.

emitterDataMap is a fresh loose-map type introduced solely to dodge PrintJson's type switch, and m["_notice"] = notice mutates the caller's own map in place — any code retaining a reference to data after this call will see an injected _notice key it never added. As per coding guidelines, **/*.go files should "Parse map[string]interface{} into typed structs at boundaries... Do not introduce new loose-map code."

This appears to replicate pre-existing FormatValue JSON-branch behavior for byte-parity, so I'm not asking for behavior change now — but consider copying into a local map (maps.Clone or manual copy) before injecting _notice, so the emitter never mutates caller-owned data as a side effect.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/output/emitter.go` around lines 224 - 241, Update
Emitter.printLegacyDataJSON to avoid mutating the caller-supplied map when
adding _notice: clone the map into a local copy before injection, while
preserving the existing JSON output and PrintJson behavior. Remove the need for
the emitterDataMap loose-map workaround if the copied data can be passed through
the established typed path.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/output/emitter_legacy_diff_test.go`:
- Around line 434-465: The TestEmitterStreamPageMatchesPaginationLegacyOracle
cases must cover the opts.JQ != "" branch. Add a jq oracle case that supplies a
non-empty JQ expression, then validate the resulting error with errors.As and
assert the ValidationError.Param field directly; do not rely solely on
assertEquivalentError, which only compares ProblemOf.

In `@internal/output/emitter.go`:
- Around line 174-180: Update the Raw no-JQ branch in the emitter’s Success
method to return the error from enc.Encode(env) instead of discarding it, so
output write failures propagate to callers. Add coverage in
TestEmitterPropagatesOutputError for Raw: true with an empty JQ value and a
failing writer.

---

Nitpick comments:
In `@internal/output/emitter.go`:
- Around line 203-222: Extract the duplicated unknown-format warning and JSON
fallback used by emitFormatted and StreamPage into a shared resolveFormat
helper, preserving both paths’ current behavior and warning text. Leave the
unsupported-format fallback unchanged for now, but keep the helper boundary
suitable for later replacement with a typed validation error.
- Around line 224-241: Update Emitter.printLegacyDataJSON to avoid mutating the
caller-supplied map when adding _notice: clone the map into a local copy before
injection, while preserving the existing JSON output and PrintJson behavior.
Remove the need for the emitterDataMap loose-map workaround if the copied data
can be passed through the established typed path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9964bff4-3fb6-48e0-b7c2-2ebece4a3bba

📥 Commits

Reviewing files that changed from the base of the PR and between 49b4ccc and 84d1f97.

📒 Files selected for processing (2)
  • internal/output/emitter.go
  • internal/output/emitter_legacy_diff_test.go

Comment thread internal/output/emitter_legacy_compat_test.go
Comment thread internal/output/emitter.go Outdated
Introduce a leaf Emitter in internal/output that composes the existing
output primitives (content-safety scan, envelope, jq, format rendering,
notice) behind a single command-scoped port. The emitter is unwired: no
production caller is migrated, so CLI output stays byte-for-byte unchanged.

A differential test harness drives the real legacy entry points
(RuntimeContext.Out/OutRaw/OutFormat/..., WriteSuccessEnvelope and the
pagination formatter) and asserts byte-identical stdout/stderr plus typed
errors, locking behavior before later slices migrate callers.
@sang-neo03
sang-neo03 force-pushed the feat/output-emitter-convergence branch from 84d1f97 to 001e2f8 Compare July 15, 2026 07:26
@sang-neo03

Copy link
Copy Markdown
Collaborator Author

Both CodeRabbit findings are addressed in 001e2f8:

  • Critical (raw JSON path in emitter.go): the encoder error is now returned (return enc.Encode(env)) instead of discarded, so Success no longer reports success on a failed write. Locked by a new TestEmitterRawJSONPropagatesWriteError.
  • Minor (StreamPage --jq): added TestEmitterStreamPageRejectsJQ, which asserts the *errs.ValidationError and its --jq param via errors.As.

The only remaining red check is e2e-live, which is failing on upstream HTTP 429 rate-limiting of the shared test account (unrelated to this change, which adds two unwired files and touches no live API path).

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd emitter.go internal/output --exec sed -n '1,260p' {}

Length of output: 7680


Confirmed both fixes in the current emitter.go:

  • The raw-JSON envelope path now return enc.Encode(env) instead of discarding the error, so a write failure will surface as an error from Success.
  • StreamPage returns a *errs.ValidationError with --jq when opts.JQ != "", before any pagination state is touched.

Both look correctly implemented and match the description. Kicking off a full review now to check the rest of the diff, including the new test file.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

- split Emitter.Success/PartialFailure and drop EmitOptions.OK so a
  missing ok flag can no longer silently emit ok:false
- give StreamPage its own StreamOptions (format + pretty) instead of
  reusing EmitOptions, making "jq needs aggregation" a compile-time fact
- pin the Emitter jq-error contract (returns error, writes no stderr);
  the caller adapter re-emits the legacy stderr line on migration
- add in-package tests driving the real apiPaginate/servicePaginate over
  a mock transport: multi-page aggregation, empty-result fallback,
  MarkRaw handling, and the business-error raw-response red line

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/api/api_paginate_test.go`:
- Around line 36-52: Update the test-client helpers in
cmd/api/api_paginate_test.go lines 36-52 and
cmd/service/service_paginate_test.go lines 36-52 to use cmdutil.TestFactory(t,
config) instead of constructing the client directly, and set
LARKSUITE_CLI_CONFIG_DIR to t.TempDir() in each suite to isolate configuration
state.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ca931a8c-643b-4d7c-ab6a-4e016c330a6e

📥 Commits

Reviewing files that changed from the base of the PR and between 001e2f8 and ac4bdca.

📒 Files selected for processing (4)
  • cmd/api/api_paginate_test.go
  • cmd/service/service_paginate_test.go
  • internal/output/emitter.go
  • internal/output/emitter_legacy_diff_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/output/emitter.go

Comment thread cmd/api/api_paginate_test.go Outdated
Replace the hand-rolled RoundTripper + APIClient construction in the
apiPaginate/servicePaginate tests with cmdutil.TestFactory and its
httpmock.Registry, and isolate LARKSUITE_CLI_CONFIG_DIR to t.TempDir(),
matching the repo's standard HTTP-mocked test convention. Assertions and
coverage (multi-page aggregation, empty-result fallback, MarkRaw, and the
business-error raw-response red line) are unchanged.
Migrate the success-output surfaces onto internal/output's Emitter,
byte-for-byte identical (proven by frozen golden diffs and the real
paginate/HandleResponse tests):

- RuntimeContext.Out/OutRaw/OutFormat/OutFormatRaw/OutPartialFailure now
  build an Emitter and call Success/PartialFailure; emit and outFormat are
  removed. An adapter maps the returned error back to the legacy
  outputErrOnce / jq-error stderr / exit-code behavior.
- WriteSuccessEnvelope degrades to a thin Emitter.Success delegate; its 8
  callers are unchanged.
- apiPaginate/servicePaginate stream pages via Emitter.StreamPage; the
  aggregate and business-error raw-response branches are untouched.
- HandleResponse routes its non-JSON structured-response branch through
  Emitter.Success.

Frozen golden fixtures replace the runtime legacy oracles so the
differential harness cannot go self-referential after migration.
@github-actions github-actions Bot added size/XL Architecture-level or global-impact change and removed size/L Large or sensitive change across domains or core paths labels Jul 18, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@shortcuts/common/runner.go`:
- Around line 680-692: Update RuntimeContext.handleEmitterError in
shortcuts/common/runner.go:680-692 to record every non-nil emitter error in
outputErr while only printing the error when JqExpr is active. Ensure OutRaw at
shortcuts/common/runner.go:718-724 preserves the returned writer error in
outputErr, and update shortcuts/common/runner_jq_test.go:124-135 to assert that
the writer error is retained rather than discarded.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1cf55f0a-1e99-40a0-b999-7dfcfb8031f4

📥 Commits

Reviewing files that changed from the base of the PR and between 8c88a3c and 4de1852.

📒 Files selected for processing (13)
  • cmd/api/api.go
  • cmd/api/api_paginate_test.go
  • cmd/service/service.go
  • cmd/service/service_paginate_test.go
  • internal/client/response.go
  • internal/client/response_test.go
  • internal/output/emitter_legacy_diff_test.go
  • internal/output/envelope_success.go
  • internal/output/testdata/runtime_context_legacy.golden.json
  • internal/output/testdata/write_success_envelope_legacy.golden.json
  • shortcuts/common/runner.go
  • shortcuts/common/runner_contentsafety_test.go
  • shortcuts/common/runner_jq_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • cmd/api/api_paginate_test.go

Comment thread shortcuts/common/runner.go
…back

printLegacyDataJSON now normalizes via toGeneric first (matching FormatValue), so a struct / named-map payload retains its injected _notice on the unknown-format -> JSON fallback rather than dropping it silently. Add a regression test that fails against the pre-fix path.
Route every Emitter stdout path through a render-to-buffer-then-copy helper so a marshal/render failure leaves stdout empty and surfaces a typed internal error (with cause), and a stdout write failure is propagated instead of silently swallowed. Leaf writers gain error-returning Write* cores; the legacy Print*/FormatValue wrappers keep their exact behavior for unmigrated callers.

- handleEmitterError now captures every error, not only the jq/safety branches; flip OutRaw's write-error test to assert propagation.
- Clone the map before injecting _notice so a caller's payload is never mutated and an existing _notice is never overwritten.
- Preserve jq's own typed error (validation/api) on a bad expression or runtime failure; only wrap genuine stdout write failures.
- Split tests: normative emitter_contract_test.go vs frozen emitter_legacy_compat_test.go (base SHA recorded, self-update env vars removed).
@sang-neo03 sang-neo03 changed the title refactor: add output emitter contract and differential harness refactor: converge success output through a single Emitter that owns the write Jul 20, 2026
- Move the base-SHA note below the copyright header in the renamed legacy-compat test so the license-header check sees a valid header at the top.
- Route the leaf wrappers' marshal/format stderr messages through a single legacyStderrf helper (one //nolint:forbidigo) instead of bare os.Stderr, preserving exact legacy behavior for unmigrated direct callers while passing forbidigo; drop the now-unused os imports.
Align FormatAsCSV/FormatAsCSVPaginated and FormatValue/FormatPage's CSV branch with the other leaf wrappers: report only marshal failures, swallow write failures. Previously they emitted a 'csv write error' for the (empty) line and the JSON-fallback write failures that the pre-refactor code ignored, and mislabeled a JSON write failure as a CSV one. Failure-path only; success output is unchanged (golden double-diff still byte-for-byte).
@sang-neo03
sang-neo03 merged commit 4c1a92c into main Jul 21, 2026
25 checks passed
@sang-neo03
sang-neo03 deleted the feat/output-emitter-convergence branch July 21, 2026 06:32
@liangshuo-1 liangshuo-1 mentioned this pull request Jul 21, 2026
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature size/XL Architecture-level or global-impact change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants