Add ZIO module #49

Merged
mprihoda merged 19 commits from feature/zio-module into main 2026-06-16 20:47:48 +00:00
mprihoda commented 2026-06-16 20:14:59 +00:00 (Migrated from github.com)

Summary

Adds a third runtime adapter — zio — to the SDK, alongside direct (Ox) and effectful (cats-effect/fs2), all thin wrappers over the shared core module. The ZIO module exposes the same surface adapted to ZIO idioms: a typed error channel (IO[CLIError, A] / ZStream[Any, CLIError, Message] / ZIO[Scope, CLIError, Session]), ZStream-based query streaming, and a scoped multi-turn Session backed by a long-lived CLI process.

The build now has four modules: core, direct, effectful, zio. The zio module depends only on core — no domain logic is duplicated; it implements core's tagless-final ConversationLogIndex[F]/ConversationLogReader[F] with zio.Task.

What's included

  • Public API (ClaudeCode): ask, query, querySync, queryResult, session; trait Session (send/stream/sessionId); a package object re-exporting core model + the full CLIError ADT + log types for single-import use.
  • Internals: ProcessManager (streams the CLI via zio-process, scoped resources, finalizer ordering, timeouts), SessionProcess (long-lived process, stdin queue, multi-turn routing, deterministic teardown), CLIDiscovery, JsonParser, FileSystemOps.
  • Log readers: ZioConversationLogIndex / ZioConversationLogReader.
  • Tests: unit + integration (mock CLI scripts) + a gated E2E test (ANTHROPIC_API_KEY).
  • Docs updated (README.md, ARCHITECTURE.md, CLAUDE.md).

Library versions

Verified against Maven Central before opening this PR — all on the latest published releases:

Artifact Version
zio / zio-streams / zio-test / zio-test-sbt 2.1.26
zio-process 0.8.0

Review & fixes

The branch was put through the multi-skill code review (style, testing, security, scala3, composition, architecture, zio, api, repository). Contested "critical" findings were verified empirically; three were false positives:

  • ZStream.takeUntil is inclusive (equivalent to fs2 takeThrough) — the ResultMessage is not dropped.
  • os-lib's / rejects path separators in non-literal segments (throws InvalidSegment) — no sessionId path traversal.
  • The claimed send "silent prompt loss" was wrong: offer on a shut-down unbounded ZIO queue interrupts, it does not return false.

Confirmed findings were then fixed under TDD:

  • send to a released session now surfaces a typed SessionProcessDied (guards on stdinQueue.isShutdown) instead of an interruption — matching effectful's parity (only direct, with explicit close(), uses SessionClosedError).
  • validateConfiguration surfaces a real error on an I/O fault instead of mapping every failure to "directory does not exist"; the existence/directory classification is extracted into a pure, directly-tested cwdError.
  • Log-index listings are guarded with os.isFile (matching direct), so a directory named *.jsonl no longer produces a phantom entry.
  • CLIDiscovery PURPOSE comment now describes behavior, not implementation.
  • Test coverage filled to sibling parity: cwd-convenience methods + env factory, infallible parseJsonLine, log-index decoy/meta-sidecar edge cases, and integration tests for malformed-JSON-mid-turn, session-id refresh from a ResultMessage, and send-to-dead → SessionProcessDied.

An adversarial verification pass over the diff surfaced one real gap (an untested listSubAgents guard branch), now closed with a decoy-directory regression test (confirmed red without the guard).

Test status

All gates green: __.compile 584/584 (no warnings), __.fix --check 279/279, __.checkFormat, unit __.test 526/526, zio.itest 244/244.

Note: a full __.itest run intermittently fails one pre-existing timing/concurrency test in the direct module (ProcessManagerTest.should cleanup resources during concurrent operations with mixed failures) — it passes in isolation and is unrelated to this branch (which touches only zio/).

🤖 Generated with Claude Code

