Repository navigation
feat(cli): uniform JSON envelope with structured errors and changed reporting - #608
Conversation
…eporting
Wrap the --json / --output of the orchestration commands (release, gate,
standing-pr) in one uniform envelope so humans, CI, and agents share a single
output contract:
{ schemaVersion, status, changed, data, warnings, errors }
- data carries each command's payload verbatim (VersionOutput etc.) — the
envelope wraps, never replaces, so the manifest-compat invariant holds.
- structured error codes replace prose-only failures in JSON mode, each with a
category and a conservative retryable flag; the process exit code maps to the
error code (config -> 2, llm -> 5, ...).
- changed distinguishes real work from a no-op: dry runs, skip-if-published,
and the read-only gate all report changed: false.
- stream discipline: only the envelope is written to stdout, all diagnostics go
to stderr, so parsing stdout as JSON is always safe.
- the Action unwraps the envelope in parseReleaseOutput, so every existing
Action output is derived unchanged.
First PR for the #544 epic. Deliberately scoped to the orchestration commands;
follow-ups extend the envelope to the version|notes|publish pipe commands (with
input unwrapping to preserve the pipe) and expose dry-run as a structured plan.
Claude-Session: https://claude.ai/code/session_01K6CmYRXTZQYs4MNo6AETxT
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Standing release PR: #614 · v0.41.3 queued (4 packages) · open 9m · ✅ ready to merge Release Preview — v0.41.2 → v0.42.0
These changes will be added to the release PR (#614) when merged: Changelogmonorepo 0.41.2 → 0.42.0
After merge — predicted release
Updated automatically by ReleaseKit |
…sumers The uniform --json envelope nests each result under `data`, so the e2e helper run_cli_json and the package-test release-output validator — both of which read `.versionOutput` at the top level — saw it move and failed. Unwrap the envelope to its data payload in both (tolerating non-enveloped/legacy output), so the existing `.versionOutput.*` reads keep working. Claude-Session: https://claude.ai/code/session_01K6CmYRXTZQYs4MNo6AETxT Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
_release.reusable.yml runs the release and standing-pr publish CLI directly and reads `.versionOutput` from its stdout — which the uniform envelope now nests under `data`. Without unwrapping, has_changes would read false and no tags would push. Unwrap RELEASE_OUTPUT to its data payload at both capture points (tolerating non-enveloped output), so the downstream reads and release-output.json are unchanged. Claude-Session: https://claude.ai/code/session_01K6CmYRXTZQYs4MNo6AETxT Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pre-flight CLI arg checks (--stable with --prerelease, --draft with --from-draft, invalid --from-draft, invalid --pr) exited via console.error + process.exit before the enveloped path, so in --json / --output mode they produced no envelope (empty stdout, no output file) — breaking the uniform error contract for ordinary input errors. The --pr check also exited with GENERAL_ERROR instead of INPUT_ERROR. Add a typed InputError (code INPUT_ERROR) in core and a failInput() helper that emits a structured INPUT_ERROR envelope, logs to stderr, and exits with the input-error code; route all pre-flight validations through it. Claude-Session: https://claude.ai/code/session_01K6CmYRXTZQYs4MNo6AETxT Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Addressed both Greptile P1 findings in 2bc0a4b:
Fix: a typed |
…error on unresolved PR Three follow-ups to the envelope contract, from review of #608. `standing-pr publish` derived `changed` from `versionOutput.updates`, which comes from the merged PR's manifest and is populated on every run — the versions were already computed and landed on `main` by the merge. A re-run where every package was skipped as already-published still reported `changed: true`, the exact case the docs advertised it for. `publishDidChange` reads `publishOutput` instead. Release tags are created before the pipeline, `git.pushed` is set for any push attempt, and the GitHub-release stage reports success for an already-existing release, so registry results are the only honest signal — same `success && !skipped && !alreadyPublished` predicate the verify stage uses. `runStandingPRPublish` logged and returned null when it could not resolve a PR to publish (no GitHub context, no merged standing PR, unreadable or numberless event payload). Those now throw. Returning null made the envelope assert `status: "success"` on a misconfiguration — the one field an agent branches on. Null stays the answer for the genuine no-ops: pull_request:closed fires for every closed PR, so a non-release head ref or an unmerged PR must remain a quiet skip. `publishFromManifest` has no no-op outcome at all once a PR is named, so it now returns `ReleaseOutput` rather than `ReleaseOutput | null`. The docs claimed every `--json` command emits an envelope; `version`, `notes`, and `publish` do not, deliberately — they pipe JSON between processes and enveloping them means teaching the consuming side to unwrap. Scoped the claim to the orchestration commands and called out the exception. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FADRmu9uhtCk3StMoqwV27
…747) * fix(notes): drop an entry's own refs from LLM-rewritten descriptions The model sometimes copies an entry's reference into its rewrite, and templates then append it again: "(#608) (#608)". The enhance tasks now remove the entry's own refs from the description they return. Closes #719 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(notes): assert enhance-and-categorize strips duplicate refs Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com>
First PR for the #544 epic (agent-grade CLI contract). Establishes the uniform
--jsonenvelope on the orchestration commands so humans, CI, and agents share one output surface.The envelope
Every
--json/--outputresult fromrelease,gate, andstanding-pris now wrapped:{ "schemaVersion": 1, // contract version; stable across minor releases "status": "success", // "success" | "error" "changed": true, // did work, vs. already in the desired state "data": { /* … */ }, // command payload verbatim (VersionOutput, gate result); null on error "warnings": [{ "code": "…", "message": "…" }], "errors": [{ "code": "…", "category": "…", "retryable": false, "message": "…" }] }What's in this PR (mapped to the epic's scope)
@releasekit/coremodule (successEnvelope/errorEnvelope), applied viaemitResult/emitErrorin the release, gate, and standing-pr commands.datawraps the payload, never replaces it —release --jsonstill exposesVersionOutputatdata.versionOutput, so the manifest-compat invariant holds.toEnvelopeErrormaps thrown errors to{ code, category, retryable, message }.retryableis conservative (onlytruefor known-transient failures, e.g. anLLMErrorthat classified a timeout/429/5xx), so an agent never retries an unknown failure.exitCodeForErrormaps the code to the process exit code (config → 2,llm → 5, …).changedreporting — a dry run is neverchanged;standing-pr publishreportschanged: falseon skip-if-published;gateis read-only (changed: false).console.error), so parsing stdout as JSON is always safe.run-action.mjs'sparseReleaseOutputunwraps the envelope'sdata, so every existing Action output (tags,has-changes,version-output,should-release, …) is derived unchanged. Legacy un-enveloped output is tolerated.docs/cli.mdwith the shape, the error-code/exit-code table, and the stability guarantee.Deliberately deferred (follow-up PRs within #544)
version | notes | publishpipe commands. These pipe JSON between processes, so enveloping their output requires the consuming side to unwrap on input — a self-contained change kept separate to avoid touching the inter-process contract here. This PR leaves that pipe untouched (those packages don't import release'semitResult).dataplan.Testing
envelope.spec.ts(core) — builders, error mapping, exit-code mapping.emitResult.spec.ts+release-command.spec.tsfor the envelope shape; newemitErrorcoverage.parseReleaseOutputtests for envelope unwrap (success + error envelopes) alongside the legacy path.typecheck+biome check+ eslint clean.Part of #544.
Greptile Summary
This PR establishes a uniform
--jsonoutput envelope for therelease,gate, andstanding-prorchestration commands, replacing bare JSON payloads with a versioned{ schemaVersion, status, changed, data, warnings, errors }wrapper shared by humans, CI, and agents.@releasekit/coreenvelope module —successEnvelope/errorEnvelopebuilders,toEnvelopeErrorfor structured error mapping, andexitCodeForErrorfor process exit routing; all wired intoemitResult/emitError/failInputin the release, gate, and standing-pr commands.console.error+process.exitguards in all three commands are replaced byfailInput, so--json/--outputcallers always receive a structuredINPUT_ERRORenvelope even on pre-flight validation failures.changedsemantics per command — dry runs alwaysfalse;standing-pr publishinspects registry results viapublishDidChange;gateis hardwired tofalse;runStandingPRPublishnull-return paths converted to throws so they surface asstatus:\"error\"rather than silent no-ops.run-action.mjs'sparseReleaseOutputunwraps.datatransparently; the workflow YAML andhelpers.shadd the same jq unwrap at their respective layers; legacy un-enveloped output is tolerated at every layer.Confidence Score: 5/5
Safe to merge — the envelope contract is additive, all three command layers consistently emit the uniform wrapper, and the input-validation fast-exit paths that previously bypassed the JSON channel are now correctly routed through failInput.
The two previously flagged validation bypass issues are both resolved: bare process.exit guards in release-command and standing-pr-command now go through failInput, which emits an error envelope before exiting. The publishFromManifest and runStandingPRPublish null-return paths are converted to throws, eliminating the silent success no-op risk. The categoryForCode derivation, publishDidChange predicate, and action-layer unwrapping are all correct and covered by the new test suite.
Files Needing Attention: No files require special attention.
Important Files Changed
Reviews (5): Last reviewed commit: "fix(cli): derive standing-pr publish `ch..." | Re-trigger Greptile