@@ -13,13 +13,63 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313 ` AcpSchema.JSONRPCError.from(exception) ` instead of ` exception.toJsonRpcError() ` , and
1414 ` error.toException() ` instead of ` new AcpProtocolException(error) ` . Dependencies between the ` spec ` and
1515 ` error ` packages now run one way.
16+ - ** Every package is null-marked (JSpecify ` @NullMarked ` ), and the nullness is now part of the API.**
17+ Types are non-null unless annotated ` @Nullable ` , and the annotations say where absence is legal:
18+ ` AcpSchema ` record components follow the ACP schema (optional and nullable fields are ` @Nullable ` ,
19+ 217 of them; required ones are not), as do the JSON-RPC envelope (` id ` , ` params ` , ` result ` , ` error ` ,
20+ ` data ` ), ` AcpJsonMapper.readValue ` (the JSON ` null ` literal), ` getClientCapabilities() ` and
21+ ` getAgentCapabilities() ` (before initialization), ` AcpProtocolException.getData() ` , and the
22+ annotation-driven support types (resolved arguments, handler return values, interceptor results).
23+ Kotlin and other nullness-aware callers see these types; Java callers compile unchanged.
24+ - ** Breaking: ` SyncPromptContext.askChoice ` returns ` Optional<String> ` ** , empty when the client cancels
25+ the choice (it was documented to return null, and failed instead, below). ` PromptContext.askChoice `
26+ completes empty on cancellation. Migration: ` askChoice(...).orElse(...) ` , or test ` isPresent() ` .
27+ - ** Breaking: ` CommandResult ` carries a nullable exit code and the terminating signal.** Its components
28+ are ` (String output, @Nullable Integer exitCode, @Nullable String signal, boolean timedOut) ` : a
29+ command killed by a signal has no exit code. The ` (output, int exitCode) ` and
30+ ` (output, int exitCode, boolean timedOut) ` constructors remain. Migration: ` exitCode() ` returns
31+ ` Integer ` ; use ` success() ` , or check ` exitCode() ` for null before comparing it.
32+ - ** Breaking: ` Command.env() ` is never null** ; it is empty when no variables are set (` Command.of `
33+ now builds it with ` Map.of() ` , and the canonical constructor takes a non-null map). Migration: test
34+ ` env().isEmpty() ` instead of ` env() == null ` .
35+ - ` AcpException.getMessage() ` is declared non-null: every constructor sets a message.
36+ - ` StdioAcpClientTransport.awaitForExit() ` before ` connect ` throws ` IllegalStateException ` instead of
37+ ` NullPointerException ` .
38+ - ` AcpInvocationContext.Builder.build() ` requires ` acpMethod ` and ` request ` .
39+
40+ ### Fixed
41+
42+ Found by the NullAway adoption; each has a test.
43+
44+ - A request or notification that omits ` params ` (legal JSON-RPC) reached its handler as ` null ` ; it now
45+ reads as an empty object, as if the peer had sent ` {} ` .
46+ - A request whose handler produced no result got no JSON-RPC response at all, so the peer waited for
47+ its timeout: a core request handler that completed empty, and in ` acp-agent-support ` a ` void ` or
48+ null-returning handler method, or one vetoed by an interceptor. Such requests are now answered with
49+ an ` INTERNAL_ERROR ` saying the handler produced no response.
50+ - A handler exception without a message produced a JSON-RPC error without the required ` message ` ; the
51+ exception's type name is sent instead.
52+ - A success response without a ` result ` failed the request with an opaque ` NullPointerException ` ; the
53+ error now says the response carried no result.
54+ - ` PromptContext.askChoice ` failed the prompt with a ` NullPointerException ` when the client cancelled.
55+ - ` PromptContext.execute ` failed the prompt with a ` NullPointerException ` when the command was killed
56+ by a signal (no exit code).
57+ - ` AcpSchema.deserializeJsonRpcMessage ` threw ` NullPointerException ` for the JSON ` null ` literal
58+ instead of the documented ` IllegalArgumentException ` .
1659
1760### Build
1861
1962- Architecture rules (ArchUnit) guard the package structure: acp-core's layers (util and json under the
2063 protocol, protocol under capabilities, capabilities under client and agent, which never depend on each
2164 other), no package cycles, no Jackson databind in acp-core, and no Jetty types on the path of
2265 ` StreamableHttpAcpServlet ` , which must stay mountable in any Servlet 6 container.
66+ - Null safety is enforced, not just declared: every package is ` @NullMarked ` (JSpecify 1.0.0, a
67+ compile dependency of each module; optional in ` acp-annotations ` , which keeps no transitive
68+ dependencies), and NullAway 0.13.8 on Error Prone 2.50.0 checks main sources at ERROR. Error Prone
69+ needs JDK 21, so the check runs in a ` nullaway ` profile activated on JDK 21+ and in a new JDK 21 CI
70+ job; the JDK 17 build and release are unchanged. Compilation uses ` --release 17 ` , so a JDK 21 build
71+ still compiles against the Java 17 API. ` .mvn/jvm.config ` opens the javac internals Error Prone needs
72+ (harmless on JDK 17).
2373
2474## [ 0.18.0] - 2026-09-25
2575
0 commit comments