diff --git a/.githooks/pre-commit b/.githooks/pre-commit
new file mode 100644
index 000000000..389bcda90
--- /dev/null
+++ b/.githooks/pre-commit
@@ -0,0 +1,29 @@
+#!/bin/sh
+#
+# Pre-commit hook that runs Spotless check on the Java SDK when Java source
+# files are staged. Only triggers if changes exist under java/src/.
+#
+# To install this hook, run from the repository root:
+# git config core.hooksPath .githooks
+#
+
+# Only run Spotless if staged changes include Java source files under java/src/
+if ! git diff --cached --name-only | grep -q '^java/src/'; then
+ exit 0
+fi
+
+echo "Running Spotless check on java/ ..."
+
+# Run spotless check from the java directory
+(cd java && mvn spotless:check -q)
+
+if [ $? -ne 0 ]; then
+ echo ""
+ echo "β Spotless check failed!"
+ echo " Run 'cd java && mvn spotless:apply' to fix formatting issues."
+ echo ""
+ exit 1
+fi
+
+echo "β Spotless check passed"
+exit 0
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index 1dad5f95c..000338e23 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -4,13 +4,14 @@
## Big picture π§
-- The repo implements language SDKs (Node/TS, Python, Go, .NET) that speak to the **Copilot CLI** via **JSONβRPC** (see `README.md` and `nodejs/src/client.ts`).
-- Typical flow: your App β SDK client β JSON-RPC β Copilot CLI (server mode). The CLI must be installed or you can connect to an external CLI server via the `CLI URL option (language-specific casing)` (Node: `cliUrl`, Go: `CLIUrl`, .NET: `CliUrl`, Python: `cli_url`).
+- The repo implements language SDKs (Node/TS, Python, Go, .NET, Java) that speak to the **Copilot CLI** via **JSONβRPC** (see `README.md` and `nodejs/src/client.ts`).
+- Typical flow: your App β SDK client β JSON-RPC β Copilot CLI (server mode). The CLI must be installed or you can connect to an external CLI server via the `CLI URL option (language-specific casing)` (Node: `cliUrl`, Go: `CLIUrl`, .NET: `CliUrl`, Python: `cli_url`, Java: `cliUrl`).
## Most important files to read first π
- Top-level: `README.md` (architecture + quick start)
- Language entry points: `nodejs/src/client.ts`, `python/README.md`, `go/README.md`, `dotnet/README.md`
+- Java: `java/README.md`, `java/pom.xml`
- Test harness & E2E: `test/harness/*`, Python harness wrapper `python/e2e/testharness/proxy.py`
- Schemas & type generation: `nodejs/scripts/generate-session-types.ts`
- Session snapshots used by E2E: `test/snapshots/` (used by the replay proxy)
@@ -26,12 +27,15 @@
- Go: `cd go && go test ./...`
- .NET: `cd dotnet && dotnet test test/GitHub.Copilot.SDK.Test.csproj`
- **.NET testing note:** Never add `InternalsVisibleTo` to any project file when writing tests. Tests must only access public APIs.
+ - Java: `cd java && mvn clean verify` (full build + tests), `mvn spotless:apply` (format code before commit)
+ - **Java testing note:** Always use `mvn verify` without `-q` and without piping through `grep`. Never add `InternalsVisibleTo` equivalent β tests must only access public APIs.
## Testing & E2E tips βοΈ
- E2E runs against a local **replaying CAPI proxy** (see `test/harness/server.ts`). Most language E2E harnesses spawn that server automatically (see `python/e2e/testharness/proxy.py`).
- Tests rely on YAML snapshot exchanges under `test/snapshots/` β to add test scenarios, add or edit the appropriate YAML files and update tests.
- The harness prints `Listening: http://...` β tests parse this URL to configure CLI or proxy.
+- Java E2E tests use `E2ETestContext` which manages a `CapiProxy` (Node.js replaying proxy). The harness is cloned during Maven's `generate-test-resources` phase to `java/target/copilot-sdk/`.
## Project-specific conventions & patterns β
@@ -42,13 +46,14 @@
## Integration & environment notes β οΈ
-- The SDK requires a Copilot CLI installation or an external server reachable via the `CLI URL option (language-specific casing)` (Node: `cliUrl`, Go: `CLIUrl`, .NET: `CliUrl`, Python: `cli_url`) or `COPILOT_CLI_PATH`.
+- The SDK requires a Copilot CLI installation or an external server reachable via the `CLI URL option (language-specific casing)` (Node: `cliUrl`, Go: `CLIUrl`, .NET: `CliUrl`, Python: `cli_url`, Java: `cliUrl`) or `COPILOT_CLI_PATH`.
- Some scripts (typegen, formatting) call external tools: `gofmt`, `dotnet format`, `tsx` (available via npm), `quicktype`/`quicktype-core` (used by the Node typegen script), and `prettier` (provided as an npm devDependency). Most of these are available through the repo's package scripts or devDependenciesβrun `just install` (and `cd nodejs && npm ci`) to install them. Ensure the required tools are available in CI / developer machines.
- Tests may assume `node >= 18`, `python >= 3.9`, platform differences handled (Windows uses `shell=True` for npx in harness).
+- Java requires JDK 17+ and Maven 3.9+. Java E2E tests also require Node.js (for the replay proxy).
## Where to add new code or tests π§
-- SDK code: `nodejs/src`, `python/copilot`, `go`, `dotnet/src`, `rust/src`
-- Unit tests: `nodejs/test`, `python/*`, `go/*`, `dotnet/test`, `rust/tests`
-- E2E tests: `*/e2e/` folders that use the shared replay proxy and `test/snapshots/`
-- Generated types: update schema in `@github/copilot` then run `cd nodejs && npm run generate:session-types` and commit generated files in `src/generated` or language generated location.
+- SDK code: `nodejs/src`, `python/copilot`, `go`, `dotnet/src`, `rust/src`, `java/src/main/java`
+- Unit tests: `nodejs/test`, `python/*`, `go/*`, `dotnet/test`, `rust/tests`, `java/src/test/java`
+- E2E tests: `*/e2e/` folders that use the shared replay proxy and `test/snapshots/`, `java/src/test/java/**/e2e/`
+- Generated types: update schema in `@github/copilot` then run `cd nodejs && npm run generate:session-types` and commit generated files in `src/generated` or language generated location. Java generated types: `java/src/generated/java`
diff --git a/.github/skills/java-coding-skill/SKILL.md b/.github/skills/java-coding-skill/SKILL.md
new file mode 100644
index 000000000..e48ad00ed
--- /dev/null
+++ b/.github/skills/java-coding-skill/SKILL.md
@@ -0,0 +1,757 @@
+---
+name: java-coding-skill
+description: "Use this skill whenever editing `*.java` files in the `java/` SDK in order to write idiomatic, well-structured Java code for the Copilot SDK"
+---
+
+# Java Coding Skill
+
+## Core Principles
+
+- The SDK is in public preview and may have breaking changes
+- Requires Java 17 or later
+- Requires GitHub Copilot CLI installed and in PATH
+- Uses `CompletableFuture` for all async operations
+- Implements `AutoCloseable` for resource cleanup (try-with-resources)
+
+## Installation
+
+### Maven
+
+```xml
+
+ com.github
+ copilot-sdk-java
+ ${copilot-sdk-java.version}
+
+```
+
+### Gradle
+
+```groovy
+implementation "com.github:copilot-sdk-java:${copilotSdkJavaVersion}"
+```
+
+## Client Initialization
+
+### Basic Client Setup
+
+```java
+try (var client = new CopilotClient()) {
+ client.start().get();
+ // Use client...
+}
+```
+
+### Client Configuration Options
+
+When creating a CopilotClient, use `CopilotClientOptions`:
+
+- `cliPath` - Path to CLI executable (default: "copilot" from PATH)
+- `cliArgs` - Extra arguments prepended before SDK-managed flags
+- `cliUrl` - URL of existing CLI server (e.g., "localhost:8080"). When provided, client won't spawn a process
+- `port` - Server port (default: 0 for random, only when `useStdio` is false)
+- `useStdio` - Use stdio transport instead of TCP (default: true)
+- `logLevel` - Log level: "error", "warn", "info", "debug", "trace" (default: "info")
+- `autoStart` - Auto-start server on first request (default: true)
+- `autoRestart` - Auto-restart on crash (default: true)
+- `cwd` - Working directory for the CLI process
+- `environment` - Environment variables for the CLI process
+- `gitHubToken` - GitHub token for authentication
+- `useLoggedInUser` - Use logged-in `gh` CLI auth (default: true unless token provided)
+- `onListModels` - Custom model list handler for BYOK scenarios
+
+```java
+var options = new CopilotClientOptions()
+ .setCliPath("/path/to/copilot")
+ .setLogLevel("debug")
+ .setAutoStart(true)
+ .setAutoRestart(true)
+ .setGitHubToken(System.getenv("GITHUB_TOKEN"));
+
+try (var client = new CopilotClient(options)) {
+ client.start().get();
+ // Use client...
+}
+```
+
+### Manual Server Control
+
+For explicit control:
+```java
+var client = new CopilotClient(new CopilotClientOptions().setAutoStart(false));
+client.start().get();
+// Use client...
+client.stop().get();
+```
+
+Use `forceStop()` when `stop()` takes too long.
+
+## Session Management
+
+### Creating Sessions
+
+Use `SessionConfig` for configuration. The permission handler is **required**:
+
+```java
+var session = client.createSession(new SessionConfig()
+ .setModel("gpt-5")
+ .setStreaming(true)
+ .setTools(List.of(...))
+ .setSystemMessage(new SystemMessageConfig()
+ .setMode(SystemMessageMode.APPEND)
+ .setContent("Custom instructions"))
+ .setAvailableTools(List.of("tool1", "tool2"))
+ .setExcludedTools(List.of("tool3"))
+ .setProvider(new ProviderConfig().setType("openai"))
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
+).get();
+```
+
+### Session Config Options
+
+- `sessionId` - Custom session ID
+- `clientName` - Application name
+- `model` - Model name ("gpt-5", "claude-sonnet-4.5", etc.)
+- `reasoningEffort` - "low", "medium", "high", "xhigh"
+- `tools` - Custom tools exposed to the CLI
+- `systemMessage` - System message customization
+- `availableTools` - Allowlist of tool names
+- `excludedTools` - Blocklist of tool names
+- `provider` - Custom API provider configuration (BYOK)
+- `streaming` - Enable streaming response chunks (default: false)
+- `workingDirectory` - Session working directory
+- `mcpServers` - MCP server configurations
+- `customAgents` - Custom agent configurations
+- `agent` - Pre-select agent by name
+- `infiniteSessions` - Infinite sessions configuration
+- `skillDirectories` - Skill SKILL.md directories
+- `disabledSkills` - Skills to disable
+- `configDir` - Config directory path
+- `hooks` - Session lifecycle hooks
+- `onPermissionRequest` - **REQUIRED** permission handler
+- `onUserInputRequest` - User input handler
+- `onEvent` - Event handler registered before session creation
+
+All setters return `SessionConfig` for method chaining.
+
+### Resuming Sessions
+
+```java
+var session = client.resumeSession(sessionId, new ResumeSessionConfig()
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
+).get();
+```
+
+### Session Operations
+
+- `session.getSessionId()` - Get session identifier
+- `session.send(prompt)` / `session.send(MessageOptions)` - Send message, returns message ID
+- `session.sendAndWait(prompt)` / `session.sendAndWait(MessageOptions)` - Send and wait for response (60s timeout)
+- `session.sendAndWait(options, timeoutMs)` - Send and wait with custom timeout
+- `session.abort()` - Abort current processing
+- `session.getMessages()` - Get all events/messages
+- `session.setModel(modelId)` - Switch to a different model
+- `session.log(message)` / `session.log(message, "warning", false)` / `session.log(message, "error", false)` - Log to session timeline with level `"info"`, `"warning"`, or `"error"`
+- `session.close()` - Clean up resources
+
+## Event Handling
+
+### Event Subscription Pattern
+
+Use `CompletableFuture` for waiting on session events:
+
+```java
+var done = new CompletableFuture();
+
+session.on(event -> {
+ if (event instanceof AssistantMessageEvent msg) {
+ System.out.println(msg.getData().content());
+ } else if (event instanceof SessionIdleEvent) {
+ done.complete(null);
+ }
+});
+
+session.send(new MessageOptions().setPrompt("Hello"));
+done.get();
+```
+
+### Type-Safe Event Handling
+
+Use the typed `on()` overload for compile-time safety:
+
+```java
+session.on(AssistantMessageEvent.class, msg -> {
+ System.out.println(msg.getData().content());
+});
+
+session.on(SessionIdleEvent.class, idle -> {
+ done.complete(null);
+});
+```
+
+### Unsubscribing from Events
+
+The `on()` method returns a `Closeable`:
+
+```java
+var subscription = session.on(event -> { /* handler */ });
+// Later...
+subscription.close();
+```
+
+### Event Types
+
+Use pattern matching (Java 17+) for event handling:
+
+```java
+session.on(event -> {
+ if (event instanceof UserMessageEvent userMsg) {
+ // Handle user message
+ } else if (event instanceof AssistantMessageEvent assistantMsg) {
+ System.out.println(assistantMsg.getData().content());
+ } else if (event instanceof AssistantMessageDeltaEvent delta) {
+ System.out.print(delta.getData().deltaContent());
+ } else if (event instanceof ToolExecutionStartEvent toolStart) {
+ // Tool execution started
+ } else if (event instanceof ToolExecutionCompleteEvent toolComplete) {
+ // Tool execution completed
+ } else if (event instanceof SessionStartEvent start) {
+ // Session started
+ } else if (event instanceof SessionIdleEvent idle) {
+ // Session is idle (processing complete)
+ } else if (event instanceof SessionErrorEvent error) {
+ System.err.println("Error: " + error.getData().message());
+ }
+});
+```
+
+### Event Error Handling
+
+Control how errors in event handlers are handled:
+
+```java
+// Set a custom error handler
+session.setEventErrorHandler(ex -> {
+ logger.error("Event handler error", ex);
+});
+
+// Or set the error propagation policy
+session.setEventErrorPolicy(EventErrorPolicy.SUPPRESS_AND_LOG_ERRORS);
+```
+
+## Streaming Responses
+
+### Enabling Streaming
+
+Set `streaming(true)` in SessionConfig:
+
+```java
+var session = client.createSession(new SessionConfig()
+ .setModel("gpt-5")
+ .setStreaming(true)
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
+).get();
+```
+
+### Handling Streaming Events
+
+Handle both delta events (incremental) and final events:
+
+```java
+var done = new CompletableFuture();
+
+session.on(event -> {
+ switch (event) {
+ case AssistantMessageDeltaEvent delta ->
+ // Incremental text chunk
+ System.out.print(delta.getData().deltaContent());
+ case AssistantReasoningDeltaEvent reasoningDelta ->
+ // Incremental reasoning chunk (model-dependent)
+ System.out.print(reasoningDelta.getData().deltaContent());
+ case AssistantMessageEvent msg ->
+ // Final complete message
+ System.out.println("\n--- Final ---\n" + msg.getData().content());
+ case AssistantReasoningEvent reasoning ->
+ // Final reasoning content
+ System.out.println("--- Reasoning ---\n" + reasoning.getData().content());
+ case SessionIdleEvent idle ->
+ done.complete(null);
+ default -> { }
+ }
+});
+
+session.send(new MessageOptions().setPrompt("Tell me a story"));
+done.get();
+```
+
+Note: Final events (`AssistantMessageEvent`, `AssistantReasoningEvent`) are ALWAYS sent regardless of streaming setting.
+
+## Custom Tools
+
+### Defining Tools
+
+Use `ToolDefinition.create()` with JSON Schema parameters and a `ToolHandler`:
+
+```java
+var tool = ToolDefinition.create(
+ "get_weather",
+ "Get weather for a location",
+ Map.of(
+ "type", "object",
+ "properties", Map.of(
+ "location", Map.of("type", "string", "description", "City name")
+ ),
+ "required", List.of("location")
+ ),
+ invocation -> {
+ String location = (String) invocation.getArguments().get("location");
+ return CompletableFuture.completedFuture("Sunny in " + location);
+ }
+);
+
+var session = client.createSession(new SessionConfig()
+ .setModel("gpt-5")
+ .setTools(List.of(tool))
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
+).get();
+```
+
+### Type-Safe Tool Arguments
+
+Use `getArgumentsAs()` for deserialization into a typed record or class:
+
+```java
+record WeatherArgs(String location, String unit) {}
+
+var tool = ToolDefinition.create(
+ "get_weather",
+ "Get weather for a location",
+ Map.of(
+ "type", "object",
+ "properties", Map.of(
+ "location", Map.of("type", "string"),
+ "unit", Map.of("type", "string", "enum", List.of("celsius", "fahrenheit"))
+ ),
+ "required", List.of("location")
+ ),
+ invocation -> {
+ var args = invocation.getArgumentsAs(WeatherArgs.class);
+ return CompletableFuture.completedFuture(
+ Map.of("temp", 72, "unit", args.unit(), "location", args.location())
+ );
+ }
+);
+```
+
+### Overriding Built-In Tools
+
+```java
+var override = ToolDefinition.createOverride(
+ "built_in_tool_name",
+ "Custom description",
+ Map.of("type", "object", "properties", Map.of(...)),
+ invocation -> CompletableFuture.completedFuture("custom result")
+);
+```
+
+### Tool Return Types
+
+- Return any JSON-serializable value (String, Map, List, record, POJO)
+- The SDK automatically serializes the return value and sends it back to the CLI
+
+### Tool Execution Flow
+
+When Copilot invokes a tool, the client automatically:
+1. Deserializes the arguments
+2. Runs your handler function
+3. Serializes the return value
+4. Responds to the CLI
+
+## Permission Handling
+
+### Required Permission Handler
+
+A permission handler is **mandatory** when creating or resuming sessions:
+
+```java
+// Approve all requests (for development/testing)
+new SessionConfig()
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
+
+// Custom permission logic
+new SessionConfig()
+ .setOnPermissionRequest((request, invocation) -> {
+ if ("dangerous-action".equals(request.getKind())) {
+ return CompletableFuture.completedFuture(
+ new PermissionRequestResult().setKind(PermissionRequestResultKind.DENIED)
+ );
+ }
+ return CompletableFuture.completedFuture(
+ new PermissionRequestResult().setKind(PermissionRequestResultKind.APPROVED)
+ );
+ })
+```
+
+## User Input Handling
+
+Handle user input requests from the agent:
+
+```java
+new SessionConfig()
+ .setOnUserInputRequest((request, invocation) -> {
+ System.out.println("Agent asks: " + request.getQuestion());
+ String answer = scanner.nextLine();
+ return CompletableFuture.completedFuture(
+ new UserInputResponse()
+ .setAnswer(answer)
+ .setWasFreeform(true)
+ );
+ })
+```
+
+## System Message Customization
+
+### Append Mode (Default - Preserves Guardrails)
+
+```java
+var session = client.createSession(new SessionConfig()
+ .setModel("gpt-5")
+ .setSystemMessage(new SystemMessageConfig()
+ .setMode(SystemMessageMode.APPEND)
+ .setContent("""
+
+ - Always check for security vulnerabilities
+ - Suggest performance improvements when applicable
+
+ """))
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
+).get();
+```
+
+### Replace Mode (Full Control - Removes Guardrails)
+
+```java
+var session = client.createSession(new SessionConfig()
+ .setModel("gpt-5")
+ .setSystemMessage(new SystemMessageConfig()
+ .setMode(SystemMessageMode.REPLACE)
+ .setContent("You are a helpful assistant."))
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
+).get();
+```
+
+## File Attachments
+
+Attach files to messages using `Attachment`:
+
+```java
+session.send(new MessageOptions()
+ .setPrompt("Analyze this file")
+ .setAttachments(List.of(
+ new Attachment("file", "/path/to/file.java", "My File")
+ ))
+);
+```
+
+## Message Delivery Modes
+
+Use the `mode` property in `MessageOptions`:
+
+- `"enqueue"` - Queue message for processing (default)
+- `"immediate"` - Process message immediately
+
+```java
+session.send(new MessageOptions()
+ .setPrompt("...")
+ .setMode("enqueue")
+);
+```
+
+## Convenience: Send and Wait
+
+Use `sendAndWait()` to send a message and block until the assistant responds:
+
+```java
+// With default 60-second timeout
+AssistantMessageEvent response = session.sendAndWait("What is 2+2?").get();
+System.out.println(response.getData().content());
+
+// With custom timeout
+AssistantMessageEvent response = session.sendAndWait(
+ new MessageOptions().setPrompt("Write a long story"),
+ 120_000 // 120 seconds
+).get();
+```
+
+## Multiple Sessions
+
+Sessions are independent and can run concurrently:
+
+```java
+var session1 = client.createSession(new SessionConfig()
+ .setModel("gpt-5")
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
+).get();
+
+var session2 = client.createSession(new SessionConfig()
+ .setModel("claude-sonnet-4.5")
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
+).get();
+
+session1.send(new MessageOptions().setPrompt("Hello from session 1"));
+session2.send(new MessageOptions().setPrompt("Hello from session 2"));
+```
+
+## Bring Your Own Key (BYOK)
+
+Use custom API providers via `ProviderConfig`:
+
+```java
+// OpenAI
+var session = client.createSession(new SessionConfig()
+ .setProvider(new ProviderConfig()
+ .setType("openai")
+ .setBaseUrl("https://api.openai.com/v1")
+ .setApiKey("sk-..."))
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
+).get();
+
+// Azure OpenAI
+var session = client.createSession(new SessionConfig()
+ .setProvider(new ProviderConfig()
+ .setType("azure")
+ .setAzure(new AzureOptions()
+ .setEndpoint("https://my-resource.openai.azure.com")
+ .setDeployment("gpt-4"))
+ .setBearerToken("..."))
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
+).get();
+```
+
+## Session Lifecycle Management
+
+### Listing Sessions
+
+```java
+var sessions = client.listSessions().get();
+for (var metadata : sessions) {
+ System.out.println("Session: " + metadata.getSessionId());
+}
+```
+
+### Deleting Sessions
+
+```java
+client.deleteSession(sessionId).get();
+```
+
+### Checking Connection State
+
+```java
+var state = client.getState();
+```
+
+### Lifecycle Event Subscription
+
+```java
+AutoCloseable subscription = client.onLifecycle(event -> {
+ System.out.println("Lifecycle event: " + event);
+});
+// Later...
+subscription.close();
+```
+
+## Error Handling
+
+### Standard Exception Handling
+
+```java
+try {
+ var session = client.createSession(new SessionConfig()
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
+ ).get();
+ session.sendAndWait("Hello").get();
+} catch (ExecutionException ex) {
+ Throwable cause = ex.getCause();
+ System.err.println("Error: " + cause.getMessage());
+} catch (Exception ex) {
+ System.err.println("Error: " + ex.getMessage());
+}
+```
+
+### Session Error Events
+
+Monitor `SessionErrorEvent` for runtime errors:
+
+```java
+session.on(SessionErrorEvent.class, error -> {
+ System.err.println("Session Error: " + error.getData().message());
+});
+```
+
+## Connectivity Testing
+
+Use `ping()` to verify server connectivity:
+
+```java
+var response = client.ping("test message").get();
+```
+
+## Status and Authentication
+
+```java
+// Get CLI version and protocol info
+var status = client.getStatus().get();
+
+// Check authentication status
+var authStatus = client.getAuthStatus().get();
+
+// List available models
+var models = client.listModels().get();
+```
+
+## Resource Cleanup
+
+### Automatic Cleanup with try-with-resources
+
+ALWAYS use try-with-resources for automatic disposal:
+
+```java
+try (var client = new CopilotClient()) {
+ client.start().get();
+ try (var session = client.createSession(new SessionConfig()
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get()) {
+ // Use session...
+ }
+}
+// Resources automatically cleaned up
+```
+
+### Manual Cleanup
+
+If not using try-with-resources:
+
+```java
+var client = new CopilotClient();
+try {
+ client.start().get();
+ // Use client...
+} finally {
+ client.stop().get();
+}
+```
+
+## Best Practices
+
+1. **Always use try-with-resources** for `CopilotClient` and `CopilotSession`
+2. **Always provide a permission handler** - it is required for `createSession` and `resumeSession`
+3. **Use `CompletableFuture`** properly - call `.get()` to block, or chain with `.thenApply()`/`.thenCompose()`
+4. **Use `sendAndWait()`** for simple request-response patterns instead of manual event handling
+5. **Handle `SessionErrorEvent`** for robust error handling
+6. **Use pattern matching** (switch with sealed types) for event handling
+7. **Enable streaming** for better UX in interactive scenarios
+8. **Close event subscriptions** (`Closeable`) when no longer needed
+9. **Use `SystemMessageMode.APPEND`** to preserve safety guardrails
+10. **Provide descriptive tool names and descriptions** for better model understanding
+11. **Handle both delta and final events** when streaming is enabled
+12. **Use `getArgumentsAs()`** for type-safe tool argument deserialization
+
+## Common Patterns
+
+### Simple Query-Response
+
+```java
+try (var client = new CopilotClient()) {
+ client.start().get();
+
+ try (var session = client.createSession(new SessionConfig()
+ .setModel("gpt-5")
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get()) {
+
+ var response = session.sendAndWait("What is 2+2?").get();
+ System.out.println(response.getData().content());
+ }
+}
+```
+
+### Event-Driven Conversation
+
+```java
+try (var client = new CopilotClient()) {
+ client.start().get();
+
+ try (var session = client.createSession(new SessionConfig()
+ .setModel("gpt-5")
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get()) {
+
+ var done = new CompletableFuture();
+
+ session.on(AssistantMessageEvent.class, msg ->
+ System.out.println(msg.getData().content()));
+
+ session.on(SessionIdleEvent.class, idle ->
+ done.complete(null));
+
+ session.send(new MessageOptions().setPrompt("What is 2+2?"));
+ done.get();
+ }
+}
+```
+
+### Multi-Turn Conversation
+
+```java
+try (var session = client.createSession(new SessionConfig()
+ .setModel("gpt-5")
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get()) {
+
+ var response1 = session.sendAndWait("What is the capital of France?").get();
+ System.out.println(response1.getData().content());
+
+ var response2 = session.sendAndWait("What is its population?").get();
+ System.out.println(response2.getData().content());
+}
+```
+
+### Tool with Complex Return Type
+
+```java
+record UserInfo(String id, String name, String email, String role) {}
+
+var tool = ToolDefinition.create(
+ "get_user",
+ "Retrieve user information",
+ Map.of(
+ "type", "object",
+ "properties", Map.of(
+ "userId", Map.of("type", "string", "description", "User ID")
+ ),
+ "required", List.of("userId")
+ ),
+ invocation -> {
+ String userId = (String) invocation.getArguments().get("userId");
+ return CompletableFuture.completedFuture(
+ new UserInfo(userId, "John Doe", "john@example.com", "Developer")
+ );
+ }
+);
+```
+
+### Session Hooks
+
+```java
+var session = client.createSession(new SessionConfig()
+ .setModel("gpt-5")
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
+ .setHooks(new SessionHooks()
+ .setOnPreToolUse((input, invocation) -> {
+ System.out.println("About to execute tool: " + input);
+ var decision = new PreToolUseHookOutput().setKind("allow");
+ return CompletableFuture.completedFuture(decision);
+ })
+ .setOnPostToolUse((output, invocation) -> {
+ System.out.println("Tool execution complete: " + output);
+ return CompletableFuture.completedFuture(null);
+ }))
+).get();
+```
diff --git a/.github/workflows/java-publish-maven.yml b/.github/workflows/java-publish-maven.yml
new file mode 100644
index 000000000..0567a4b56
--- /dev/null
+++ b/.github/workflows/java-publish-maven.yml
@@ -0,0 +1,255 @@
+name: Publish to Maven Central
+
+env:
+ # Disable Husky Git hooks in CI to prevent local development hooks
+ # (e.g., pre-commit formatting checks) from running during automated
+ # workflows that perform git commits and pushes.
+ HUSKY: 0
+
+on:
+ workflow_dispatch:
+ inputs:
+ releaseVersion:
+ description: "Release version (e.g., 1.0.0). If empty, derives from pom.xml by removing -SNAPSHOT"
+ required: false
+ type: string
+ developmentVersion:
+ description: "Next development version (e.g., 1.0.1-SNAPSHOT). If empty, increments patch version"
+ required: false
+ type: string
+ prerelease:
+ description: "Is this a prerelease?"
+ type: boolean
+ required: false
+ default: false
+
+permissions:
+ contents: write
+ id-token: write
+
+concurrency:
+ group: publish-maven
+ cancel-in-progress: false
+
+jobs:
+ publish-maven:
+ name: Publish Java SDK to Maven Central
+ runs-on: ubuntu-latest
+ outputs:
+ version: ${{ steps.versions.outputs.release_version }}
+ steps:
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ fetch-depth: 0
+ token: ${{ secrets.JAVA_RELEASE_TOKEN }}
+
+ - name: Configure Git for Maven Release
+ run: |
+ git config user.name "github-actions[bot]"
+ git config user.email "github-actions[bot]@users.noreply.github.com"
+
+ - uses: ./.github/actions/setup-copilot
+
+ - name: Set up JDK 17
+ uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
+ with:
+ java-version: "17"
+ distribution: "microsoft"
+ cache: "maven"
+ server-id: central
+ server-username: MAVEN_USERNAME
+ server-password: MAVEN_PASSWORD
+ gpg-private-key: ${{ secrets.JAVA_GPG_SECRET_KEY }}
+ gpg-passphrase: JAVA_GPG_PASSPHRASE
+
+ - name: Determine versions
+ id: versions
+ run: |
+ CURRENT_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
+ echo "Current pom.xml version: $CURRENT_VERSION"
+
+ # Determine release version
+ if [ -n "${{ inputs.releaseVersion }}" ]; then
+ RELEASE_VERSION="${{ inputs.releaseVersion }}"
+ else
+ # Remove -SNAPSHOT suffix if present
+ RELEASE_VERSION="${CURRENT_VERSION%-SNAPSHOT}"
+ fi
+ echo "Release version: $RELEASE_VERSION"
+
+ # Determine next development version
+ if [ -n "${{ inputs.developmentVersion }}" ]; then
+ DEV_VERSION="${{ inputs.developmentVersion }}"
+ if [[ "$DEV_VERSION" != *-SNAPSHOT ]]; then
+ echo "::error::developmentVersion '${DEV_VERSION}' must end with '-SNAPSHOT' (e.g., '${DEV_VERSION}-SNAPSHOT'). The maven-release-plugin requires the next development version to be a snapshot."
+ exit 1
+ fi
+ else
+ # Split version: supports "0.1.32", "0.1.32-java.0", and "0.1.32-java-preview.0" formats
+ # Validate RELEASE_VERSION format explicitly to provide clear errors
+ if ! echo "$RELEASE_VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+(-(beta-)?java(-preview)?\.[0-9]+)?$'; then
+ echo "Error: RELEASE_VERSION '$RELEASE_VERSION' is invalid. Expected format: M.M.P, M.M.P-java.N, M.M.P-java-preview.N, M.M.P-beta-java.N, or M.M.P-beta-java-preview.N (e.g., 1.2.3, 1.2.3-java.0, 1.2.3-java-preview.0, 1.2.3-beta-java.0, or 1.2.3-beta-java-preview.0)." >&2
+ exit 1
+ fi
+ # Extract the base M.M.P portion (before any qualifier)
+ BASE_VERSION=$(echo "$RELEASE_VERSION" | grep -oE '^[0-9]+\.[0-9]+\.[0-9]+')
+ QUALIFIER=$(echo "$RELEASE_VERSION" | sed "s|^${BASE_VERSION}||")
+ IFS='.' read -r MAJOR MINOR PATCH <<< "$BASE_VERSION"
+ NEXT_PATCH=$((PATCH + 1))
+ DEV_VERSION="${MAJOR}.${MINOR}.${NEXT_PATCH}${QUALIFIER}-SNAPSHOT"
+ fi
+ echo "Next development version: $DEV_VERSION"
+
+ echo "release_version=$RELEASE_VERSION" >> $GITHUB_OUTPUT
+ echo "dev_version=$DEV_VERSION" >> $GITHUB_OUTPUT
+
+ echo "### Version Summary" >> $GITHUB_STEP_SUMMARY
+ echo "- **Release version:** $RELEASE_VERSION" >> $GITHUB_STEP_SUMMARY
+ echo "- **Next development version:** $DEV_VERSION" >> $GITHUB_STEP_SUMMARY
+
+ - name: Update documentation with release version
+ id: update-docs
+ run: |
+ VERSION="${{ steps.versions.outputs.release_version }}"
+
+ # Read the reference implementation SDK commit hash that this release is synced to
+ REFERENCE_IMPL_HASH=$(cat .lastmerge)
+ REFERENCE_IMPL_SHORT="${REFERENCE_IMPL_HASH:0:7}"
+ REFERENCE_IMPL_URL="https://github.com/github/copilot-sdk/commit/${REFERENCE_IMPL_HASH}"
+ echo "Reference implementation SDK sync: ${REFERENCE_IMPL_SHORT} (${REFERENCE_IMPL_URL})"
+
+ # Update CHANGELOG.md with release version and Reference implementation sync hash
+ ./.github/scripts/release/update-changelog.sh "${VERSION}" "${REFERENCE_IMPL_HASH}"
+
+ # Update version in README.md (supports any version qualifier like -java.N, -java-preview.N, -beta-java.N)
+ sed -i "s|[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\(-[a-z][a-z0-9-]*\.[0-9][0-9]*\)*|${VERSION}|g" README.md
+ sed -i "s|copilot-sdk-java:[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\(-[a-z][a-z0-9-]*\.[0-9][0-9]*\)*|copilot-sdk-java:${VERSION}|g" README.md
+
+ # Update snapshot version in README.md
+ DEV_VERSION="${{ steps.versions.outputs.dev_version }}"
+ sed -i "s|[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\(-[a-z][a-z0-9-]*\.[0-9][0-9]*\)*-SNAPSHOT|${DEV_VERSION}|g" README.md
+
+ # Update version in jbang-example.java
+ sed -i "s|copilot-sdk-java:[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\(-[a-z][a-z0-9-]*\.[0-9][0-9]*\)*|copilot-sdk-java:${VERSION}|g" jbang-example.java
+ sed -i 's|copilot-sdk-java:${project\.version}|copilot-sdk-java:'"${VERSION}"'|g' jbang-example.java
+
+ # Update version in cookbook files (hardcoded for direct GitHub browsing and JBang usage)
+ find src/site/markdown/cookbook -name "*.md" -type f -exec \
+ sed -i "s|copilot-sdk-java:[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\(-[a-z][a-z0-9-]*\.[0-9][0-9]*\)*|copilot-sdk-java:${VERSION}|g" {} \;
+
+ # Commit the documentation changes before release:prepare (requires clean working directory)
+ git add CHANGELOG.md README.md jbang-example.java src/site/markdown/cookbook/
+ git commit -m "docs: update version references to ${VERSION}"
+
+ # Save the commit SHA for potential rollback
+ echo "docs_commit_sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
+
+ git push origin main
+
+ - name: Prepare Release
+ run: |
+ mvn -B release:prepare \
+ -DreleaseVersion=${{ steps.versions.outputs.release_version }} \
+ -DdevelopmentVersion=${{ steps.versions.outputs.dev_version }} \
+ -DtagNameFormat=v@{project.version} \
+ -DpushChanges=true \
+ -Darguments="-DskipTests"
+ env:
+ MAVEN_USERNAME: ${{ secrets.JAVA_MAVEN_CENTRAL_USERNAME }}
+ MAVEN_PASSWORD: ${{ secrets.JAVA_MAVEN_CENTRAL_PASSWORD }}
+ JAVA_GPG_PASSPHRASE: ${{ secrets.JAVA_GPG_PASSPHRASE }}
+
+ - name: Perform Release and Deploy to Maven Central
+ run: |
+ mvn -B release:perform \
+ -Dgoals="deploy" \
+ -Darguments="-DskipTests -Prelease"
+ env:
+ MAVEN_USERNAME: ${{ secrets.JAVA_MAVEN_CENTRAL_USERNAME }}
+ MAVEN_PASSWORD: ${{ secrets.JAVA_MAVEN_CENTRAL_PASSWORD }}
+ JAVA_GPG_PASSPHRASE: ${{ secrets.JAVA_GPG_PASSPHRASE }}
+
+ - name: Rollback documentation commit on failure
+ if: failure() && steps.update-docs.outputs.docs_commit_sha != ''
+ run: |
+ echo "Release failed, rolling back documentation commit..."
+ git revert --no-edit ${{ steps.update-docs.outputs.docs_commit_sha }}
+ git push origin main
+
+ # Also run Maven release:rollback to clean up any partial release state
+ mvn -B release:rollback || true
+
+ github-release:
+ name: Create GitHub Release
+ needs: publish-maven
+ if: github.ref == 'refs/heads/main'
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ fetch-depth: 0
+ - name: Create GitHub Release
+ run: |
+ VERSION="${{ needs.publish-maven.outputs.version }}"
+ GROUP_ID="com.github"
+ ARTIFACT_ID="copilot-sdk-java"
+ CURRENT_TAG="v${VERSION}"
+
+ if gh release view "${CURRENT_TAG}" >/dev/null 2>&1; then
+ echo "Release ${CURRENT_TAG} already exists. Skipping creation."
+ exit 0
+ fi
+
+ # Generate release notes from template
+ export VERSION GROUP_ID ARTIFACT_ID
+ RELEASE_NOTES=$(envsubst < .github/workflows/notes.template)
+
+ # Get the previous tag for generating notes
+ PREV_TAG=$(git tag --list 'v*' --sort=-version:refname \
+ | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+(-java(-preview)?\.[0-9]+)?$' \
+ | grep -Fxv "${CURRENT_TAG}" \
+ | head -n 1)
+
+ echo "Current tag: ${CURRENT_TAG}"
+ echo "Previous tag: ${PREV_TAG}"
+
+ # Build the gh release command
+ GH_ARGS=("${CURRENT_TAG}")
+ GH_ARGS+=("--title" "GitHub Copilot SDK for Java ${VERSION}")
+ GH_ARGS+=("--notes" "${RELEASE_NOTES}")
+ GH_ARGS+=("--generate-notes")
+
+ if [ -n "$PREV_TAG" ]; then
+ GH_ARGS+=("--notes-start-tag" "$PREV_TAG")
+ fi
+
+ ${{ inputs.prerelease == true && 'GH_ARGS+=("--prerelease")' || '' }}
+
+ gh release create "${GH_ARGS[@]}"
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Move 'latest' tag to new release
+ run: |
+ VERSION="${{ needs.publish-maven.outputs.version }}"
+ git tag -f latest "v${VERSION}"
+ git push origin latest --force
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ deploy-site:
+ name: Deploy Documentation
+ needs: [publish-maven, github-release]
+ runs-on: ubuntu-latest
+ permissions:
+ actions: write
+ contents: read
+ steps:
+ - name: Trigger site deployment
+ run: |
+ gh workflow run deploy-site.yml \
+ --repo ${{ github.repository }} \
+ -f version="${{ needs.publish-maven.outputs.version }}" \
+ -f publish_as_latest=true
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/java-publish-snapshot.yml b/.github/workflows/java-publish-snapshot.yml
new file mode 100644
index 000000000..d7720ceb2
--- /dev/null
+++ b/.github/workflows/java-publish-snapshot.yml
@@ -0,0 +1,55 @@
+name: Publish Snapshot to Maven Central
+
+env:
+ HUSKY: 0
+
+on:
+ schedule:
+ - cron: "0 7 * * 1-5" # Mon-Fri at 07:00 UTC
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+concurrency:
+ group: publish-snapshot
+ cancel-in-progress: false
+
+jobs:
+ publish-snapshot:
+ name: Publish SNAPSHOT to Maven Central
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ fetch-depth: 0
+
+ - uses: ./.github/actions/setup-copilot
+
+ - name: Set up JDK 17
+ uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
+ with:
+ java-version: "17"
+ distribution: "microsoft"
+ cache: "maven"
+ server-id: central
+ server-username: MAVEN_USERNAME
+ server-password: MAVEN_PASSWORD
+
+ - name: Verify version is a SNAPSHOT
+ run: |
+ VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
+ echo "Publishing version: $VERSION"
+ if [[ "$VERSION" != *"-SNAPSHOT" ]]; then
+ echo "ERROR: This workflow only publishes SNAPSHOT versions. Current version: $VERSION"
+ exit 1
+ fi
+ echo "### Snapshot Publish" >> $GITHUB_STEP_SUMMARY
+ echo "- **Version:** $VERSION" >> $GITHUB_STEP_SUMMARY
+ echo "- **Repository:** Maven Central Snapshots" >> $GITHUB_STEP_SUMMARY
+
+ - name: Deploy Snapshot
+ run: mvn -B deploy -DskipTests
+ env:
+ MAVEN_USERNAME: ${{ secrets.JAVA_MAVEN_CENTRAL_USERNAME }}
+ MAVEN_PASSWORD: ${{ secrets.JAVA_MAVEN_CENTRAL_PASSWORD }}
diff --git a/.gitignore b/.gitignore
index 4821b5e65..23915a160 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,5 @@ test/scenarios/**/rust/Cargo.lock
# C# Dev Kit
*.csproj.lscache
+
+*job-logs.txt*
diff --git a/80-java-monorepo-add-01-remove-before-merge/20260512-prompts.md b/80-java-monorepo-add-01-remove-before-merge/20260512-prompts.md
new file mode 100644
index 000000000..8073365b7
--- /dev/null
+++ b/80-java-monorepo-add-01-remove-before-merge/20260512-prompts.md
@@ -0,0 +1,432 @@
+
+### 17:17 Prompt
+
+Looking at `temporary-prompts/dd-2989727-move-java-to-monorepo-plan.md`, for each of the existing workflows in section "1B. `copilot-sdk-00` Workflows", with the exception of the already language specific `*-sdk-tests.yml`, what changes would one need to make to these workflows to ensure that none of the content in `java` would accidentally get picked up by those workflows?
+
+### 17:24 Response
+
+Read [](file:///e%3A/workareas/copilot-sdk-java-00/temporary-prompts/dd-2989727-move-java-to-monorepo-plan.md#1-1), lines 1 to 200
+
+Read [](file:///e%3A/workareas/copilot-sdk-java-00/temporary-prompts/dd-2989727-move-java-to-monorepo-plan.md#200-200), lines 200 to 400
+
+Let me look at the actual workflow files in the monorepo to assess each one.
+
+Searched for files matching `copilot-sdk-00` Β· `.github/workflows/*`, 20 matches
+
+Searched for files matching `copilot-sdk-00` Β· `.github/workflows/*`, 32 matches
+
+Created 13 todos
+
+Let me read all the non-language-specific workflow files in parallel:
+
+Read [](file:///e%3A/workareas/copilot-sdk-00/.github/workflows/codegen-check.yml)
+
+Read [](file:///e%3A/workareas/copilot-sdk-00/.github/workflows/publish.yml#1-1), lines 1 to 200
+
+Read [](file:///e%3A/workareas/copilot-sdk-00/.github/workflows/scenario-builds.yml#1-1), lines 1 to 200
+
+Read [](file:///e%3A/workareas/copilot-sdk-00/.github/workflows/docs-validation.yml)
+
+Read [](file:///e%3A/workareas/copilot-sdk-00/.github/workflows/update-copilot-dependency.yml)
+
+Read [](file:///e%3A/workareas/copilot-sdk-00/.github/workflows/copilot-setup-steps.yml)
+
+Read [](file:///e%3A/workareas/copilot-sdk-00/.github/workflows/verify-compiled.yml)
+
+Read [](file:///e%3A/workareas/copilot-sdk-00/.github/workflows/collect-corrections.yml)
+
+Read [](file:///e%3A/workareas/copilot-sdk-00/.github/workflows/corrections-tests.yml)
+
+Now let me read the remaining publish.yml and the agentic workflow `.md` files:
+
+Read [](file:///e%3A/workareas/copilot-sdk-00/.github/workflows/publish.yml#200-200), lines 200 to 315
+
+Read [](file:///e%3A/workareas/copilot-sdk-00/.github/workflows/issue-classification.md#1-1), lines 1 to 100
+
+Read [](file:///e%3A/workareas/copilot-sdk-00/.github/workflows/issue-triage.md)
+
+Read [](file:///e%3A/workareas/copilot-sdk-00/.github/workflows/handle-bug.md)
+
+Read [](file:///e%3A/workareas/copilot-sdk-00/.github/workflows/handle-documentation.md)
+
+Read [](file:///e%3A/workareas/copilot-sdk-00/.github/workflows/handle-enhancement.md)
+
+Read [](file:///e%3A/workareas/copilot-sdk-00/.github/workflows/handle-question.md)
+
+Read [](file:///e%3A/workareas/copilot-sdk-00/.github/workflows/cross-repo-issue-analysis.md#1-1), lines 1 to 100
+
+Read [](file:///e%3A/workareas/copilot-sdk-00/.github/workflows/release-changelog.md#1-1), lines 1 to 100
+
+Read [](file:///e%3A/workareas/copilot-sdk-00/.github/workflows/sdk-consistency-review.md)
+
+Starting: *Compile analysis* (2/2)
+
+Let me also check the codegen script to see if it could pick up Java content:
+
+Read [](file:///e%3A/workareas/copilot-sdk-00/scripts/codegen)
+
+Read [](file:///e%3A/workareas/copilot-sdk-00/.github/workflows/scenario-builds.yml#1-1), lines 1 to 30
+
+Read [](file:///e%3A/workareas/copilot-sdk-00/scripts/codegen/package.json)
+
+Completed: *Compile analysis* (2/2)
+
+Here's the analysis of each non-`*-sdk-tests.yml` workflow in section 1B, grouped by risk level:
+
+---
+
+## HIGH RISK β Workflows that would actively process or misrepresent Java content
+
+### 1. codegen-check.yml
+- **Problem**: The `push` trigger on `main` has **no path filter** β every push to `main` (including Java-only changes) fires this workflow. While the existing codegen scripts (package.json `generate` script) only run `typescript.ts`, `csharp.ts`, `python.ts`, `go.ts`, `rust.ts`, if a `java.ts` is later merged into codegen, the `npm run generate` step would execute it too.
+- **Changes needed**:
+ - Add `paths-ignore: ['java/**']` to the `push` trigger, or add explicit `paths:` matching the existing language dirs.
+ - Alternatively, after Java codegen is merged, do **not** add `java.ts` to the `generate` script in package.json β keep `generate:java` as a separate script only invoked by `java-sdk-tests.yml` / a separate `java-codegen-check` job.
+
+### 2. update-copilot-dependency.yml
+- **Problem**: The `npm run generate` step ([line ~77](copilot-sdk-00/.github/workflows/update-copilot-dependency.yml)) runs **all** codegen scripts. If `java.ts` is added to the `generate` script, it would generate Java code. The subsequent "Format generated code" step only formats nodejs, dotnet, and rust β Java output would be committed **unformatted**.
+- **Changes needed**:
+ - Ensure the `generate` npm script in package.json does NOT include Java.
+ - OR add an explicit `java/**` exclusion from the `git add -A` / commit step.
+ - The "Format generated code" step currently only references nodejs, dotnet, rust β if Java codegen is ever included, add `cd ../java && mvn spotless:apply` there, or better, keep Java out of this workflow entirely.
+
+### 3. publish.yml
+- **Problem**: Manually triggered, but the `github-release` job creates a GitHub Release with auto-generated notes (`--generate-notes`). These notes include **all** PRs merged since the last tag, including Java PRs. Java changes would appear in release notes for a release that doesn't include Java.
+- **Changes needed**:
+ - Add `--exclude-labels java` or similar filtering to the `gh release create` steps so Java-only PRs are excluded from the release notes.
+ - Alternatively, document that the release-changelog.md agentic workflow is the source of truth and configure `--notes ""` plus rely on the changelog agent to filter.
+
+### 4. release-changelog.md
+- **Problem**: Gathers ALL merged PRs/commits between tags and categorizes by SDK. Java PRs would be included in the changelog. The prompt says "TypeScript and C# as the primary languages" for code snippets and only lists Node, Python, Go, .NET. Java changes would either be miscategorized or lumped into "Other changes."
+- **Changes needed**:
+ - Add explicit instruction in the prompt: "Exclude PRs that only touch `java/**` β the Java SDK has its own independent release and changelog."
+ - Or add a filter step before the agent runs that identifies and excludes Java-only commits.
+
+---
+
+## MEDIUM RISK β Workflows where Java content would cause incorrect agent behavior
+
+### 5. sdk-consistency-review.md
+- **Problem**: Path triggers are `nodejs/**`, `python/**`, `go/**`, `dotnet/**` β does NOT include `java/**`. The "SDK Locations" in the prompt lists only 4 SDKs. If a PR touches both `dotnet/**` and `java/**`, the workflow fires (due to dotnet), but the agent would **not check Java for consistency** because it's not in its list.
+- **Changes needed**:
+ - This is a deliberate non-inclusion per the plan (Java is to be added later). But to ensure the agent doesn't stumble upon java and give incorrect consistency advice, add a note to the prompt: "The java directory exists but is managed separately β do not include it in your consistency analysis."
+
+### 6. issue-triage.md
+- **Problem**: The `add-labels.allowed` list includes `sdk/dotnet`, `sdk/go`, `sdk/nodejs`, `sdk/python` but NOT `sdk/java`. A Java-related issue would not receive proper SDK labeling. The agent's description says "implementations in .NET, Go, Node.js, and Python" β no Java.
+- **Changes needed**:
+ - Until Java labeling is desired: add a note to the prompt telling the agent that java exists but should not receive an SDK label (or should receive a generic label like `needs-info` and be left for human triage).
+
+### 7. issue-classification.md
+- **Problem**: Description says "multi-language SDK (Node.js/TypeScript, Python, Go, .NET)." A Java-related issue would still be classified (bug/enhancement/question/documentation are language-agnostic categories), but the agent lacks Java context and might misjudge whether behavior is a bug vs. working-as-designed.
+- **Changes needed**:
+ - Add a note: "The java SDK also exists in this repository but has a separate triage process. If an issue specifically concerns the Java SDK, classify it normally but note in your comment that it relates to the Java SDK."
+
+### 8. handle-bug.md
+- **Problem**: The investigation instructions tell the agent to search src, copilot, go, src β **not** java. A Java bug report would be investigated only in the other SDKs, leading to incorrect conclusions.
+- **Changes needed**:
+ - Add a guard: "If the issue specifically references the Java SDK (java), note this in your comment and do not attempt to analyze Java source code β the Java SDK is maintained separately."
+
+### 9. cross-repo-issue-analysis.md
+- **Problem**: Same as handle-bug β explicit directory list for searching SDK code doesn't include java. A Java SDK issue labeled `runtime triage` would be analyzed without looking at the Java code.
+- **Changes needed**:
+ - Add `java/src/` to the list of SDK directories to search, or add the same "Java is maintained separately" guard.
+
+---
+
+## LOW RISK β Workflows with minimal or no concern
+
+### 10. scenario-builds.yml
+- **Problem**: The `find test/scenarios` commands look for language-specific markers (`*/typescript/package.json`, `*/python/main.py`, `*/go/go.mod`, `*.csproj`). None of these match Java files. The path triggers include `test/scenarios/**`, so Java scenario changes would trigger the workflow but **no Java job would run** (wasted CI only).
+- **Changes needed**:
+ - Minor: add `!test/scenarios/**/java/**` to paths or rely on the fact that no job processes Java files. No functional risk.
+
+### 11. docs-validation.yml
+- **Problem**: PR trigger includes `docs/**`. If Java code snippets (```java fenced blocks) are added to docs, the extraction scripts might pull them but there's no `validate:java` step. Depending on the extraction script behavior, these could be silently skipped or cause errors.
+- **Changes needed**:
+ - Check docs-validation extraction scripts. If they extract by fenced code block language tag, ensure unknown languages are gracefully skipped. If they extract all blocks, add Java to the skip list.
+
+### 12. copilot-setup-steps.yml
+- **No risk**. Only triggers on changes to itself. Installs dependencies for nodejs, python, go, dotnet. Java would need its own additions to this file eventually, but the existing workflow won't touch java.
+
+### 13. verify-compiled.yml
+- **No risk (actually desired)**. Triggers on `.github/workflows/*.md` and `.lock.yml`. If Java agentic workflows (like `java-codegen-fix.md`) are added to workflows, this workflow correctly verifies their lock files. This is **correct behavior**, not accidental pickup.
+
+### 14. collect-corrections.yml
+- **No risk**. Triggers on `repository_dispatch` and `workflow_dispatch`. Runs a specific JS script. Language-agnostic.
+
+### 15. corrections-tests.yml
+- **No risk**. Only triggers on `scripts/corrections/**`. No language-specific content.
+
+### 16. handle-documentation.md, handle-enhancement.md, handle-question.md
+- **No risk**. These just add labels. No language-specific logic or code search.
+
+---
+
+## Summary of key protective measures
+
+| # | Action | Workflows protected |
+|---|--------|-------------------|
+| 1 | Keep `java.ts` out of the package.json `generate` script | codegen-check.yml, update-copilot-dependency.yml |
+| 2 | Add `paths-ignore: ['java/**']` to codegen-check.yml push trigger | codegen-check.yml |
+| 3 | Filter Java PRs from release notes generation | publish.yml, release-changelog.md |
+| 4 | Add "Java is separately managed" guardrails to agentic prompts | issue-classification.md, issue-triage.md, handle-bug.md, cross-repo-issue-analysis.md, sdk-consistency-review.md |
+
+### 17:31 Prompt
+
+I'm not familiar with `just`. From what language ecosystem does this come? Do Java shops use it?
+
+### 17:33 Response
+
+
+
+`just` is a **command runner** inspired by `make`, written in **Rust** by Casey Rodarmor. It comes from the Rust ecosystem but is language-agnostic β it's essentially a modern, simpler alternative to `Makefile` for running project-specific commands.
+
+**Do Java shops use it?** Rarely. Java projects overwhelmingly rely on **Maven** or **Gradle** as their task runners (via lifecycle phases, plugins, and custom tasks). `just` is more popular in Rust, Go, and polyglot/scripting-heavy repos where there's no dominant build tool that already serves as a command runner.
+
+In this monorepo (`copilot-sdk-00`), `just` makes sense because it unifies commands across 5+ language ecosystems (`just test`, `just format`, `just lint`) β each of which has its own native build tool underneath. The justfile delegates to `npm`, go, dotnet, `uv`, `cargo`, etc. per language.
+
+### 17:33 Prompt
+
+But might it make sense to simply not make `just` touch the Java?
+
+### 17:34 Response
+
+
+
+Yes, absolutely. Maven already serves as Java's command runner, and every Java developer already knows `mvn verify`, `mvn spotless:apply`, etc. Adding `just` targets for Java would mean:
+
+- Maintaining a **redundant indirection layer** (`just test-java` β `cd java && mvn verify`)
+- Requiring Java contributors to install a tool (`just`) that adds zero value over what Maven already provides
+- Creating a discoverability problem β Java devs would need to know to look in the justfile instead of the pom.xml
+
+The other languages in the monorepo benefit from `just` because their native tools don't provide a unified CLI across the repo. But Maven is already a self-contained build/test/format/lint/publish lifecycle β it doesn't need wrapping.
+
+The practical approach: **don't add Java targets to the justfile**. Document in README.md that Java uses `mvn` commands directly, same as it does today. The top-level `just test` / `just format` / `just lint` recipes would continue to cover the other 5 SDKs, and Java would stay self-contained under java.
+
+### 17:39 Prompt
+
+The file `temporary-prompts/dd-2989727-move-java-to-monorepo-plan.md` was output from our work yesterday. One thing I am aware you are missing is what to do about the `src/test/prompts/PROMPT-smoke-test.md` smoke test. This test currently assumes a specific structure of the top level `README.md`. We will need to update this file and the `README.md` itself on the monorepo side so it can continue to function. Add an "Appendix C: Java smoketest" section describing how to handle this. Cover the workflows that invoke this.
+
+Make a reference to Appendix C at the first mention of the smoke test, which is here:
+
+```
+| `run-smoke-test.yml` | Builds SDK, installs locally, runs Copilot CLI smoke test on JDK 17 + JDK 25 | Java | M |
+```
+
+Update that **Brief Description** cell to include a reference to your new Appendix C.
+
+### 17:54 Response
+
+Searched for files matching `copilot-sdk-java-00` Β· `.github/workflows/run-smoke-test*`, 1 match
+
+Read [](file:///e%3A/workareas/copilot-sdk-java-00/src/test/prompts/PROMPT-smoke-test.md#1-1), lines 1 to 100
+
+Read [](file:///e%3A/workareas/copilot-sdk-java-00/README.md#1-1), lines 1 to 100
+
+Read [](file:///e%3A/workareas/copilot-sdk-java-00/src/test/prompts/PROMPT-smoke-test.md#100-100), lines 100 to 136
+
+Read [](file:///e%3A/workareas/copilot-sdk-java-00/.github/workflows/run-smoke-test.yml)
+
+Now let me also check the monorepo's README to understand what exists there, and find the end of the plan document for the appendix:
+
+Read [](file:///e%3A/workareas/copilot-sdk-java-00/temporary-prompts/dd-2989727-move-java-to-monorepo-plan.md#360-360), lines 360 to 500
+
+Read [](file:///e%3A/workareas/copilot-sdk-00/README.md#1-1), lines 1 to 50
+
+Read [](file:///e%3A/workareas/copilot-sdk-java-00/temporary-prompts/dd-2989727-move-java-to-monorepo-plan.md#500-500), lines 500 to 539
+
+Now I have all the context. Let me also check the monorepo's README.md to understand what exists there:
+
+Read [](file:///e%3A/workareas/copilot-sdk-00/java/README.md#1-1), lines 1 to 50
+
+Read [](file:///e%3A/workareas/copilot-sdk-java-00/README.md#100-100), lines 100 to 100
+
+Read [](file:///e%3A/workareas/copilot-sdk-java-00/README.md#60-60), lines 60 to 150
+
+Now I have full context. Let me also check if there are any workflows that call run-smoke-test.yml:
+
+Searched for text `run-smoke-test` (`copilot-sdk-java-00` Β· `.github/workflows/**`), 1 result
+
+Read [](file:///e%3A/workareas/copilot-sdk-java-00/.github/workflows/build-test.yml#20-20), lines 20 to 40
+
+Searched for text `smoke-test` (`copilot-sdk-java-00` Β· `.github/workflows/**`), 18 results
+
+Now I have all the context. Let me also check the `publish-maven.yml` to see if it also calls the smoke test:
+
+Searched for text `smoke` (`copilot-sdk-java-00` Β· `.github/workflows/publish-maven.yml`), no results
+
+Good, only build-test.yml calls the smoke test. Now I have everything I need. Let me make the two edits to the plan document:
+
+Replacing 1 lines with 1 lines in [](file:///e%3A/workareas/copilot-sdk-java-00/temporary-prompts/dd-2989727-move-java-to-monorepo-plan.md)
+
+
+```
+
+```
+Now find the exact end of the document to add Appendix C:
+
+Read [](file:///e%3A/workareas/copilot-sdk-java-00/temporary-prompts/dd-2989727-move-java-to-monorepo-plan.md#539-539), lines 539 to 539
+
+Read [](file:///e%3A/workareas/copilot-sdk-java-00/temporary-prompts/dd-2989727-move-java-to-monorepo-plan.md#535-535), lines 535 to 539
+
+Replacing 1 lines with 103 lines in [](file:///e%3A/workareas/copilot-sdk-java-00/temporary-prompts/dd-2989727-move-java-to-monorepo-plan.md)
+
+
+```
+
+```
+Now add Appendix C to the table of contents:
+
+Read [](file:///e%3A/workareas/copilot-sdk-java-00/temporary-prompts/dd-2989727-move-java-to-monorepo-plan.md#1-1), lines 1 to 12
+
+Replacing 8 lines with 11 lines in [](file:///e%3A/workareas/copilot-sdk-java-00/temporary-prompts/dd-2989727-move-java-to-monorepo-plan.md)
+
+
+```
+
+```
+Done. Three edits made to dd-2989727-move-java-to-monorepo-plan.md:
+
+1. **Table of Contents** β added links for Appendices A, B, and C
+2. **run-smoke-test.yml row** β appended "(see Appendix C)" to the description
+3. **Appendix C: Java Smoketest** β new section at the end covering:
+ - How the smoke test works today (the prompt-driven design where PROMPT-smoke-test.md instructs Copilot CLI to read the README, extract Quick Start code, build, and run)
+ - What breaks (the prompt says "top level README" but post-migration that's the monorepo README, not README.md)
+ - The four required changes: update the prompt's README path, ensure README.md has the real content (not the monorepo placeholder), add `working-directory: ./java` to the workflow, and update the reusable workflow reference
+ - Risk table and verification checklist
+
+Made changes.
+
+### 17:55 Prompt
+
+I consider it essential that the work to implement the plan described in `dd-2989727-move-java-to-monorepo-plan.md` be done in a topic branch in the `upstream` remote for `copilot-sdk-00`. I am also aware that many of the CI/CD aspects require things to be on `main`. Without changing `dd-2989727-move-java-to-monorepo-plan.md` just yet, look at the "Risk Register" section and describe some of the risks of this `main` requirement. Or am I misunderstanding the risk and it's possible to iterate and make changes to CI/CD without requiring stuff reside on `main`?
+
+### 18:00 Response
+
+You're right to flag this β there is real tension, but it's more nuanced than "everything needs `main`." Let me break down exactly what can and can't be validated on a topic branch.
+
+## What WORKS on a topic branch (via PR)
+
+GitHub Actions uses the **PR branch's version** of workflow files for `pull_request`-triggered workflows. So these are fully testable:
+
+- **`java-sdk-tests.yml`** (the core CI) β `pull_request` with path triggers works from a branch
+- **codegen-check.yml** changes β same, `pull_request` trigger
+- **Reusable workflow calls** (`uses: ./.github/workflows/java-smoke-test.yml`) β resolved from the PR branch
+- **Composite actions** (`uses: .github.`) β same
+- **All source code** β `java/src/`, `java/pom.xml`, test infrastructure
+- **`CODEOWNERS`**, **`dependabot.yaml`**, **justfile**, **copilot-instructions.md** β take effect on merge
+
+So the core development loop β "does Java build, test, and pass CI in the monorepo" β is fully achievable on a topic branch.
+
+## What CANNOT be tested on a topic branch
+
+### 1. Smoke test β hard-gated on `main`
+
+Both jobs in run-smoke-test.yml have:
+```yaml
+if: github.ref == 'refs/heads/main'
+```
+On a PR, `github.ref` is `refs/pull/NNN/merge`, so both jobs are **unconditionally skipped**. You cannot validate the smoke test migration works until the workflow is on `main`. This is the most relevant risk from the register given your Appendix C work.
+
+### 2. `workflow_dispatch`-only workflows
+
+These can only be manually triggered from branches that **already exist in the repo** and are selectable in the Actions UI. New workflow files on a topic branch won't appear in the dispatch dropdown until they're on the default branch (or at least pushed). The affected workflows:
+- `java-publish.yml`
+- `java-publish-snapshot.yml`
+- `java-deploy-site.yml`
+- `java-smoke-test.yml` (when triggered manually)
+- update-copilot-dependency.yml
+
+### 3. Scheduled workflows
+
+`schedule` triggers **only run from the default branch**. So `java-reference-impl-sync.md` (the daily check for new dotnet/nodejs changes) won't execute until it's on `main`.
+
+### 4. copilot-setup-steps.yml
+
+The Copilot coding agent reads this **exclusively from the default branch**. Adding JDK 17 + Maven setup won't take effect for agent sessions until merged to `main`. This means you can't use the coding agent to help with Java tasks in the monorepo until this lands.
+
+### 5. Required status checks β chicken-and-egg
+
+You can't add `java-sdk-tests` as a required check in branch protection until the workflow has **run at least once** and GitHub knows the check name exists. First run happens on the PR, but you can't enforce it as required until after merge.
+
+### 6. Secrets availability
+
+For `pull_request` events, secrets are available for PRs from branches **within the same repo** (not forks). Since this is an internal topic branch on the `upstream` remote, `COPILOT_GITHUB_TOKEN` and other secrets should be accessible. This is **not** a blocker β but it's worth verifying the repo's settings haven't restricted secrets to protected branches only.
+
+### 7. GitHub Pages deployment
+
+Pages deployment is typically configured for `main`. The `java-deploy-site.yml` workflow can't be validated until on `main`.
+
+## Practical implication
+
+The migration is naturally split into what the plan calls Phases 1-2 (source code + core CI) vs. Phases 3-4 (publish, smoke test, agentic workflows). Phases 1-2 are fully testable on a topic branch. Phases 3-4 inherently require `main`.
+
+The key question is whether you merge Phases 1-2 as one PR and then iterate Phases 3-4 as follow-up PRs on `main`, or whether you try to land everything in one big PR and accept that the `main`-gated pieces are unvalidated until merge. The former is safer β a broken smoke test workflow is recoverable, but a broken `java-sdk-tests.yml` that blocks the merge queue is not.
+
+The risk register items H1 (Maven Central Trusted Publisher), H4 (secret provisioning), and M3 (branch protection) are all in this "can't validate until `main`" category, which is why Phase 0 (pre-flight) exists β those should be resolved **before** any code lands.
+
+### 18:25 Prompt
+
+Consider this "MEDIUM RISK" item:
+
+```
+| M1 | **Codegen `package.json` merge** | Java codegen has its own `@github/copilot` dependency; monorepo codegen gets it from `nodejs/node_modules` | Align Java codegen to use the same dependency source. May need to add `generate:java` script to monorepo's `scripts/codegen/package.json`. |
+```
+
+I am inclined to **not** put the codegen into the monorepo's
+
+```
+| `.github/workflows/codegen-check.yml` | **Merge** into existing | `.github/workflows/codegen-check.yml` |
+```
+
+For fear of messing up existing non-Java aspects. What are the pros and cons of keeping this separate?
+
+### 18:28 Prompt
+
+Good instinct. This is the same logic as the `just` discussion β Java is self-contained and doesn't need to be force-fitted into the existing cross-language machinery.
+
+## Pros of keeping Java codegen separate (`java-codegen-check.yml`)
+
+- **Zero blast radius** β you cannot break Node/C#/Python/Go/Rust codegen. This is the strongest argument.
+- **No JDK in codegen-check.yml** β the existing workflow installs Node, Go, and Rust. Adding JDK + Maven for one more language bloats it and slows it for everyone.
+- **update-copilot-dependency.yml stays untouched** β that workflow runs `npm run generate` and formats for nodejs/dotnet/rust. Keeping Java out means no changes to a workflow that touches all other languages.
+- **Independent `@github/copilot` version cadence** β Java already lags the reference impl by design (it's ported, not in lockstep). Forcing the same `@github/copilot` version across all codegen could break Java if a schema change hasn't been ported yet.
+- **Consistent with Java's independent versioning** β Java already has its own publish workflow, its own version scheme. Separate codegen follows the same principle.
+
+## Cons of keeping it separate
+
+- **`@github/copilot` version drift** β when the monorepo updates to `@github/copilot@1.0.50`, Java might still be on `1.0.45`. This is intentional but could confuse contributors who expect uniformity. Need to document it.
+- **Two codegen check workflows** β more YAML to maintain, two required status checks instead of one.
+- **Java needs its own `update-copilot-dependency` mechanism** β the existing workflow won't cover Java, so you need a separate `java-update-copilot-dependency.yml` (or keep the one from `copilot-sdk-java` adapted for monorepo paths).
+- **java.ts location is awkward** β if the file lives in codegen alongside the others but isn't in the shared `generate` script, it's confusing. If it lives in `java/scripts/codegen/`, it's clear but diverges from the monorepo layout.
+
+## Recommendation
+
+Keep it separate. The version-drift con is actually a **feature** β it's how the Java SDK already works, and the `reference-impl-sync` mechanism handles catching up deliberately. The `java.ts` location question resolves cleanly: put it in `java/scripts/codegen/` with its own package.json, making the boundary explicit. This also means Appendix A should change from:
+
+> java.ts β Copy β java.ts
+
+to:
+
+> java.ts β Copy β `java/scripts/codegen/java.ts`
+
+### 18:28 Prompt
+
+Yes, do change Appendix A.
+
+### 18:29 Response
+
+Replacing 2 lines with 2 lines in [](file:///e%3A/workareas/copilot-sdk-java-00/temporary-prompts/dd-2989727-move-java-to-monorepo-plan.md)
+
+
+```
+
+```
+Done. Both the `java.ts` and its package.json now stay self-contained under `java/scripts/codegen/` instead of merging into the monorepo's shared codegen.
+
+Made changes.
+
+### 18:30 Prompt
+
+Actually, I am now keeping `dd-2989727-move-java-to-monorepo-plan.md` under version control in `.\80-java-monorepo-add-01-remove-before-merge\dd-2989727-move-java-to-monorepo-plan.md` in `copilot-sdk-00`. Make the changes there.
+
diff --git a/80-java-monorepo-add-01-remove-before-merge/20260513-prompts.md b/80-java-monorepo-add-01-remove-before-merge/20260513-prompts.md
new file mode 100644
index 000000000..b509025e8
--- /dev/null
+++ b/80-java-monorepo-add-01-remove-before-merge/20260513-prompts.md
@@ -0,0 +1,442 @@
+### 14:07 Prompt
+
+The plan calls for
+
+```
+6. Update `CODEOWNERS`:
+ - Add `java/ @github/copilot-sdk-java`
+```
+
+Every human member of the `copilot-sdk-java` team is already a member of `@github/copilot-sdk-java`. So I assert no further action is necessary on this file in `copilot-sdk-00`. Do you agree?
+
+### 14:09 Response
+
+Iβm going to inspect the existing `CODEOWNERS` entry and the surrounding repo setup so I can answer your assertion against what the plan item is actually doing.
+
+Searched for files matching `copilot-sdk-00` Β· `**/CODEOWNERS`, 1 match
+
+Searched for regex `copilot-sdk-java|java/` (`copilot-sdk-00`), 50 results
+
+Iβve confirmed the plan item is specifically about path ownership in the monorepo, not about whether the same humans are already covered indirectly; next Iβm reading the current CODEOWNERS file itself.
+
+Read [](file:///e%3A/workareas/copilot-sdk-00/.github/CODEOWNERS)
+
+I donβt agree.
+
+Right now CODEOWNERS contains only:
+
+```text
+* @github/copilot-sdk
+```
+
+So adding `java/ @github/copilot-sdk-java` would still change behavior in an important way: it makes java reviews route to the Java team explicitly, rather than relying on broad ownership by `@github/copilot-sdk`. The fact that the human members overlap today doesnβt make the entry redundant, because CODEOWNERS is about repository policy and future-proof routing, not just current membership coincidence.
+
+Two practical reasons the explicit java entry still matters:
+- it documents that java has a distinct ownership boundary inside the monorepo
+- it preserves the intended reviewers if team membership diverges later, or if branch protection / required review rules key off CODEOWNERS matches
+
+So my recommendation is: keep the plan item, unless your actual policy decision is that Java should remain owned by the general monorepo team with no special routing.
+
+### 14:11 Prompt
+
+Is this correct:
+
+The purpose of the `CODEOWNERS` file is to establish an explicit per-path mapping between paths and teams responsible for maintaining the content within those paths.
+
+### 14:12 Response
+
+Yes, thatβs basically correct.
+
+A slightly tighter version would be:
+
+CODEOWNERS establishes explicit ownership rules that map repository paths to users or teams, mainly so GitHub can automatically request reviews and apply code-owner-based branch protection behavior for changes to those paths.
+
+That wording matters because βresponsible for maintainingβ is true in a social sense, but the fileβs concrete function is operational: review routing and policy enforcement.
+
+### 14:12 Prompt
+
+But does it make sense for path entries in `CODEOWNERS` to "reach out" of the current repo in which the `CODEOWNERS` file resides?
+
+### 14:13 Response
+
+No. That would not make sense.
+
+CODEOWNERS only has meaning within the repository that contains it. Its path patterns match paths in that same repo, and its owners must be users or teams that GitHub can resolve for that repo. It does not express ownership for files in some other repository, and it does not βreach outβ across repo boundaries.
+
+So if a CODEOWNERS entry in `github/copilot-sdk` says `java/ @github/copilot-sdk-java`, that is still about ownership of the java directory inside `github/copilot-sdk`, not about the separate `github/copilot-sdk-java` repository. The team name may happen to reference the Java maintainers, but the scope of the rule remains local to the monorepo.
+
+### 14:13 Prompt
+
+So what is the meaning of the RHS of an entry in `CODEOWNERS`, if it's not a repo identifier?
+
+### 14:14 Response
+
+The RHS is an owner specifier, not a repository identifier.
+
+In GitHub CODEOWNERS, the right-hand side names the users or teams who own matching paths for that repositoryβs review/policy purposes. Those values are things like:
+
+- `@username`
+- `@org/team-name`
+- sometimes an email address, depending on GitHub context, though teams/users are the common case
+
+So in:
+
+```text
+java/ @github/copilot-sdk-java
+```
+
+- java means βthe java path inside this repositoryβ
+- `@github/copilot-sdk-java` means βthe GitHub team named `copilot-sdk-java` in the `github` organizationβ
+
+That RHS does not point to a repo. It points to a principal GitHub can use as a code owner for matching files in the current repo.
+
+### 14:15 Prompt
+
+So how does one browse the teams registry within the `github` organization?
+
+### 14:15 Response
+
+If you have the right GitHub org permissions, the usual way is in the GitHub web UI:
+
+`github` organization page -> `Teams`
+
+From there you can browse the team list, search by name, and open a team like `copilot-sdk-java`.
+
+Direct URL shape is usually:
+
+```text
+https://github.com/orgs/github/teams
+https://github.com/orgs/github/teams/copilot-sdk-java
+```
+
+A couple of practical wrinkles:
+- whether you can actually view the full team registry depends on your org visibility and permissions
+- some teams may be secret or partially hidden, so you might not see everything even inside the org
+- for a repo-specific check, the more relevant question is whether `@github/copilot-sdk-java` has access to `github/copilot-sdk`, not just whether the team exists
+
+If you want, I can help you phrase the exact criterion for the migration plan: βteam exists,β βteam is visible,β or βteam has sufficient repo access.β
+
+### 14:24 Prompt
+
+Review the change to this file for syntactic and semantic correctness.
+
+### 14:25 Response
+
+Iβm going to inspect the current CODEOWNERS diff and the surrounding repo state so I can review both syntax and what the new rule would actually do.
+
+Read changed files in [](file:///e%3A/workareas/copilot-sdk-00)
+
+Read [](file:///e%3A/workareas/copilot-sdk-00/.github/CODEOWNERS)
+
+Findings first: I donβt see a syntax problem in CODEOWNERS.
+
+Semantically, the new line is correct if your intent is βfiles under java in this monorepo should be code-owned by the `@github/copilot-sdk-java` team.β In GitHub CODEOWNERS, the later, more specific rule wins, so:
+
+```text
+* @github/copilot-sdk
+java/ @github/copilot-sdk-java
+```
+
+means the whole repo defaults to `@github/copilot-sdk`, while java is explicitly owned by the Java team. That is valid and coherent.
+
+Open question / assumption: this only works operationally if `@github/copilot-sdk-java` is a resolvable team and has the necessary access to the `github/copilot-sdk` repository. If that team is missing, hidden in a way GitHub canβt use here, or lacks repo access, the entry can be semantically wrong in practice even though the file syntax is fine.
+
+Change summary: the edit is syntactically fine and semantically appropriate for a monorepo path-specific ownership rule. The only external dependency is team existence plus repo access.
+
+### 14:25 Prompt
+
+I created the `@github/copilot-sdk-java` team as a "child" of `@github/copilot-sdk`. Does that guarantee path access?
+
+### 14:28 Response
+
+No, that does not guarantee it.
+
+A parent/child team relationship in GitHub mainly helps with organization and inherited membership/visibility patterns, but it does not by itself guarantee that the child team is usable as a code owner for a repo unless that team also has appropriate access to that repository.
+
+For CODEOWNERS, the practical check is still:
+- does `@github/copilot-sdk-java` exist as a team in the `github` org?
+- does that team have access to the `github/copilot-sdk` repository?
+- is the team visible/usable enough for GitHub to resolve it in code owner rules?
+
+So making `copilot-sdk-java` a child of `copilot-sdk` is suggestive and often helpful administratively, but I would not treat it as proof. The thing you want to verify is explicit repo access on `github/copilot-sdk` for that child team.
+
+### 14:28 Prompt
+
+You stated
+
+> - does `@github/copilot-sdk-java` exist as a team in the `github` org?
+
+Yes. I created it.
+
+> - does that team have access to the `github/copilot-sdk` repository?
+
+Well, I do. But that's probably because I have been added to that repo. How can I make sure the "team" entity has access?
+
+### 14:29 Response
+
+You want to check the team-to-repo grant directly, not infer it from your personal access.
+
+In the GitHub UI, the cleanest path is to open the team and inspect its repositories:
+`Organization` -> `Teams` -> `copilot-sdk-java` -> `Repositories`
+
+If `github/copilot-sdk` appears there, the team has been granted access. If it does not, the team itself does not currently have repo access, even if you personally do.
+
+The inverse view also works:
+`github/copilot-sdk` -> `Settings` -> `Collaborators and teams`
+
+That page should list `copilot-sdk-java` as a team with a permission level such as `Read`, `Write`, `Maintain`, or `Admin`. For CODEOWNERS, the important thing is that GitHub can resolve the team for that repo; in practice, checking that it appears there is the reliable test.
+
+So the short answer is: look for the team on the repoβs `Collaborators and teams` page, or look for the repo on the teamβs `Repositories` page. That tells you about the team entity, not just you as an individual.
+
+### 15:01 Prompt
+
+I am working on https://github.com/github/copilot-sdk-partners/issues/90 now. Use `gh` to inspect this work item.
+
+I come to the `RELEASE_TOKEN` secret.
+
+I expect it would be wise to scope this token so its name is `JAVA_RELEASE_TOKEN`. I expct this would require changes in the workflow files in `copilot-sdk-java-00` as they are moved to `copilot-sdk-00`. Inspect the workflow files in the former and tell me what must change to honor `JAVA_RELEASE_TOKEN` instead of the unscoped `RELEASE_TOKEN`.
+
+### 15:04 Response
+
+Iβll search for workflow files in copilot-sdk-java-00 that reference `RELEASE_TOKEN` and identify what would need to change to use `JAVA_RELEASE_TOKEN` instead.
+
+Searched for text `RELEASE_TOKEN` (`copilot-sdk-java-00` Β· `.github/workflows/**`), 1 result
+
+Read [](file:///e%3A/workareas/copilot-sdk-java-00/.github/workflows/publish-maven.yml#30-30), lines 30 to 60
+
+Read [](file:///e%3A/workareas/copilot-sdk-java-00/.github/workflows/publish-maven.yml#1-1), lines 1 to 30
+
+The only workflow reference to `RELEASE_TOKEN` is in the publish-maven.yml file, specifically as the `token` for the `actions/checkout` step.
+
+To switch to `JAVA_RELEASE_TOKEN`, you must:
+- Change all instances of `${{ secrets.RELEASE_TOKEN }}` to `${{ secrets.JAVA_RELEASE_TOKEN }}` in publish-maven.yml.
+
+No other workflow files reference `RELEASE_TOKEN`, so only this change is required. If you move or adapt this workflow into the monorepo, ensure the secret is set as `JAVA_RELEASE_TOKEN` in the repoβs GitHub Actions secrets.
+
+### 15:12 Prompt
+
+We must do the same thing with `GPG_SECRET_KEY` and `GPG_PASSPHRASE`. Those should have the `JAVA` prefix. Analyze the workflows in `copilot-sdk-java-00` and list what needs to change.
+
+### 15:22 Response
+
+Iβll search for all workflow references to `GPG_SECRET_KEY` and `GPG_PASSPHRASE` in copilot-sdk-java-00 to identify what must be updated to use `JAVA_GPG_SECRET_KEY` and `JAVA_GPG_PASSPHRASE`.
+
+Searched for text `GPG_SECRET_KEY` (`copilot-sdk-java-00` Β· `.github/workflows/**`), 1 result
+
+Searched for text `GPG_PASSPHRASE` (`copilot-sdk-java-00` Β· `.github/workflows/**`), 5 results
+
+Read [](file:///e%3A/workareas/copilot-sdk-java-00/.github/workflows/publish-maven.yml#60-60), lines 60 to 180
+
+To use `JAVA_GPG_SECRET_KEY` and `JAVA_GPG_PASSPHRASE` instead of the unscoped names, you must:
+
+- Change all instances of `${{ secrets.GPG_SECRET_KEY }}` to `${{ secrets.JAVA_GPG_SECRET_KEY }}`
+- Change all instances of `${{ secrets.GPG_PASSPHRASE }}` to `${{ secrets.JAVA_GPG_PASSPHRASE }}`
+
+in the following places in publish-maven.yml:
+- The `gpg-private-key` input to `actions/checkout` (or any other action using it)
+- All `env:` blocks that set `GPG_PASSPHRASE` for Maven commands
+
+Summary of lines to update:
+- Line 62: `gpg-private-key: ${{ secrets.GPG_SECRET_KEY }}`
+- Line 160, 170: `GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}` (in two steps)
+- Any other direct references to these secrets in the workflow
+
+No other workflow files reference these secrets, so only this file needs updating. Also, ensure the new secrets are set in the repoβs GitHub Actions secrets.
+
+### 15:39 Prompt
+
+Given that I just used this command to obtain the value for the new `JAVA_GPG_SECRET_KEY` secret, how would I correctly archive the full set of information needed to hand off this keypair to another party, assuming full trust.
+
+### 15:42 Response
+
+For a complete, transferable handoff of a GPG signing identity, archive all of these items together:
+
+1. Secret key material
+- Export the secret key in armored form.
+- If you have subkeys, export the full secret key bundle, not just a single subkey.
+
+2. Public key
+- Export the corresponding public key (armored).
+- This lets the recipient re-import and verify identity metadata cleanly.
+
+3. Fingerprint and key metadata
+- Record full fingerprint, key ID, creation date, expiration date, and UID(s).
+- Include the expected signing subkey fingerprint if different.
+
+4. Revocation certificate
+- Include the pre-generated revocation certificate.
+- This is critical if compromise is later suspected.
+
+5. Trust/ownertrust data (optional but useful)
+- Export ownertrust so recipient can restore trust settings quickly.
+
+6. Passphrase handling
+- If passphrase-protected, provide the passphrase separately from the key archive, even in a full-trust handoff.
+- Best practice is two channels (for example: file transfer for key archive, voice/secure chat for passphrase).
+
+7. Operational notes
+- What this key is used for (for example, Maven release signing).
+- Where it is expected to be installed (CI secret name, local keyring use, etc.).
+- Rotation/expiry policy and who owns lifecycle management.
+
+Recommended packaging flow:
+1. Put exported files plus a README into one folder.
+2. Create a tar/zip archive.
+3. Encrypt that archive to the recipient (or recipients) with their public key.
+4. Sign the encrypted archive with your current signing key.
+5. Transfer.
+6. Recipient verifies your signature and fingerprint out-of-band before use.
+
+Minimal file set to include:
+- secret-key.asc
+- public-key.asc
+- fingerprint.txt
+- revocation.asc
+- ownertrust.txt
+- handoff-notes.txt
+
+If you want, I can draft a concrete Windows PowerShell command sequence for creating this bundle and producing an encrypted handoff artifact end-to-end.
+
+### 15:45 Prompt
+
+Write it as a bash script to `80-java-monorepo-add-01-remove-before-merge/ghcpsp-90-gpg-key-archive.sh`
+
+### 15:46 Response
+
+Iβm going to check whether that script file already exists, then Iβll create it with an end-to-end export, bundle, encrypt, and sign flow you can run directly.
+
+Searched for files matching `copilot-sdk-00` Β· `80-java-monorepo-add-01-remove-before-merge/ghcpsp-90-gpg-key-archive.sh`, no matches
+
+Iβm creating the script now in the requested path with sane defaults, argument validation, and all artifacts needed for a trusted handoff package.
+
+Created [](file:///e%3A/workareas/copilot-sdk-00/80-java-monorepo-add-01-remove-before-merge/ghcpsp-90-gpg-key-archive.sh)
+
+Created the script at ghcpsp-90-gpg-key-archive.sh.
+
+What it does:
+- Validates required tools and arguments
+- Exports secret key, public key, ownertrust, fingerprint details
+- Includes revocation certificate if found in the standard GnuPG location
+- Writes handoff notes with operational guidance
+- Produces both:
+ - plain tar.gz bundle
+ - armored encrypted+signed bundle for transfer
+
+Usage:
+ghcpsp-90-gpg-key-archive.sh [output-dir]
+
+If you want, I can also add a companion verify/import script for the receiving party.
+
+Made changes.
+
+### 15:59 Prompt
+
+Consider this secret from the plan file.
+
+```
+| `COPILOT_GITHUB_TOKEN` | `build-test.yml`, `codegen-agentic-fix.lock.yml` | || Token for Copilot CLI in CI |
+```
+
+Is there anything special about that secret name? Is it some kind of well-known implicit secret name in the agentic machinery? Or can we safely treat it as "local" and therefore safely scope it to Java?
+
+### 16:01 Response
+
+Iβll trace where `COPILOT_GITHUB_TOKEN` is referenced in both repos and check whether itβs treated as a convention or just an ordinary secret wiring choice.
+
+Searched for text `COPILOT_GITHUB_TOKEN` (`copilot-sdk-00`), 153 results
+
+Searched for text `COPILOT_GITHUB_TOKEN` (`copilot-sdk-java-00`), 200 results
+
+I found strong evidence that the *environment variable* `COPILOT_GITHUB_TOKEN` is conventional for the CLI, while the *GitHub secret* name is workflow-configurable; Iβll grab the exact lines so the recommendation is concrete.
+
+Read [](file:///e%3A/workareas/copilot-sdk-00/docs/auth/authenticate.md#232-232), lines 232 to 312
+
+Read [](file:///e%3A/workareas/copilot-sdk-java-00/.github/workflows/codegen-agentic-fix.lock.yml#120-120), lines 120 to 145
+
+Read [](file:///e%3A/workareas/copilot-sdk-java-00/.github/workflows/build-test.yml#20-20), lines 20 to 40
+
+Short answer: it is not a GitHub Actions implicit secret name, but it is a strong convention in Copilot/agentic workflows.
+
+What is special:
+- The SDK/CLI auth lookup explicitly prioritizes the environment variable COPILOT_GITHUB_TOKEN. See authenticate.md and authenticate.md.
+- Agentic lock workflows explicitly validate and wire that name. See codegen-agentic-fix.lock.yml and codegen-agentic-fix.lock.yml.
+- Non-agentic Java CI also passes that name through workflow secrets today. See build-test.yml.
+
+So can you scope the repository secret name to Java?
+- Yes, safely, if you keep the runtime environment variable name as COPILOT_GITHUB_TOKEN.
+- In practice: map secrets.JAVA_COPILOT_GITHUB_TOKEN into env.COPILOT_GITHUB_TOKEN (or equivalent secret pass-through in reusable workflows).
+
+Guidance for migration:
+1. Treat the GitHub secret name as local/configurable.
+2. Preserve COPILOT_GITHUB_TOKEN as the env var presented to CLI/agent steps.
+3. For agentic workflows, update source workflow definitions and recompile lock files rather than hand-editing lock files.
+
+### 16:02 Prompt
+
+Is that secret name used in `copilot-sdk-00`?
+
+### 16:03 Response
+
+Yes.
+
+`COPILOT_GITHUB_TOKEN` is used extensively in copilot-sdk-00, including in multiple workflow lock files and docs. Examples:
+
+- handle-question.lock.yml
+- handle-question.lock.yml
+- release-changelog.lock.yml
+- authenticate.md
+- README.md
+
+So in this repo it is definitely an actively used name.
+
+### 16:03 Prompt
+
+Given its existing use, I will opt to not change its name or usage during the migration. Does this seem sound?
+
+### 16:04 Response
+
+Yes, that is a sound choice.
+
+Keeping `COPILOT_GITHUB_TOKEN` unchanged during migration is low risk because:
+- it is already widely used in `copilot-sdk-00`
+- it matches existing agentic workflow conventions
+- it avoids churn across workflow source + lock files + docs at the same time as the Java move
+
+This is a good βstabilize first, rename later if neededβ decision.
+
+### 16:25 Prompt
+
+I'm back now to `80-java-monorepo-add-01-remove-before-merge/ghcpsp-90-gpg-key-archive.sh`
+
+Please do write the script that the recipient must run to bring the archived key into their environment. Name it appropriately.
+
+### 16:27 Response
+
+Iβm going to inspect the archive script you already have, then Iβll add a matching recipient-side import script in the same folder that verifies, decrypts, extracts, and imports the key material safely.
+
+Read [](file:///e%3A/workareas/copilot-sdk-00/80-java-monorepo-add-01-remove-before-merge/ghcpsp-90-gpg-key-archive.sh)
+
+Iβm adding a companion recipient script now that validates signature/decryption status, checks the expected fingerprint, imports keys, and optionally imports ownertrust.
+
+Created [](file:///e%3A/workareas/copilot-sdk-00/80-java-monorepo-add-01-remove-before-merge/ghcpsp-90-gpg-key-import.sh)
+
+Implemented. I added the recipient-side script at ghcpsp-90-gpg-key-import.sh.
+
+What it does:
+- Decrypts and verifies the signed archive with GPG
+- Confirms signature validity via GPG status output
+- Extracts the bundle safely
+- Parses and reports the expected fingerprint from bundle metadata
+- Imports public key, then secret key
+- Optionally imports ownertrust when requested
+- Verifies imported secret key fingerprint matches metadata
+
+Usage:
+- ghcpsp-90-gpg-key-import.sh
+- ghcpsp-90-gpg-key-import.sh
+- ghcpsp-90-gpg-key-import.sh --import-ownertrust
+
+Natural next step:
+1. Add execute permissions to both scripts if needed and run a dry test with a non-production keypair.
+
+Made changes.
diff --git a/80-java-monorepo-add-01-remove-before-merge/20260515-prompts.md b/80-java-monorepo-add-01-remove-before-merge/20260515-prompts.md
new file mode 100644
index 000000000..6d5e12148
--- /dev/null
+++ b/80-java-monorepo-add-01-remove-before-merge/20260515-prompts.md
@@ -0,0 +1,536 @@
+### 10:11 Prompt
+
+Read the `80-java-monorepo-add-01-remove-before-merge\dd-2989727-move-java-to-monorepo-plan.md` and the two `*-prompts.md` to get the context of what we've been working on.
+
+My current problem is the branch protection rules as tracked in this text in the plan.
+
+```markdown
+- [β ] **Confirm branch protection** β will new required status checks be accepted? See https://github.com/github/copilot-sdk-partners/issues/95 .
+```
+
+The maintainer has this question:
+
+---
+
+## @edburns Can you clarify why we need an action to be able to push directly to main rather than having the action produce a PR?
+
+I know there is a good answer for this, but I can't easily articulate it. Can you help me?
+
+### 10:16 Response
+
+The workflow that needs direct-to-main push is `java-publish.yml` (adapted from `publish-maven.yml`), specifically because of how `maven-release-plugin` works.
+
+Here's the articulation:
+
+---
+
+**Why `maven-release-plugin` requires direct push to `main`:**
+
+The Maven Release Plugin performs an **atomic, multi-step commit sequence** as part of its `release:prepare` + `release:perform` lifecycle:
+
+1. Removes `-SNAPSHOT` from pom.xml version β commits as "prepare release vX.Y.Z"
+2. Creates a git tag `vX.Y.Z` pointing at that exact commit
+3. Bumps pom.xml to next `-SNAPSHOT` version β commits as "prepare for next development iteration"
+4. Pushes both commits + tag in one operation
+5. `release:perform` then checks out the tag and deploys the artifact to Maven Central
+
+**Why a PR doesn't work here:**
+
+- The **tag must point to the exact commit** with the non-SNAPSHOT version on `main`. If that commit lives on a PR branch, the tag references a commit that isn't on the default branch β Maven Central metadata and `` URLs become inconsistent.
+- A PR introduces a **race condition**: other commits could merge between the release commit and the SNAPSHOT-bump commit, breaking the plugin's assumption of consecutive commits.
+- There is **no human decision left to make** β the decision to release was already made when someone triggered the `workflow_dispatch`. The commits are mechanical version bookkeeping, not code changes requiring review.
+- The entire operation is **idempotent and reversible** (revert the two commits + delete the tag) if something goes wrong.
+
+This is the standard pattern used by virtually all Maven projects that release via CI (Spring, Apache, Quarkus, etc.). The alternative β manually managing versions and tags outside the plugin β defeats the purpose of using `maven-release-plugin` and introduces more error-prone manual steps.
+
+---
+
+Want me to draft this as a comment you can post directly on the issue?
+
+### 10:17 Prompt
+
+Yes.
+
+### 10:18 Response
+
+Read [](file:///e%3A/workareas/copilot-sdk-java-00/.github/workflows/publish-maven.yml#1-1), lines 1 to 180
+
+Here's a comment you can post:
+
+---
+
+The workflow that needs this is `java-publish.yml` (the Maven release workflow). It uses `maven-release-plugin`, which performs an atomic multi-commit sequence directly on `main`:
+
+1. Removes `-SNAPSHOT` from pom.xml β commits `[maven-release-plugin] prepare release v1.2.3`
+2. Creates git tag `v1.2.3` pointing at that exact commit
+3. Bumps pom.xml to next `-SNAPSHOT` β commits `[maven-release-plugin] prepare for next development iteration`
+4. Pushes both commits + tag in one operation
+5. Checks out the tag and deploys the signed artifact to Maven Central
+
+**Why a PR can't substitute here:**
+
+- **The tag must point to a commit on `main`.** Maven Central's POM metadata, `` URLs, and reproducibility guarantees all assume the tagged commit is on the default branch. A tag pointing at a PR branch commit breaks downstream tooling (Dependabot, IDE source attachment, `mvn scm:changelog`).
+- **The two commits must be consecutive.** If a PR is opened between the "set release version" commit and the "set next SNAPSHOT" commit, other PRs could merge in between, meaning `main` would contain a non-SNAPSHOT pom.xml that doesn't correspond to any published artifact β a broken state.
+- **There's no remaining human decision.** The release was already authorized by triggering `workflow_dispatch`. The commits are mechanical version bookkeeping, not code changes requiring review.
+- **The workflow already has rollback.** If anything fails, it reverts the documentation commit and runs `mvn release:rollback` (lines 168β177 of the current publish-maven.yml).
+
+This is the standard pattern used by Maven projects that release via CI (Spring, Apache, Quarkus, etc.). The `JAVA_RELEASE_TOKEN` PAT with `contents:write` scope is specifically for this use case.
+
+Note: only this one workflow (`java-publish.yml`) needs the bypass. All other Java workflows (`java-sdk-tests.yml`, `java-smoke-test.yml`, `java-codegen-fix.md`, etc.) operate via normal PRs.
+
+---
+
+### 10:21 Prompt
+
+That's close, but let's take the ADR in `docs/adr/adr-002-maven-version-and-reference-implementation-tracking.md` into account regarding tag names.
+
+You stated:
+
+> 2. Creates git tag `v1.2.3` pointing at that exact commit
+
+In our case, per the ADR, the tag would be `v1.2.3-java.0`. To avoid confusion, rewrite your post-ready response using this tag name wherever you need a tag.
+
+### 10:24 Response
+
+Here's the revised comment:
+
+---
+
+The workflow that needs this is `java-publish.yml` (the Maven release workflow). It uses `maven-release-plugin`, which performs an atomic multi-commit sequence directly on `main`:
+
+1. Removes `-SNAPSHOT` from pom.xml β commits `[maven-release-plugin] prepare release v1.2.3-java.0`
+2. Creates git tag `v1.2.3-java.0` pointing at that exact commit. See the [ADR](https://github.com/github/copilot-sdk-java/blob/main/docs/adr/adr-002-maven-version-and-reference-implementation-tracking.md).
+3. Bumps pom.xml to next `-SNAPSHOT` β commits `[maven-release-plugin] prepare for next development iteration`
+4. Pushes both commits + tag in one operation
+5. Checks out the tag and deploys the signed artifact to Maven Central
+
+**Why a PR can't substitute here:**
+
+- **The tag must point to a commit on `main`.** Maven Central's POM metadata, `` URLs, and reproducibility guarantees all assume the tagged commit is on the default branch. A tag pointing at a PR branch commit breaks downstream tooling (Dependabot, IDE source attachment, `mvn scm:changelog`).
+- **The two commits must be consecutive.** If a PR is opened between the "set release version" commit and the "set next SNAPSHOT" commit, other PRs could merge in between, meaning `main` would contain a non-SNAPSHOT pom.xml that doesn't correspond to any published artifact β a broken state.
+- **There's no remaining human decision.** The release was already authorized by triggering `workflow_dispatch`. The commits are mechanical version bookkeeping, not code changes requiring review.
+- **The workflow already has rollback.** If anything fails, it reverts the documentation commit and runs `mvn release:rollback`.
+
+This is the standard pattern used by Maven projects that release via CI (Spring, Apache, Quarkus, etc.). The `JAVA_RELEASE_TOKEN` PAT with `contents:write` scope is specifically for this use case.
+
+Note: only this one workflow (`java-publish.yml`) needs the bypass. All other Java workflows (`java-sdk-tests.yml`, `java-smoke-test.yml`, `java-codegen-fix.md`, etc.) operate via normal PRs.
+
+---
+
+### 13:57 Prompt
+
+The code in `copilot-sdk-java-00` has been updated. In particular there have been changes to the workflows there. I am aware we have copied over at least one workflow in the process of performing Phase 0 work according to the plan.
+
+Examine the recent workflow changes in `copilot-sdk-java-00` and make the corresponding Java changes to keep the `copilot-sdk-00` side up to date.
+
+## ghcp-sp-95-branch-protection
+
+Consider this text from the plan.
+
+```markdown
+- [β ] **Confirm branch protection** β will new required status checks be accepted? See https://github.com/github/copilot-sdk-partners/issues/95 .
+```
+
+Use the `gh` CLI to look at this comment on the issue: https://github.com/github/copilot-sdk-partners/issues/95#issuecomment-4462156144
+
+The last statement in that comment is this:
+
+> Steve requested I look into the cost of doing the trade-off item 4 and if the cost is deemed too high, we can do it after GA.
+
+It is now time to do this investigation. I suspect what Steve was told by his agent isn't quite the full story. I suspect plenty of projects still use `maven-release-plugin`, but do want to follow current best practices.
+
+One aspect that I want to be clear on: this SDK is much smaller than Quarkus or Spring. Is there evidence to support the assertion that projects of this size still do use `maven-release-plugin` even in 2026-05? Or, is it the case that even small projects have abandoned in favor of other options.
+
+Finally, what about the use of the `central-publishing-maven-plugin`? Does that need to be considered in this branch protection conversation?
+
+Write up your findings in a file `ghcp-sp-95-branch-protection-findings-01.md` as a sibling to the plan file.
+
+### 14:43 Response
+
+Understood β focusing purely on the branch protection investigation. Let me gather one more piece of evidence on `maven-release-plugin` activity before writing the findings.
+
+Fetched https://central.sonatype.com/artifact/org.apache.maven.plugins/maven-release-plugin/versions
+
+Read [](file:///c%3A/Users/edburns/workareas/copilot-sdk-java-00/docs/adr/adr-002-maven-version-and-reference-implementation-tracking.md)
+
+Good β `maven-release-plugin` v3.3.1 was released December 2025, very actively maintained. Now let me write the findings document.
+
+Created [](file:///c%3A/Users/edburns/workareas/copilot-sdk-00/80-java-monorepo-add-01-remove-before-merge/ghcp-sp-95-branch-protection-findings-01.md)
+
+Created ghcp-sp-95-branch-protection-findings-01.md.
+
+Key findings:
+
+- **`maven-release-plugin` is not legacy** β v3.3.1 released Dec 2025, three releases in two months. The agent's characterization was inaccurate.
+- **Small projects still use it** β Apache's own Maven plugins and commons libraries use it. The "move away" trend is specific to large multi-module monorepos (Spring uses Gradle anyway).
+- **CI-friendly alternative costs ~2 days** β complete workflow rewrite, `flatten-maven-plugin` addition, and testing against Maven Central staging. Doable but adds risk during migration.
+- **`central-publishing-maven-plugin` is orthogonal** β it works with either approach and doesn't affect the branch-protection question.
+- **Recommendation:** Keep `maven-release-plugin` for GA, defer CI-friendly migration to post-GA. The bypass is scoped to a single `workflow_dispatch`-triggered workflow.
+
+Made changes.
+
+### 14:47 Prompt
+
+I want to spice up the analysis with some usage data. Can you find some evidence that downloads of `maven-release-plugin` have been recently high and sustained?
+
+## Define freeze policy
+
+### 15:03 Prompt
+
+Phase 0 states:
+
+```markdown
+- [ ] **Define Freeze window policy** β declare a short freeze window in `copilot-sdk-java` to avoid merge conflicts during migration
+```
+
+I'm very loath to freeze agentic syncing. How hard would it be to adopt a policy of manually syncing any changes that come in to `copilot-sdk-java-00` during the migration? I'm pretty much the only committer, and the agentic sync workflow is the only one whot that runs regularly and pushes commits. I could change that to once a week on Friday and probably be ok.
+
+### 15:06 Response
+
+This is very practical given your situation. The risk calculus:
+
+**Why it's easy:**
+
+- You're the sole human committer β no surprise PRs to worry about
+- Reference-impl-sync changes are predictable: generated code under generated, .lastmerge, package.json, and occasionally hand-written Java adjustments
+- Any drift is trivially detectable: `git log --oneline copilot-sdk-java-00/main` tells you exactly what landed
+- Phase 1 (copy source) is the only phase truly sensitive to drift β Phases 2-4 create _new_ monorepo files that don't conflict
+
+**Suggested policy instead of a freeze:**
+
+1. Reduce reference-impl-sync to weekly/Friday (as you said)
+2. Do Phase 1 copy early in the week, right after verifying java-00 main is clean
+3. After each sync lands on Friday, forward-port the diff into `copilot-sdk-00/java/` β a straightforward `git diff PREV_HASH..NEW_HASH` in java-00 applied to the monorepo
+4. Once Phase 1 merges and the monorepo is the source of truth, disable the sync workflow entirely
+
+**The one thing to watch:** if a sync lands _during_ an active Phase 1 PR review, you'd need to rebase. But since you control the sync schedule, just don't trigger it while a PR is open.
+
+A hard freeze is overkill here. Want me to update the plan to reflect this policy?
+
+### 15:06 Prompt
+
+Yes.
+
+## dd-2997995-phase-01
+
+### 15:20 Prompt
+
+The plan states
+
+```markdown
+- `instructions/` β `java/instructions/` (or merge into monorepo copilot-instructions)
+```
+
+When I look in the `copilot-sdk-00` I observe there is a SKILL for rust, but I don't see any language specific instructions. Am I missing something? Produce a plan for doing the little sub-step of your Phase 01 here.
+
+### 15:29 Response
+
+Explore instruction/skill structure
+
+Searched for files matching `copilot-sdk-00` Β· `**/*.instructions.md`, no matches
+
+Searched for files matching `copilot-sdk-java-00` Β· `**/*.instructions.md`, 1 match
+
+Searched for files matching `copilot-sdk-00` Β· `**/.copilot`, no matches
+
+Searched for files matching `copilot-sdk-java-00` Β· `**/.copilot`, no matches
+
+Read [](file:///c%3A/Users/edburns/workareas/copilot-sdk-00/.github)
+
+Read [](file:///c%3A/Users/edburns/workareas/copilot-sdk-java-00/.github)
+
+Read [](file:///c%3A/Users/edburns/workareas/copilot-sdk-00/.github/skills)
+
+Read [](file:///c%3A/Users/edburns/workareas/copilot-sdk-java-00/.github/skills)
+
+Read [](file:///c%3A/Users/edburns/workareas/copilot-sdk-00/.github/agents)
+
+Read [](file:///c%3A/Users/edburns/workareas/copilot-sdk-java-00/.github/agents)
+
+Read [](file:///c%3A/Users/edburns/workareas/copilot-sdk-00/.github/skills/rust-coding-skill)
+
+Read [](file:///c%3A/Users/edburns/workareas/copilot-sdk-java-00/.github/skills/agentic-merge-reference-impl)
+
+Read [](file:///c%3A/Users/edburns/workareas/copilot-sdk-java-00/.github/skills/commit-as-pull-request)
+
+Read [](file:///c%3A/Users/edburns/workareas/copilot-sdk-java-00/.github/skills/documentation-coverage)
+
+Read [](file:///c%3A/Users/edburns/workareas/copilot-sdk-00/nodejs)
+
+Read [](file:///c%3A/Users/edburns/workareas/copilot-sdk-00/python)
+
+Read [](file:///c%3A/Users/edburns/workareas/copilot-sdk-00/go)
+
+Read [](file:///c%3A/Users/edburns/workareas/copilot-sdk-00/dotnet)
+
+Read [](file:///c%3A/Users/edburns/workareas/copilot-sdk-00/rust)
+
+Searched for files matching `copilot-sdk-00` Β· `*.instructions.md`, no matches
+
+Searched for files matching `copilot-sdk-00` Β· `nodejs/**/*.instructions.md`, no matches
+
+Searched for files matching `copilot-sdk-00` Β· `python/**/*.instructions.md`, no matches
+
+Searched for files matching `copilot-sdk-00` Β· `go/**/*.instructions.md`, no matches
+
+Searched for files matching `copilot-sdk-00` Β· `dotnet/**/*.instructions.md`, no matches
+
+Searched for files matching `copilot-sdk-00` Β· `rust/**/*.instructions.md`, no matches
+
+Searched for files matching `copilot-sdk-00` Β· `**/instructions`, no matches
+
+Searched for files matching `copilot-sdk-java-00` Β· `**/instructions`, no matches
+
+Read [](file:///c%3A/Users/edburns/workareas/copilot-sdk-java-00/instructions)
+
+Read [](file:///c%3A/Users/edburns/workareas/copilot-sdk-java-00/instructions/copilot-sdk-java.instructions.md#1-1), lines 1 to 300
+
+Read skill [rust-coding-skill](file:///c%3A/Users/edburns/workareas/copilot-sdk-00/.github/skills/rust-coding-skill/SKILL.md?vscodeLinkType%3Dskill#1-1), lines 1 to 100
+
+Read skill [agentic-merge-reference-impl](file:///c%3A/Users/edburns/workareas/copilot-sdk-java-00/.github/skills/agentic-merge-reference-impl/SKILL.md?vscodeLinkType%3Dskill)
+
+Read skill [commit-as-pull-request](file:///c%3A/Users/edburns/workareas/copilot-sdk-java-00/.github/skills/commit-as-pull-request/SKILL.md?vscodeLinkType%3Dskill)
+
+Read skill [documentation-coverage](file:///c%3A/Users/edburns/workareas/copilot-sdk-java-00/.github/skills/documentation-coverage/SKILL.md?vscodeLinkType%3Dskill)
+
+Read skill [rust-coding-skill](file:///c%3A/Users/edburns/workareas/copilot-sdk-00/.github/skills/rust-coding-skill/SKILL.md?vscodeLinkType%3Dskill)
+
+Read skill `rust-coding-skill`: [](file:///c%3A/Users/edburns/workareas/copilot-sdk-00/.github/skills/rust-coding-skill/examples.md#1-1), lines 1 to 150
+
+Read [](file:///c%3A/Users/edburns/workareas/copilot-sdk-00/.github/agents/agentic-workflows.agent.md#1-1), lines 1 to 50
+
+Read [](file:///c%3A/Users/edburns/workareas/copilot-sdk-java-00/.github/agents/agentic-workflows.agent.md#1-1), lines 1 to 50
+
+Read [](file:///c%3A/Users/edburns/workareas/copilot-sdk-java-00/instructions/copilot-sdk-java.instructions.md#300-300), lines 300 to 600
+
+Read [](file:///c%3A/Users/edburns/AppData/Roaming/Code%20-%20Insiders/User/prompts)
+
+Read [](file:///c%3A/Users/edburns/workareas/copilot-sdk-java-00/instructions/copilot-sdk-java.instructions.md#600-600), lines 600 to 757
+
+Read [](file:///c%3A/Users/edburns/workareas/copilot-sdk-java-00/.github/prompts)
+
+Read [](file:///c%3A/Users/edburns/workareas/copilot-sdk-00/.github/prompts)
+
+Read [](file:///c%3A/Users/edburns/workareas/copilot-sdk-00/.github/actions)
+
+Read [](file:///c%3A/Users/edburns/workareas/copilot-sdk-java-00/.github/actions)
+
+Read [](file:///c%3A/Users/edburns/workareas/copilot-sdk-java-00/.github/aw)
+
+Searched for files matching `copilot-sdk-00` Β· `**/*.prompt.md`, no matches
+
+Searched for files matching `copilot-sdk-java-00` Β· `**/*.prompt.md`, 4 matches
+
+Searched for files matching `copilot-sdk-00` Β· `**/*.agent.md`, 2 matches
+
+Searched for files matching `copilot-sdk-java-00` Β· `**/*.agent.md`, 1 match
+
+Searched for regex `language.*instruction|instructions.*language|rust.*skill|coding.*skill` (`copilot-sdk-00` Β· `.github/copilot-instructions.md`), no results
+
+Searched for regex `language.*instruction|instructions.*language|skill|agent` (`copilot-sdk-java-00` Β· `.github/copilot-instructions.md`), 13 results
+
+Read [](file:///c%3A/Users/edburns/AppData/Roaming/Code%20-%20Insiders/User/workspaceStorage/42e680be608c5e153963c50a3611d619/GitHub.copilot-chat/chat-session-resources/7b272067-59c1-463a-b094-300b246bfc71/toolu_vrtx_017hNdUf5o7v8yju8ZR3EbQ1__vscode-1778880555537/content.txt#1-1), lines 1 to 200
+
+You're correct β the monorepo has no per-language instructions directories. The pattern it uses instead is **skills**. The `rust-coding-skill` at rust-coding-skill serves the same purpose as Java's copilot-sdk-java.instructions.md β it provides language-specific coding rules and API patterns.
+
+Here's the sub-plan:
+
+## Plan: Migrate copilot-sdk-java.instructions.md
+
+### Current State
+
+| Repo | Mechanism | Content |
+| ------------ | -------------------------------- | -------------------------------------------------------------------------- |
+| **Monorepo** | SKILL.md | Rust-specific coding rules, API patterns, idioms (~300 lines) |
+| **Monorepo** | copilot-instructions.md | Cross-language repo governance (build commands, architecture, conventions) |
+| **Java SDK** | copilot-sdk-java.instructions.md | Java-specific coding rules, API usage patterns (~1400 lines) |
+| **Java SDK** | copilot-instructions.md | Java repo governance (build commands, architecture, conventions) |
+
+### Approach: Create a `java-coding-skill` (follows the Rust pattern)
+
+**Do NOT** put it at `java/instructions/` β that doesn't match the monorepo convention.
+**Do NOT** merge 1400 lines into copilot-instructions.md β that would bloat the central file.
+
+**Instead**, create `.github/skills/java-coding-skill/SKILL.md` following the `rust-coding-skill` pattern.
+
+### Steps
+
+1. **Create** `.github/skills/java-coding-skill/SKILL.md`
+ - Add YAML frontmatter with `name: java-coding-skill` and a description like `"Use this skill whenever editing *.java files in the java/ SDK..."`
+ - Body = content from copilot-sdk-java.instructions.md
+
+2. **Merge Java repo governance** into monorepo's copilot-instructions.md
+ - The Java SDK's copilot-instructions.md contains build commands (`mvn clean verify`, `mvn spotless:apply`), architecture notes (package structure, handler patterns), and test conventions.
+ - Add a `## Java` section to the monorepo's instructions file (paralleling how it already covers Node, Python, Go, .NET, Rust).
+ - Keep it concise β the detailed API patterns live in the skill.
+
+3. **Do NOT copy** instructions as a directory to `java/instructions/` β the monorepo doesn't use that pattern.
+
+### Result
+
+```
+.github/
+ βββ copilot-instructions.md # Add Java section (build, test, conventions)
+ βββ skills/
+ β βββ rust-coding-skill/
+ β β βββ SKILL.md
+ β β βββ examples.md
+ β βββ java-coding-skill/ # NEW
+ β βββ SKILL.md # From instructions/copilot-sdk-java.instructions.md
+```
+
+### What This Means for the Plan
+
+The line in Phase 1:
+
+> instructions β `java/instructions/` (or merge into monorepo copilot-instructions)
+
+Should be updated to:
+
+> copilot-sdk-java.instructions.md β `.github/skills/java-coding-skill/SKILL.md` (follows the `rust-coding-skill` pattern); Java repo governance merged into copilot-instructions.md
+
+Want me to execute this, or just update the plan text?
+
+### 15:32 Prompt
+
+Update the plan first.
+
+### 15:34 Response
+
+Compacted conversation
+
+Read [](file:///c%3A/Users/edburns/workareas/copilot-sdk-00/80-java-monorepo-add-01-remove-before-merge/dd-2989727-move-java-to-monorepo-plan.md#1-1), lines 1 to 120
+
+Read [](file:///c%3A/Users/edburns/workareas/copilot-sdk-00/80-java-monorepo-add-01-remove-before-merge/dd-2989727-move-java-to-monorepo-plan.md#120-120), lines 120 to 200
+
+Let me also check the appendix for any related references.
+
+Searched for text `instructions` (`**/dd-2989727-move-java-to-monorepo-plan.md`), 20 results
+
+Let me read the relevant sections around the other references:
+
+Read [](file:///c%3A/Users/edburns/workareas/copilot-sdk-00/80-java-monorepo-add-01-remove-before-merge/dd-2989727-move-java-to-monorepo-plan.md#365-365), lines 365 to 380
+
+Read [](file:///c%3A/Users/edburns/workareas/copilot-sdk-00/80-java-monorepo-add-01-remove-before-merge/dd-2989727-move-java-to-monorepo-plan.md#445-445), lines 445 to 465
+
+Read [](file:///c%3A/Users/edburns/workareas/copilot-sdk-00/80-java-monorepo-add-01-remove-before-merge/dd-2989727-move-java-to-monorepo-plan.md#500-500), lines 500 to 520
+
+Now I have the full picture. There are four places to update:
+
+1. **Line 49** (Phase 1): instructions β `java/instructions/`
+2. **Line 373** (Β§6A table): `Move to java/`
+3. **Line 456** (checklist): `copilot-instructions.md includes Java guidance`
+4. **Line ~516** (Appendix A): instructions β `java/instructions/`
+
+Replacing 4 lines with 5 lines in [](file:///c%3A/Users/edburns/workareas/copilot-sdk-00/80-java-monorepo-add-01-remove-before-merge/dd-2989727-move-java-to-monorepo-plan.md)
+
+```
+
+```
+
+Updated all four references in the plan:
+
+1. **Phase 1 (line 49):** instructions β now points to `.github/skills/java-coding-skill/SKILL.md`
+2. **Β§6A inventory table (line 373):** "Move to java" β "Create `.github/skills/java-coding-skill/SKILL.md`"
+3. **Verification checklist (line 456):** Split into two items β one for copilot-instructions.md governance section, one for the new skill file
+4. **Appendix A (line ~516):** "Move β `java/instructions/`" β "Create skill β `.github/skills/java-coding-skill/SKILL.md`"
+
+Made changes.
+
+### 15:37 Prompt
+
+Now that you have updated the plan file, produce a net-new, very focused plan just for this part: making the monorepo smart about Java, while following all conventions. This plan will be executed by a separate agent to do the work, so include everything an agent could possibly need into this prompt. Write the prompt as a sibling to the plan file, but call it `dd-2998002-make-the-monorepo-smart-about-java-plan.md`. Include checkboxes so the agent can follow along and not get confused and lose context. Be explicit about the goals.
+
+### 15:43 Response
+
+Let me gather the exact source materials the agent plan needs to reference.
+
+Read [](file:///c%3A/Users/edburns/workareas/copilot-sdk-java-00/instructions/copilot-sdk-java.instructions.md#1-1), lines 1 to 30
+
+Read [](file:///c%3A/Users/edburns/workareas/copilot-sdk-00/.github/copilot-instructions.md)
+
+Read [](file:///c%3A/Users/edburns/workareas/copilot-sdk-java-00/.github/copilot-instructions.md#1-1), lines 1 to 80
+
+Read skill [rust-coding-skill](file:///c%3A/Users/edburns/workareas/copilot-sdk-00/.github/skills/rust-coding-skill/SKILL.md?vscodeLinkType%3Dskill#1-1), lines 1 to 15
+
+Read [](file:///c%3A/Users/edburns/workareas/copilot-sdk-java-00/.github/copilot-instructions.md#80-80), lines 80 to 260
+
+Read [](file:///c%3A/Users/edburns/workareas/copilot-sdk-java-00/instructions/copilot-sdk-java.instructions.md)
+
+Now I have all the source materials. Let me create the focused plan document.
+
+Created [](file:///c%3A/Users/edburns/workareas/copilot-sdk-00/80-java-monorepo-add-01-remove-before-merge/dd-2998002-make-the-monorepo-smart-about-java-plan.md)
+
+Created dd-2998002-make-the-monorepo-smart-about-java-plan.md. It includes:
+
+- **Explicit goals** β two deliverables (skill file + governance merge)
+- **Non-goals** β what NOT to do (no `java/instructions/` directory, no other language changes)
+- **7-step checklist** with checkboxes for the agent to follow
+- **Exact YAML frontmatter** to use (matching the `rust-coding-skill` pattern)
+- **Precise edit instructions** for each section of copilot-instructions.md (6aβ6f)
+- **Verification step** with 4 confirmation checks
+- **Appendix A** β describes the Java instructions source file structure (757 lines, 22 sections)
+- **Appendix B** β describes the Java governance content to merge, with guidance on what belongs in governance vs. the skill
+
+Made changes.
+
+### 15:44 Prompt
+
+I intend to use Copilot CLI `copilot --yolo` and ask it to execute the plan as a prompt. It will need to have access to both the monorepo and the java. When I interact with you here in VS Code, that comes by virtue of **Add folder to workspace**. How do I do that with Copilot CLI?
+
+### 15:45 Response
+
+Copilot CLI doesn't have a multi-root workspace concept. It operates from whatever directory you `cd` into. Two practical options:
+
+**Option 1 (recommended): Run from the monorepo, reference Java repo by relative path**
+
+```powershell
+cd c:\Users\edburns\workareas\copilot-sdk-00
+copilot --yolo
+```
+
+Then in your prompt, tell it:
+
+> The Java SDK source repo is at copilot-sdk-java-00. Read copilot-sdk-java.instructions.md for the source content.
+
+This way the monorepo's copilot-instructions.md is automatically discovered (it's in the git root), and the agent can still read files from the sibling repo via copilot-sdk-java-00.
+
+**Option 2: Run from the common parent**
+
+```powershell
+cd c:\Users\edburns\workareas
+copilot --yolo
+```
+
+Both repos are accessible as `copilot-sdk-00/` and `copilot-sdk-java-00/`, but **neither repo's copilot-instructions.md will be auto-loaded** since the parent isn't a git repo.
+
+**I'd go with Option 1** β the monorepo is where all changes land, and the plan document already tells the agent exactly which files to read from the Java repo. You might want to update the plan's Step 2 to explicitly say copilot-sdk-java.instructions.md so the path is unambiguous.
+
+### 15:46 Prompt
+
+Yes, use the relative path option.
+
+Please update the plan to use the relative path thing. Assume `../copilot-sdk-java-00` is the relative path from the `copilot-sdk-00` directory in which I will run copilot --yolo.
+
+### 15:47 Response
+
+Read [](file:///c%3A/Users/edburns/workareas/copilot-sdk-00/80-java-monorepo-add-01-remove-before-merge/dd-2998002-make-the-monorepo-smart-about-java-plan.md#1-1), lines 1 to 60
+
+Read [](file:///c%3A/Users/edburns/workareas/copilot-sdk-00/80-java-monorepo-add-01-remove-before-merge/dd-2998002-make-the-monorepo-smart-about-java-plan.md#55-55), lines 55 to 120
+
+Now I have the full picture. The references to update are in Steps 2 and 5 where the plan tells the agent where to find files from the Java repo.
+
+Replacing 21 lines with 22 lines in [](file:///c%3A/Users/edburns/workareas/copilot-sdk-00/80-java-monorepo-add-01-remove-before-merge/dd-2998002-make-the-monorepo-smart-about-java-plan.md)
+
+```
+
+```
+
+Updated the plan with four changes:
+
+1. **Added "Execution Context" section** β tells the agent it's running from the monorepo root and the Java repo is at copilot-sdk-java-00 (read-only)
+2. **Step 2** β source file path is now copilot-sdk-java.instructions.md
+3. **Step 3 body content** β references the same copilot-sdk-java-00 path
+4. **Step 5** β governance file path is now copilot-instructions.md
+
+Made changes.
diff --git a/80-java-monorepo-add-01-remove-before-merge/20260518-prompts.md b/80-java-monorepo-add-01-remove-before-merge/20260518-prompts.md
new file mode 100644
index 000000000..dc5d012ae
--- /dev/null
+++ b/80-java-monorepo-add-01-remove-before-merge/20260518-prompts.md
@@ -0,0 +1,186 @@
+## ghcp-sp-95
+
+### 10:44 Prompt
+
+In issue 95, you stated exactly this as the remedy for the branch protection problem.
+
+```bash
+gh api repos/github/copilot-sdk/rulesets \
+ -X POST \
+ --input - <<'EOF'
+{
+ "name": "ghcp-sp-95-java-branch-protection",
+ "target": "branch",
+ "enforcement": "active",
+ "conditions": {
+ "ref_name": {
+ "include": ["refs/heads/main"],
+ "exclude": []
+ }
+ },
+ "bypass_actors": [
+ {
+ "actor_id": 5,
+ "actor_type": "RepositoryRole",
+ "bypass_mode": "always"
+ }
+ ],
+ "rules": [
+ { "type": "deletion" },
+ {
+ "type": "pull_request",
+ "parameters": {
+ "required_approving_review_count": 1,
+ "dismiss_stale_reviews_on_push": false,
+ "require_code_owner_review": false,
+ "require_last_push_approval": false,
+ "required_review_thread_resolution": false,
+ "allowed_merge_methods": ["merge", "squash", "rebase"]
+ }
+ },
+ { "type": "non_fast_forward" }
+ ]
+}
+EOF
+```
+
+I have approval from the stakeholders to do this. But because I'm very new to the repo and want to protect my reputation as a careful and risk-averse engineer, I want to know exactly how I can revert exactly this change, should problems arise.
+
+Can you give me the command that will effectively "undo" the operation?
+
+### 10:48 Response
+
+The POST will return a JSON response containing an `id` field. To undo, you delete that specific ruleset:
+
+```bash
+gh api repos/github/copilot-sdk/rulesets/ -X DELETE
+```
+
+### Practical workflow:
+
+1. **When you create it**, capture the ID from the response:
+
+ ```bash
+ gh api repos/github/copilot-sdk/rulesets \
+ -X POST \
+ --input - <<'EOF'
+ ... (your payload) ...
+ EOF
+ ```
+
+ The response JSON will include `"id": 12345678`. Note that number.
+
+2. **If you need to revert:**
+
+ ```bash
+ gh api repos/github/copilot-sdk/rulesets/12345678 -X DELETE
+ ```
+
+3. **If you lose the ID**, you can find it:
+ ```bash
+ gh api repos/github/copilot-sdk/rulesets --jq '.[] | select(.name == "Protect main") | .id'
+ ```
+
+The DELETE removes the ruleset entirely β returning the repo to the state before you created it (assuming step 1 from the issue, deleting the classic branch protection, has already been done separately). There is no partial state; it's a clean removal.
+
+### 10:50 Prompt
+
+Here is the post again. I've changed the "name":
+
+```bash
+gh api repos/github/copilot-sdk/rulesets \
+ -X POST \
+ --input - <<'EOF'
+{
+ "name": "ghcp-sp-95-java-branch-protection",
+ "target": "branch",
+ "enforcement": "active",
+ "conditions": {
+ "ref_name": {
+ "include": ["refs/heads/main"],
+ "exclude": []
+ }
+ },
+ "bypass_actors": [
+ {
+ "actor_id": 5,
+ "actor_type": "RepositoryRole",
+ "bypass_mode": "always"
+ }
+ ],
+ "rules": [
+ { "type": "deletion" },
+ {
+ "type": "pull_request",
+ "parameters": {
+ "required_approving_review_count": 1,
+ "dismiss_stale_reviews_on_push": false,
+ "require_code_owner_review": false,
+ "require_last_push_approval": false,
+ "required_review_thread_resolution": false,
+ "allowed_merge_methods": ["merge", "squash", "rebase"]
+ }
+ },
+ { "type": "non_fast_forward" }
+ ]
+}
+EOF
+```
+
+Can you please write a PowerShell script I can execute that does the command and make s it so I can capture the necessary response? Write it to `temporary-prompts/ghcp-sp-95-enable-java-branch-protection.ps1`.
+
+### 10:55 Response
+
+Created [](file:///e%3A/workareas/copilot-sdk-java-00/temporary-prompts/ghcp-sp-95-enable-java-branch-protection.ps1)
+
+Created at ghcp-sp-95-enable-java-branch-protection.ps1. It pipes the JSON payload to `gh api`, parses the response to extract the ruleset ID, and prints the exact DELETE command you'd need to revert.
+
+Made changes.
+
+### 10:54 Prompt
+
+Here is the output
+
+```
+Ruleset created successfully.
+ Name: ghcp-sp-95-java-branch-protection
+ ID: 16552308
+
+To revert this change:
+ gh api repos/github/copilot-sdk/rulesets/16552308 -X DELETE
+```
+
+### 20:00 Response
+
+Phase 1 is complete. Here's a summary:
+
+Phase 1 Complete β
+
+Two commits made on edburns/80-java-monorepo-add-01:
+
+1. 446547c β Copied all Java SDK source files into java/ (src, pom.xml, config, scripts, docs, mvnw, etc.)
+2. f0aebe7 β Updated pom.xml to use local monorepo test harness instead of git clone:
+ - Removed antrun git-clone plugin entirely
+ - Changed copilot.sdk.clone.dir to ${project.basedir}/.. (monorepo root)
+ - Updated URLs to github/copilot-sdk
+ - Updated comments to reflect monorepo layout
+
+Verification: mvn clean verify passes β BUILD SUCCESS (9 min, all tests green).
+
+## dd-2989720-move-to-monorepo
+
+The file `80-java-monorepo-add-01-remove-before-merge\dd-2989727-move-java-to-monorepo-plan.md` is a high-level plan for migrating the code in `..\copilot-sdk-java-00` (source), which is a local clone of https://github.com/github/copilot-sdk-java , to reside in the `java` directory within `copilot-sdk-00` (dest), which is a local clone of https://github.com/github/copilot-sdk .
+
+Read the whole thing now.
+
+Next, I want the agent to handle "Phase 1: Copy Source Code (No Workflows Yet)". Note that the markdown strikethrough steps have already been completed.
+
+Next, write out a fully detailed prompt sufficient to feed to an agent with `copilot --yolo` to instruct the agent to do Phase 1.
+
+- β β β Assume copilot will be invoked in `copilot-sdk-00` (dest) and that `..\copilot-sdk-java-00` is the correct relative path for the source.
+- β β β Tell the agent about the paths.
+- β β β Tell the agent it is safe to do the work in the current topic branch, making fine-grained commits with reasonable commit log messages.
+- Tell the agent to read `80-java-monorepo-add-01-remove-before-merge\dd-2989727-move-java-to-monorepo-plan.md` first.
+- βββ Tell the agent to not do any other phases.
+
+Write the prompt to the file `80-java-monorepo-add-01-remove-before-merge\dd-2997995.md`.
diff --git a/80-java-monorepo-add-01-remove-before-merge/20260519-prompts.md b/80-java-monorepo-add-01-remove-before-merge/20260519-prompts.md
new file mode 100644
index 000000000..12c7a20f0
--- /dev/null
+++ b/80-java-monorepo-add-01-remove-before-merge/20260519-prompts.md
@@ -0,0 +1,27 @@
+# DAY: 2026-05-19
+
+## dd-2989720-move-to-monorepo
+
+### 14:00 Prompt
+
+The file `80-java-monorepo-add-01-remove-before-merge\dd-2989727-move-java-to-monorepo-plan.md` is a high-level plan for migrating the code in `..\copilot-sdk-java-00` (source), which is a local clone of https://github.com/github/copilot-sdk-java , to reside in the `java` directory within `copilot-sdk-00` (dest), which is a local clone of https://github.com/github/copilot-sdk .
+
+Read the whole thing now.
+
+Next, I want the agent to handle "Phase 2: CI Workflows". Note that the Phase 0 and Phase 1 are already complete.
+
+Next, write out a fully detailed prompt sufficient to feed to an agent with `copilot --yolo` to instruct the agent to do Phase 2.
+
+- β β β Assume copilot will be invoked in `copilot-sdk-00` (dest) and that `..\copilot-sdk-java-00` is the correct relative path for the source.
+- β β β Tell the agent about the paths.
+- β β β Tell the agent it is safe to do the work in the current topic branch, making fine-grained commits with reasonable commit log messages.
+- Tell the agent to read `80-java-monorepo-add-01-remove-before-merge\dd-2989727-move-java-to-monorepo-plan.md` first.
+- βββ Tell the agent to not do any other phases.
+
+Write the prompt to the file `80-java-monorepo-add-01-remove-before-merge\dd-3001051-phase-2-plan.md`.
+
+### 14:07 Response
+
+Created `dd-3001051-phase-2-plan.md` with the full Phase 2 agent prompt. It covers all 5 tasks (java-sdk-tests.yml, codegen-check.yml merge, java-codegen-fix.md, copilot-setup-steps.yml merge, dependabot.yaml update) with specific paths, requirements, and constraints scoped to Phase 2 only.
+
+Made changes.
diff --git a/80-java-monorepo-add-01-remove-before-merge/dd-2989727-move-java-to-monorepo-plan.md b/80-java-monorepo-add-01-remove-before-merge/dd-2989727-move-java-to-monorepo-plan.md
new file mode 100644
index 000000000..74506ff43
--- /dev/null
+++ b/80-java-monorepo-add-01-remove-before-merge/dd-2989727-move-java-to-monorepo-plan.md
@@ -0,0 +1,639 @@
+# DD-2989727: Move Java SDK into copilot-sdk Monorepo β Plan
+
+## Table of Contents
+
+1. [Migration Plan β Phases](#1-migration-plan--phases)
+2. [Permissions and Secrets Challenges](#2-permissions-and-secrets-challenges)
+3. [Naming Convention Proposal](#3-naming-convention-proposal)
+4. [Current Language Separation Assessment](#4-current-language-separation-assessment)5. [Workflow Inventory Tables](#5-workflow-inventory-tables)
+5. [Agents, Skills, Prompts, and Supporting Resources Inventory](#6-agents-skills-prompts-and-supporting-resources-inventory)
+6. [Pitfalls and Risk Register](#7-pitfalls-and-risk-register)
+7. [Post-Migration Verification Checklist](#8-post-migration-verification-checklist)
+
+- [Appendix A: Files to Copy vs. Merge vs. Delete](#appendix-a-files-to-copy-vs-merge-vs-delete)
+- [Appendix B: Unique Java Concerns vs Other Languages](#appendix-b-unique-java-concerns-vs-other-languages)
+- [Appendix C: Java Smoketest](#appendix-c-java-smoketest)
+
+---
+
+## 1. Migration Plan β Phases
+
+### Phase 0: β Pre-Flight (Before Writing Any Code)
+
+- [β ] **Provision secrets** in `github/copilot-sdk` (see Β§2A) See https://github.com/github/copilot-sdk-partners/issues/90
+- [β ] **Verify CODEOWNERS team** access. See https://github.com/github/copilot-sdk-partners/issues/89
+- [β ] **Check Maven Central Trusted Publisher** β can `github/copilot-sdk` publish to `com.github:copilot-sdk-java`? See
+- [β ] **Check GitHub Pages** β is it enabled? Can Java docs coexist? See https://github.com/github/copilot-sdk-partners/issues/85
+- [β ] **Confirm branch protection** β will new required status checks be accepted? See https://github.com/github/copilot-sdk-partners/issues/95 .
+- [β ] **Create tracking issue** in `github/copilot-sdk` for this migration. See https://github.co/github/copilot-sdk-partners/issues/80
+- [β ] **Define drift-management policy** β instead of a hard freeze, adopt a manual forward-port policy:
+ 1. Reduce `reference-impl-sync` schedule in `copilot-sdk-java` to weekly (Fridays only)
+ 2. Perform Phase 1 copy early in the week, right after verifying `copilot-sdk-java` main is clean
+ 3. After each Friday sync lands, forward-port the diff into `copilot-sdk/java/` (`git diff PREV..NEW` in java repo, applied to monorepo)
+ 4. Once Phase 1 merges and the monorepo is the source of truth, disable the sync workflow in `copilot-sdk-java` entirely
+ - **Rationale:** A hard freeze is unnecessary because (a) there is a single human committer, (b) the only automated commit source is the reference-impl-sync workflow whose schedule is controllable, and (c) any drift is trivially detectable via `git log`. The one constraint: do not trigger a sync while a Phase 1 PR is under active review.
+
+### Phase 1: β Copy Source Code (No Workflows Yet)
+
+**Goal**: Get all Java source code building and testing in the monorepo without any CI/CD.
+
+1. Copy `copilot-sdk-java-00/` contents into `copilot-sdk-00/java/`:
+ - `src/` (main, test, generated, site)
+ - `pom.xml`
+ - `config/` (checkstyle, spotbugs)
+ - `scripts/codegen/` β merge `java.ts` into `copilot-sdk-00/scripts/codegen/`
+ - `CHANGELOG.md`, `README.md`, `jbang-example.java`
+ - `.lastmerge` β `java/.lastmerge`
+ - β ~~`.githooks/` β `java/.githooks/`~~
+ - `docs/adr/` β `java/docs/adr/`
+ - β ~~`instructions/copilot-sdk-java.instructions.md` β `.github/skills/java-coding-skill/SKILL.md` (follows the `rust-coding-skill` pattern); Java repo governance merged into `.github/copilot-instructions.md`~~
+
+2. Update `pom.xml` paths if needed (should be self-contained under `java/`).
+
+3. Verify `mvn clean verify` works from `java/` directory locally. Make necessary changes so the test infrastructure is copied locally, rather than checked out.
+
+### Phase 2: CI Workflows
+
+**Goal**: Java CI runs on PRs and main pushes within the monorepo.
+
+1. Create `java-sdk-tests.yml` (adapted from `build-test.yml`):
+ - Path triggers: `java/**`, `test/**`, `.github/workflows/java-sdk-tests.yml`
+ - Uses monorepo's `setup-copilot` action (or create `java/setup-copilot` action)
+ - Runs on 3 OS matrix (match other SDKs)
+
+2. Merge Java into `codegen-check.yml`:
+ - Add `java/src/generated/**` to path triggers
+ - Add a job that runs Java codegen and diffs
+
+3. Create `java-codegen-agentic-fix.md` (adapted from `codegen-agentic-fix.md`):
+ - Update paths, remove cross-repo references
+ - Compile with `gh aw compile`
+
+4. Merge Java into `copilot-setup-steps.yml`:
+ - Add JDK 17 setup step
+ - Add Maven cache
+
+5. Update `dependabot.yaml`:
+ - Add Maven ecosystem entry for `/java`
+
+### Phase 3: Publish Workflows
+
+**Goal**: Java can be independently published from the monorepo.
+
+1. Create `java-publish.yml` (adapted from `publish-maven.yml`):
+ - All paths updated to `java/` prefix
+ - Working directory set to `java/`
+ - Uses monorepo secrets
+ - **Independent trigger** β not part of the unified `publish.yml`
+
+2. Create `java-publish-snapshot.yml` (adapted from `publish-snapshot.yml`):
+ - Similar path/directory updates
+
+3. ~~Create `java-deploy-site.yml` (adapted from `deploy-site.yml`):~~
+ ~~- Adjust GitHub Pages setup for coexistence~~
+ ~~- May need a subdirectory deployment strategy~~
+
+4. Create `java-smoke-test.yml` (adapted from `run-smoke-test.yml`).
+
+5. Migrate `notes.template` to `java/.github/notes.template` or similar.
+
+### Phase 4: Agentic Workflows and Skills
+
+**Goal**: Agentic automation works for Java within the monorepo.
+
+1. **`reference-impl-sync`** β **`java-reference-impl-sync.md`** β **REWORK** for intra-repo operation:
+ - **Trigger**: `schedule` (daily) + `workflow_dispatch` (same as today)
+ - **Behavior change**: Instead of cloning `github/copilot-sdk` and comparing commits, it:
+ 1. Reads `java/.lastmerge` (now a monorepo commit SHA)
+ 2. Runs `git log ..HEAD -- dotnet/src/ nodejs/src/` to find new reference-impl changes
+ 3. If changes exist β creates an issue assigned to Copilot agent (same as today)
+ 4. If no changes β closes stale sync issues (same as today)
+ - **Key simplification**: No cross-repo clone, no remote URL handling, no token for external repo access
+ - **Compile**: `gh aw compile java-reference-impl-sync.md`
+
+2. **`agentic-merge-reference-impl` skill** β **REWORK** for intra-repo operation:
+ - **Current behavior**: Clones `github/copilot-sdk`, checks out the target commit, computes a diff of `dotnet/src/` and `nodejs/src/` against the Java repo's `.lastmerge`, then applies equivalent Java changes.
+ - **New behavior**:
+ 1. Reads `java/.lastmerge` to get the base commit SHA
+ 2. Computes `git diff ..HEAD -- dotnet/src/ nodejs/src/` (all local, no clone needed)
+ 3. Analyzes the diff to identify what changed semantically (new methods, renamed types, new events, etc.)
+ 4. Applies equivalent idiomatic Java changes under `java/src/`
+ 5. Runs `mvn verify` from `java/` to validate
+ 6. Updates `java/.lastmerge` to the current HEAD SHA
+ 7. Commits and pushes (via `commit-as-pull-request` skill or direct push)
+ - **Scripts to update**: `.github/scripts/reference-impl-sync/` β all 5 scripts assume cross-repo operation:
+ - `merge-reference-impl-start.sh` β remove `git clone`, replace with local `git diff`
+ - `merge-reference-impl-diff.sh` β simplify to intra-repo diff
+ - `merge-reference-impl-finish.sh` β update `java/.lastmerge` with monorepo SHA
+ - `sync-cli-version-from-reference-impl.sh` β now reads from local `nodejs/package.json` directly
+ - `sync-codegen-version.sh` β now reads from local `scripts/codegen/package.json`
+ - **Prompt files to update**:
+ - `.github/prompts/agentic-merge-reference-impl.prompt.md` β remove cross-repo instructions, add intra-repo paths
+ - `.github/prompts/coding-agent-merge-reference-impl-instructions.md` β same
+ - **SKILL.md** β update with new paths and simplified flow
+ - Restore the updating of the `readonly-copilot-sdk-ref-impl-version-from-lastmerge-file-updated-by-reference-impl-sync` POM property:
+ pin to. It is updated automatically by
+ .github/scripts/reference-impl-sync/sync-cli-version-from-reference-impl.sh
+
+3. **`sdk-consistency-review`** β Update:
+ - Add `java/**` to path triggers in the `.md` frontmatter
+ - Update agent prompt to include Java in the list of SDKs to review
+
+4. **`issue-triage`** β Update:
+ - Add `sdk/java` label to the list of per-SDK labels
+
+5. Merge `agentic-workflows.agent.md` β use the monorepo's (newer) version, no action needed.
+
+6. Migrate `documentation-coverage` skill to monorepo's skills directory (as `java-documentation-coverage`).
+
+7. Migrate `commit-as-pull-request` skill (check if monorepo already has equivalent).
+
+### Phase 5: Cross-Cutting Updates
+
+1. Update monorepo `copilot-instructions.md` to include Java section.
+2. Update monorepo `README.md` to list Java as a supported language.
+3. Update `scenario-builds.yml` to include Java scenarios (if applicable).
+4. Update `docs-validation.yml` to include Java code snippets.
+5. Update `lsp.json` to add Java LSP config (optional).
+6. Add Java to `docs/` getting-started and feature pages.
+7. Update `sdk-protocol-version.json` if Java needs it.
+
+### Phase 6: Cutover and Cleanup
+
+1. **Disable CI** in `copilot-sdk-java` (remove or disable workflows).
+2. **Archive** `copilot-sdk-java` repo (make read-only).
+3. **Update external references**:
+ - Maven Central POM `` URLs
+ - README badges pointing to the new repo
+ - Javadoc.io configuration
+ - Any links in copilot documentation
+4. **Remove duplicate resources** that were merged rather than moved.
+5. **Run full CI** in monorepo to validate everything.
+
+---
+
+## 2. Permissions and Secrets Challenges
+
+### 2A. Secrets That Must Be Provisioned in copilot-sdk
+
+The Java SDK publish workflow requires secrets that **do not currently exist** in the `copilot-sdk` repo:
+
+| Old Secret | Old Used By | New Secret | New Used By | Notes |
+| ------------------------ | ------------------------------------------------ | ----------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------ |
+| `RELEASE_TOKEN` | `publish-maven.yml` | `JAVA_RELEASE_TOKEN` | `java-publish-maven.yml` | PAT with `contents:write` for pushing tags/commits during maven-release-plugin |
+| `GPG_SECRET_KEY` | `publish-maven.yml` | `JAVA_GPG_SECRET_KEY` | `java-publish-maven.yml` | GPG private key for signing Maven artifacts |
+| `GPG_PASSPHRASE` | `publish-maven.yml` | `JAVA_GPG_PASSPHRASE` | `java-publish-maven.yml` | Passphrase for the GPG key |
+| `MAVEN_CENTRAL_USERNAME` | `publish-maven.yml`, `publish-snapshot.yml` | `JAVA_MAVEN_CENTRAL_USERNAME` | `java-publish-maven.yml`, `java-publish-snapshot.yml` | Sonatype/Maven Central credentials |
+| `MAVEN_CENTRAL_PASSWORD` | `publish-maven.yml`, `publish-snapshot.yml` | `JAVA_MAVEN_CENTRAL_PASSWORD` | `java-publish-maven.yml`, `java-publish-snapshot.yml` | Sonatype/Maven Central credentials |
+| `COPILOT_GITHUB_TOKEN` | `build-test.yml`, `codegen-agentic-fix.lock.yml` | unchanged | | Token for Copilot CLI in CI |
+
+### 2B. Existing Secrets in copilot-sdk That May Conflict
+
+| Secret | Used By | Concern |
+| ------------------------------------------------------ | -------------------- | ------------------------------------------------------------ |
+| `CARGO_REGISTRY_TOKEN` | `publish.yml` (Rust) | No conflict |
+| `GH_AW_GITHUB_TOKEN` / `GH_AW_GITHUB_MCP_SERVER_TOKEN` | Agentic workflows | Likely already present; Java agentic workflows need the same |
+
+### 2C. Permissions / Access to Provision
+
+- [β ] **Repository secrets**: File a ticket to add the 6 Java-specific secrets to `github/copilot-sdk`. See https://github.com/github/copilot-sdk-partners/issues/90
+- [β ] **CODEOWNERS team**: ~~Ensure `@github/copilot-sdk-java` team has access to `github/copilot-sdk` and is added to CODEOWNERS for `java/**`.~~ See https://github.com/github/copilot-sdk-partners/issues/89 .
+- [β] **Maven Central Trusted Publisher**: Currently configured for `github/copilot-sdk-java`. Must be updated to also allow publishing from `github/copilot-sdk` (or create a new namespace mapping). **This is the highest-risk permission issue** β Maven Central's Trusted Publisher setup ties the repository name to the publish flow. See https://github.com/github/copilot-sdk-partners/issues/91
+- [β ] **GitHub Pages**: ~~If `deploy-site.yml` moves, check if GitHub Pages is enabled on the monorepo and whether Java docs can coexist with any existing docs deployment.~~ See https://github.com/github/copilot-sdk-partners/issues/85
+- [β] **Branch protection**: Ensure `main` branch protection rules in copilot-sdk permit the Java CI workflows (merge queues, required status checks, etc.). See https://github.com/github/copilot-sdk-partners/issues/95 .
+- [ ] **Copilot coding agent**: Ensure the agent is enabled for `github/copilot-sdk` and the `copilot-setup-steps.yml` is updated to include Java tooling.
+
+---
+
+## 3. Naming Convention Proposal
+
+### Current State
+
+The monorepo already uses a partially consistent pattern:
+
+- **Test workflows**: `{language}-sdk-tests.yml` (e.g., `dotnet-sdk-tests.yml`, `go-sdk-tests.yml`)
+- **Cross-language workflows**: descriptive kebab-case names (e.g., `codegen-check.yml`, `publish.yml`)
+- **Agentic workflows**: descriptive kebab-case (e.g., `issue-triage.md`, `handle-bug.md`)
+
+### Proposed Convention
+
+**Use kebab-case throughout. Language-specific workflows start with the language name.**
+
+#### Language-specific workflow naming: `{language}-{purpose}.yml`
+
+| Current (copilot-sdk) | Current (copilot-sdk-java) | Proposed New Name |
+| ---------------------- | ------------------------------- | ------------------------------------------------------------------------------- |
+| `nodejs-sdk-tests.yml` | β | `nodejs-sdk-tests.yml` (keep) |
+| `dotnet-sdk-tests.yml` | β | `dotnet-sdk-tests.yml` (keep) |
+| `go-sdk-tests.yml` | β | `go-sdk-tests.yml` (keep) |
+| `python-sdk-tests.yml` | β | `python-sdk-tests.yml` (keep) |
+| `rust-sdk-tests.yml` | β | `rust-sdk-tests.yml` (keep) |
+| β | `build-test.yml` | **`java-sdk-tests.yml`** |
+| β | `publish-maven.yml` | **`java-publish.yml`** |
+| β | `publish-snapshot.yml` | **`java-publish-snapshot.yml`** |
+| β | `deploy-site.yml` | **`java-deploy-site.yml`** |
+| β | `run-smoke-test.yml` | **`java-smoke-test.yml`** |
+| β | `codegen-check.yml` | **Merge into existing `codegen-check.yml`** (add Java paths + job) |
+| β | `codegen-agentic-fix.md` | **`java-codegen-fix.md`** + `.lock.yml` |
+| β | `reference-impl-sync.md` | **`java-reference-impl-sync.md`** + `.lock.yml` (reworked for intra-repo) |
+| β | `update-copilot-dependency.yml` | **Merge into existing `update-copilot-dependency.yml`** (add Java codegen step) |
+| β | `copilot-setup-steps.yml` | **Merge into existing** (add JDK 17 + Maven setup) |
+| β | `agentics-maintenance.yml` | Already exists via gh-aw in the monorepo; **do not duplicate** |
+
+#### Cross-language workflow naming: `{purpose}.yml` (no language prefix)
+
+Keep existing names: `publish.yml`, `codegen-check.yml`, `scenario-builds.yml`, `docs-validation.yml`, etc.
+
+#### Summary of naming rules
+
+1. **Language-specific** workflows: `{language}-{purpose}.yml` / `.md`
+2. **Cross-language** workflows: `{purpose}.yml` / `.md` (no prefix)
+3. **Kebab-case** throughout (already the convention)
+4. **Agentic workflows**: same pattern but with `.md` extension
+5. **Lock files**: auto-generated, always `{name}.lock.yml`
+
+---
+
+## 4. Current Language Separation Assessment
+
+### Are the languages in copilot-sdk-00 already sufficiently separated?
+
+**Mostly yes, with a few cross-cutting concerns:**
+
+#### Well-separated
+
+- **Source code**: Each language lives in its own top-level directory (`nodejs/`, `python/`, `go/`, `dotnet/`, `rust/`). Java will go in `java/`.
+- **Test workflows**: Each has its own `{language}-sdk-tests.yml` with path-scoped triggers (only fires on changes to that language's directory + `test/`).
+- **Dependabot**: Already per-ecosystem, per-directory entries.
+
+#### Cross-cutting concerns (potential friction points)
+
+| Concern | Current State | Impact on Java |
+| --------------------------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| **Shared test harness** (`test/harness/`) | Node.js-based replay proxy used by all E2E tests | Java already uses this (clones it at build time from `copilot-sdk` repo). When in-repo, can reference it directly β **simpler**. |
+| **Shared test snapshots** (`test/snapshots/`) | YAML snapshot files consumed by all languages | Java can share these β **positive change**. |
+| **Unified codegen** (`scripts/codegen/`) | One `package.json` with generators for TS, C#, Python, Go, Rust | Java codegen (`java.ts`) must be **merged in**. The Java codegen currently has its own `package.json` with a direct `@github/copilot` dependency; the monorepo codegen gets it via `nodejs/node_modules`. This needs reconciliation. |
+| **`justfile`** | Has per-language targets (`format-go`, `test-dotnet`, etc.) | Must add `format-java`, `lint-java`, `test-java`, `install-java` targets. |
+| **Unified `publish.yml`** | Single workflow publishes all languages with one version number | **Java CANNOT join this** β Java has its own versioning scheme (`X.Y.Z-java.N`). Java must keep a separate `java-publish.yml`. |
+| **`sdk-consistency-review`** agentic workflow | Reviews PRs for cross-SDK parity (currently watches nodejs, python, go, dotnet) | Must add `java/` to the path triggers and update the agent prompt to include Java. |
+| **`copilot-setup-steps.yml`** | Sets up Node, Python, Go, .NET, Rust | Must add JDK 17 + Maven. |
+| **`copilot-instructions.md`** | Monorepo-wide instructions | Must incorporate Java-specific guidance. |
+| **`CODEOWNERS`** | Single `* @github/copilot-sdk` | ~~Must add `java/ @github/copilot-sdk-java` line.~~ |
+| **`lsp.json`** | Configures C# and Go language servers for Copilot agent | May want to add Java LSP (jdtls or similar) β **optional**. |
+
+### The Big Question: `reference-impl-sync`
+
+Currently, the Java SDK has a scheduled workflow that polls `github/copilot-sdk` for new commits and creates issues for the Copilot agent to port. **This workflow is still needed** when Java lives in the same repo β the primary maintainers of `dotnet/` and `nodejs/` are not Java experts, and changes to those SDKs still need to be detected and ported into `java/`.
+
+What changes is the **mechanism**: instead of polling a remote repository, the workflow watches for commits that land on `main` touching `dotnet/src/` or `nodejs/src/` and compares against `java/.lastmerge` (which now stores a monorepo commit SHA rather than a cross-repo one).
+
+**Recommendation**:
+
+1. **Keep `java/.lastmerge`** β it stores the last monorepo commit SHA whose `dotnet/`/`nodejs/` changes have been ported into Java. This is the anchor for diffing.
+2. **Keep `reference-impl-sync` as `java-reference-impl-sync.md`** β reworked for intra-repo operation (see Β§6 Phase 4 for details).
+3. **Keep `agentic-merge-reference-impl` skill** β reworked so that instead of cloning a remote repo, it reads diffs from the local `dotnet/` and `nodejs/` directories relative to the SHA in `java/.lastmerge`.
+4. The `sdk-consistency-review` workflow provides an additional safety net on PRs, but is **not a replacement** for the scheduled sync β it only fires on PRs, not when changes land on `main` without Java updates.
+
+---
+
+## 5. Workflow Inventory Tables
+
+### 5A. copilot-sdk-java-00 Workflows (Source)
+
+| YAML File Name | Brief Description | Primary Language | Complexity |
+| -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | ---------------------- | ---------- |
+| `build-test.yml` | Main CI: Spotless, build, Javadoc, `mvn verify`, JaCoCo coverage badges | Java | L |
+| `codegen-check.yml` | Re-runs Java codegen, commits regenerated files to PRs, triggers agentic fix on failure | Java | M |
+| `codegen-agentic-fix.md` + `.lock.yml` | Agentic: auto-fixes compilation/test failures caused by codegen changes | Java | L |
+| `reference-impl-sync.md` + `.lock.yml` | Agentic: checks for new commits in `github/copilot-sdk`, creates issue for Copilot agent to port | Java | L |
+| `publish-maven.yml` | Publishes release to Maven Central via `maven-release-plugin`, GPG signing, GitHub Release creation | Java | XL |
+| `publish-snapshot.yml` | Publishes SNAPSHOT builds to Maven Central Snapshots on a weekday schedule | Java | M |
+| `deploy-site.yml` | Builds/deploys versioned Maven site docs to GitHub Pages | Java | M |
+| `run-smoke-test.yml` | Builds SDK, installs locally, runs Copilot CLI smoke test on JDK 17 + JDK 25 (see [Appendix C](#appendix-c-java-smoketest)) | Java | M |
+| `update-copilot-dependency.yml` | Updates `@github/copilot` npm dep in codegen, re-runs generator, creates PR | Java | M |
+| `copilot-setup-steps.yml` | Environment setup for Copilot coding agent (JDK 17, Node 22, gh-aw, pre-commit hooks) | Java | S |
+| `agentics-maintenance.yml` | Auto-generated gh-aw maintenance: closes expired discussions/issues/PRs | Cross-language (infra) | S |
+| `notes.template` | Release notes template for Maven Central (not a workflow) | Java | S |
+
+### 5B. copilot-sdk-00 Workflows (Target Monorepo)
+
+| YAML File Name | Brief Description | Primary Language | Complexity |
+| -------------------------------------------- | ------------------------------------------------------------------------------- | ---------------------- | ---------- |
+| `nodejs-sdk-tests.yml` | Build + test Node.js SDK on 3 OS, prettier, ESLint, typecheck, E2E | Node.js | L |
+| `dotnet-sdk-tests.yml` | Build + test .NET SDK on 3 OS, format check, E2E via replay proxy | .NET | L |
+| `go-sdk-tests.yml` | Build + test Go SDK on 3 OS, gofmt, golangci-lint, E2E | Go | L |
+| `python-sdk-tests.yml` | Build + test Python SDK on 3 OS, ruff, ty, E2E via pytest | Python | L |
+| `rust-sdk-tests.yml` | Build + test Rust SDK on 3 OS, nightly fmt, clippy, cargo test | Rust | L |
+| `codegen-check.yml` | Verifies generated files across Node, .NET, Python, Go, Rust | Cross-language | M |
+| `publish.yml` | Publishes all SDKs (npm, NuGet, PyPI, Go tags, crates.io) from a single version | Cross-language | XL |
+| `scenario-builds.yml` | Verifies example scenarios build for each language | Cross-language | M |
+| `docs-validation.yml` | Extracts and validates code snippets from `docs/` | Cross-language | M |
+| `update-copilot-dependency.yml` | Updates `@github/copilot` dep, re-runs codegen, opens PR | Cross-language | M |
+| `copilot-setup-steps.yml` | Agent env setup: Node, Python, Go, .NET, Rust, just, gh-aw | Cross-language | M |
+| `verify-compiled.yml` | Ensures `.lock.yml` files match `.md` sources | Cross-language (infra) | S |
+| `collect-corrections.yml` | Collects triage agent feedback | Cross-language (infra) | S |
+| `corrections-tests.yml` | Tests for triage correction scripts | Cross-language (infra) | S |
+| `issue-classification.md` + `.lock.yml` | Agentic: classifies issues β routes to handle-\* handlers | Cross-language | M |
+| `issue-triage.md` + `.lock.yml` | Agentic: labels, acknowledges, requests clarification, closes dupes | Cross-language | L |
+| `handle-bug.md` + `.lock.yml` | Agentic: investigates bug issues | Cross-language | M |
+| `handle-documentation.md` + `.lock.yml` | Agentic: handles doc-related issues | Cross-language | S |
+| `handle-enhancement.md` + `.lock.yml` | Agentic: labels enhancement issues | Cross-language | S |
+| `handle-question.md` + `.lock.yml` | Agentic: labels question issues | Cross-language | S |
+| `cross-repo-issue-analysis.md` + `.lock.yml` | Agentic: checks if issue root cause is in copilot-agent-runtime | Cross-language | M |
+| `release-changelog.md` + `.lock.yml` | Agentic: generates release notes, updates CHANGELOG | Cross-language | M |
+| `sdk-consistency-review.md` + `.lock.yml` | Agentic: reviews PRs for cross-SDK feature parity | Cross-language | L |
+
+---
+
+## 6. Agents, Skills, Prompts, and Supporting Resources Inventory
+
+### 6A. copilot-sdk-java-00
+
+| Resource | Location | Purpose | Must Migrate? |
+| ---------------------------------------------------------------- | -------------------------------------------- | -------------------------------------------- | ---------------------------------------------------------------------------------------- |
+| **Agent:** `agentic-workflows.agent.md` | `.github/agents/` | Dispatcher for gh-aw workflow creation/debug | Yes (merge with monorepo version) |
+| **Skill:** `agentic-merge-reference-impl` | `.github/skills/` + `.github/prompts/` | Merges reference impl changes into Java | Yes β **must be reworked** (no longer cross-repo) |
+| **Skill:** `commit-as-pull-request` | `.github/skills/` + `.github/prompts/` | Creates branch, pushes, opens PR | Yes (may already exist in monorepo) |
+| **Skill:** `documentation-coverage` | `.github/skills/` + `.github/prompts/` | Assesses Java docs coverage | Yes |
+| **Prompt:** `coding-agent-merge-reference-impl-instructions.md` | `.github/prompts/` | Instructions for coding agent merge | Yes |
+| **Prompt:** `test-coverage-assessment.prompt.md` | `.github/prompts/` | Test coverage assessment | Yes |
+| **Composite Action:** `setup-copilot` | `.github/actions/setup-copilot/` | Sets up Copilot CLI for Java tests | Yes β **adapt paths** |
+| **Composite Action:** `test-report` | `.github/actions/test-report/` | Test report generation | Yes |
+| **Scripts:** `release/`, `ci/`, `build/`, `reference-impl-sync/` | `.github/scripts/` | Release, CI, sync automation | Yes β **path rewrites** |
+| **Dependabot:** `dependabot.yml` | `.github/` | Maven + GitHub Actions updates | Merge into monorepo's `dependabot.yaml` |
+| **CODEOWNERS** | `.github/` | ~~`@github/copilot-sdk-java` | Merge into monorepo's CODEOWNERS~~ |
+| **Issue Templates:** bug, documentation, feature, maintenance | `.github/ISSUE_TEMPLATE/` | Issue forms | Assess whether monorepo issue triage covers this |
+| **PR Template** | `.github/pull_request_template.md` | PR form | Merge or keep per-language |
+| **Release Config** | `.github/release.yml` | Auto-generated release notes config | Merge |
+| **copilot-instructions.md** | `.github/` | Agent instructions for Java SDK | Merge (scoped to `java/`) |
+| **Site templates** | `.github/templates/` | HTML/CSS for GitHub Pages | Migrate to `java/` |
+| **Coverage badge script** | `.github/scripts/generate-coverage-badge.sh` | JaCoCo badge generation | Migrate |
+| **`.lastmerge`** | repo root | Tracks last merged ref-impl commit | **This concept changes** β see Β§6 |
+| **`.githooks/pre-commit`** | repo root | Runs `mvn spotless:check` | Migrate to `java/.githooks/` |
+| **`instructions/copilot-sdk-java.instructions.md`** | `instructions/` | VS Code copilot instructions | Create `.github/skills/java-coding-skill/SKILL.md` (follows `rust-coding-skill` pattern) |
+
+### 6B. copilot-sdk-00
+
+| Resource | Location | Purpose |
+| --------------------------------------- | -------------------------------- | ----------------------------------------------------- |
+| **Agent:** `agentic-workflows.agent.md` | `.github/agents/` | Same dispatcher (newer version with more routing) |
+| **Agent:** `docs-maintenance.agent.md` | `.github/agents/` | Docs auditor agent |
+| **Skill:** `rust-coding-skill` | `.github/skills/` | Rust-specific coding skill |
+| **Composite Action:** `setup-copilot` | `.github/actions/setup-copilot/` | Sets up Copilot CLI from nodejs package |
+| **Command:** `triage_feedback.yml` | `.github/commands/` | Repository dispatch for triage feedback |
+| **LSP Config:** `lsp.json` | `.github/` | C#, Go language server configs |
+| **Dependabot:** `dependabot.yaml` | `.github/` | npm, pip, gomod, nuget, github-actions, devcontainers |
+| **CODEOWNERS** | `.github/` | `@github/copilot-sdk` |
+| **copilot-instructions.md** | `.github/` | Monorepo-wide agent instructions |
+
+---
+
+## 7. Pitfalls and Risk Register
+
+### HIGH RISK
+
+| # | Risk | Impact | Mitigation |
+| --- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| H1 | **Maven Central Trusted Publisher** repo-name mismatch | Cannot publish Java releases from monorepo | Verify/update Trusted Publisher config in Maven Central **before** migration. If the GAV is bound to `github/copilot-sdk-java`, it must be updated. |
+| H2 | **Unified `publish.yml` version collision** | All SDKs in monorepo share one version. Java has independent `X.Y.Z-java.N` versions. | Java must keep a **separate** publish workflow. Do NOT merge into `publish.yml`. |
+| H3 | **`agentic-merge-reference-impl` breaks** | The core Java development loop relies on this skill to stay in sync with .NET/Node changes | Must be carefully reworked for intra-repo operation before cutover. Test thoroughly with a dry-run on a feature branch. The skill + its 5 shell scripts + 2 prompt files all assume cross-repo cloning. |
+| H4 | **Secret provisioning delay** | Can't publish or run full CI until secrets are provisioned | Start secret provisioning **immediately** (Phase 0). |
+| H5 | **Test harness path changes** | Java E2E tests currently clone `copilot-sdk` at build time to get `test/harness/` and `test/snapshots/`. In-repo, these paths change. | Update `pom.xml` and test infrastructure to reference local `test/` directory instead of cloning. **This simplifies things significantly.** |
+
+### MEDIUM RISK
+
+| # | Risk | Impact | Mitigation |
+| --- | --------------------------------------- | ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
+| M1 | **Codegen `package.json` merge** | Java codegen has its own `@github/copilot` dependency; monorepo codegen gets it from `nodejs/node_modules` | Align Java codegen to use the same dependency source. May need to add `generate:java` script to monorepo's `scripts/codegen/package.json`. |
+| M2 | ~~**GitHub Pages conflict**~~ | ~~Java deploys versioned docs to Pages. Monorepo may have its own Pages setup.~~ | ~~Use subdirectory deployment or a separate Pages branch for Java.~~ |
+| M3 | **Branch protection / required checks** | New `java-sdk-tests` check may not be in the required list | Add to branch protection after first successful run. |
+| M4 | **CODEOWNERS team permissions** | `@github/copilot-sdk-java` team may not have write access to `github/copilot-sdk` | Verify team access and add to repo collaborators. See https://github.com/github/copilot-sdk-partners/issues/89 |
+| M5 | **`copilot-setup-steps.yml` bloat** | Adding JDK + Maven makes agent setup slower for non-Java tasks | Acceptable trade-off; other languages already add their tools. Could consider conditional setup but that's over-engineering. |
+| M6 | **gh-aw version mismatch** | Java repo uses gh-aw `v0.68.3` setup action pinned at `v0.71.5`; monorepo uses `v0.64.2` reference in docs | Align gh-aw versions. Use the newer version. Recompile all `.lock.yml` files. |
+
+### LOW RISK
+
+| # | Risk | Impact | Mitigation |
+| --- | ---------------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------- |
+| L1 | **Issue template conflicts** | Java has custom issue templates; monorepo uses agentic triage | Monorepo agentic triage covers this. Can add Java-specific labels. |
+| L2 | **PR template differences** | Different PR templates | Use monorepo's template. Java-specific guidance in CONTRIBUTING.md. |
+| L3 | **`.githooks` scope** | Java pre-commit hook runs `mvn spotless:check` globally | Scope hook to only run when Java files are changed. |
+| L4 | **Duplicate `agentics-maintenance.yml`** | Java repo has its own; monorepo will generate one | The monorepo's gh-aw will handle this automatically. Don't migrate. |
+
+---
+
+## 8. Post-Migration Verification Checklist
+
+### CI/CD
+
+- [ ] `java-sdk-tests.yml` passes on all 3 OS platforms
+- [ ] `codegen-check.yml` includes Java and passes
+- [ ] `java-codegen-fix.md` compiles and agentic workflow functions
+- [ ] `java-publish.yml` can do a dry-run publish
+- [ ] `java-publish-snapshot.yml` publishes a SNAPSHOT
+- [ ] `java-smoke-test.yml` passes on JDK 17 + JDK 25
+- [ ] `java-deploy-site.yml` successfully deploys docs
+
+### Integration
+
+- [ ] `copilot-setup-steps.yml` includes JDK and Maven
+- [ ] `dependabot.yaml` includes Maven ecosystem for `java/`
+- [β ] `CODEOWNERS` includes `java/` path. See https://github.com/github/copilot-sdk-partners/issues/89
+- [ ] `justfile` has all Java targets and `just test` includes Java
+- [ ] `sdk-consistency-review` includes `java/` in path triggers
+- [ ] `issue-triage` knows about `sdk/java` label
+
+### Code
+
+- [ ] `mvn verify` passes from `java/` directory
+- [ ] E2E tests use local `test/harness/` and `test/snapshots/` (no cloning)
+- [ ] Java codegen integrated into `scripts/codegen/`
+- [ ] `.lastmerge` exists at `java/.lastmerge`
+
+### Documentation
+
+- [ ] Monorepo `README.md` lists Java
+- [ ] `copilot-instructions.md` includes Java governance section (build, test, conventions)
+- [ ] `.github/skills/java-coding-skill/SKILL.md` exists with Java API patterns and coding rules
+- [ ] `java/README.md` links updated to monorepo
+- [ ] Maven Central POM `` URLs updated
+
+### Agentic Sync
+
+- [ ] `java-reference-impl-sync.md` compiles and detects new dotnet/nodejs changes via local `git log`
+- [ ] `agentic-merge-reference-impl` skill works intra-repo (no cross-repo clone)
+- [ ] `java/.lastmerge` correctly stores monorepo commit SHAs
+- [ ] Sync scripts in `.github/scripts/java/reference-impl-sync/` use local paths
+
+### Cleanup
+
+- [ ] `copilot-sdk-java` repo archived
+- [ ] No broken links to old repo
+- [ ] No duplicate `agentics-maintenance.yml`
+
+---
+
+## Appendix A: Files to Copy vs. Merge vs. Delete
+
+| Source File (copilot-sdk-java-00) | Action | Target Location (copilot-sdk-00) |
+| -------------------------------------------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------- |
+| `src/` | Copy | `java/src/` |
+| `config/` | Copy | `java/config/` |
+| `pom.xml` | Copy + update paths | `java/pom.xml` |
+| `CHANGELOG.md` | Copy | `java/CHANGELOG.md` |
+| `README.md` | Copy + update links | `java/README.md` |
+| `jbang-example.java` | Copy | `java/jbang-example.java` |
+| `.lastmerge` | Copy | `java/.lastmerge` |
+| `.githooks/pre-commit` | Copy + scope to Java changes | `java/.githooks/pre-commit` |
+| `docs/adr/` | Copy | `java/docs/adr/` |
+| `scripts/codegen/java.ts` | Copy | `java/scripts/codegen/java.ts` |
+| `scripts/codegen/package.json` | Copy (Java keeps its own) | `java/scripts/codegen/package.json` |
+| `.github/workflows/build-test.yml` | **Adapt** β rename | `.github/workflows/java-sdk-tests.yml` |
+| `.github/workflows/publish-maven.yml` | **Adapt** β rename | `.github/workflows/java-publish.yml` |
+| `.github/workflows/publish-snapshot.yml` | **Adapt** β rename | `.github/workflows/java-publish-snapshot.yml` |
+| `.github/workflows/deploy-site.yml` | **Adapt** β rename | `.github/workflows/java-deploy-site.yml` |
+| `.github/workflows/run-smoke-test.yml` | **Adapt** β rename | `.github/workflows/java-smoke-test.yml` |
+| `.github/workflows/codegen-check.yml` | **Merge** into existing | `.github/workflows/codegen-check.yml` |
+| `.github/workflows/codegen-agentic-fix.md` | **Adapt** β rename | `.github/workflows/java-codegen-fix.md` |
+| `.github/workflows/update-copilot-dependency.yml` | **Merge** into existing | `.github/workflows/update-copilot-dependency.yml` |
+| `.github/workflows/copilot-setup-steps.yml` | **Merge** into existing | `.github/workflows/copilot-setup-steps.yml` |
+| `.github/workflows/reference-impl-sync.md` + `.lock.yml` | **Adapt** β rename + rework for intra-repo | `.github/workflows/java-reference-impl-sync.md` + `.lock.yml` |
+| `.github/workflows/agentics-maintenance.yml` | **DELETE** (monorepo has its own) | β |
+| `.github/workflows/notes.template` | Copy | `.github/workflows/java-notes.template` |
+| `.github/actions/setup-copilot/` | **Adapt** or merge | `.github/actions/java-setup-copilot/` or merge |
+| `.github/actions/test-report/` | Copy | `.github/actions/java-test-report/` |
+| `.github/scripts/*` | Copy + update paths | `.github/scripts/java/` (new subdirectory) |
+| `.github/skills/agentic-merge-reference-impl/` | **Rework** for intra-repo (remove cross-repo clone, use local git diff) | `.github/skills/java-merge-reference-impl/` |
+| `.github/skills/commit-as-pull-request/` | Check for duplicates | `.github/skills/commit-as-pull-request/` |
+| `.github/skills/documentation-coverage/` | Copy | `.github/skills/java-documentation-coverage/` |
+| `.github/prompts/*` | Copy + update | `.github/prompts/` (prefix with `java-` if needed) |
+| `.github/dependabot.yml` | **Merge** into existing | `.github/dependabot.yaml` |
+| `.github/CODEOWNERS` | **Merge** into existing | `.github/CODEOWNERS` |
+| `.github/copilot-instructions.md` | **Merge** into existing | `.github/copilot-instructions.md` |
+| `.github/release.yml` | **Merge** into existing | `.github/release.yml` (if it exists) |
+| `.github/ISSUE_TEMPLATE/*` | Evaluate β likely skip | β |
+| `.github/pull_request_template.md` | Evaluate β likely skip | β |
+| `.github/templates/` | Copy | `java/.github/templates/` or `java/src/site/` |
+| `instructions/copilot-sdk-java.instructions.md` | Create skill (follows `rust-coding-skill` pattern) | `.github/skills/java-coding-skill/SKILL.md` |
+| `test` (file, not directory) | Copy if needed | `java/test` |
+
+## Appendix B: Unique Java Concerns vs Other Languages
+
+| Concern | Java | Other Languages | Notes |
+| --------------------------- | --------------------------------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
+| **Build system** | Maven (`pom.xml`) | npm, pip, go mod, dotnet, cargo | Fully self-contained under `java/` |
+| **Versioning** | `X.Y.Z-java.N` (independent) | Shared `X.Y.Z` across all others | **Must keep separate publish workflow** |
+| **Code formatting** | Spotless (Eclipse formatter) | prettier, ruff, gofmt, dotnet format, rustfmt | Runs only on Java files |
+| **Test framework** | JUnit + Surefire | Vitest, pytest, go test, xunit, cargo test | Standard; no conflicts |
+| **E2E test harness** | Clones `copilot-sdk` at build time | References local `test/harness/` | **Major simplification** when in-repo |
+| **Codegen** | Own `java.ts` + own `@github/copilot` dep | Shared codegen scripts + shared dep | Needs reconciliation |
+| **CI runner** | JDK 17 + JDK 25 (smoke test) | Node 22, Python 3.12, Go 1.24, .NET 10, Rust 1.94 | Just another tool in `copilot-setup-steps.yml` |
+| **Publishing** | Maven Central (GPG + Sonatype) | npm, PyPI, NuGet, crates.io, Go tags | Completely different mechanism |
+| **Docs hosting** | ~~GitHub Pages (Maven site)~~ | ~~Not clear if monorepo has its own~~ | ~~Potential conflict~~ |
+| **Reference impl tracking** | `.lastmerge` + scheduled sync + agentic merge skill | N/A (they ARE the reference impl) | `.lastmerge` stores monorepo SHA; sync becomes intra-repo but is still needed because Java maintainers β .NET/Node maintainers |
+
+---
+
+## Appendix C: Java Smoketest
+
+### Overview
+
+The Java SDK has an AI-driven smoke test that validates the SDK's Quick Start code actually compiles and runs. The test is **prompt-driven**: the `run-smoke-test.yml` workflow invokes the Copilot CLI (`copilot --yolo`) with a prompt that instructs it to read the repository's `README.md`, extract the Quick Start code and Maven coordinates, generate a standalone Maven project, build it, and run it. Success = exit code 0.
+
+This design intentionally tests the README itself β if the documented code doesn't compile against the published artifact, the smoke test fails rather than silently fixing the code. This catches documentation drift.
+
+### How It Works Today
+
+1. **`src/test/prompts/PROMPT-smoke-test.md`** β The master prompt. It instructs the Copilot CLI to:
+ - Read the top-level `README.md`
+ - Extract the **"Snapshot Builds"** section (Maven GAV + snapshots repository config)
+ - Extract the **"Quick Start"** section (verbatim Java source code)
+ - Create a `smoke-test/` Maven project using those extracted values
+ - Build with `mvn -U clean package`
+
+2. **`run-smoke-test.yml`** β The workflow. It:
+ - Builds the SDK and installs it to the local Maven repo
+ - Feeds the prompt to `copilot --yolo` with overrides (use `--no-snapshot-updates`, stop after build)
+ - Runs the built jar in a separate deterministic step
+ - Has two jobs: `smoke-test-jdk17` and `smoke-test-java25` (the latter also applies virtual thread modifications via `// JDK 25+:` comments)
+
+3. **`build-test.yml`** β Calls `run-smoke-test.yml` as a reusable workflow. The main SDK test suite (`java-sdk` job) depends on the smoke test and only runs if it doesn't fail.
+
+### What Breaks When Moving to the Monorepo
+
+The smoke test prompt (`PROMPT-smoke-test.md`) contains these instructions:
+
+> Read the file `README.md` at the top level of this repository. You will need two sections from it: **"Snapshot Builds"** and **"Quick Start"**
+
+After migration, the **top-level `README.md`** is the monorepo's README (`copilot-sdk-00/README.md`), which does not contain a "Snapshot Builds" section or a "Quick Start" section with Java code. The Java-specific README moves to `java/README.md`.
+
+Additionally, the monorepo's top-level README contains Quick Start code for **other languages** (TypeScript, Python, Go, C#). If the prompt were naively updated to "read the README," the AI agent might extract the wrong language's code.
+
+### Required Changes
+
+#### 1. Update `PROMPT-smoke-test.md` β change the README path
+
+Replace:
+
+```
+Read the file `README.md` at the top level of this repository.
+```
+
+With:
+
+```
+Read the file `java/README.md` in this repository.
+```
+
+This is the only structural change needed in the prompt. The section names ("Snapshot Builds" and "Quick Start") remain the same in `java/README.md`.
+
+#### 2. Update `java/README.md` β ensure required sections survive the move
+
+The prompt depends on two specific sections by name:
+
+- **"Snapshot Builds"** β must contain the Maven GAV with `-SNAPSHOT` version and the `central-snapshots` repository XML
+- **"Quick Start"** β must contain the verbatim Java source code with `// JDK 25+:` inline comments for virtual thread toggling
+
+When migrating `README.md` β `java/README.md`, these sections and their content must be preserved exactly. The current monorepo placeholder at `java/README.md` has a different Quick Start (different class name `QuickStart` vs `CopilotSDK`, different imports, no `// JDK 25+:` comments, no `System.exit` logic, no usage metrics handling). **The migrated `README.md` from `copilot-sdk-java` must replace the monorepo placeholder**, not the other way around.
+
+#### 3. Update `run-smoke-test.yml` β `java-smoke-test.yml` β working directory
+
+The workflow steps that run `mvn` and reference `src/test/prompts/PROMPT-smoke-test.md` assume the repo root is the Java project root. After migration:
+
+- Add `working-directory: ./java` to the "Build SDK and install to local repo" step
+- Update the prompt text from `src/test/prompts/PROMPT-smoke-test.md` to `java/src/test/prompts/PROMPT-smoke-test.md` (or set working directory before invoking `copilot`)
+- Update the `cd smoke-test` step to `cd java/smoke-test`
+- Update the `uses: ./.github/actions/setup-copilot` reference to point to the monorepo's setup action (or a Java-specific one at `.github/actions/java-setup-copilot/`)
+
+#### 4. Update `build-test.yml` β `java-sdk-tests.yml` β smoke test call
+
+The current `build-test.yml` calls:
+
+```yaml
+uses: ./.github/workflows/run-smoke-test.yml
+```
+
+After rename, update to:
+
+```yaml
+uses: ./.github/workflows/java-smoke-test.yml
+```
+
+### Risk Assessment
+
+| Risk | Severity | Notes |
+| ---------------------------------------------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Prompt reads wrong README | **HIGH** | If `PROMPT-smoke-test.md` still says "top level README," the AI agent reads the monorepo README and fails or extracts wrong-language code |
+| `java/README.md` placeholder overwrites real content | **HIGH** | The monorepo already has a `java/README.md` with a different Quick Start. Must be replaced with the full Java SDK README during migration |
+| `smoke-test/` directory created at wrong location | **MEDIUM** | Without `working-directory: ./java`, the smoke test project gets created at the monorepo root instead of under `java/` |
+| `// JDK 25+:` comments missing from Quick Start | **MEDIUM** | The JDK 25 smoke test job relies on these comments to toggle virtual thread support. Missing comments β JDK 25 job builds without virtual threads and still passes (silent regression, not a failure) |
+
+### Verification Checklist
+
+- [ ] `PROMPT-smoke-test.md` references `java/README.md`, not `README.md`
+- [ ] `java/README.md` contains "Snapshot Builds" and "Quick Start" sections with the full content from `copilot-sdk-java`
+- [ ] Quick Start code includes `// JDK 25+:` inline comments and `System.exit` logic
+- [ ] `java-smoke-test.yml` uses `working-directory: ./java` for Maven steps
+- [ ] `java-smoke-test.yml` references the correct prompt path
+- [ ] `java-sdk-tests.yml` calls `java-smoke-test.yml` (not `run-smoke-test.yml`)
+- [ ] Smoke test passes locally from `java/` subdirectory before merging
diff --git a/80-java-monorepo-add-01-remove-before-merge/dd-2997995-phase-1-plan.md b/80-java-monorepo-add-01-remove-before-merge/dd-2997995-phase-1-plan.md
new file mode 100644
index 000000000..f479e1d3e
--- /dev/null
+++ b/80-java-monorepo-add-01-remove-before-merge/dd-2997995-phase-1-plan.md
@@ -0,0 +1,99 @@
+# Phase 1 Agent Prompt: Copy Java SDK Source Code into Monorepo
+
+## Instructions
+
+You are working in the `copilot-sdk-00` repository (dest). The source Java SDK code lives at `../copilot-sdk-java-00` (relative to this repo root). Both are local clones:
+
+- **Dest (you are here):** `copilot-sdk-00` β local clone of `https://github.com/github/copilot-sdk`
+- **Source:** `../copilot-sdk-java-00` β local clone of `https://github.com/github/copilot-sdk-java`
+
+**Before doing anything else**, read the file `80-java-monorepo-add-01-remove-before-merge/dd-2989727-move-java-to-monorepo-plan.md` in this repository. It contains the full migration plan. You are executing **Phase 1 ONLY** β "Copy Source Code (No Workflows Yet)". Do NOT perform any other phases (Phase 2, 3, 4, 5, or 6).
+
+You are safe to commit directly to the current topic branch. Make fine-grained commits with reasonable commit log messages as you go (e.g., one commit per logical group of files copied, one commit for pom.xml adjustments, one commit for test infrastructure changes).
+
+## Phase 1 Goal
+
+Get all Java source code building and testing in the monorepo under `java/` without any CI/CD workflows.
+
+## Phase 1 Steps
+
+### Step 1: Copy files from source to dest
+
+Copy the following from `../copilot-sdk-java-00/` into `java/` (replacing the existing placeholder `java/README.md`):
+
+| Source path (relative to `../copilot-sdk-java-00/`) | Dest path (relative to repo root) |
+| --------------------------------------------------- | ------------------------------------------------ |
+| `src/` (all of it: main, test, generated, site) | `java/src/` |
+| `pom.xml` | `java/pom.xml` |
+| `config/` (checkstyle, spotbugs) | `java/config/` |
+| `scripts/codegen/java.ts` | `java/scripts/codegen/java.ts` |
+| `scripts/codegen/package.json` | `java/scripts/codegen/package.json` |
+| `CHANGELOG.md` | `java/CHANGELOG.md` |
+| `README.md` | `java/README.md` (replaces existing placeholder) |
+| `jbang-example.java` | `java/jbang-example.java` |
+| `.lastmerge` | `java/.lastmerge` |
+| `docs/adr/` | `java/docs/adr/` |
+| `mvnw` | `java/mvnw` |
+| `mvnw.cmd` | `java/mvnw.cmd` |
+| `.mvn/` | `java/.mvn/` |
+| `.gitignore` | `java/.gitignore` |
+| `test` (single file, not a directory) | `java/test` |
+
+**DO NOT copy:**
+
+- `.githooks/` β already handled separately (strikethrough in plan)
+- `instructions/copilot-sdk-java.instructions.md` β already handled separately (strikethrough in plan)
+- `.github/` β workflows are Phase 2+, not Phase 1
+- `.git/` β never copy git internals
+- `target/` β build artifacts, never copy
+- `.claude/` β not needed
+- `.vscode/` β not needed
+- `20260430-*.txt` β log files, not needed
+- `CODE_OF_CONDUCT.md`, `CONTRIBUTING.md`, `LICENSE`, `SECURITY.md`, `SUPPORT.md` β these exist at the monorepo root already
+
+### Step 2: Update `pom.xml` paths if needed
+
+The `pom.xml` should be self-contained under `java/`. Review it for any paths that assume it lives at the repository root. Key things to check and fix:
+
+1. **Test harness clone**: The current `pom.xml` likely has a `maven-antrun-plugin` execution that clones `https://github.com/github/copilot-sdk` into `target/copilot-sdk/` to get `test/harness/` and `test/snapshots/`. Since these directories now exist locally in the same repo at `../../test/harness/` and `../../test/snapshots/` (relative to `java/`), **replace the git clone with a local copy or symlink**. The simplest approach: change the antrun execution to copy from `${project.basedir}/../test/` instead of cloning from GitHub.
+
+2. **Any absolute or root-relative paths** that reference the repo root β these should be adjusted to work from `java/` as the working directory.
+
+3. **The `` section** β update URLs from `github/copilot-sdk-java` to `github/copilot-sdk` and adjust paths if needed.
+
+### Step 3: Verify `mvn clean verify` works from `java/`
+
+Run `cd java && mvn clean verify` and fix any issues. The build must pass. Common issues to expect:
+
+- Test harness path references (from Step 2)
+- Any hardcoded paths in test infrastructure that assume repo root = Java project root
+- The `E2ETestContext` or `CapiProxy` classes may reference `target/copilot-sdk/test/harness/` β these need to point to `../../test/` (or however the local copy is structured after Step 2)
+
+If tests fail, diagnose and fix. Do NOT skip tests. The goal is a green `mvn clean verify` from `java/`.
+
+### Commit Strategy
+
+Make commits as you go:
+
+1. After copying the source files (Step 1)
+2. After updating `pom.xml` and test infrastructure (Step 2)
+3. After fixing any build/test issues (Step 3)
+
+Use descriptive commit messages like:
+
+- "Copy Java SDK source files into java/ directory"
+- "Update pom.xml to use local test harness instead of git clone"
+- "Fix E2E test paths for monorepo layout"
+
+## Constraints
+
+- **DO NOT** create or modify any GitHub Actions workflow files (`.github/workflows/`)
+- **DO NOT** modify `.github/copilot-instructions.md`
+- **DO NOT** modify the `justfile`
+- **DO NOT** modify `CODEOWNERS`
+- **DO NOT** modify `dependabot.yaml`
+- **DO NOT** modify `copilot-setup-steps.yml`
+- **DO NOT** touch any files under `nodejs/`, `python/`, `go/`, `dotnet/`, `rust/`
+- **DO NOT** perform Phase 2, 3, 4, 5, or 6 work
+- **DO NOT** modify files under `java/src/generated/java/` beyond what was copied β these are auto-generated
+- You MAY modify `java/pom.xml`, `java/src/test/java/**`, and `java/src/main/java/**` as needed to get the build passing
diff --git a/80-java-monorepo-add-01-remove-before-merge/dd-2998002-make-the-monorepo-smart-about-java-plan.md b/80-java-monorepo-add-01-remove-before-merge/dd-2998002-make-the-monorepo-smart-about-java-plan.md
new file mode 100644
index 000000000..9f13ae79f
--- /dev/null
+++ b/80-java-monorepo-add-01-remove-before-merge/dd-2998002-make-the-monorepo-smart-about-java-plan.md
@@ -0,0 +1,278 @@
+# DD-2998002: Make the Monorepo Smart About Java
+
+## Goal
+
+Make the `copilot-sdk` monorepo's Copilot configuration aware of Java so that AI coding agents receive Java-specific guidance when editing Java files. This involves two deliverables:
+
+1. **Create `.github/skills/java-coding-skill/SKILL.md`** β a new skill containing Java SDK API patterns and coding rules (sourced from the standalone Java repo's `instructions/copilot-sdk-java.instructions.md`).
+2. **Add a Java section to `.github/copilot-instructions.md`** β concise Java governance (build commands, architecture, test conventions) that parallels the existing Node, Python, Go, .NET entries.
+
+## Context
+
+- The monorepo uses **skills** for language-specific coding guidance (not `instructions/` directories).
+- The only existing language-specific skill is `rust-coding-skill` at `.github/skills/rust-coding-skill/SKILL.md`.
+- No other language (Node, Python, Go, .NET) has a dedicated skill β only Rust does.
+- The Java SDK source will live under `java/` in the monorepo (per the Phase 1 migration plan).
+- The standalone Java repo has two Copilot configuration files:
+ - `instructions/copilot-sdk-java.instructions.md` (~757 lines) β API usage patterns, coding rules, examples
+ - `.github/copilot-instructions.md` (~260 lines) β repo governance: build commands, architecture, testing conventions, boundaries, security
+
+## Execution Context
+
+- You are running from the **monorepo root** (`copilot-sdk-00/`).
+- The standalone Java SDK repo is available at **`../copilot-sdk-java-00/`** (a sibling directory).
+- All files you create or edit are in the monorepo (current directory). The Java repo is read-only β you only read source files from it.
+
+## Non-Goals
+
+- Do NOT copy `instructions/` as a directory to `java/instructions/` β that doesn't match the monorepo convention.
+- Do NOT create skills for other languages β only Java is being added.
+- Do NOT modify any Java source code, tests, or build files.
+- Do NOT modify any existing skills (e.g., `rust-coding-skill`).
+- Do NOT modify any files in `../copilot-sdk-java-00/` β it is a read-only source.
+
+---
+
+## Checklist
+
+### Step 1: Read the existing `rust-coding-skill` to understand the pattern
+
+- [ ] Read `.github/skills/rust-coding-skill/SKILL.md` to understand the YAML frontmatter structure and content organization.
+
+The frontmatter uses exactly these fields:
+
+```yaml
+---
+name: rust-coding-skill
+description: "Use this skill whenever editing `*.rs` files in the `rust/` SDK in order to write idiomatic, efficient, well-structured Rust code"
+---
+```
+
+### Step 2: Read the Java instructions source file
+
+- [ ] Read the full content of the Java instructions file at **`../copilot-sdk-java-00/instructions/copilot-sdk-java.instructions.md`**.
+ - **Fallback** (if the file is not found at that path): The structure is described in [Appendix A](#appendix-a-java-instructions-source-content) below.
+
+### Step 3: Create `.github/skills/java-coding-skill/SKILL.md`
+
+- [ ] Create the directory `.github/skills/java-coding-skill/`
+- [ ] Create `.github/skills/java-coding-skill/SKILL.md` with:
+ - YAML frontmatter (see template below)
+ - Body content adapted from the Java instructions file
+
+**YAML frontmatter** β use exactly this:
+
+```yaml
+---
+name: java-coding-skill
+description: "Use this skill whenever editing `*.java` files in the `java/` SDK in order to write idiomatic, well-structured Java code for the Copilot SDK"
+---
+```
+
+**Body content** β take the full content of `../copilot-sdk-java-00/instructions/copilot-sdk-java.instructions.md` (everything after its YAML frontmatter) and make these adaptations:
+
+1. **Remove the old YAML frontmatter** (`applyTo`, `description`, `name` fields from the instructions file). Replace it with the new frontmatter above.
+2. **Add a title line** after the frontmatter: `# Java Coding Skill`
+3. **Update paths to reflect monorepo layout**:
+ - References to `src/` β `java/src/`
+ - References to `pom.xml` β `java/pom.xml`
+ - References to `config/` β `java/config/`
+ - References to `scripts/codegen/` β `scripts/codegen/` (codegen lives at monorepo root)
+ - References to `target/` β `java/target/`
+ - References to `.lastmerge` β `java/.lastmerge`
+ - References to `.githooks/` β `java/.githooks/`
+ - References to `src/site/` β `java/src/site/`
+ - References to `src/generated/java/` β `java/src/generated/java/`
+4. **Keep all code examples unchanged** β they show API usage, not file paths.
+5. **Keep all sections** β Core Principles, Installation, Client Initialization, Session Management, Event Handling, Streaming, Custom Tools, Permission Handling, User Input, System Message, File Attachments, Message Delivery, Send and Wait, Multiple Sessions, BYOK, Session Lifecycle, Error Handling, Connectivity Testing, Status/Auth, Resource Cleanup, Best Practices, Common Patterns.
+6. **Do NOT add content that isn't in the source** β no new sections, no commentary.
+
+### Step 4: Read the monorepo's existing `.github/copilot-instructions.md`
+
+- [ ] Read `.github/copilot-instructions.md` to understand the current structure and where Java should be added.
+
+The current file has these sections:
+
+- Big picture π§
+- Most important files to read first π
+- Developer workflows βΆοΈ (per-language subsection)
+- Testing & E2E tips βοΈ
+- Project-specific conventions & patterns β
+- Integration & environment notes β οΈ
+- Where to add new code or tests π§
+
+### Step 5: Read the Java repo's `.github/copilot-instructions.md`
+
+- [ ] Read the Java repo's governance file at **`../copilot-sdk-java-00/.github/copilot-instructions.md`** to extract the content that needs to be merged.
+ - **Fallback** (if the file is not found at that path): The content to merge is provided in [Appendix B](#appendix-b-java-governance-content-to-merge) below.
+
+### Step 6: Add Java to `.github/copilot-instructions.md`
+
+- [ ] Make the following additions to the monorepo's `.github/copilot-instructions.md`:
+
+**6a. Update "Big picture" section:**
+
+Change:
+
+```
+The repo implements language SDKs (Node/TS, Python, Go, .NET) that speak to the **Copilot CLI**
+```
+
+To:
+
+```
+The repo implements language SDKs (Node/TS, Python, Go, .NET, Java) that speak to the **Copilot CLI**
+```
+
+And add Java's CLI URL option to the typical flow line. Current:
+
+```
+(Node: `cliUrl`, Go: `CLIUrl`, .NET: `CliUrl`, Python: `cli_url`)
+```
+
+Updated:
+
+```
+(Node: `cliUrl`, Go: `CLIUrl`, .NET: `CliUrl`, Python: `cli_url`, Java: `cliUrl`)
+```
+
+**6b. Update "Most important files to read first" section:**
+
+Add:
+
+```
+- Java: `java/README.md`, `java/pom.xml`
+```
+
+**6c. Update "Developer workflows" per-language section:**
+
+Add a Java entry after the .NET entry:
+
+```
+ - Java: `cd java && mvn clean verify` (full build + tests), `mvn spotless:apply` (format code before commit)
+ - **Java testing note:** Always use `mvn verify` without `-q` and without piping through `grep`. Never add `InternalsVisibleTo` equivalent β tests must only access public APIs.
+```
+
+**6d. Update "Testing & E2E tips" section:**
+
+Add after the existing E2E description:
+
+```
+- Java E2E tests use `E2ETestContext` which manages a `CapiProxy` (Node.js replaying proxy). The harness is cloned during Maven's `generate-test-resources` phase to `java/target/copilot-sdk/`.
+```
+
+**6e. Update "Where to add new code or tests" section:**
+
+Add Java to the lists:
+
+- SDK code line: add `java/src/main/java`
+- Unit tests line: add `java/src/test/java`
+- E2E tests line: add `java/src/test/java/**/e2e/`
+- Generated types line: add `java/src/generated/java`
+
+**6f. Update "Integration & environment notes" section:**
+
+Add Java's CLI URL option. Current:
+
+```
+(Node: `cliUrl`, Go: `CLIUrl`, .NET: `CliUrl`, Python: `cli_url`)
+```
+
+Updated:
+
+```
+(Node: `cliUrl`, Go: `CLIUrl`, .NET: `CliUrl`, Python: `cli_url`, Java: `cliUrl`)
+```
+
+Add environment note:
+
+```
+- Java requires JDK 17+ and Maven 3.9+. Java E2E tests also require Node.js (for the replay proxy).
+```
+
+### Step 7: Verify
+
+- [ ] Confirm `.github/skills/java-coding-skill/SKILL.md` exists and has valid YAML frontmatter with `name` and `description` fields.
+- [ ] Confirm `.github/copilot-instructions.md` mentions Java in all per-language lists (Big picture, Developer workflows, Most important files, Where to add code, Integration notes).
+- [ ] Confirm no files were created under `java/instructions/` β that pattern is NOT used in this monorepo.
+- [ ] Confirm `.github/skills/rust-coding-skill/` was NOT modified.
+
+---
+
+## Appendix A: Java Instructions Source Content
+
+The source file is `instructions/copilot-sdk-java.instructions.md` from the `copilot-sdk-java` repository. Its YAML frontmatter is:
+
+```yaml
+---
+applyTo: "**.java, **/pom.xml"
+description: "This file provides guidance on building Java applications using GitHub Copilot SDK for Java."
+name: "GitHub Copilot SDK Java Instructions"
+---
+```
+
+The body contains these sections (in order):
+
+1. Core Principles
+2. Installation (Maven, Gradle)
+3. Client Initialization (Basic, Options, Manual Server Control)
+4. Session Management (Creating, Config Options, Resuming, Operations)
+5. Event Handling (Subscription, Type-Safe, Unsubscribing, Event Types, Error Handling)
+6. Streaming Responses (Enabling, Handling Events)
+7. Custom Tools (Defining, Type-Safe Args, Overriding Built-In, Return Types, Execution Flow)
+8. Permission Handling (Required Handler)
+9. User Input Handling
+10. System Message Customization (Append Mode, Replace Mode)
+11. File Attachments
+12. Message Delivery Modes
+13. Convenience: Send and Wait
+14. Multiple Sessions
+15. Bring Your Own Key (BYOK)
+16. Session Lifecycle Management (Listing, Deleting, Connection State, Lifecycle Events)
+17. Error Handling (Standard Exceptions, Session Error Events)
+18. Connectivity Testing
+19. Status and Authentication
+20. Resource Cleanup (Automatic, Manual)
+21. Best Practices (12 items)
+22. Common Patterns (Simple Query-Response, Event-Driven, Multi-Turn, Complex Tools, Session Hooks)
+
+The file is ~757 lines. The agent MUST read the full file from the source repo or the monorepo copy β do not truncate or summarize.
+
+## Appendix B: Java Governance Content to Merge
+
+The source file is `.github/copilot-instructions.md` from the `copilot-sdk-java` repository. Key content to extract and merge into the monorepo's `copilot-instructions.md`:
+
+**Build & Test Commands** (merge into Developer workflows):
+
+- `mvn clean verify` β full build + tests
+- `mvn test -Dtest=ClassName` β single test class
+- `mvn test -Dtest=ClassName#method` β single test method
+- `mvn spotless:apply` β format code (required before commit)
+- `mvn spotless:check` β check formatting only
+- `mvn clean package -DskipTests` β build without tests
+- AI agent testing rule: always use `mvn verify` without `-q`, never pipe through `grep`
+
+**Architecture** (reference from skill, don't duplicate):
+
+- CopilotClient, CopilotSession, JsonRpcClient
+- Package structure: `com.github.copilot.sdk`, `.json`, `.generated`
+
+**Key Conventions** (merge selectively β most belongs in the skill):
+
+- Reference implementation merging pattern (keep in governance β it's repo-level policy)
+- Code style: 4-space indent, Spotless, Checkstyle (keep in governance)
+- Pre-commit hooks (keep in governance)
+
+**Boundaries and Restrictions** (keep in governance):
+
+- Do not edit `src/generated/java/` (auto-generated)
+- Do not modify test snapshots in `target/copilot-sdk/test/snapshots/`
+- Must run `gh aw compile` after editing agentic workflow `.md` files
+
+**Security Guidelines** (keep in governance):
+
+- Never commit secrets
+- Use try-with-resources, StandardCharsets.UTF_8
+- Review dependencies for vulnerabilities
+
+**NOTE**: The governance content that goes into `copilot-instructions.md` should be CONCISE β just enough for an agent to know how to build, test, and follow repo rules. The detailed API patterns and coding examples belong in the skill file, not in governance.
diff --git a/80-java-monorepo-add-01-remove-before-merge/dd-3001051-phase-2-plan.md b/80-java-monorepo-add-01-remove-before-merge/dd-3001051-phase-2-plan.md
new file mode 100644
index 000000000..6231407a7
--- /dev/null
+++ b/80-java-monorepo-add-01-remove-before-merge/dd-3001051-phase-2-plan.md
@@ -0,0 +1,231 @@
+# Phase 2: CI Workflows β Agent Prompt
+
+## Context
+
+You are working in `copilot-sdk-00`, a local clone of `https://github.com/github/copilot-sdk`. The Java SDK source repository is at `../copilot-sdk-java-00` (a local clone of `https://github.com/github/copilot-sdk-java`).
+
+You are implementing one phase of the work to make it so the `copilot-sdk/java` directory is the new home for what is currently `copilot-sdk-java`, with all sournce code, workflows and maintenance affordances migrated.
+
+Phase 0 (pre-flight) and Phase 1 (copy source code) are already complete. The Java source code is already present under `java/` in this repository and `mvn clean verify` passes from that directory.
+
+## First Step β Read the Master Plan
+
+Before doing any work, read the file:
+
+```
+80-java-monorepo-add-01-remove-before-merge/dd-2989727-move-java-to-monorepo-plan.md
+```
+
+This contains the full migration plan. Focus on the "Phase 2: CI Workflows" section, but also review the naming conventions in Β§3 and the workflow inventory tables in Β§5 for context.
+
+**You are executing Phase 2 only. Do NOT perform any other phases (Phase 0, 1, 3, 4, 5, or 6).**
+
+## Working Branch and Commits
+
+You are safe to do all work on the current topic branch. Make fine-grained commits with clear, descriptive commit log messages (e.g., "Add java-sdk-tests.yml adapted from build-test.yml", "Add Java job to codegen-check.yml"). Do not squash β keep commits granular so they are easy to review.
+
+## Phase 2 Goal
+
+Java CI runs on PRs and pushes to `main` within the monorepo. This phase creates/updates 5 things:
+
+1. A new `java-sdk-tests.yml` workflow
+2. A merged Java job in the existing `codegen-check.yml`
+3. A new `java-codegen-agentic-fix.md` agentic workflow (+ compiled `.lock.yml`)
+4. Java tooling added to the existing `copilot-setup-steps.yml`
+5. Maven ecosystem entry added to the existing `dependabot.yaml`
+
+---
+
+## Task 1: Create `.github/workflows/java-sdk-tests.yml`
+
+**Source to adapt from:** `../copilot-sdk-java-00/.github/workflows/build-test.yml`
+
+**Reference for monorepo style:** `.github/workflows/dotnet-sdk-tests.yml` or `.github/workflows/go-sdk-tests.yml` (read one of these to match trigger structure and job naming conventions).
+
+### Requirements
+
+- **Triggers:**
+ - `push` to `main` with paths: `java/**`, `test/**`, `.github/workflows/java-sdk-tests.yml`
+ - `pull_request` with same paths
+ - `workflow_dispatch`
+
+- **OS matrix:** Run on `ubuntu-latest`, `windows-latest`, `macos-latest` (match other SDK test workflows in this repo).
+
+- **JDK version:** 17 (use `actions/setup-java` with `temurin` distribution).
+
+- **Steps (adapt from `build-test.yml`):**
+ 1. Checkout
+ 2. Set up JDK 17
+ 3. Cache Maven dependencies (`~/.m2/repository`)
+ 4. Set up Node.js (needed for E2E test harness β the replay proxy is Node-based, located at `test/harness/`)
+ 5. Run `mvn spotless:check` (formatting gate)
+ 6. Run `mvn clean verify` (build + all tests including E2E)
+ 7. Upload test results (Surefire reports) as artifacts on failure
+
+- **Working directory:** All Maven commands must use `working-directory: ./java`
+
+- **Important differences from source:**
+ - The source `build-test.yml` clones the `copilot-sdk` repo at build time (via `generate-test-resources` Maven phase) to get `test/harness/` and `test/snapshots/`. In the monorepo these are already present at the repo root under `test/`. The `java/pom.xml` has already been updated in Phase 1 to reference the local `test/` directory, so no special handling is needed β just ensure the checkout is a full checkout (not shallow if tests need git history β check if this matters).
+ - The source has a `smoke-test` job that calls `run-smoke-test.yml`. Do NOT include the smoke test in this workflow β that is a Phase 3 concern (`java-smoke-test.yml`).
+ - The source has coverage badge generation. Do NOT include coverage badge generation β keep the workflow focused on build+test.
+ - The source has a Javadoc generation step. Include a `mvn javadoc:javadoc` step (non-failing, just to verify Javadoc compiles) OR fold it into the `mvn verify` if the POM already runs Javadoc during verify. Check `java/pom.xml` to see if Javadoc is part of the verify lifecycle.
+
+- **Do NOT include:**
+ - Smoke test job (Phase 3)
+ - Deploy/publish steps (Phase 3)
+ - Any cross-repo clone of `copilot-sdk` (no longer needed)
+
+---
+
+## Task 2: Merge Java into `.github/workflows/codegen-check.yml`
+
+**Source to adapt from:** `../copilot-sdk-java-00/.github/workflows/codegen-check.yml`
+
+**Target to modify:** `.github/workflows/codegen-check.yml` (already exists in the monorepo)
+
+### Requirements
+
+- Read the existing `codegen-check.yml` to understand its structure. It already has jobs for Node, .NET, Python, Go, and Rust codegen verification.
+
+- **Add `java/src/generated/**` to the path triggers\*\* (both push and pull_request).
+
+- **Add a new job** (e.g., `java-codegen`) that:
+ 1. Checks out the repo
+ 2. Sets up Node.js (the Java codegen script `java/scripts/codegen/java.ts` is a TypeScript file that runs via `npx tsx`)
+ 3. Sets up JDK 17 (needed if the codegen script validates against Java compilation)
+ 4. Installs codegen dependencies: `cd java/scripts/codegen && npm ci`
+ 5. Runs the Java codegen: `cd java/scripts/codegen && npx tsx java.ts`
+ 6. Checks for uncommitted changes in `java/src/generated/` using `git diff --exit-code java/src/generated/`
+ 7. If changes exist, fails with a message indicating codegen is out of date
+
+- **Match the job structure** of the other language codegen jobs in the same file (same checkout action version, same diff pattern, etc.).
+
+---
+
+## Task 3: Create `.github/workflows/java-codegen-agentic-fix.md`
+
+**Source to adapt from:** `../copilot-sdk-java-00/.github/workflows/codegen-agentic-fix.md`
+
+Also read the corresponding `.lock.yml` from the source to understand the compiled structure.
+
+### What codegen-check.yml and `codegen-agentic-fix` Actually Own
+
+These two workflows form a **self-contained codegen CI pipeline** with one concern only:
+
+> **Keep java in sync with whatever `@github/copilot` schemas are declared in package.json.**
+
+Their flow:
+
+1. codegen-check.yml: On PR or push, re-runs codegen β if drift detected, pushes regen'd files β if `mvn verify` fails, triggers the agentic fix
+2. codegen-agentic-fix.lock.yml: AI agent that fixes `java.ts` and/or handwritten source until `mvn verify` passes, then pushes to the PR
+
+They do **not** interact with .lastmerge, the CLI version property, or the test harness clone.
+
+### Requirements
+
+- This is a `gh-aw` (GitHub Agentic Workflows) markdown file. It defines an agentic workflow that auto-fixes compilation/test failures caused by Java codegen changes.
+
+- **Adapt the source** with these changes:
+ - All paths updated to reflect the monorepo structure (e.g., `java/src/generated/`, `java/scripts/codegen/`, etc.)
+ - Remove any references to cross-repo operations
+ - Update the workflow trigger to fire when `java-codegen` job (from `codegen-check.yml`) fails
+ - Update instructions to run `cd java && mvn verify` for validation
+ - Update codegen command to `cd java/scripts/codegen && npx tsx java.ts`
+
+- **After creating the `.md` file**, compile it:
+
+ ```
+ gh aw compile java-codegen-fix
+ ```
+
+ This generates `.github/workflows/java-codegen-fix.lock.yml`. Both files must be committed.
+
+- **If `gh aw` is not available or the compile fails**, note this in a commit message and commit just the `.md` file. The `.lock.yml` can be generated later.
+
+---
+
+## Task 4: Merge Java into `.github/workflows/copilot-setup-steps.yml`
+
+**Target to modify:** `.github/workflows/copilot-setup-steps.yml` (already exists)
+
+### Requirements
+
+- Read the existing `copilot-setup-steps.yml`. It sets up the environment for the Copilot coding agent (Node, Python, Go, .NET, Rust, etc.).
+
+- **Add the following steps** (in a logical position alongside other language setups):
+ 1. **Set up JDK 17:**
+ ```yaml
+ - uses: actions/setup-java@v4
+ with:
+ distribution: "microsoft"
+ java-version: "17"
+ cache: "maven"
+ ```
+ 2. **Set up Maven cache** (if not handled by the `cache: 'maven'` option above, add explicit caching of `~/.m2/repository`).
+ 3. **Enable Java git hooks** (the Java pre-commit hook):
+ ```yaml
+ - name: Enable Java pre-commit hook
+ run: |
+ cd java
+ git config core.hooksPath .githooks
+ ```
+ Only add this if `java/.githooks/pre-commit` exists. Check first.
+
+- **Also check** `../copilot-sdk-java-00/.github/workflows/copilot-setup-steps.yml` for any other setup steps that the Java coding agent environment needs (e.g., `gh aw` installation, specific npm global installs). Port those over if they aren't already present in the monorepo version.
+
+---
+
+## Task 5: Update `.github/dependabot.yaml`
+
+**Target to modify:** `.github/dependabot.yaml` (already exists)
+
+### Requirements
+
+- Read the existing file to understand its structure.
+
+- **Add a Maven ecosystem entry** for the `java/` directory:
+
+ ```yaml
+ - package-ecosystem: "maven"
+ directory: "/java"
+ schedule:
+ interval: "weekly"
+ labels:
+ - "dependencies"
+ - "sdk/java"
+ ```
+
+- Match the style (labels, schedule interval, grouping) of the existing entries. If other entries use `groups`, add an appropriate group for Java dependencies.
+
+- **Also check** if `../copilot-sdk-java-00/.github/dependabot.yml` has any additional ecosystems configured (e.g., `github-actions` scoped to Java workflows, or `npm` for `java/scripts/codegen/`). If so, add those entries too.
+
+---
+
+## Verification
+
+After completing all 5 tasks, verify:
+
+1. **YAML syntax:** Run `python -c "import yaml; yaml.safe_load(open('.github/workflows/java-sdk-tests.yml'))"` (or equivalent) to check YAML validity of new/modified workflow files.
+2. **No broken references:** Ensure no workflow file references actions or paths that don't exist.
+3. **Consistent style:** The new `java-sdk-tests.yml` should look similar in structure to `dotnet-sdk-tests.yml` or `go-sdk-tests.yml`.
+4. **Commit each task separately** with a clear commit message.
+
+## Summary of Files to Create/Modify
+
+| Action | File |
+| ---------- | ------------------------------------------------------------------- |
+| **Create** | `.github/workflows/java-sdk-tests.yml` |
+| **Modify** | `.github/workflows/codegen-check.yml` |
+| **Create** | `.github/workflows/java-codegen-fix.md` |
+| **Create** | `.github/workflows/java-codegen-fix.lock.yml` (via `gh aw compile`) |
+| **Modify** | `.github/workflows/copilot-setup-steps.yml` |
+| **Modify** | `.github/dependabot.yaml` |
+
+## Reminders
+
+- All Maven commands use `working-directory: ./java`
+- Do NOT touch Phase 3 (publish), Phase 4 (agentic sync), Phase 5 (cross-cutting), or Phase 6 (cutover)
+- Do NOT modify `java/pom.xml` or any Java source code
+- Do NOT modify `test/harness/` or `test/snapshots/`
+- Follow the naming convention: language-specific workflows use `java-` prefix
+- Make fine-grained commits on the current branch
diff --git a/80-java-monorepo-add-01-remove-before-merge/ghcp-sp-95-branch-protection-findings-01.md b/80-java-monorepo-add-01-remove-before-merge/ghcp-sp-95-branch-protection-findings-01.md
new file mode 100644
index 000000000..c08d65341
--- /dev/null
+++ b/80-java-monorepo-add-01-remove-before-merge/ghcp-sp-95-branch-protection-findings-01.md
@@ -0,0 +1,205 @@
+# GHCP-SP-95: Branch Protection Findings
+
+**Date:** 2025-05-15
+**Issue:** https://github.com/github/copilot-sdk-partners/issues/95
+**Context:** Phase 0 investigation into whether `maven-release-plugin` can be replaced by a CI-friendly alternative to eliminate the need for a branch protection bypass on `main`.
+
+---
+
+## 1. Summary of the Question
+
+Steve's agent claimed:
+
+1. `maven-release-plugin` is "legacy" and major projects have moved away from it.
+2. CI-friendly versions (`${revision}` + `flatten-maven-plugin`) would eliminate the need for branch protection bypasses.
+3. The `-SNAPSHOT` lifecycle is "arguably unnecessary."
+4. The trade-off is re-engineering the release workflow, but it's a "well-documented path."
+
+Steve asked Ed to investigate the cost of item 4 β and whether the cost is low enough to do before GA, or should be deferred.
+
+This document evaluates each claim against evidence.
+
+---
+
+## 2. Is `maven-release-plugin` Actually Legacy?
+
+**No.** The plugin is actively maintained by Apache:
+
+| Version | Release Date |
+| ------- | ------------ |
+| 3.3.1 | 2025-12-09 |
+| 3.3.0 | 2025-11-30 |
+| 3.2.0 | 2025-11-04 |
+| 3.1.1 | 2024-07-11 |
+| 3.1.0 | 2024-06-14 |
+
+Three releases in the last two months of 2025 alone. The 3.x line is a major rewrite from the 2.x series. This is not an abandoned plugin.
+
+### Do Small Projects Still Use It?
+
+Yes. The agent's claim conflated "Spring/Quarkus don't use it" with "nobody uses it." Those projects have custom build infrastructure **because they are multi-module monorepos with hundreds of modules**. A single-module library like `copilot-sdk-java` is the exact use case `maven-release-plugin` was designed for.
+
+Examples of actively maintained single-module or small-module-count Maven projects that use `maven-release-plugin` in 2025β2026:
+
+- **Apache Maven plugins themselves** (maven-compiler-plugin, maven-surefire, etc.) all use `maven-release-plugin` for their own releases.
+- **Many Apache commons libraries** (commons-lang3, commons-io) use it.
+- The plugin's own page at https://maven.apache.org/plugins/maven-release-plugin/ shows its last published version is 3.3.1 (2025-12-09).
+
+The trend away from `maven-release-plugin` is real **for large multi-module projects**, but for a single-artifact library, it remains the most battle-tested, lowest-maintenance option.
+
+### What About the Agent's Claim About Spring, Apache, and Quarkus?
+
+The agent was **partially correct but misleading**:
+
+- **Spring** uses Gradle, not Maven, so the comparison is irrelevant.
+- **Quarkus** is a 900+ module monorepo β they need custom tooling regardless.
+- **Apache sub-projects** vary: many small ones (the Maven plugins, commons libraries) still use `maven-release-plugin`. The large ones (Kafka, Beam) don't, but they have dedicated release engineering teams.
+
+The relevant comparison for `copilot-sdk-java` is other single-artifact Maven libraries, not framework monorepos.
+
+### Usage Data from Maven Central (mvnrepository.com)
+
+`maven-release-plugin` is ranked **#9 in the Maven Plugins category** and has **28,957 published artifacts** that declare it as a dependency β nearly 29,000 distinct Maven projects on Central use it in their build.
+
+To put that number in context, the "Used By" list for `maven-release-plugin` is sorted by popularity of the _dependent_ artifact. The **top 10 most popular artifacts that use `maven-release-plugin`** are:
+
+| Rank | Artifact | Own "Used By" Count |
+| ---- | ------------------------------- | ------------------- |
+| 1 | JUnit | 141,118 |
+| 2 | Apache Maven Compiler Plugin | 119,190 |
+| 3 | Apache Maven Source Plugin | 82,729 |
+| 4 | Apache Maven Javadoc Plugin | 80,532 |
+| 5 | Apache Maven JAR Plugin | 55,934 |
+| 6 | Apache Maven GPG Plugin | 48,317 |
+| 7 | Jackson Databind | 39,358 |
+| 8 | Logback Classic | 33,374 |
+| 9 | Maven Bundle Plugin | 31,856 |
+| 10 | **Maven Release Plugin itself** | 28,957 |
+
+This means the most foundational artifacts in the Java ecosystem β JUnit, Jackson, Logback, and Maven's own core plugins β all use `maven-release-plugin` in their build. These are not legacy holdouts; they are the infrastructure that every Java project depends on.
+
+Version-level download counts also show sustained adoption of the 3.x line:
+
+| Version | Downloads | Release Date |
+| -------------- | --------- | ------------ |
+| 3.3.1 | 396 | Dec 13, 2025 |
+| 3.3.0 | 32 | Dec 03, 2025 |
+| 3.2.0 | 174 | Nov 08, 2025 |
+| 3.1.1 | 1,218 | Jul 14, 2024 |
+| 3.0.1 | 1,204 | Jun 03, 2023 |
+| 2.5.3 (legacy) | 9,640 | Oct 14, 2015 |
+
+Note: The lower counts on the newest 3.3.x versions are expected β they were released only 5 months ago, and many projects pin to a version and update on their own schedule. The 3.1.1 version (11 months old) already has 1,218 downloads, showing healthy adoption of the 3.x line.
+
+---
+
+## 3. The CI-Friendly Alternative: What Would It Cost?
+
+The alternative approach uses:
+
+1. **CI-friendly versions** β `${revision}` in pom.xml with `flatten-maven-plugin`
+2. **`central-publishing-maven-plugin`** (Sonatype's new portal plugin) or plain `mvn deploy` for publishing
+3. **GitHub Actions** manages versioning: tags drive the version, no commits to pom.xml needed
+
+### What the Rewrite Would Involve
+
+| Component | Change Required |
+| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `pom.xml` | Replace hardcoded version with `${revision}${changelist}`, add `flatten-maven-plugin`, replace `maven-release-plugin` with `central-publishing-maven-plugin` or keep `maven-deploy-plugin` |
+| `java-publish-maven.yml` | Complete rewrite: remove `release:prepare`/`release:perform`, replace with `mvn deploy -Drevision=X.Y.Z -Dchangelist=`, add explicit `git tag` + `git push --tags` steps |
+| `java-publish-snapshot.yml` | Moderate rewrite: pass `-Drevision=X.Y.Z -Dchangelist=-SNAPSHOT` |
+| Version calculation scripts | Rewrite β currently `maven-release-plugin` computes next version; would need custom shell logic (which we already partially have in the "Determine versions" step) |
+| `.lastmerge` / changelog scripts | Update references to how version is determined |
+| ADR-002 | Update to reflect new versioning scheme (tag format stays the same) |
+| Rollback logic | Rewrite β no more `mvn release:rollback`; instead, delete the tag and the workflow is idempotent |
+| Local developer workflow | `mvn install` still works (uses default `${revision}` from properties); `mvn release:prepare` no longer available for local releases |
+
+### Estimated Effort
+
+| Aspect | Estimate |
+| ---------------------------- | ------------------------------------------------------------------------------------------------ |
+| pom.xml changes | Small (1β2 hours) |
+| Workflow rewrite | Medium (4β8 hours) β this is the bulk of the work |
+| Testing the new publish flow | Medium (4β8 hours) β need a dry-run against Maven Central staging |
+| Updating docs, ADR, scripts | Small (2β3 hours) |
+| Risk of breaking a release | **Medium** β the current flow has been tested through 3 published releases; new flow is untested |
+| **Total** | **~1.5β2.5 days of focused work** |
+
+---
+
+## 4. Does `central-publishing-maven-plugin` Matter Here?
+
+**It's a separate concern that could be done independently.**
+
+`central-publishing-maven-plugin` (currently at v0.10.0) is Sonatype's new recommended way to deploy to Maven Central via their portal API. It replaces the older `nexus-staging-maven-plugin` / OSSRH staging workflow.
+
+Key facts:
+
+- It works with **either** `maven-release-plugin` **or** CI-friendly versions. It's orthogonal to the branch-protection question.
+- Our current `publish-maven.yml` uses `mvn release:perform -Dgoals="deploy"`, which invokes the standard `maven-deploy-plugin`. This still works β Sonatype hasn't deprecated the old OSSRH route yet.
+- Switching to `central-publishing-maven-plugin` would be a good modernization step but **does not affect whether we need branch protection bypass**.
+- The snapshot workflow already uses plain `mvn deploy`, which also works with the old route.
+
+**Recommendation:** Consider switching to `central-publishing-maven-plugin` as a separate, lower-priority improvement. It does not intersect with the branch-protection decision.
+
+---
+
+## 5. The Real Trade-Off
+
+### Keeping `maven-release-plugin` (Status Quo)
+
+| Pros | Cons |
+| --------------------------------------------- | ---------------------------------------------------------------- |
+| Already working and tested through 3 releases | Requires branch protection bypass for one workflow |
+| Battle-tested pattern, well-understood | The bypass is a permanent exception in repo policy |
+| Plugin actively maintained (v3.3.1, Dec 2025) | Commits directly to `main` (though mechanical, not code changes) |
+| Rollback built-in (`mvn release:rollback`) | |
+| Zero additional engineering work | |
+
+### Switching to CI-Friendly Versions
+
+| Pros | Cons |
+| ---------------------------------------------------------- | --------------------------------------------------------- |
+| No branch protection bypass needed | ~2 days of engineering work |
+| pom.xml never changes in `main` for releases | New, untested publish pipeline |
+| Aligns with Maven's stated modern direction | More shell scripting in workflows (version calc, tagging) |
+| Version is always derived from tag, single source of truth | `flatten-maven-plugin` adds build complexity |
+| | Loss of `mvn release:rollback` safety net |
+| | Risk of a broken release during cutover |
+
+---
+
+## 6. Recommendation
+
+**Keep `maven-release-plugin` for GA. Defer the CI-friendly migration to a post-GA improvement.**
+
+Rationale:
+
+1. **Risk vs. reward timing is wrong.** We're in Phase 0 of a monorepo migration. Adding a release infrastructure rewrite on top of the migration increases risk for no immediate user benefit. The branch protection bypass is scoped to a single `workflow_dispatch`-triggered workflow β it's not a standing vulnerability.
+
+2. **The plugin is not legacy.** v3.3.1 was released December 2025. It's the most actively maintained it's been in years. The agent's characterization was inaccurate.
+
+3. **The bypass scope is minimal.** Only `java-publish.yml` needs it. The bypass can be configured as a ruleset exception for a specific GitHub App or PAT, limited to commits matching `[maven-release-plugin]*` patterns.
+
+4. **Post-GA is the right time.** After GA, when the monorepo migration is complete and the release cadence is established, a CI-friendly migration can be done as a focused improvement with proper testing, including dry-run publishes to Maven Central staging.
+
+### If Steve Requires No Bypass Before GA
+
+If the monorepo maintainer absolutely cannot grant a branch protection bypass, the fallback is:
+
+1. Switch to CI-friendly versions + `central-publishing-maven-plugin` (~2 days work)
+2. Accept the risk of a new, untested release pipeline during migration
+3. Plan for at least one "dry-run" release to validate the pipeline before the first real GA publish
+
+This is doable but adds unnecessary risk during an already complex migration phase.
+
+---
+
+## 7. Action Items
+
+| # | Action | Priority | Timing |
+| --- | ----------------------------------------------------------------------------------------------- | -------- | ------------- |
+| 1 | Request branch protection bypass scoped to `JAVA_RELEASE_TOKEN` PAT for `java-publish.yml` only | High | Now (Phase 0) |
+| 2 | Document the bypass in the monorepo's security/access policy | Medium | Phase 2 |
+| 3 | Evaluate CI-friendly version migration as post-GA improvement | Low | Post-GA |
+| 4 | Evaluate `central-publishing-maven-plugin` adoption (orthogonal) | Low | Post-GA |
diff --git a/80-java-monorepo-add-01-remove-before-merge/ghcp-sp-95-enable-java-branch-protection.ps1 b/80-java-monorepo-add-01-remove-before-merge/ghcp-sp-95-enable-java-branch-protection.ps1
new file mode 100644
index 000000000..4cdf46686
--- /dev/null
+++ b/80-java-monorepo-add-01-remove-before-merge/ghcp-sp-95-enable-java-branch-protection.ps1
@@ -0,0 +1,55 @@
+# Create repository ruleset for github/copilot-sdk per issue ghcp-sp-95
+# To revert: gh api repos/github/copilot-sdk/rulesets/ -X DELETE
+
+$payload = @'
+{
+ "name": "ghcp-sp-95-java-branch-protection",
+ "target": "branch",
+ "enforcement": "active",
+ "conditions": {
+ "ref_name": {
+ "include": ["refs/heads/main"],
+ "exclude": []
+ }
+ },
+ "bypass_actors": [
+ {
+ "actor_id": 5,
+ "actor_type": "RepositoryRole",
+ "bypass_mode": "always"
+ }
+ ],
+ "rules": [
+ { "type": "deletion" },
+ {
+ "type": "pull_request",
+ "parameters": {
+ "required_approving_review_count": 1,
+ "dismiss_stale_reviews_on_push": false,
+ "require_code_owner_review": false,
+ "require_last_push_approval": false,
+ "required_review_thread_resolution": false,
+ "allowed_merge_methods": ["merge", "squash", "rebase"]
+ }
+ },
+ { "type": "non_fast_forward" }
+ ]
+}
+'@
+
+$response = $payload | gh api repos/github/copilot-sdk/rulesets -X POST --input -
+
+if ($LASTEXITCODE -ne 0) {
+ Write-Error "Failed to create ruleset. Exit code: $LASTEXITCODE"
+ exit 1
+}
+
+$parsed = $response | ConvertFrom-Json
+$rulesetId = $parsed.id
+
+Write-Host "Ruleset created successfully." -ForegroundColor Green
+Write-Host " Name: $($parsed.name)"
+Write-Host " ID: $rulesetId"
+Write-Host ""
+Write-Host "To revert this change:" -ForegroundColor Yellow
+Write-Host " gh api repos/github/copilot-sdk/rulesets/$rulesetId -X DELETE"
diff --git a/80-java-monorepo-add-01-remove-before-merge/ghcpsp-90-gpg-key-archive.sh b/80-java-monorepo-add-01-remove-before-merge/ghcpsp-90-gpg-key-archive.sh
new file mode 100644
index 000000000..238cf768b
--- /dev/null
+++ b/80-java-monorepo-add-01-remove-before-merge/ghcpsp-90-gpg-key-archive.sh
@@ -0,0 +1,165 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+# Create an encrypted, signed handoff package for a GPG keypair.
+#
+# Usage:
+# ./ghcpsp-90-gpg-key-archive.sh [output-dir]
+#
+# Example:
+# ./ghcpsp-90-gpg-key-archive.sh 0123ABCD jane@example.com ./out
+
+usage() {
+ cat <<'EOF'
+Usage:
+ ghcpsp-90-gpg-key-archive.sh [output-dir]
+
+Arguments:
+ key-id-or-fingerprint The secret key to export and hand off.
+ recipient-key-id-or-email Recipient key used to encrypt the bundle.
+ output-dir Optional output directory (default: current directory).
+
+Outputs:
+ .tar.gz Plain archive containing transfer files.
+ .tar.gz.asc Encrypted + signed archive for transfer.
+
+Notes:
+ - Share the passphrase over a separate channel.
+ - Keep the plain archive only as long as needed, then securely delete it.
+EOF
+}
+
+require_cmd() {
+ local cmd="$1"
+ if ! command -v "$cmd" >/dev/null 2>&1; then
+ echo "Error: required command not found: $cmd" >&2
+ exit 1
+ fi
+}
+
+if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
+ usage
+ exit 0
+fi
+
+if [[ $# -lt 2 || $# -gt 3 ]]; then
+ usage >&2
+ exit 1
+fi
+
+KEY_ID="$1"
+RECIPIENT="$2"
+OUTPUT_DIR="${3:-.}"
+
+require_cmd gpg
+require_cmd tar
+require_cmd awk
+require_cmd sed
+require_cmd date
+
+mkdir -p "$OUTPUT_DIR"
+
+# Confirm key material is available locally.
+if ! gpg --list-secret-keys "$KEY_ID" >/dev/null 2>&1; then
+ echo "Error: no secret key found for: $KEY_ID" >&2
+ exit 1
+fi
+
+# Confirm recipient key exists locally for encryption.
+if ! gpg --list-keys "$RECIPIENT" >/dev/null 2>&1; then
+ echo "Error: recipient public key not found locally: $RECIPIENT" >&2
+ exit 1
+fi
+
+FPR="$(gpg --list-secret-keys --with-colons "$KEY_ID" | awk -F: '/^fpr:/ {print $10; exit}')"
+if [[ -z "$FPR" ]]; then
+ echo "Error: unable to determine fingerprint for key: $KEY_ID" >&2
+ exit 1
+fi
+
+SHORT_FPR="${FPR: -16}"
+STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
+PREFIX="gpg-key-handoff-${SHORT_FPR}-${STAMP}"
+
+WORK_DIR="$(mktemp -d)"
+cleanup() {
+ rm -rf "$WORK_DIR"
+}
+trap cleanup EXIT
+
+BUNDLE_DIR="$WORK_DIR/$PREFIX"
+mkdir -p "$BUNDLE_DIR"
+
+SECRET_ASC="$BUNDLE_DIR/secret-key.asc"
+PUBLIC_ASC="$BUNDLE_DIR/public-key.asc"
+FINGERPRINT_TXT="$BUNDLE_DIR/fingerprint.txt"
+OWNERTRUST_TXT="$BUNDLE_DIR/ownertrust.txt"
+NOTES_TXT="$BUNDLE_DIR/handoff-notes.txt"
+
+# Export key material.
+gpg --armor --export-secret-keys "$KEY_ID" > "$SECRET_ASC"
+gpg --armor --export "$KEY_ID" > "$PUBLIC_ASC"
+gpg --export-ownertrust > "$OWNERTRUST_TXT"
+
+# Capture key identity details.
+{
+ echo "Primary fingerprint: $FPR"
+ echo
+ echo "Secret key listing:"
+ gpg --list-secret-keys --keyid-format LONG "$KEY_ID"
+ echo
+ echo "Public key listing:"
+ gpg --list-keys --keyid-format LONG "$KEY_ID"
+} > "$FINGERPRINT_TXT"
+
+# Try to locate an existing revocation certificate (GnuPG default layout).
+REVOCATION_ASC="$BUNDLE_DIR/revocation.asc"
+REVOCATION_SOURCE="${GNUPGHOME:-$HOME/.gnupg}/openpgp-revocs.d/${FPR}.rev"
+if [[ -f "$REVOCATION_SOURCE" ]]; then
+ cp "$REVOCATION_SOURCE" "$REVOCATION_ASC"
+else
+ {
+ echo "Revocation certificate was not found at:"
+ echo " $REVOCATION_SOURCE"
+ echo
+ echo "If needed, generate one manually on a trusted host and add it to this bundle."
+ } > "$BUNDLE_DIR/revocation-missing.txt"
+fi
+
+cat > "$NOTES_TXT" < [output-dir] [--import-ownertrust]
+#
+# Example:
+# ./ghcpsp-90-gpg-key-import.sh ./gpg-key-handoff-....tar.gz.asc ./out --import-ownertrust
+
+usage() {
+ cat <<'EOF'
+Usage:
+ ghcpsp-90-gpg-key-import.sh [output-dir] [--import-ownertrust]
+
+Arguments:
+ sealed-archive.asc Armored encrypted+signed archive produced by the sender.
+ output-dir Optional output directory for extracted files (default: ./recipient-import).
+ --import-ownertrust Optional: import ownertrust.txt from the bundle.
+
+What this script does:
+1. Decrypts and validates the signed archive with gpg.
+2. Extracts bundle contents.
+3. Verifies fingerprint metadata exists.
+4. Imports public and secret keys.
+5. Optionally imports ownertrust.
+
+Important:
+- Verify the reported fingerprint out-of-band before using the key.
+- Key passphrase is required when the secret key is used, not necessarily at import.
+EOF
+}
+
+require_cmd() {
+ local cmd="$1"
+ if ! command -v "$cmd" >/dev/null 2>&1; then
+ echo "Error: required command not found: $cmd" >&2
+ exit 1
+ fi
+}
+
+if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
+ usage
+ exit 0
+fi
+
+if [[ $# -lt 1 || $# -gt 3 ]]; then
+ usage >&2
+ exit 1
+fi
+
+SEALED_ARCHIVE="$1"
+OUTPUT_DIR="./recipient-import"
+IMPORT_OWNERTRUST="false"
+
+for arg in "${@:2}"; do
+ case "$arg" in
+ --import-ownertrust)
+ IMPORT_OWNERTRUST="true"
+ ;;
+ *)
+ OUTPUT_DIR="$arg"
+ ;;
+ esac
+done
+
+require_cmd gpg
+require_cmd tar
+require_cmd awk
+require_cmd grep
+require_cmd sed
+require_cmd mkdir
+
+if [[ ! -f "$SEALED_ARCHIVE" ]]; then
+ echo "Error: archive not found: $SEALED_ARCHIVE" >&2
+ exit 1
+fi
+
+umask 077
+mkdir -p "$OUTPUT_DIR"
+
+WORK_DIR="$(mktemp -d)"
+cleanup() {
+ rm -rf "$WORK_DIR"
+}
+trap cleanup EXIT
+
+DECRYPTED_TAR="$WORK_DIR/handoff.tar.gz"
+STATUS_LOG="$WORK_DIR/gpg-status.log"
+
+# Decrypt and capture machine-readable GPG status for integrity checks.
+if ! gpg --batch --status-fd=1 --decrypt --output "$DECRYPTED_TAR" "$SEALED_ARCHIVE" > "$STATUS_LOG"; then
+ echo "Error: failed to decrypt/verify archive. Confirm recipient key and signer key are present." >&2
+ exit 1
+fi
+
+if ! grep -q "^\[GNUPG:\] VALIDSIG " "$STATUS_LOG"; then
+ echo "Error: archive decrypted but signature validity could not be confirmed." >&2
+ echo "Status log: $STATUS_LOG" >&2
+ exit 1
+fi
+
+TOP_DIR="$(tar -tzf "$DECRYPTED_TAR" | awk -F/ 'NR==1 {print $1}')"
+if [[ -z "$TOP_DIR" ]]; then
+ echo "Error: could not determine top-level bundle directory." >&2
+ exit 1
+fi
+
+tar -xzf "$DECRYPTED_TAR" -C "$OUTPUT_DIR"
+BUNDLE_DIR="$OUTPUT_DIR/$TOP_DIR"
+
+SECRET_ASC="$BUNDLE_DIR/secret-key.asc"
+PUBLIC_ASC="$BUNDLE_DIR/public-key.asc"
+FINGERPRINT_TXT="$BUNDLE_DIR/fingerprint.txt"
+OWNERTRUST_TXT="$BUNDLE_DIR/ownertrust.txt"
+
+if [[ ! -f "$SECRET_ASC" || ! -f "$PUBLIC_ASC" || ! -f "$FINGERPRINT_TXT" ]]; then
+ echo "Error: bundle is missing expected files (secret/public/fingerprint)." >&2
+ exit 1
+fi
+
+EXPECTED_FPR="$(awk -F': ' '/^Primary fingerprint:/ {print $2; exit}' "$FINGERPRINT_TXT" | sed 's/[[:space:]]*$//')"
+if [[ -z "$EXPECTED_FPR" ]]; then
+ echo "Error: could not parse expected fingerprint from $FINGERPRINT_TXT" >&2
+ exit 1
+fi
+
+echo "Expected fingerprint from bundle metadata: $EXPECTED_FPR"
+echo "Verify this fingerprint out-of-band with the sender before trusting key usage."
+
+# Import public first, then secret material.
+gpg --import "$PUBLIC_ASC"
+gpg --import "$SECRET_ASC"
+
+if [[ "$IMPORT_OWNERTRUST" == "true" ]]; then
+ if [[ -f "$OWNERTRUST_TXT" ]]; then
+ gpg --import-ownertrust "$OWNERTRUST_TXT"
+ echo "Ownertrust imported from bundle."
+ else
+ echo "Warning: --import-ownertrust requested, but ownertrust.txt was not found."
+ fi
+fi
+
+IMPORTED_FPR="$(gpg --with-colons --list-secret-keys "$EXPECTED_FPR" | awk -F: '/^fpr:/ {print $10; exit}')"
+if [[ "$IMPORTED_FPR" != "$EXPECTED_FPR" ]]; then
+ echo "Error: imported key fingerprint does not match bundle metadata." >&2
+ echo "Expected: $EXPECTED_FPR" >&2
+ echo "Actual: ${IMPORTED_FPR:-}" >&2
+ exit 1
+fi
+
+echo
+echo "Import successful."
+echo "Bundle extracted to: $BUNDLE_DIR"
+echo "Imported fingerprint: $IMPORTED_FPR"
+echo
+echo "Recommended next steps:"
+echo "1) Confirm fingerprint with sender through an independent channel."
+echo "2) Store revocation certificate from the bundle in offline secure storage."
+echo "3) Securely delete extracted secret-key material after operational handoff."
diff --git a/java/.gitignore b/java/.gitignore
new file mode 100644
index 000000000..654d3278f
--- /dev/null
+++ b/java/.gitignore
@@ -0,0 +1,16 @@
+.DS_Store
+target
+examples-test/
+.merge-env
+blog-copilotsdk/
+.claude/worktrees
+smoke-test
+*job-logs.txt*
+temporary-prompts/
+changebundle.txt*
+.classpath
+.project
+.settings
+scripts/codegen/node_modules/
+*~
+*.sln
diff --git a/java/.lastmerge b/java/.lastmerge
new file mode 100644
index 000000000..88ed2a952
--- /dev/null
+++ b/java/.lastmerge
@@ -0,0 +1 @@
+f6c1adf8329ad4206e5ed2e8d12fb8082bc841a2
diff --git a/java/.mvn/wrapper/maven-wrapper.properties b/java/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 000000000..8dea6c227
--- /dev/null
+++ b/java/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,3 @@
+wrapperVersion=3.3.4
+distributionType=only-script
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip
diff --git a/java/CHANGELOG.md b/java/CHANGELOG.md
new file mode 100644
index 000000000..8e174dd21
--- /dev/null
+++ b/java/CHANGELOG.md
@@ -0,0 +1,535 @@
+# Changelog
+
+All notable changes to the GitHub Copilot SDK for Java will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
+
+> Note: This file is automatically modified by scripts and coding agents. Do not change it manually.
+
+## [Unreleased]
+
+> **Reference implementation sync:** [`github/copilot-sdk@e20f5be`](https://github.com/github/copilot-sdk/commit/e20f5bef125860accb30c60d1b35109371a77f16)
+
+## [1.0.0-beta-java.4] - 2026-05-16
+
+> **Reference implementation sync:** [`github/copilot-sdk@e20f5be`](https://github.com/github/copilot-sdk/commit/e20f5bef125860accb30c60d1b35109371a77f16)
+## [1.0.0-beta-java.3] - 2026-05-11
+
+> **Reference implementation sync:** [`github/copilot-sdk@4a0437b`](https://github.com/github/copilot-sdk/commit/4a0437bb03a0b60a1867f14ae8e3faf053afa5aa)
+## [1.0.0-beta-java.2] - 2026-05-08
+
+> **Reference implementation sync:** [`github/copilot-sdk@066a69c`](https://github.com/github/copilot-sdk/commit/066a69c1e849adf1bd98564ab1b52316ec471182)
+## [1.0.0-beta-java.1] - 2026-05-05
+
+> **Reference implementation sync:** [`github/copilot-sdk@c063458`](https://github.com/github/copilot-sdk/commit/c063458ecc3d606766f04cf203b11b08de672cc8)
+## [0.3.0-java.2] - 2026-04-26
+
+> **Reference implementation sync:** [`github/copilot-sdk@dd2dcbc`](https://github.com/github/copilot-sdk/commit/dd2dcbc439256acfb9feb2cff07c0b9c820091b8)
+## [0.3.0-java-preview.1] - 2026-04-21
+
+> **Reference implementation sync:** [`github/copilot-sdk@922959f`](https://github.com/github/copilot-sdk/commit/922959f4a7b83509c3620d4881733c6c5677f00c)
+## [0.3.0-java-preview.0] - 2026-04-21
+
+> **Reference implementation sync:** [`github/copilot-sdk@c3fa6cb`](https://github.com/github/copilot-sdk/commit/c3fa6cbfb83d4a20b7912b1a17013d48f5a277a1)
+## [0.2.2-java.1] - 2026-04-07
+
+> **Reference implementation sync:** [`github/copilot-sdk@c3fa6cb`](https://github.com/github/copilot-sdk/commit/c3fa6cbfb83d4a20b7912b1a17013d48f5a277a1)
+### Added
+
+- Slash commands β register `/command` handlers invoked from the CLI TUI via `SessionConfig.setCommands()` (reference implementation: [`f7fd757`](https://github.com/github/copilot-sdk/commit/f7fd757))
+- `CommandDefinition`, `CommandContext`, `CommandHandler`, `CommandWireDefinition` β types for defining and handling slash commands
+- `CommandExecuteEvent` β event dispatched when a registered slash command is executed
+- Elicitation (UI dialogs) β incoming handler via `SessionConfig.setOnElicitationRequest()` and outgoing convenience methods via `session.getUi()` (reference implementation: [`f7fd757`](https://github.com/github/copilot-sdk/commit/f7fd757))
+- `ElicitationContext`, `ElicitationHandler`, `ElicitationParams`, `ElicitationResult`, `ElicitationResultAction`, `ElicitationSchema`, `InputOptions` β types for elicitation
+- `ElicitationRequestedEvent` β event dispatched when an elicitation request is received
+- `SessionUiApi` β convenience API on `session.getUi()` for `confirm()`, `select()`, `input()`, and `elicitation()` calls
+- `SessionCapabilities` and `SessionUiCapabilities` β session capability reporting populated from create/resume response
+- `CapabilitiesChangedEvent` β event dispatched when session capabilities are updated
+- `CopilotClient.getSessionMetadata(String)` β O(1) session lookup by ID
+- `GetSessionMetadataResponse` β response type for `getSessionMetadata`
+
+### Fixed
+
+- Permission events already resolved by a pre-hook now short-circuit before invoking the client-side handler
+- `SessionUiApi` Javadoc now uses valid Java null-check syntax instead of `?.`
+- README updated to say "GitHub Copilot CLI 1.0.17" instead of "GitHub Copilot 1.0.17"
+
+## [0.2.1-java.1] - 2026-04-02
+
+> **Reference implementation sync:** [`github/copilot-sdk@4088739`](https://github.com/github/copilot-sdk/commit/40887393a9e687dacc141a645799441b0313ff15)
+## [0.2.1-java.0] - 2026-03-26
+
+> **Reference implementation sync:** [`github/copilot-sdk@4088739`](https://github.com/github/copilot-sdk/commit/40887393a9e687dacc141a645799441b0313ff15)
+### Added
+
+- `UnknownSessionEvent` β forward-compatible placeholder for event types not yet known to the SDK; unknown events are now dispatched to handlers instead of being silently dropped (reference implementation: [`d82fd62`](https://github.com/github/copilot-sdk/commit/d82fd62))
+- `PermissionRequestResultKind.NO_RESULT` β new constant that signals the handler intentionally abstains from answering a permission request, leaving it unanswered for another client (reference implementation: [`df59a0e`](https://github.com/github/copilot-sdk/commit/df59a0e))
+- `ToolDefinition.skipPermission` field and `ToolDefinition.createSkipPermission()` factory β marks a tool to skip the permission prompt (reference implementation: [`10c4d02`](https://github.com/github/copilot-sdk/commit/10c4d02))
+- `SystemMessageMode.CUSTOMIZE` β new enum value for fine-grained system prompt customization (reference implementation: [`005b780`](https://github.com/github/copilot-sdk/commit/005b780))
+- `SectionOverrideAction` enum β specifies the operation on a system prompt section (replace, remove, append, prepend, transform) (reference implementation: [`005b780`](https://github.com/github/copilot-sdk/commit/005b780))
+- `SectionOverride` class β describes how one section of the system prompt should be modified, with optional transform callback (reference implementation: [`005b780`](https://github.com/github/copilot-sdk/commit/005b780))
+- `SystemPromptSections` constants β well-known section identifier strings for use with CUSTOMIZE mode (reference implementation: [`005b780`](https://github.com/github/copilot-sdk/commit/005b780))
+- `SystemMessageConfig.setSections(Map)` β section-level overrides for CUSTOMIZE mode (reference implementation: [`005b780`](https://github.com/github/copilot-sdk/commit/005b780))
+- `systemMessage.transform` RPC handler β the SDK now registers a handler that invokes transform callbacks registered in the session config (reference implementation: [`005b780`](https://github.com/github/copilot-sdk/commit/005b780))
+- `CopilotSession.setModel(String, String)` β new overload that accepts an optional reasoning effort level (reference implementation: [`ea90f07`](https://github.com/github/copilot-sdk/commit/ea90f07))
+- `CopilotSession.log(String, String, Boolean, String)` β new overload with an optional `url` parameter (minor addition)
+- `BlobAttachment` class β inline base64-encoded binary attachment for messages (e.g., images) (reference implementation: [`698b259`](https://github.com/github/copilot-sdk/commit/698b259))
+- `MessageAttachment` sealed interface β type-safe base for all attachment types (`Attachment`, `BlobAttachment`), with Jackson polymorphic serialization support
+- `TelemetryConfig` class β OpenTelemetry configuration for the CLI server; set on `CopilotClientOptions.setTelemetry()` (reference implementation: [`f2d21a0`](https://github.com/github/copilot-sdk/commit/f2d21a0))
+- `CopilotClientOptions.setTelemetry(TelemetryConfig)` β enables OpenTelemetry instrumentation in the CLI server (reference implementation: [`f2d21a0`](https://github.com/github/copilot-sdk/commit/f2d21a0))
+
+### Changed
+
+- `Attachment` record now implements `MessageAttachment` sealed interface
+- `BlobAttachment` class now implements `MessageAttachment` sealed interface and is `final`
+- `MessageOptions.setAttachments(List extends MessageAttachment>)` β parameter type changed from `List` to `List extends MessageAttachment>` to support both `Attachment` and `BlobAttachment` in the same list with full compile-time safety
+- `SendMessageRequest.setAttachments(List)` β matching change for the internal request type
+
+### Deprecated
+
+- `CopilotClientOptions.setAutoRestart(boolean)` β this option has no effect and will be removed in a future release
+
+## [0.1.32-java.0] - 2026-03-17
+
+> **Reference implementation sync:** [`github/copilot-sdk@062b61c`](https://github.com/github/copilot-sdk/commit/062b61c8aa63b9b5d45fa1d7b01723e6660ffa83)
+## [1.0.11] - 2026-03-12
+
+> **Reference implementation sync:** [`github/copilot-sdk@062b61c`](https://github.com/github/copilot-sdk/commit/062b61c8aa63b9b5d45fa1d7b01723e6660ffa83)
+### Added
+
+- `CopilotClientOptions.setOnListModels(Supplier>>)` β custom handler for `listModels()` used in BYOK mode to return models from a custom provider instead of querying the CLI (reference implementation: [`e478657`](https://github.com/github/copilot-sdk/commit/e478657))
+- `SessionConfig.setAgent(String)` β pre-selects a custom agent by name when creating a session (reference implementation: [`7766b1a`](https://github.com/github/copilot-sdk/commit/7766b1a))
+- `ResumeSessionConfig.setAgent(String)` β pre-selects a custom agent by name when resuming a session (reference implementation: [`7766b1a`](https://github.com/github/copilot-sdk/commit/7766b1a))
+- `SessionConfig.setOnEvent(Consumer)` β registers an event handler before the `session.create` RPC is issued, ensuring no early events are missed (reference implementation: [`4125fe7`](https://github.com/github/copilot-sdk/commit/4125fe7))
+- `ResumeSessionConfig.setOnEvent(Consumer)` β registers an event handler before the `session.resume` RPC is issued (reference implementation: [`4125fe7`](https://github.com/github/copilot-sdk/commit/4125fe7))
+- New broadcast session event types (protocol v3): `ExternalToolRequestedEvent` (`external_tool.requested`), `ExternalToolCompletedEvent` (`external_tool.completed`), `PermissionRequestedEvent` (`permission.requested`), `PermissionCompletedEvent` (`permission.completed`), `CommandQueuedEvent` (`command.queued`), `CommandCompletedEvent` (`command.completed`), `ExitPlanModeRequestedEvent` (`exit_plan_mode.requested`), `ExitPlanModeCompletedEvent` (`exit_plan_mode.completed`), `SystemNotificationEvent` (`system.notification`) (reference implementation: [`1653812`](https://github.com/github/copilot-sdk/commit/1653812), [`396e8b3`](https://github.com/github/copilot-sdk/commit/396e8b3))
+- `CopilotSession.log(String)` and `CopilotSession.log(String, String, Boolean)` β log a message to the session timeline (reference implementation: [`4125fe7`](https://github.com/github/copilot-sdk/commit/4125fe7))
+
+### Changed
+
+- **Protocol version bumped to v3.** The SDK now supports CLI servers running v2 or v3 (backward-compatible range). Sessions are now registered in the client's session map *before* the `session.create`/`session.resume` RPC is issued, ensuring broadcast events emitted immediately on session start are never dropped (reference implementation: [`4125fe7`](https://github.com/github/copilot-sdk/commit/4125fe7), [`1653812`](https://github.com/github/copilot-sdk/commit/1653812))
+- In protocol v3, tool calls and permission requests that have a registered handler are now handled automatically via `ExternalToolRequestedEvent` and `PermissionRequestedEvent` broadcast events; results are sent back via `session.tools.handlePendingToolCall` and `session.permissions.handlePendingPermissionRequest` RPC calls (reference implementation: [`1653812`](https://github.com/github/copilot-sdk/commit/1653812))
+
+## [1.0.10] - 2026-03-03
+
+> **Reference implementation sync:** [`github/copilot-sdk@dcd86c1`](https://github.com/github/copilot-sdk/commit/dcd86c189501ce1b46b787ca60d90f3f315f3079)
+### Added
+
+- `CopilotSession.setModel(String)` β changes the model for an existing session mid-conversation; the new model takes effect for the next message, and conversation history is preserved (reference implementation: [`bd98e3a`](https://github.com/github/copilot-sdk/commit/bd98e3a))
+- `ToolDefinition.createOverride(String, String, Map, ToolHandler)` β creates a tool definition that overrides a built-in CLI tool with the same name (reference implementation: [`f843c80`](https://github.com/github/copilot-sdk/commit/f843c80))
+- `ToolDefinition` record now includes `overridesBuiltInTool` field; when `true`, signals to the CLI that the custom tool intentionally replaces a built-in (reference implementation: [`f843c80`](https://github.com/github/copilot-sdk/commit/f843c80))
+- `CopilotSession.listAgents()` β lists custom agents available for selection (reference implementation: [`9d998fb`](https://github.com/github/copilot-sdk/commit/9d998fb))
+- `CopilotSession.getCurrentAgent()` β gets the currently selected custom agent (reference implementation: [`9d998fb`](https://github.com/github/copilot-sdk/commit/9d998fb))
+- `CopilotSession.selectAgent(String)` β selects a custom agent for the session (reference implementation: [`9d998fb`](https://github.com/github/copilot-sdk/commit/9d998fb))
+- `CopilotSession.deselectAgent()` β deselects the current custom agent (reference implementation: [`9d998fb`](https://github.com/github/copilot-sdk/commit/9d998fb))
+- `CopilotSession.compact()` β triggers immediate session context compaction (reference implementation: [`9d998fb`](https://github.com/github/copilot-sdk/commit/9d998fb))
+- `AgentInfo` β new JSON type representing a custom agent with `name`, `displayName`, and `description` (reference implementation: [`9d998fb`](https://github.com/github/copilot-sdk/commit/9d998fb))
+- New event types: `SessionTaskCompleteEvent` (`session.task_complete`), `AssistantStreamingDeltaEvent` (`assistant.streaming_delta`), `SubagentDeselectedEvent` (`subagent.deselected`) (reference implementation: various commits)
+- `AssistantTurnStartEvent` data now includes `interactionId` field
+- `AssistantMessageEvent` data now includes `interactionId` field
+- `ToolExecutionCompleteEvent` data now includes `model` and `interactionId` fields
+- `SkillInvokedEvent` data now includes `pluginName` and `pluginVersion` fields
+- `AssistantUsageEvent` data now includes `copilotUsage` field with `CopilotUsage` and `TokenDetails` nested types
+- E2E tests for custom tool permission approval and denial flows (reference implementation: [`388f2f3`](https://github.com/github/copilot-sdk/commit/388f2f3))
+
+### Changed
+
+- **Breaking:** `createSession(SessionConfig)` now requires a non-null `onPermissionRequest` handler; throws `IllegalArgumentException` if not provided (reference implementation: [`279f6c4`](https://github.com/github/copilot-sdk/commit/279f6c4))
+- **Breaking:** `resumeSession(String, ResumeSessionConfig)` now requires a non-null `onPermissionRequest` handler; throws `IllegalArgumentException` if not provided (reference implementation: [`279f6c4`](https://github.com/github/copilot-sdk/commit/279f6c4))
+- **Breaking:** The no-arg `createSession()` and `resumeSession(String)` overloads were removed (reference implementation: [`279f6c4`](https://github.com/github/copilot-sdk/commit/279f6c4))
+- `AssistantMessageDeltaEvent` data: `totalResponseSizeBytes` field moved to new `AssistantStreamingDeltaEvent` (reference implementation: various)
+
+### Fixed
+
+- Permission checks now also apply to SDK-registered custom tools, invoking the `onPermissionRequest` handler with `kind="custom-tool"` before executing tools (reference implementation: [`388f2f3`](https://github.com/github/copilot-sdk/commit/388f2f3))
+
+## [1.0.9] - 2026-02-16
+
+> **Reference implementation sync:** [`github/copilot-sdk@e40d57c`](https://github.com/github/copilot-sdk/commit/e40d57c86e18b495722adbf42045288c03924342)
+### Added
+
+#### Cookbook with Practical Recipes
+
+Added a comprehensive cookbook with 5 practical recipes demonstrating common SDK usage patterns. All examples are JBang-compatible and can be run directly without a full Maven project setup.
+
+**Recipes:**
+- **Error Handling** - Connection failures, timeouts, cleanup patterns, tool errors
+- **Multiple Sessions** - Parallel conversations, custom session IDs, lifecycle management
+- **Managing Local Files** - AI-powered file organization with grouping strategies
+- **PR Visualization** - Interactive CLI tool for analyzing PR age distribution via GitHub MCP Server
+- **Persisting Sessions** - Save and resume conversations across restarts
+
+**Location:** `src/site/markdown/cookbook/`
+
+**Usage:**
+```bash
+jbang BasicErrorHandling.java
+jbang MultipleSessions.java
+jbang PRVisualization.java github/copilot-sdk
+```
+
+Each recipe includes JBang prerequisites, usage instructions, and best practices.
+
+#### Session Context and Filtering
+
+Added session context tracking and filtering capabilities to help manage multiple Copilot sessions across different repositories and working directories.
+
+**New Classes:**
+- `SessionContext` - Represents working directory context (cwd, gitRoot, repository, branch) with fluent setters
+- `SessionListFilter` - Filter sessions by context fields (extends SessionContext)
+- `SessionContextChangedEvent` - Event fired when working directory context changes between turns
+
+**Updated APIs:**
+- `SessionMetadata.getContext()` - Returns optional context information for persisted sessions
+- `CopilotClient.listSessions(SessionListFilter)` - New overload to filter sessions by context criteria
+
+**Example:**
+```java
+// List sessions for a specific repository
+var filter = new SessionListFilter()
+ .setRepository("owner/repo")
+ .setBranch("main");
+var sessions = client.listSessions(filter).get();
+
+// Access context information
+for (var session : sessions) {
+ var ctx = session.getContext();
+ if (ctx != null) {
+ System.out.println("CWD: " + ctx.getCwd());
+ System.out.println("Repo: " + ctx.getRepository());
+ }
+}
+
+// Listen for context changes
+session.on(SessionContextChangedEvent.class, event -> {
+ SessionContext newContext = event.getData();
+ System.out.println("Working directory changed to: " + newContext.getCwd());
+});
+```
+
+**Requirements:**
+- GitHub Copilot CLI 0.0.409 or later
+
+## [1.0.8] - 2026-02-08
+
+> **Reference implementation sync:** [`github/copilot-sdk@05e3c46`](https://github.com/github/copilot-sdk/commit/05e3c46c8c23130c9c064dc43d00ec78f7a75eab)
+
+### Added
+
+#### ResumeSessionConfig Parity with SessionConfig
+Added missing options to `ResumeSessionConfig` for parity with `SessionConfig` when resuming sessions. You can now change the model, system message, tool filters, and other settings when resuming:
+
+- `model` - Change the AI model when resuming
+- `systemMessage` - Override or extend the system prompt
+- `availableTools` - Restrict which tools are available
+- `excludedTools` - Disable specific tools
+- `configDir` - Override configuration directory
+- `infiniteSessions` - Configure infinite session behavior
+
+**Example:**
+```java
+var config = new ResumeSessionConfig()
+ .setModel("claude-sonnet-4")
+ .setReasoningEffort("high")
+ .setSystemMessage(new SystemMessageConfig()
+ .setMode(SystemMessageMode.APPEND)
+ .setContent("Focus on security."));
+
+var session = client.resumeSession(sessionId, config).get();
+```
+
+#### EventErrorHandler for Custom Error Handling
+Added `EventErrorHandler` interface for custom handling of exceptions thrown by event handlers. Set via `session.setEventErrorHandler()` to receive the event and exception when a handler fails.
+
+```java
+session.setEventErrorHandler((event, exception) -> {
+ logger.error("Handler failed for event: " + event.getType(), exception);
+});
+```
+
+#### EventErrorPolicy for Dispatch Control
+Added `EventErrorPolicy` enum to control whether event dispatch continues or stops when a handler throws an exception. Errors are always logged at `WARNING` level. The default policy is `PROPAGATE_AND_LOG_ERRORS` which stops dispatch on the first error. Set `SUPPRESS_AND_LOG_ERRORS` to continue dispatching despite errors:
+
+```java
+session.setEventErrorPolicy(EventErrorPolicy.SUPPRESS_AND_LOG_ERRORS);
+```
+
+The `EventErrorHandler` is always invoked regardless of the policy.
+
+#### Type-Safe Event Handlers
+Promoted type-safe `on(Class, Consumer)` event handlers as the primary API. Handlers now receive strongly-typed events instead of raw `AbstractSessionEvent`.
+
+```java
+session.on(AssistantMessageEvent.class, msg -> {
+ System.out.println(msg.getData().getContent());
+});
+```
+
+#### SpotBugs Static Analysis
+Integrated SpotBugs for static code analysis with exclusion filters for `events` and `json` packages.
+
+### Changed
+
+- **Copilot CLI**: Minimum version updated to **0.0.405**
+- **CopilotClient**: Made `final` to prevent Finalizer attacks (security hardening)
+- **JBang Example**: Refactored `jbang-example.java` with streamlined session creation and usage metrics display
+- **Code Style**: Use `var` for local variable type inference throughout the codebase
+
+### Fixed
+
+- **SpotBugs OS_OPEN_STREAM**: Wrap `BufferedReader` in try-with-resources to prevent resource leaks
+- **SpotBugs REC_CATCH_EXCEPTION**: Narrow exception catch in `JsonRpcClient.handleMessage()`
+- **SpotBugs DM_DEFAULT_ENCODING**: Add explicit UTF-8 charset to `InputStreamReader`
+- **SpotBugs EI_EXPOSE_REP**: Add defensive copies to collection getters in events and JSON packages
+
+## [1.0.7] - 2026-02-05
+
+### Added
+
+#### Session Lifecycle Hooks
+Extended the hooks system with three new hook types for session lifecycle control:
+- **`onSessionStart`** - Called when a session starts (new or resumed)
+- **`onSessionEnd`** - Called when a session ends
+- **`onUserPromptSubmitted`** - Called when the user submits a prompt
+
+New types:
+- `SessionStartHandler`, `SessionStartHookInput`, `SessionStartHookOutput`
+- `SessionEndHandler`, `SessionEndHookInput`, `SessionEndHookOutput`
+- `UserPromptSubmittedHandler`, `UserPromptSubmittedHookInput`, `UserPromptSubmittedHookOutput`
+
+#### Session Lifecycle Events (Client-Level)
+Added client-level lifecycle event subscriptions:
+- `client.onLifecycle(handler)` - Subscribe to all session lifecycle events
+- `client.onLifecycle(eventType, handler)` - Subscribe to specific event types
+- `SessionLifecycleEventTypes.CREATED`, `DELETED`, `UPDATED`, `FOREGROUND`, `BACKGROUND`
+
+New types: `SessionLifecycleEvent`, `SessionLifecycleEventMetadata`, `SessionLifecycleHandler`
+
+#### Foreground Session Control (TUI+Server Mode)
+For servers running with `--ui-server`:
+- `client.getForegroundSessionId()` - Get the session displayed in TUI
+- `client.setForegroundSessionId(sessionId)` - Switch TUI display to a session
+
+New types: `GetForegroundSessionResponse`, `SetForegroundSessionResponse`
+
+#### New Event Types
+- **`SessionShutdownEvent`** - Emitted when session is shutting down, includes reason and exit code
+- **`SkillInvokedEvent`** - Emitted when a skill is invoked, includes skill name and context
+
+#### Extended Event Data
+- `AssistantMessageEvent.Data` - Added `id`, `isLastReply`, `thinkingContent` fields
+- `AssistantUsageEvent.Data` - Added `outputReasoningTokens` field
+- `SessionCompactionCompleteEvent.Data` - Added `success`, `messagesRemoved`, `tokensRemoved` fields
+- `SessionErrorEvent.Data` - Extended with additional error context
+
+#### Documentation
+- New **[hooks.md](src/site/markdown/hooks.md)** - Comprehensive guide covering all 5 session hooks with examples for security gates, logging, result enrichment, and lifecycle management
+- Expanded **[documentation.md](src/site/markdown/documentation.md)** with all 33 event types, `getMessages()`, `abort()`, and custom timeout examples
+- Enhanced **[advanced.md](src/site/markdown/advanced.md)** with session hooks, lifecycle events, and foreground session control
+- Added **[.github/copilot-instructions.md](.github/copilot-instructions.md)** for AI assistants
+
+#### Testing
+- `SessionEventParserTest` - 850+ lines of unit tests for JSON event deserialization
+- `SessionEventsE2ETest` - End-to-end tests for session event lifecycle
+- `ErrorHandlingTest` - Tests for error handling scenarios
+- Enhanced `E2ETestContext` with snapshot validation and expected prompt logging
+- Added logging configuration (`logging.properties`)
+
+#### Build & CI
+- JaCoCo 0.8.14 for test coverage reporting
+- Coverage reports generated at `target/site/jacoco-coverage/`
+- New test report action at `.github/actions/test-report/`
+- JaCoCo coverage summary in workflow summary
+- Coverage report artifact upload
+
+### Changed
+
+- **Copilot CLI**: Minimum version updated from 0.0.400 to **0.0.404**
+- Refactored `ProcessInfo` and `Connection` to use records
+- Extended `SessionHooks` to support 5 hook types (was 2)
+- Renamed test methods to match snapshot naming conventions with Javadoc
+
+### Fixed
+
+- Improved timeout exception handling with detailed logging
+- Test infrastructure improvements for proxy resilience
+
+## [1.0.6] - 2026-02-02
+
+### Added
+
+- Auth options for BYOK configuration (`authType`, `apiKey`, `organizationId`, `endpoint`)
+- Reasoning effort configuration (`reasoningEffort` in session config)
+- User input handler for freeform user prompts (`UserInputHandler`, `UserInputRequest`, `UserInputResponse`)
+- Pre-tool use and post-tool use hooks (`PreToolUseHandler`, `PostToolUseHandler`)
+- VSCode launch and debug configurations
+- Logging configuration for test debugging
+
+### Changed
+
+- Enhanced permission request handling with graceful error recovery
+- Updated test harness integration to clone from reference implementation SDK
+- Improved logging for session events and user input requests
+
+### Fixed
+
+- Non-null answer enforcement in user input responses for CLI compatibility
+- Permission handler error handling improvements
+
+## [1.0.5] - 2026-01-29
+
+### Added
+
+- Skills configuration: `skillDirectories` and `disabledSkills` in `SessionConfig`
+- Skill events handling (`SkillInvokedEvent`)
+- Javadoc verification step in build workflow
+- Deploy-site job for automatic documentation deployment after releases
+
+### Changed
+
+- Merged reference implementation SDK changes (commit 87ff5510)
+- Added agentic-merge-reference-impl Claude skill for tracking reference implementation changes
+
+### Fixed
+
+- Resume session handling to keep first client alive
+- Build workflow updated to use `test-compile` instead of `compile`
+- NPM dependency installation in CI workflow
+- Enhanced error handling in permission request processing
+- Checkstyle and Maven Resources Plugin version updates
+- Test harness CLI installation to match reference implementation version
+
+## [1.0.4] - 2026-01-27
+
+### Added
+
+- Advanced usage documentation with comprehensive examples
+- Getting started guide with Maven and JBang instructions
+- Package-info.java files for `com.github.copilot.sdk`, `events`, and `json` packages
+- `@since` annotations on all public classes
+- Versioned documentation with version selector on GitHub Pages
+- Maven resources plugin for site markdown filtering
+
+### Changed
+
+- Refactored tool argument handling for improved type safety
+- Optimized event listener registration in examples
+- Enhanced site navigation with documentation links
+- Merged reference implementation SDK changes from commit f902b76
+
+### Fixed
+
+- BufferedReader replaced with BufferedInputStream for accurate JSON-RPC byte reading
+- Timeout thread now uses daemon thread to prevent JVM exit blocking
+- XML root element corrected from `` to `` in site.xml
+- Badge titles in README for consistency
+
+## [1.0.3] - 2026-01-26
+
+### Added
+
+- MCP Servers documentation and integration examples
+- Infinite sessions documentation section
+- Versioned documentation template with version selector
+- Guidelines for porting reference implementation SDK changes to Java
+- Configuration for automatically generated release notes
+
+### Changed
+
+- Renamed and retitled GitHub Actions workflows for clarity
+- Improved gh-pages initialization and remote setup
+
+### Fixed
+
+- Documentation navigation to include MCP Servers section
+- GitHub Pages deployment workflow to use correct branch
+- Enhanced version handling in documentation build steps
+- Rollback mechanism added for release failures
+
+## [1.0.2] - 2026-01-25
+
+### Added
+
+- Infinite sessions support with `InfiniteSessionConfig` and workspace persistence
+- GitHub Actions workflow for GitHub Pages deployment
+- Daily schedule trigger for SDK E2E tests
+- Checkstyle configuration and Maven integration
+
+### Changed
+
+- Updated GitHub Actions to latest action versions
+- Enhanced Maven site deployment with documentation versioning
+- Simplified GitHub release title naming convention
+
+### Fixed
+
+- Documentation links in site.xml and README for consistency
+- Maven build step to include `clean` for fresh builds
+- Image handling in README and site generation
+
+## [1.0.1] - 2026-01-22
+
+### Added
+
+- Metadata APIs implementation
+- Tool execution progress event (`ToolExecutionProgressEvent`)
+- SDK protocol version 2 support
+- Image in README for visual representation
+- Detailed sections in README with usage examples
+- Badges for build status, Maven Central, Java version, and license
+
+### Changed
+
+- Enhanced version handling in Maven release workflow
+- Updated SCM connection URLs to use HTTPS
+
+### Fixed
+
+- GitHub release command version formatting and title
+- Documentation commit messages to include version information
+- JBang dependency declaration with correct group ID
+
+## [1.0.0] - 2026-01-21
+
+### Added
+
+- Initial release of the GitHub Copilot SDK for Java
+- Core classes: `CopilotClient`, `CopilotSession`, `JsonRpcClient`
+- Session configuration with `SessionConfig`
+- Custom tools with `ToolDefinition` and `ToolHandler`
+- Event system with 30+ event types extending `AbstractSessionEvent`
+- Permission handling with `PermissionHandler`
+- BYOK (Bring Your Own Key) support with `ProviderConfig`
+- MCP server integration via `McpServerConfig`
+- System message customization with `SystemMessageConfig`
+- File attachments support
+- Streaming responses with delta events
+- JBang example for quick testing
+- GitHub Actions workflows for testing and Maven Central publishing
+- Pre-commit hook for Spotless code formatting
+- Comprehensive API documentation
+
+[Unreleased]: https://github.com/github/copilot-sdk-java/compare/v1.0.0-beta-java.4...HEAD
+[1.0.0-beta-java.4]: https://github.com/github/copilot-sdk-java/compare/v1.0.0-beta-java.3...v1.0.0-beta-java.4
+[1.0.0-beta-java.3]: https://github.com/github/copilot-sdk-java/compare/v1.0.0-beta-java.2...v1.0.0-beta-java.3
+[1.0.0-beta-java.2]: https://github.com/github/copilot-sdk-java/compare/v1.0.0-beta-java.1...v1.0.0-beta-java.2
+[1.0.0-beta-java.1]: https://github.com/github/copilot-sdk-java/compare/v0.3.0-java.2...v1.0.0-beta-java.1
+[0.3.0-java.2]: https://github.com/github/copilot-sdk-java/compare/v0.2.2-java.1...v0.3.0-java.2
+[0.3.0-java-preview.1]: https://github.com/github/copilot-sdk-java/compare/v0.2.2-java.1...v0.3.0-java-preview.1
+[0.3.0-java-preview.0]: https://github.com/github/copilot-sdk-java/compare/v0.2.2-java.1...v0.3.0-java-preview.0
+[0.2.2-java.1]: https://github.com/github/copilot-sdk-java/compare/v0.2.1-java.1...v0.2.2-java.1
+[0.2.1-java.1]: https://github.com/github/copilot-sdk-java/compare/v0.2.1-java.0...v0.2.1-java.1
+[0.2.1-java.0]: https://github.com/github/copilot-sdk-java/compare/v0.1.32-java.0...v0.2.1-java.0
+[0.1.32-java.0]: https://github.com/github/copilot-sdk-java/compare/v1.0.11...v0.1.32-java.0
+[1.0.11]: https://github.com/github/copilot-sdk-java/compare/v1.0.10...v1.0.11
+[1.0.10]: https://github.com/github/copilot-sdk-java/compare/v1.0.9...v1.0.10
+[1.0.9]: https://github.com/github/copilot-sdk-java/compare/v1.0.8...v1.0.9
+[1.0.8]: https://github.com/github/copilot-sdk-java/compare/v1.0.7...v1.0.8
+[1.0.7]: https://github.com/github/copilot-sdk-java/compare/v1.0.6...v1.0.7
+[1.0.6]: https://github.com/github/copilot-sdk-java/compare/v1.0.5...v1.0.6
+[1.0.5]: https://github.com/github/copilot-sdk-java/compare/v1.0.4...v1.0.5
+[1.0.4]: https://github.com/github/copilot-sdk-java/compare/v1.0.3...v1.0.4
+[1.0.3]: https://github.com/github/copilot-sdk-java/compare/v1.0.2...v1.0.3
+[1.0.2]: https://github.com/github/copilot-sdk-java/compare/v1.0.1...v1.0.2
+[1.0.1]: https://github.com/github/copilot-sdk-java/compare/1.0.0...v1.0.1
+[1.0.0]: https://github.com/github/copilot-sdk-java/releases/tag/1.0.0
diff --git a/java/README.md b/java/README.md
index f197cb549..f2b342dfc 100644
--- a/java/README.md
+++ b/java/README.md
@@ -1,82 +1,215 @@
# GitHub Copilot SDK for Java
-Java SDK for programmatic control of GitHub Copilot CLI via JSON-RPC.
-
[](https://github.com/github/copilot-sdk-java/actions/workflows/build-test.yml)
-[](https://central.sonatype.com/artifact/com.github/copilot-sdk-java)
-[](https://openjdk.org/)
+[](https://github.com/github/copilot-sdk-java/actions/workflows/deploy-site.yml)
+[](https://github.github.io/copilot-sdk-java/snapshot/jacoco/index.html)
[](https://github.github.io/copilot-sdk-java/)
-[](https://javadoc.io/doc/com.github/copilot-sdk-java/latest/index.html)
+[](https://openjdk.org/)
+[](https://opensource.org/licenses/MIT)
-## Quick Start
+#### Latest release
+[](https://github.com/github/copilot-sdk-java/releases)
+[](https://github.com/github/copilot-sdk-java/releases)
+[](https://central.sonatype.com/artifact/com.github/copilot-sdk-java)
+[](https://github.github.io/copilot-sdk-java/latest/)
+[](https://javadoc.io/doc/com.github/copilot-sdk-java/latest/index.html)
+
+## Background
+
+> βΉοΈ **Public Preview:** This SDK tracks the [GitHub Copilot SDKs](https://github.com/github/copilot-sdk) for [.NET](https://github.com/github/copilot-sdk/tree/main/dotnet) and [Node.js](https://github.com/github/copilot-sdk/tree/main/nodejs). While in public preview, minor breaking changes may still occur between releases.
+
+Java SDK for programmatic control of GitHub Copilot CLI, enabling you to build AI-powered applications and agentic workflows.
+
+## Installation
+
+### Requirements
+
+- Java 17 or later. **JDK 25 recommended**. Selecting JDK 25 enables the use of virtual threads, as shown in the [Quick Start](#quick-start).
+- GitHub Copilot CLI 1.0.17 or later installed and in `PATH` (or provide custom `cliPath`)
+
+### Maven
+
+```xml
+
+ com.github
+ copilot-sdk-java
+ 1.0.0-beta-java.4
+
+```
-**π¦ The Java SDK is maintained in a separate repository: [`github/copilot-sdk-java`](https://github.com/github/copilot-sdk-java)**
+#### Snapshot Builds
-> **Note:** This SDK is in public preview and may change in breaking ways.
+Snapshot builds of the next development version are published to Maven Central Snapshots. To use them, add the repository and update the dependency version in your `pom.xml`:
+
+```xml
+
+
+ central-snapshots
+ https://central.sonatype.com/repository/maven-snapshots/
+ true
+
+
+
+
+ com.github
+ copilot-sdk-java
+ 1.0.0-beta-java.5-SNAPSHOT
+
+```
+
+### Gradle
+
+```groovy
+implementation 'com.github:copilot-sdk-java:1.0.0-beta-java.4'
+```
+
+## Quick Start
```java
import com.github.copilot.sdk.CopilotClient;
-import com.github.copilot.sdk.events.AssistantMessageEvent;
-import com.github.copilot.sdk.events.SessionIdleEvent;
+import com.github.copilot.sdk.generated.AssistantMessageEvent;
+import com.github.copilot.sdk.generated.SessionUsageInfoEvent;
+import com.github.copilot.sdk.json.CopilotClientOptions;
import com.github.copilot.sdk.json.MessageOptions;
import com.github.copilot.sdk.json.PermissionHandler;
import com.github.copilot.sdk.json.SessionConfig;
-public class QuickStart {
+import java.util.concurrent.Executors;
+
+public class CopilotSDK {
public static void main(String[] args) throws Exception {
+ var lastMessage = new String[]{null};
+
// Create and start client
- try (var client = new CopilotClient()) {
+ try (var client = new CopilotClient()) { // JDK 25+: comment out this line
+ // JDK 25+: uncomment the following 3 lines for virtual thread support
+ // var options = new CopilotClientOptions()
+ // .setExecutor(Executors.newVirtualThreadPerTaskExecutor());
+ // try (var client = new CopilotClient(options)) {
client.start().get();
- // Create a session (onPermissionRequest is required)
+ // Create a session
var session = client.createSession(
- new SessionConfig()
- .setModel("gpt-5")
- .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
- ).get();
-
- var done = new java.util.concurrent.CompletableFuture();
-
- // Handle events
- session.on(AssistantMessageEvent.class, msg ->
- System.out.println(msg.getData().content()));
- session.on(SessionIdleEvent.class, idle ->
- done.complete(null));
-
- // Send a message and wait for completion
- session.send(new MessageOptions().setPrompt("What is 2+2?"));
- done.get();
+ new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL).setModel("claude-sonnet-4.5")).get();
+
+
+ // Handle assistant message events
+ session.on(AssistantMessageEvent.class, msg -> {
+ lastMessage[0] = msg.getData().content();
+ System.out.println(lastMessage[0]);
+ });
+
+ // Handle session usage info events
+ session.on(SessionUsageInfoEvent.class, usage -> {
+ var data = usage.getData();
+ System.out.println("\n--- Usage Metrics ---");
+ System.out.println("Current tokens: " + data.currentTokens().intValue());
+ System.out.println("Token limit: " + data.tokenLimit().intValue());
+ System.out.println("Messages count: " + data.messagesLength().intValue());
+ });
+
+ // Send a message
+ var completable = session.sendAndWait(new MessageOptions().setPrompt("What is 2+2?"));
+ // and wait for completion
+ completable.get();
}
+
+ boolean success = lastMessage[0] != null && lastMessage[0].contains("4");
+ System.exit(success ? 0 : -1);
}
}
```
## Try it with JBang
-Run the SDK without setting up a full project using [JBang](https://www.jbang.dev/):
+You can run the SDK without setting up a full Java project, by using [JBang](https://www.jbang.dev/).
+
+See the full source of [`jbang-example.java`](jbang-example.java) for a complete example with more features like session idle handling and usage info events.
+
+Or run it directly from the repository:
```bash
-jbang https://github.com/github/copilot-sdk-java/blob/main/jbang-example.java
+jbang https://github.com/github/copilot-sdk-java/blob/latest/jbang-example.java
```
-## Documentation & Resources
+## Documentation
+
+π **[Full Documentation](https://github.github.io/copilot-sdk-java/)** β Complete API reference, advanced usage examples, and guides.
+
+### Quick Links
-| Resource | Link |
-| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
-| **Full Documentation** | [github.github.io/copilot-sdk-java](https://github.github.io/copilot-sdk-java/) |
-| **Getting Started Guide** | [Documentation](https://github.github.io/copilot-sdk-java/latest/documentation.html) |
-| **API Reference (Javadoc)** | [javadoc.io](https://javadoc.io/doc/com.github/copilot-sdk-java/latest/index.html) |
-| **MCP Servers Integration** | [MCP Guide](https://github.github.io/copilot-sdk-java/latest/mcp.html) |
-| **Cookbook** | [Recipes](https://github.com/github/copilot-sdk-java/tree/main/src/site/markdown/cookbook) |
-| **Source Code** | [github/copilot-sdk-java](https://github.com/github/copilot-sdk-java) |
-| **Issues & Feature Requests** | [GitHub Issues](https://github.com/github/copilot-sdk-java/issues) |
-| **Releases** | [GitHub Releases](https://github.com/github/copilot-sdk-java/releases) |
-| **Copilot Instructions** | [copilot-sdk-java.instructions.md](https://github.com/github/copilot-sdk-java/blob/main/instructions/copilot-sdk-java.instructions.md) |
+- [Getting Started](https://github.github.io/copilot-sdk-java/latest/documentation.html)
+- [Javadoc API Reference](https://github.github.io/copilot-sdk-java/latest/apidocs/)
+- [MCP Servers Integration](https://github.github.io/copilot-sdk-java/latest/mcp.html)
+- [Cookbook](src/site/markdown/cookbook/) β Practical recipes for common use cases
+
+## Projects Using This SDK
+
+| Project | Description |
+|---------|-------------|
+| [JMeter Copilot Plugin](https://github.com/brunoborges/jmeter-copilot-plugin) | JMeter plugin for AI-assisted load testing |
+
+> Want to add your project? Open a PR!
+
+## CI/CD Workflows
+
+This project uses several GitHub Actions workflows for building, testing, releasing, and syncing with the reference implementation SDK.
+
+See [WORKFLOWS.md](docs/WORKFLOWS.md) for a full overview and details on each workflow.
## Contributing
-Contributions are welcome! Please see the [Contributing Guide](https://github.com/github/copilot-sdk-java/blob/main/CONTRIBUTING.md) in the GitHub Copilot SDK for Java repository.
+Contributions are welcome! Please see the [Contributing Guide](CONTRIBUTING.md) for details.
+
+### Agentic Reference Implementation Merge and Sync
+
+This SDK tracks the official [Copilot SDK](https://github.com/github/copilot-sdk) (.NET reference implementation) and ports changes to Java. The reference implementation merge process is automated with AI assistance:
+
+**Automated sync** β A [scheduled GitHub Actions workflow](.github/workflows/reference-impl-sync.yml) runs on the schedule specified in that file. It checks for new reference implementation commits since the last merge (tracked in [`.lastmerge`](.lastmerge)), and if changes are found, creates an issue labeled `reference-impl-sync` and assigns it to the GitHub Copilot coding agent. Any previously open `reference-impl-sync` issues are automatically closed. The sync also updates the `@github/copilot` version in both `pom.xml` and `scripts/codegen/package.json` to keep schemas and test CLI in lockstep.
+
+**Reusable prompt** β The merge workflow is defined in [`agentic-merge-reference-impl.prompt.md`](.github/prompts/agentic-merge-reference-impl.prompt.md). It can be triggered manually from:
+- **VS Code Copilot Chat** β type `/agentic-merge-reference-impl`
+- **GitHub Copilot CLI** β use `copilot` CLI with the same skill reference
+
+### Development Setup
+
+```bash
+# Clone the repository
+git clone https://github.com/github/copilot-sdk-java.git
+cd copilot-sdk-java
+
+# Enable git hooks for code formatting
+git config core.hooksPath .githooks
+
+# Build and test
+mvn clean verify
+```
+
+The tests require the official [copilot-sdk](https://github.com/github/copilot-sdk) test harness, which is automatically cloned during build.
+
+## Support
+
+See [SUPPORT.md](SUPPORT.md) for how to file issues and get help.
+
+## Code of Conduct
+
+This project has adopted the [Contributor Covenant Code of Conduct](CODE_OF_CONDUCT.md). See [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) for details.
+
+## Security
+
+See [SECURITY.md](SECURITY.md) for reporting security vulnerabilities.
## License
-MIT β see [LICENSE](https://github.com/github/copilot-sdk-java/blob/main/LICENSE) for details.
+MIT β see [LICENSE](LICENSE) for details.
+
+## Acknowledgement
+
+- Initially developed with Copilot and [Bruno Borges](https://www.linkedin.com/in/brunocborges/).
+
+## Star History
+
+[](https://www.star-history.com/#github/copilot-sdk-java&Date)
+
+β Drop a star if you find this useful!
+
diff --git a/java/config/checkstyle/checkstyle.xml b/java/config/checkstyle/checkstyle.xml
new file mode 100644
index 000000000..fcde1f6c9
--- /dev/null
+++ b/java/config/checkstyle/checkstyle.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/java/config/spotbugs/spotbugs-exclude.xml b/java/config/spotbugs/spotbugs-exclude.xml
new file mode 100644
index 000000000..1c7d415f6
--- /dev/null
+++ b/java/config/spotbugs/spotbugs-exclude.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/java/docs/adr/adr-001-semver-pre-general-availability.md b/java/docs/adr/adr-001-semver-pre-general-availability.md
new file mode 100644
index 000000000..25008b0f5
--- /dev/null
+++ b/java/docs/adr/adr-001-semver-pre-general-availability.md
@@ -0,0 +1,28 @@
+# SemVer requirements pre general-availability of Reference Implementation
+
+## Context and Problem Statement
+
+Steve Sanderson agreed that `copilot-sdk-java` will track reference implementation version numbers directly, with one exception: when the Java SDK needs to ship a breaking change before 1.0, the reference implementation will bump its minor version to accommodate, giving our release a clean version number that signals the change to users.
+
+The reference implementation makes no backward compatibility guarantees pre-1.0 β and neither will we. That said, we're choosing to hold ourselves to a higher standard as a matter of good practice: we'll use minor version bumps as a signal to users when we do ship something breaking.
+
+The 2026-02 state of `copilot-sdk-java` is that it takes Java 17+ as its baseline. This decision precludes the use of Java 21 features such as virtual threads. Our pre-analysis showed the **possibility** of a significant performance benefit when using Virtual Threads with Java 21.
+
+We took an architectural decision to enable us to pursue investigating this possibility immediately.
+
+## Considered Options
+
+* Track SemVer of reference implementation, with one exception.
+* Completely avoid the need for this by doing no breaking changes pre-1.0.
+* Abandon the policy of tracking the versions of the reference implementation directly, just do our own thing.
+
+## Decision Outcome
+
+Chosen option: "Track SemVer of reference implementation, with one exception.", because this enables us to pursue Virtual Threads without delaying the first public release of `copilot-sdk-java`. Also, we're supposed to be aggressively modernizing our customers.
+
+To some extent, I would use qualifiers to mark a release as having some feature that is awaiting a reference implementation full release before it goes full ga, i.e you put out 0.1.46-virtualthreads.3 until reference implementation is ready to move to 0.2.0 then you release your virtual threads change and go 0.2.0. So I would make your agreement that your version numbers would match with the exception of qualifiers that you might add in exceptional circumstances.
+
+## Related work items
+
+- https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2745172
+
diff --git a/java/docs/adr/adr-002-maven-version-and-reference-implementation-tracking.md b/java/docs/adr/adr-002-maven-version-and-reference-implementation-tracking.md
new file mode 100644
index 000000000..248a024c2
--- /dev/null
+++ b/java/docs/adr/adr-002-maven-version-and-reference-implementation-tracking.md
@@ -0,0 +1,57 @@
+# Maven Version and tracking of versions released from the reference implementation
+
+## Context and Problem Statement
+
+Releases of this implementation track releases of the reference implementation. For each release of the reference implementation, there may follow a corresponding relase of this implementation with the same number as the reference implementation, allowing for additional metadata to version multiple releases of this implementation that track the same release of the reference implementation.
+
+## Considered Options
+
+- Simple number qualifier (0.1.32-0, 0.1.32-1, ...) fails on a subtle but important point: 0.1.32-0 is treated identically to 0.1.32 by Maven (trailing zeros are normalized away), and bare numeric qualifiers are pre-release semantics. Your "first release" would sort before the reference implementation bare version.
+
+- Java and number in the qualifier (0.1.32-java.N)
+
+ - java is an unknown qualifier that sorts correctly and accurately describes what it is β the Java-ecosystem release of this version.
+
+- sp and number in the qualifier (0.1.32-sp.N)
+
+ - sp is a known Maven qualifier with a specific semantic ("service pack"), which is slightly misleading since your -java.0 isn't a service pack β it's the primary release.
+
+My recommendation, with the test results backing it up: 0.1.32-java.0, 0.1.32-java.1, 0.1.32-java.2. It passes every constraint you stated, is accepted by Sonatype (arbitrary string, not ending in -SNAPSHOT), and is self-documenting. The only thing left to validate empirically is actually uploading one to the portal β but nothing in Sonatype's published requirements would reject it. See below for empirical validation.
+
+## Decision Outcome
+
+Choose option "Java and number in qualifier".
+
+## Supporting information
+
+Output from experiment to publish an artifact with GAV `io.github.edburns:helloworld:0.1.31-java.0`:
+
+Everything looks healthy. Here's the status:
+
+ - β GPG signing succeeded (no passphrase errors this time)
+ - β Upload to Central succeeded β deployment ID
+ e8192af5-769d-4cd3-a6d9-5fc38b4dbf10 was accepted
+ - β Authentication is working β all status checks return successfully
+ - β³ State: publishing β Central accepted the bundle and is propagating it.
+ The plugin is polling repeatedly (27+ checks so far), waiting for state to
+ change to published.
+
+ This is normal. Maven Central publishing can take 5β30 minutes to transition
+ from publishing β published. The waitUntil=published setting in the POM
+ means Maven will keep polling until it completes. Just let it run.
+
+> Given that the purpose of this exercise was to test your assertion that a GAV with a Version such as this one would be valid to publish to Maven central , can we already conclude this is a true assertion?
+
+- Yes. The version 0.1.31-java.0 has already passed Maven Central's
+ validation. The publishing state means Central accepted the bundle β
+ including its GAV β and is now replicating it to mirrors. If the version
+ were invalid, it would have been rejected during validation before entering
+ the publishing state.
+
+ So yes, the assertion holds: Maven Central accepts versions with dots inside
+ the qualifier segment (e.g., 0.1.31-java.0).
+
+## Related work items
+
+- https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2766089
+
diff --git a/java/jbang-example.java b/java/jbang-example.java
new file mode 100644
index 000000000..1c41679cd
--- /dev/null
+++ b/java/jbang-example.java
@@ -0,0 +1,42 @@
+///usr/bin/env jbang "$0" "$@" ; exit $?
+//DEPS com.github:copilot-sdk-java:1.0.0-beta-java.4
+import com.github.copilot.sdk.CopilotClient;
+import com.github.copilot.sdk.generated.AssistantMessageEvent;
+import com.github.copilot.sdk.generated.SessionUsageInfoEvent;
+import com.github.copilot.sdk.json.MessageOptions;
+import com.github.copilot.sdk.json.PermissionHandler;
+import com.github.copilot.sdk.json.SessionConfig;
+
+import static java.lang.System.out;
+
+class CopilotSDK {
+ public static void main(String[] args) throws Exception {
+ // Create and start client
+ try (var client = new CopilotClient()) {
+ client.start().get();
+
+ // Create a session
+ var session = client.createSession(
+ new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL).setModel("claude-sonnet-4.5")).get();
+
+ // Handle assistant message events
+ session.on(AssistantMessageEvent.class, msg -> {
+ out.println(msg.getData().content());
+ });
+
+ // Handle session usage info events
+ session.on(SessionUsageInfoEvent.class, usage -> {
+ var data = usage.getData();
+ out.println("\n--- Usage Metrics ---");
+ out.println("Current tokens: " + data.currentTokens().intValue());
+ out.println("Token limit: " + data.tokenLimit().intValue());
+ out.println("Messages count: " + data.messagesLength().intValue());
+ });
+
+ // Send a message
+ var completable = session.sendAndWait(new MessageOptions().setPrompt("What is 2+2?"));
+ // and wait for completion
+ completable.get();
+ }
+ }
+}
diff --git a/java/mvnw b/java/mvnw
new file mode 100644
index 000000000..bd8896bf2
--- /dev/null
+++ b/java/mvnw
@@ -0,0 +1,295 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Apache Maven Wrapper startup batch script, version 3.3.4
+#
+# Optional ENV vars
+# -----------------
+# JAVA_HOME - location of a JDK home dir, required when download maven via java source
+# MVNW_REPOURL - repo url base for downloading maven distribution
+# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
+# ----------------------------------------------------------------------------
+
+set -euf
+[ "${MVNW_VERBOSE-}" != debug ] || set -x
+
+# OS specific support.
+native_path() { printf %s\\n "$1"; }
+case "$(uname)" in
+CYGWIN* | MINGW*)
+ [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
+ native_path() { cygpath --path --windows "$1"; }
+ ;;
+esac
+
+# set JAVACMD and JAVACCMD
+set_java_home() {
+ # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
+ if [ -n "${JAVA_HOME-}" ]; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ]; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ JAVACCMD="$JAVA_HOME/jre/sh/javac"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ JAVACCMD="$JAVA_HOME/bin/javac"
+
+ if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
+ echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
+ echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
+ return 1
+ fi
+ fi
+ else
+ JAVACMD="$(
+ 'set' +e
+ 'unset' -f command 2>/dev/null
+ 'command' -v java
+ )" || :
+ JAVACCMD="$(
+ 'set' +e
+ 'unset' -f command 2>/dev/null
+ 'command' -v javac
+ )" || :
+
+ if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
+ echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
+ return 1
+ fi
+ fi
+}
+
+# hash string like Java String::hashCode
+hash_string() {
+ str="${1:-}" h=0
+ while [ -n "$str" ]; do
+ char="${str%"${str#?}"}"
+ h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
+ str="${str#?}"
+ done
+ printf %x\\n $h
+}
+
+verbose() { :; }
+[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
+
+die() {
+ printf %s\\n "$1" >&2
+ exit 1
+}
+
+trim() {
+ # MWRAPPER-139:
+ # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
+ # Needed for removing poorly interpreted newline sequences when running in more
+ # exotic environments such as mingw bash on Windows.
+ printf "%s" "${1}" | tr -d '[:space:]'
+}
+
+scriptDir="$(dirname "$0")"
+scriptName="$(basename "$0")"
+
+# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
+while IFS="=" read -r key value; do
+ case "${key-}" in
+ distributionUrl) distributionUrl=$(trim "${value-}") ;;
+ distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
+ esac
+done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties"
+[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
+
+case "${distributionUrl##*/}" in
+maven-mvnd-*bin.*)
+ MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
+ case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
+ *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
+ :Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
+ :Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
+ :Linux*x86_64*) distributionPlatform=linux-amd64 ;;
+ *)
+ echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
+ distributionPlatform=linux-amd64
+ ;;
+ esac
+ distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
+ ;;
+maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
+*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
+esac
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/
+[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
+distributionUrlName="${distributionUrl##*/}"
+distributionUrlNameMain="${distributionUrlName%.*}"
+distributionUrlNameMain="${distributionUrlNameMain%-bin}"
+MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
+MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
+
+exec_maven() {
+ unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
+ exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
+}
+
+if [ -d "$MAVEN_HOME" ]; then
+ verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+ exec_maven "$@"
+fi
+
+case "${distributionUrl-}" in
+*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
+*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
+esac
+
+# prepare tmp dir
+if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
+ clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
+ trap clean HUP INT TERM EXIT
+else
+ die "cannot create temp dir"
+fi
+
+mkdir -p -- "${MAVEN_HOME%/*}"
+
+# Download and Install Apache Maven
+verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+verbose "Downloading from: $distributionUrl"
+verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+# select .zip or .tar.gz
+if ! command -v unzip >/dev/null; then
+ distributionUrl="${distributionUrl%.zip}.tar.gz"
+ distributionUrlName="${distributionUrl##*/}"
+fi
+
+# verbose opt
+__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
+[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
+
+# normalize http auth
+case "${MVNW_PASSWORD:+has-password}" in
+'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+esac
+
+if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
+ verbose "Found wget ... using wget"
+ wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
+elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
+ verbose "Found curl ... using curl"
+ curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
+elif set_java_home; then
+ verbose "Falling back to use Java to download"
+ javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
+ targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
+ cat >"$javaSource" <<-END
+ public class Downloader extends java.net.Authenticator
+ {
+ protected java.net.PasswordAuthentication getPasswordAuthentication()
+ {
+ return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
+ }
+ public static void main( String[] args ) throws Exception
+ {
+ setDefault( new Downloader() );
+ java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
+ }
+ }
+ END
+ # For Cygwin/MinGW, switch paths to Windows format before running javac and java
+ verbose " - Compiling Downloader.java ..."
+ "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
+ verbose " - Running Downloader.java ..."
+ "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
+fi
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+if [ -n "${distributionSha256Sum-}" ]; then
+ distributionSha256Result=false
+ if [ "$MVN_CMD" = mvnd.sh ]; then
+ echo "Checksum validation is not supported for maven-mvnd." >&2
+ echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+ exit 1
+ elif command -v sha256sum >/dev/null; then
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then
+ distributionSha256Result=true
+ fi
+ elif command -v shasum >/dev/null; then
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
+ distributionSha256Result=true
+ fi
+ else
+ echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
+ echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+ exit 1
+ fi
+ if [ $distributionSha256Result = false ]; then
+ echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
+ echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
+ exit 1
+ fi
+fi
+
+# unzip and move
+if command -v unzip >/dev/null; then
+ unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
+else
+ tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
+fi
+
+# Find the actual extracted directory name (handles snapshots where filename != directory name)
+actualDistributionDir=""
+
+# First try the expected directory name (for regular distributions)
+if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then
+ if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then
+ actualDistributionDir="$distributionUrlNameMain"
+ fi
+fi
+
+# If not found, search for any directory with the Maven executable (for snapshots)
+if [ -z "$actualDistributionDir" ]; then
+ # enable globbing to iterate over items
+ set +f
+ for dir in "$TMP_DOWNLOAD_DIR"/*; do
+ if [ -d "$dir" ]; then
+ if [ -f "$dir/bin/$MVN_CMD" ]; then
+ actualDistributionDir="$(basename "$dir")"
+ break
+ fi
+ fi
+ done
+ set -f
+fi
+
+if [ -z "$actualDistributionDir" ]; then
+ verbose "Contents of $TMP_DOWNLOAD_DIR:"
+ verbose "$(ls -la "$TMP_DOWNLOAD_DIR")"
+ die "Could not find Maven distribution directory in extracted archive"
+fi
+
+verbose "Found extracted Maven distribution directory: $actualDistributionDir"
+printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url"
+mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
+
+clean || :
+exec_maven "$@"
diff --git a/java/mvnw.cmd b/java/mvnw.cmd
new file mode 100644
index 000000000..92450f932
--- /dev/null
+++ b/java/mvnw.cmd
@@ -0,0 +1,189 @@
+<# : batch portion
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Apache Maven Wrapper startup batch script, version 3.3.4
+@REM
+@REM Optional ENV vars
+@REM MVNW_REPOURL - repo url base for downloading maven distribution
+@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
+@REM ----------------------------------------------------------------------------
+
+@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
+@SET __MVNW_CMD__=
+@SET __MVNW_ERROR__=
+@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
+@SET PSModulePath=
+@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
+ IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
+)
+@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
+@SET __MVNW_PSMODULEP_SAVE=
+@SET __MVNW_ARG0_NAME__=
+@SET MVNW_USERNAME=
+@SET MVNW_PASSWORD=
+@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*)
+@echo Cannot start maven from wrapper >&2 && exit /b 1
+@GOTO :EOF
+: end batch / begin powershell #>
+
+$ErrorActionPreference = "Stop"
+if ($env:MVNW_VERBOSE -eq "true") {
+ $VerbosePreference = "Continue"
+}
+
+# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
+$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
+if (!$distributionUrl) {
+ Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
+}
+
+switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
+ "maven-mvnd-*" {
+ $USE_MVND = $true
+ $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
+ $MVN_CMD = "mvnd.cmd"
+ break
+ }
+ default {
+ $USE_MVND = $false
+ $MVN_CMD = $script -replace '^mvnw','mvn'
+ break
+ }
+}
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/
+if ($env:MVNW_REPOURL) {
+ $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" }
+ $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')"
+}
+$distributionUrlName = $distributionUrl -replace '^.*/',''
+$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
+
+$MAVEN_M2_PATH = "$HOME/.m2"
+if ($env:MAVEN_USER_HOME) {
+ $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME"
+}
+
+if (-not (Test-Path -Path $MAVEN_M2_PATH)) {
+ New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null
+}
+
+$MAVEN_WRAPPER_DISTS = $null
+if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) {
+ $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists"
+} else {
+ $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists"
+}
+
+$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain"
+$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
+$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
+
+if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
+ Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+ Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
+ exit $?
+}
+
+if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
+ Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
+}
+
+# prepare tmp dir
+$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
+$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
+$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
+trap {
+ if ($TMP_DOWNLOAD_DIR.Exists) {
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+ }
+}
+
+New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
+
+# Download and Install Apache Maven
+Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+Write-Verbose "Downloading from: $distributionUrl"
+Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+$webclient = New-Object System.Net.WebClient
+if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
+ $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
+}
+[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
+if ($distributionSha256Sum) {
+ if ($USE_MVND) {
+ Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
+ }
+ Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
+ if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
+ Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
+ }
+}
+
+# unzip and move
+Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
+
+# Find the actual extracted directory name (handles snapshots where filename != directory name)
+$actualDistributionDir = ""
+
+# First try the expected directory name (for regular distributions)
+$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain"
+$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD"
+if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) {
+ $actualDistributionDir = $distributionUrlNameMain
+}
+
+# If not found, search for any directory with the Maven executable (for snapshots)
+if (!$actualDistributionDir) {
+ Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object {
+ $testPath = Join-Path $_.FullName "bin/$MVN_CMD"
+ if (Test-Path -Path $testPath -PathType Leaf) {
+ $actualDistributionDir = $_.Name
+ }
+ }
+}
+
+if (!$actualDistributionDir) {
+ Write-Error "Could not find Maven distribution directory in extracted archive"
+}
+
+Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir"
+Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null
+try {
+ Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
+} catch {
+ if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
+ Write-Error "fail to move MAVEN_HOME"
+ }
+} finally {
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+}
+
+Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
diff --git a/java/pom.xml b/java/pom.xml
new file mode 100644
index 000000000..d251fbce6
--- /dev/null
+++ b/java/pom.xml
@@ -0,0 +1,898 @@
+
+
+
+ 4.0.0
+
+ com.github
+ copilot-sdk-java
+ 1.0.0-beta-java.5-SNAPSHOT
+ jar
+
+ GitHub Copilot SDK :: Java
+ SDK for programmatic control of GitHub Copilot CLI
+ https://github.com/github/copilot-sdk-java
+
+
+
+ MIT License
+ https://opensource.org/licenses/MIT
+
+
+
+
+
+ GitHub Copilot SDK
+ GitHub Copilot SDK
+ https://github.com/github
+
+
+
+
+ scm:git:https://github.com/github/copilot-sdk-java.git
+ scm:git:https://github.com/github/copilot-sdk-java.git
+ https://github.com/github/copilot-sdk-java
+ HEAD
+
+
+
+
+ central
+ https://central.sonatype.com/repository/maven-snapshots/
+
+
+
+
+ 17
+ UTF-8
+
+ ${project.build.directory}/copilot-sdk
+ ${copilot.sdk.clone.dir}/test
+
+ ${copilot.sdk.clone.dir}/nodejs/node_modules/@github/copilot/index.js
+
+ false
+
+ ${skip.test.harness}
+
+
+
+ ^1.0.49-1
+
+
+
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.21.3
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+ 2.21
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+ 2.21.3
+
+
+
+
+ com.github.spotbugs
+ spotbugs-annotations
+ 4.9.8
+ provided
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter
+ 5.14.4
+ test
+
+
+ org.mockito
+ mockito-core
+ 5.23.0
+ test
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ 3.12.0
+
+ public
+ true
+ none
+
+
+
+ com.github.spotbugs
+ spotbugs-maven-plugin
+ 4.9.8.3
+
+ config/spotbugs/spotbugs-exclude.xml
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.15.0
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 3.5.0
+
+
+
+ org.apache.maven.plugins
+ maven-antrun-plugin
+ 3.2.0
+
+
+ clone-or-update-copilot-sdk
+ generate-test-resources
+
+ run
+
+
+ ${skip.test.harness}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.apache.ant
+ ant
+ 1.10.17
+
+
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ 3.6.3
+
+
+ install-harness-dependencies
+ generate-test-resources
+
+ exec
+
+
+ ${skip.test.harness}
+ npm
+ ${copilot.sdk.clone.dir}/test/harness
+
+ install
+
+
+
+
+
+ install-nodejs-cli-dependencies
+ generate-test-resources
+
+ exec
+
+
+ ${skip.cli.install}
+ npm
+ ${copilot.sdk.clone.dir}/nodejs
+
+ ci
+ --ignore-scripts
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.5.5
+
+ alphabetical
+
+ ${testExecutionAgentArgs} ${surefire.jvm.args}
+
+ 2
+
+ ${copilot.tests.dir}
+ ${copilot.sdk.clone.dir}
+
+
+
+ ${copilot.cli.path}
+
+
+
+
+
+ isolated-resume-tests
+ test
+
+ test
+
+
+ isolated-resume
+
+ ${project.build.directory}/surefire-reports-isolated
+
+
+
+
+ default-test
+
+ isolated-resume
+
+
+
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+ 3.6.1
+
+
+ add-generated-source
+ generate-sources
+
+ add-source
+
+
+
+ ${project.basedir}/src/generated/java
+
+
+
+
+
+
+ com.diffplug.spotless
+ spotless-maven-plugin
+ 2.46.1
+
+
+
+ src/generated/java/**/*.java
+
+
+ 4.33
+
+
+
+
+
+ true
+ 4
+
+
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ 0.8.14
+
+
+
+ wire-up-coverage-instrumentation
+
+ prepare-agent
+
+
+
+ ${project.build.directory}/jacoco-test-results/sdk-tests.exec
+
+ testExecutionAgentArgs
+
+
+ com/github/copilot/sdk/**
+
+
+ com/github/copilot/sdk/E2ETestContext*
+ com/github/copilot/sdk/CapiProxy*
+
+
+
+
+
+ build-coverage-report-from-tests
+
+ report
+
+ verify
+
+ ${project.build.directory}/jacoco-test-results/sdk-tests.exec
+ ${project.reporting.outputDirectory}/jacoco-coverage
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+ 3.6.0
+
+ config/checkstyle/checkstyle.xml
+ true
+ true
+ false
+
+
+
+ validate
+ validate
+
+ check
+
+
+
+
+
+ com.puppycrawl.tools
+ checkstyle
+ 10.26.1
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+ 3.5.0
+
+
+ filter-site-markdown
+ pre-site
+
+ copy-resources
+
+
+ ${project.build.directory}/filtered-site/markdown
+
+
+ src/site/markdown
+ true
+
+
+
+
+
+ copy-site-descriptor
+ pre-site
+
+ copy-resources
+
+
+ ${project.build.directory}/filtered-site
+
+
+ src/site
+
+ site.xml
+
+ false
+
+
+
+
+
+ copy-site-resources
+ pre-site
+
+ copy-resources
+
+
+ ${project.build.directory}/filtered-site/resources
+
+
+ src/site/resources
+ false
+
+
+
+
+
+ overlay-jacoco-css
+ site
+
+ copy-resources
+
+
+ ${project.reporting.outputDirectory}/jacoco/jacoco-resources
+
+
+ src/site/jacoco-resources
+ false
+
+
+ true
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-site-plugin
+ 3.21.0
+
+ UTF-8
+ UTF-8
+ ${project.build.directory}/filtered-site
+ ${project.build.directory}/filtered-site-generated
+
+
+
+ org.apache.maven.doxia
+ doxia-module-markdown
+ 2.1.0
+
+
+
+
+ org.sonatype.central
+ central-publishing-maven-plugin
+ 0.10.0
+ true
+
+ central
+ true
+
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-project-info-reports-plugin
+ 3.9.0
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+
+
+
+ javadoc
+
+
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ 0.8.14
+
+
+ ${project.build.directory}/jacoco-test-results/sdk-tests.exec
+
+
+
+
+ report
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-report-plugin
+ 3.5.5
+
+
+
+ com.github.spotbugs
+ spotbugs-maven-plugin
+ 4.9.8.3
+
+ config/spotbugs/spotbugs-exclude.xml
+
+
+
+
+ org.codehaus.mojo
+ taglist-maven-plugin
+ 3.2.2
+
+
+
+
+ Todo Work
+
+
+ TODO
+ ignoreCase
+
+
+ FIXME
+ ignoreCase
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+ 3.10.0
+
+
+
+ analyze-report
+
+
+
+
+
+
+
+
+
+
+ jdk21+
+
+ [21,)
+
+
+ -XX:+EnableDynamicAgentLoading
+
+
+
+
+ skip-test-harness
+
+ true
+
+
+
+
+ skip-cli-install-when-tests-skipped
+
+
+ skipTests
+ true
+
+
+
+ true
+
+
+
+
+ skip-cli-install-when-maven-test-skip
+
+
+ maven.test.skip
+ true
+
+
+
+ true
+
+
+
+
+ debug
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ ${project.basedir}/src/test/resources/logging-debug.properties
+
+
+
+
+
+
+
+ release
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ 3.4.0
+
+
+ attach-sources
+
+ jar-no-fork
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+
+
+ attach-javadocs
+
+ jar
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-gpg-plugin
+ 3.2.8
+
+
+ sign-artifacts
+ verify
+
+ sign
+
+
+
+
+
+
+
+
+
+ update-schemas-from-npm-artifact
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ 3.6.3
+
+
+ update-copilot-schema-version
+ generate-sources
+
+ exec
+
+
+ npm
+ ${project.basedir}/scripts/codegen
+
+ install
+ @github/copilot@${copilot.schema.version}
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-enforcer-plugin
+ 3.6.2
+
+
+ require-schema-version
+ validate
+
+ enforce
+
+
+
+
+ copilot.schema.version
+ You must specify -Dcopilot.schema.version=VERSION (e.g. 1.0.25)
+
+
+
+
+
+
+
+
+
+
+
+ codegen
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ 3.6.3
+
+
+ codegen-npm-install
+ generate-sources
+
+ exec
+
+
+ npm
+ ${project.basedir}/scripts/codegen
+
+ ci
+
+
+
+
+ codegen-generate
+ generate-sources
+
+ exec
+
+
+ npm
+ ${project.basedir}/scripts/codegen
+
+ run
+ generate
+
+
+
+
+
+
+
+
+
+
diff --git a/java/scripts/codegen/.gitignore b/java/scripts/codegen/.gitignore
new file mode 100644
index 000000000..c2658d7d1
--- /dev/null
+++ b/java/scripts/codegen/.gitignore
@@ -0,0 +1 @@
+node_modules/
diff --git a/java/scripts/codegen/java.ts b/java/scripts/codegen/java.ts
new file mode 100644
index 000000000..0a96ab9f1
--- /dev/null
+++ b/java/scripts/codegen/java.ts
@@ -0,0 +1,1552 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+/**
+ * Java code generator for session-events and RPC types.
+ * Generates Java source files under src/generated/java/ from JSON Schema files.
+ */
+
+import fs from "fs/promises";
+import path from "path";
+import { fileURLToPath } from "url";
+import type { JSONSchema7 } from "json-schema";
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+/** Root of the copilot-sdk-java repo */
+const REPO_ROOT = path.resolve(__dirname, "../..");
+
+/** Event types to exclude from generation (internal/legacy types) */
+const EXCLUDED_EVENT_TYPES = new Set(["session.import_legacy"]);
+
+const AUTO_GENERATED_HEADER = `// AUTO-GENERATED FILE - DO NOT EDIT`;
+const GENERATED_FROM_SESSION_EVENTS = `// Generated from: session-events.schema.json`;
+const GENERATED_FROM_API = `// Generated from: api.schema.json`;
+const GENERATED_ANNOTATION = `@javax.annotation.processing.Generated("copilot-sdk-codegen")`;
+const COPYRIGHT = `/*---------------------------------------------------------------------------------------------\n * Copyright (c) Microsoft Corporation. All rights reserved.\n *--------------------------------------------------------------------------------------------*/`;
+
+// ββ Naming utilities βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+function toPascalCase(name: string): string {
+ return name.split(/[-_.]/).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
+}
+
+function toJavaClassName(typeName: string): string {
+ return typeName.split(/[._]/).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
+}
+
+function toCamelCase(name: string): string {
+ const pascal = toPascalCase(name);
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1);
+}
+
+function toEnumConstant(value: string): string {
+ return value.toUpperCase().replace(/[-. /:]/g, "_").replace(/^_+/, "").replace(/_+/g, "_");
+}
+
+// ββ Schema path resolution βββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+async function getSessionEventsSchemaPath(): Promise {
+ const candidates = [
+ path.join(REPO_ROOT, "scripts/codegen/node_modules/@github/copilot/schemas/session-events.schema.json"),
+ path.join(REPO_ROOT, "nodejs/node_modules/@github/copilot/schemas/session-events.schema.json"),
+ ];
+ for (const p of candidates) {
+ try {
+ await fs.access(p);
+ return p;
+ } catch {
+ // try next
+ }
+ }
+ throw new Error("session-events.schema.json not found. Run 'npm ci' in scripts/codegen first.");
+}
+
+async function getApiSchemaPath(): Promise {
+ const candidates = [
+ path.join(REPO_ROOT, "scripts/codegen/node_modules/@github/copilot/schemas/api.schema.json"),
+ path.join(REPO_ROOT, "nodejs/node_modules/@github/copilot/schemas/api.schema.json"),
+ ];
+ for (const p of candidates) {
+ try {
+ await fs.access(p);
+ return p;
+ } catch {
+ // try next
+ }
+ }
+ throw new Error("api.schema.json not found. Run 'npm ci' in scripts/codegen first.");
+}
+
+// ββ File writing βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+async function writeGeneratedFile(relativePath: string, content: string): Promise {
+ const fullPath = path.join(REPO_ROOT, relativePath);
+ await fs.mkdir(path.dirname(fullPath), { recursive: true });
+ await fs.writeFile(fullPath, content, "utf-8");
+ console.log(` β ${relativePath}`);
+ return fullPath;
+}
+
+// ββ Java type mapping βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+interface JavaTypeResult {
+ javaType: string;
+ imports: Set;
+}
+
+// Module-level state for $ref resolution during codegen.
+// Set before each schema generation pass; used by schemaTypeToJava and helpers.
+let currentDefinitions: Record = {};
+const pendingStandaloneTypes = new Map();
+
+/**
+ * Resolve a $ref in a JSON Schema against the current definitions.
+ * Returns the resolved schema, or the original if no $ref is present.
+ */
+function resolveRef(schema: JSONSchema7 | undefined): JSONSchema7 | undefined {
+ if (!schema) return schema;
+ if (schema.$ref) {
+ const name = schema.$ref.replace(/^#\/definitions\//, "");
+ const resolved = currentDefinitions[name];
+ if (!resolved) {
+ console.warn(`[codegen] Unresolved $ref: ${schema.$ref}`);
+ return schema;
+ }
+ return resolved;
+ }
+ return schema;
+}
+
+function schemaTypeToJava(
+ schema: JSONSchema7,
+ required: boolean,
+ context: string,
+ propName: string,
+ nestedTypes: Map
+): JavaTypeResult {
+ const imports = new Set();
+
+ // Resolve $ref first β register standalone types for generation
+ if (schema.$ref) {
+ const name = schema.$ref.replace(/^#\/definitions\//, "");
+ const resolved = currentDefinitions[name];
+ if (resolved) {
+ // Enum or object types β register for standalone generation, return ref name
+ if ((resolved.type === "string" && resolved.enum) ||
+ (resolved.type === "object" && resolved.properties)) {
+ pendingStandaloneTypes.set(name, resolved);
+ return { javaType: name, imports };
+ }
+ // Other types (primitives, arrays, maps, anyOf unions) β resolve and recurse
+ return schemaTypeToJava(resolved, required, context, propName, nestedTypes);
+ }
+ // Unresolved $ref β return name as-is
+ console.warn(`[codegen] Unresolved $ref: ${schema.$ref}`);
+ return { javaType: name, imports };
+ }
+
+ if (schema.anyOf) {
+ const hasNull = schema.anyOf.some((s) => typeof s === "object" && (s as JSONSchema7).type === "null");
+ const nonNull = schema.anyOf.filter((s) => typeof s === "object" && (s as JSONSchema7).type !== "null");
+ if (nonNull.length === 1) {
+ const result = schemaTypeToJava(nonNull[0] as JSONSchema7, required && !hasNull, context, propName, nestedTypes);
+ return result;
+ }
+ // Multi-branch anyOf: fall through to Object, matching the C# generator's
+ // behavior. Java has no union types, so Object is the correct erasure for
+ // anyOf[string, object] and similar multi-variant schemas.
+ console.warn(`[codegen] ${context}.${propName}: anyOf with ${nonNull.length} non-null branches β falling back to Object`);
+ return { javaType: "Object", imports };
+ }
+
+ if (schema.type === "string") {
+ if (schema.format === "uuid") {
+ imports.add("java.util.UUID");
+ return { javaType: "UUID", imports };
+ }
+ if (schema.format === "date-time") {
+ imports.add("java.time.OffsetDateTime");
+ return { javaType: "OffsetDateTime", imports };
+ }
+ if (schema.enum && Array.isArray(schema.enum)) {
+ const enumName = `${context}${toPascalCase(propName)}`;
+ nestedTypes.set(enumName, {
+ kind: "enum",
+ name: enumName,
+ values: schema.enum as string[],
+ description: schema.description,
+ });
+ return { javaType: enumName, imports };
+ }
+ return { javaType: "String", imports };
+ }
+
+ if (Array.isArray(schema.type)) {
+ const nonNullTypes = schema.type.filter((t) => t !== "null");
+ if (nonNullTypes.length === 1) {
+ const baseSchema = { ...schema, type: nonNullTypes[0] };
+ return schemaTypeToJava(baseSchema as JSONSchema7, required, context, propName, nestedTypes);
+ }
+ }
+
+ if (schema.type === "integer") {
+ // JSON Schema "integer" maps to Long (boxed β always used for records).
+ // Use primitive long for required fields in mutable-bean contexts if needed.
+ return { javaType: required ? "long" : "Long", imports };
+ }
+
+ if (schema.type === "number") {
+ return { javaType: required ? "double" : "Double", imports };
+ }
+
+ if (schema.type === "boolean") {
+ return { javaType: required ? "boolean" : "Boolean", imports };
+ }
+
+ if (schema.type === "array") {
+ const items = schema.items as JSONSchema7 | undefined;
+ if (items) {
+ // Always pass required=false so primitives are boxed (List, not List)
+ const itemResult = schemaTypeToJava(items, false, context, propName + "Item", nestedTypes);
+ imports.add("java.util.List");
+ for (const imp of itemResult.imports) imports.add(imp);
+ return { javaType: `List<${itemResult.javaType}>`, imports };
+ }
+ imports.add("java.util.List");
+ console.warn(`[codegen] ${context}.${propName}: array without typed items β falling back to List