## Summary Adds a third runtime adapter — **`zio`** — to the SDK, alongside `direct` (Ox) and `effectful` (cats-effect/fs2), all thin wrappers over the shared `core` module. The ZIO module exposes the same surface adapted to ZIO idioms: a typed error channel (`IO[CLIError, A]` / `ZStream[Any, CLIError, Message]` / `ZIO[Scope, CLIError, Session]`), `ZStream`-based query streaming, and a scoped multi-turn `Session` backed by a long-lived CLI process. The build now has four modules: `core`, `direct`, `effectful`, `zio`. The `zio` module depends only on `core` — no domain logic is duplicated; it implements `core`'s tagless-final `ConversationLogIndex[F]`/`ConversationLogReader[F]` with `zio.Task`. ## What's included - **Public API** (`ClaudeCode`): `ask`, `query`, `querySync`, `queryResult`, `session`; trait `Session` (`send`/`stream`/`sessionId`); a package object re-exporting core model + the full `CLIError` ADT + log types for single-import use. - **Internals**: `ProcessManager` (streams the CLI via `zio-process`, scoped resources, finalizer ordering, timeouts), `SessionProcess` (long-lived process, stdin queue, multi-turn routing, deterministic teardown), `CLIDiscovery`, `JsonParser`, `FileSystemOps`. - **Log readers**: `ZioConversationLogIndex` / `ZioConversationLogReader`. - **Tests**: unit + integration (mock CLI scripts) + a gated E2E test (`ANTHROPIC_API_KEY`). - Docs updated (`README.md`, `ARCHITECTURE.md`, `CLAUDE.md`). ## Library versions Verified against Maven Central before opening this PR — all on the latest published releases: | Artifact | Version | |---|---| | `zio` / `zio-streams` / `zio-test` / `zio-test-sbt` | 2.1.26 | | `zio-process` | 0.8.0 | ## Review & fixes The branch was put through the multi-skill code review (style, testing, security, scala3, composition, architecture, zio, api, repository). Contested "critical" findings were verified empirically; three were false positives: - `ZStream.takeUntil` is **inclusive** (equivalent to fs2 `takeThrough`) — the ResultMessage is not dropped. - os-lib's `/` **rejects** path separators in non-literal segments (throws `InvalidSegment`) — no `sessionId` path traversal. - The claimed `send` "silent prompt loss" was wrong: `offer` on a shut-down **unbounded** ZIO queue *interrupts*, it does not return `false`. Confirmed findings were then fixed under TDD: - **`send` to a released session** now surfaces a typed `SessionProcessDied` (guards on `stdinQueue.isShutdown`) instead of an interruption — matching `effectful`'s parity (only `direct`, with explicit `close()`, uses `SessionClosedError`). - **`validateConfiguration`** surfaces a real error on an I/O fault instead of mapping every failure to "directory does not exist"; the existence/directory classification is extracted into a pure, directly-tested `cwdError`. - **Log-index listings** are guarded with `os.isFile` (matching `direct`), so a directory named `*.jsonl` no longer produces a phantom entry. - **`CLIDiscovery`** PURPOSE comment now describes behavior, not implementation. - **Test coverage** filled to sibling parity: cwd-convenience methods + env factory, infallible `parseJsonLine`, log-index decoy/meta-sidecar edge cases, and integration tests for malformed-JSON-mid-turn, session-id refresh from a `ResultMessage`, and send-to-dead → `SessionProcessDied`. An adversarial verification pass over the diff surfaced one real gap (an untested `listSubAgents` guard branch), now closed with a decoy-directory regression test (confirmed red without the guard). ## Test status All gates green: `__.compile` 584/584 (no warnings), `__.fix --check` 279/279, `__.checkFormat`, unit `__.test` **526/526**, `zio.itest` **244/244**. > Note: a full `__.itest` run intermittently fails one **pre-existing** timing/concurrency test in the `direct` module (`ProcessManagerTest.should cleanup resources during concurrent operations with mixed failures`) — it passes in isolation and is unrelated to this branch (which touches only `zio/`). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.
No reviewers
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
iterative-works/claude-code-query!49
No description provided.