fix(cli): write an artifact --out path exactly as given

AS
8h ago
+318-116

Why

Artifact commands (image, audio and video outputs) chose the file extension from the returned MIME type and changed the caller's --out path to match:

  • --out photo.png with a JPEG response wrote photo.jpg. A script that then opened photo.png found nothing. In agent mode nothing said so: the only signal was a different path in the result.
  • Any dot suffix counted as an extension, so --out render.v1 wrote render.png and lost the .v1.

--out is the caller's choice of destination, so the CLI should write there.

What changed

  • artifact.go.stmpl:
    • An explicit --out file path that ends in a recognized media extension (png, jpg, wav, mp4, …) is written exactly as given, even if the returned MIME type implies another extension.
    • A mismatch is reported instead of corrected. Human output gets one stderr line, Note: photo.png contains image/jpeg content; its extension does not match. The result envelope gets extension_mismatch: true, so machine modes keep stderr silent and still get a signal.
    • A path without a recognized media extension still gets the returned extension appended: photo → photo.jpg, and render.v1 → render.v1.png (the suffix is kept).
    • Unchanged: matching extensions, --out pointing at a directory, and the default file name.
  • artifact_test.go.stmpl: the rename test is now TestArtifact_OutKeepsRequestedExtension. The agent-mode test checks the path is kept, stderr is silent and the envelope carries extension_mismatch. The append test covers photo, photo.v1 and release-1.2.
  • CLI README updated; review CLI SDK regenerated.

This changes documented behavior for a mismatched --out extension. Callers that read the result's path are unaffected; only a caller relying on the renamed file sees a difference.

Testing

  • TARGET=review make build-cli, then in zSDKs/sdk-cli: go test ./tests/ -run 'TestArtifact|TestAsync', go test ./internal/..., go vet ./internal/output/: pass.
  • make lint: pass.
  • Not run: the full primary CLI suite.

Public-safety check

  • This change contains no credentials, customer documents, private repository URLs, private filesystem paths, or unredacted private logs.
  • Title, body, comments, and commit messages name no customers or customer-derived identifiers, private paths or trackers, or workflow provenance, and are understandable without private context (.claude/skills/public-repo-communication/SKILL.md).
  • Generated fixtures and review SDK changes are public-safe.
  • I reviewed git diff --check.