diff --git a/.claude/skills/agentic-merge-upstream/SKILL.md b/.claude/skills/agentic-merge-upstream/SKILL.md new file mode 100644 index 000000000..32428db89 --- /dev/null +++ b/.claude/skills/agentic-merge-upstream/SKILL.md @@ -0,0 +1,7 @@ +--- +name: agentic-merge-upstream +description: Merge upstream changes from the official Copilot SDK into this Java SDK. +license: MIT +--- + +Follow instructions in the [agentic-merge-upstream prompt](../../../.github/prompts/agentic-merge-upstream.prompt.md) to merge upstream changes from the official Copilot SDK into this Java SDK. \ No newline at end of file diff --git a/.claude/skills/commit-as-pull-request/SKILL.md b/.claude/skills/commit-as-pull-request/SKILL.md new file mode 100644 index 000000000..23aef5714 --- /dev/null +++ b/.claude/skills/commit-as-pull-request/SKILL.md @@ -0,0 +1,7 @@ +--- +name: commit-as-pull-request +description: Commit current changes as a pull request β€” creates a branch, pushes, opens a PR, squash-merges, and syncs local main. +license: MIT +--- + +Follow instructions in the [commit-as-pull-request prompt](../../../.github/prompts/commit-as-pull-request.prompt.md) to take the current uncommitted changes, create a feature branch, push it, open a pull request, squash-merge it into main, and sync the local repository. diff --git a/.claude/skills/documentation-coverage/SKILL.md b/.claude/skills/documentation-coverage/SKILL.md new file mode 100644 index 000000000..562f4e29c --- /dev/null +++ b/.claude/skills/documentation-coverage/SKILL.md @@ -0,0 +1,7 @@ +--- +name: documentation-coverage +description: Assess whether the documentation in src/site/markdown/ adequately covers the Java SDK functionality. +license: MIT +--- + +Follow instructions in the [documentation-coverage prompt](../../../.github/prompts/documentation-coverage.prompt.md) to analyze gaps between the SDK's implemented features and its documentation. diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..c1965c216 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +.github/workflows/*.lock.yml linguist-generated=true merge=ours \ No newline at end of file diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 18313be52..d9372a61b 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -5,6 +5,12 @@ # git config core.hooksPath .githooks # +# Only run Spotless if staged changes include files under src/ +if ! git diff --cached --name-only | grep -q '^src/'; then + echo "No changes in src/, skipping Spotless check." + exit 0 +fi + echo "Running Spotless check..." # Run spotless check diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..6912dbc77 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* edburns@github.com diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml new file mode 100644 index 000000000..9e5a9238f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -0,0 +1,41 @@ +name: Bug +description: File a bug report +title: "[BUG]: " +labels: ["Type: Bug", "Status: Triage"] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! + - type: textarea + id: what-happened + attributes: + label: What happened? + description: What did you do? What happened? What did you expect to happen? + placeholder: Put your description of the bug here. + validations: + required: true + - type: textarea + id: versions + attributes: + label: Versions + description: What versions of the relevant software are you running? + placeholder: copilot-sdk-java v1.0.0, Java 17.0.10, Maven 3.9.6, Copilot CLI v1.0.0 + validations: + required: true + - type: textarea + id: logs + attributes: + label: Relevant log output + description: | + Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. + Please check your logs before submission to ensure sensitive information is redacted. + render: shell + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our [Code of Conduct](CODE_OF_CONDUCT.md) + options: + - label: I agree to follow this project's Code of Conduct + required: true diff --git a/.github/ISSUE_TEMPLATE/documentation.yml b/.github/ISSUE_TEMPLATE/documentation.yml new file mode 100644 index 000000000..7f049a2ed --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation.yml @@ -0,0 +1,41 @@ +name: Documentation +description: Update or add documentation +title: "[DOCS]: " +labels: ["Type: Documentation", "Status: Triage"] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill this out! + - type: textarea + id: describe-need + attributes: + label: Describe the need + description: What do you wish was different about our docs? + placeholder: Describe the need for documentation updates here. + validations: + required: true + - type: input + id: sdk_version + attributes: + label: SDK Version + description: Do these docs apply to a specific SDK version? + placeholder: copilot-sdk-java v1.0.0 + validations: + required: false + - type: textarea + id: logs + attributes: + label: Relevant log output + description: | + Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. + Please check your logs before submission to ensure sensitive information is redacted. + render: shell + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our [Code of Conduct](CODE_OF_CONDUCT.md) + options: + - label: I agree to follow this project's Code of Conduct + required: true diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml new file mode 100644 index 000000000..377cd12ab --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature.yml @@ -0,0 +1,41 @@ +name: Feature +description: Suggest an idea for a new feature or enhancement +title: "[FEAT]: " +labels: ["Type: Feature", "Status: Triage"] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill this out! + - type: textarea + id: describe-need + attributes: + label: Describe the need + description: What do you want to happen? What problem are you trying to solve? + placeholder: Describe the need for the feature. + validations: + required: true + - type: input + id: sdk_version + attributes: + label: SDK Version + description: Does this feature suggestion apply to a specific SDK version? + placeholder: copilot-sdk-java v1.0.0 + validations: + required: false + - type: textarea + id: logs + attributes: + label: Relevant log output + description: | + Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. + Please check your logs before submission to ensure sensitive information is redacted. + render: shell + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our [Code of Conduct](CODE_OF_CONDUCT.md) + options: + - label: I agree to follow this project's Code of Conduct + required: true diff --git a/.github/ISSUE_TEMPLATE/maintenance.yml b/.github/ISSUE_TEMPLATE/maintenance.yml new file mode 100644 index 000000000..de21996db --- /dev/null +++ b/.github/ISSUE_TEMPLATE/maintenance.yml @@ -0,0 +1,41 @@ +name: Maintenance +description: Dependencies, cleanup, refactoring, reworking of code +title: "[MAINT]: " +labels: ["Type: Maintenance", "Status: Triage"] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill this out! + - type: textarea + id: describe-need + attributes: + label: Describe the need + description: What do you want to happen? + placeholder: Describe the maintenance need here. + validations: + required: true + - type: input + id: sdk_version + attributes: + label: SDK Version + description: Does this maintenance apply to a specific SDK version? + placeholder: copilot-sdk-java v1.0.0 + validations: + required: false + - type: textarea + id: logs + attributes: + label: Relevant log output + description: | + Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. + Please check your logs before submission to ensure sensitive information is redacted. + render: shell + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our [Code of Conduct](CODE_OF_CONDUCT.md) + options: + - label: I agree to follow this project's Code of Conduct + required: true diff --git a/.github/actions/setup-copilot/action.yml b/.github/actions/setup-copilot/action.yml index c737f0a55..dcc99b8a7 100644 --- a/.github/actions/setup-copilot/action.yml +++ b/.github/actions/setup-copilot/action.yml @@ -1,5 +1,9 @@ name: "Setup Copilot" description: "Setup Copilot CLI for testing the Java SDK." +outputs: + cli-path: + description: "Path to the Copilot CLI executable" + value: ${{ steps.cli-path.outputs.path }} runs: using: "composite" steps: diff --git a/.github/actions/test-report/action.yml b/.github/actions/test-report/action.yml new file mode 100644 index 000000000..43d45f287 --- /dev/null +++ b/.github/actions/test-report/action.yml @@ -0,0 +1,150 @@ +name: "Test Report" +description: "Generate and publish test reports with summary for Java SDK tests." +inputs: + report-path: + description: "Path to the test report XML files (glob pattern)" + required: false + default: "target/surefire-reports/TEST-*.xml" + jacoco-path: + description: "Path to the JaCoCo XML report" + required: false + default: "target/site/jacoco-coverage/jacoco.xml" + check-name: + description: "Name for the check run" + required: false + default: "Java SDK Test Results" +runs: + using: "composite" + steps: + - name: Generate Test Summary + shell: bash + run: | + echo "## πŸ§ͺ Copilot Java SDK :: Test Results" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + REPORT_DIR=$(dirname "${{ inputs.report-path }}") + REPORT_PATTERN=$(basename "${{ inputs.report-path }}") + + if [ -d "$REPORT_DIR" ]; then + TESTS_RUN=$(grep -h "tests=" ${{ inputs.report-path }} 2>/dev/null | sed 's/.*tests="\([0-9]*\)".*/\1/' | awk '{s+=$1} END {print s}') + FAILURES=$(grep -h "failures=" ${{ inputs.report-path }} 2>/dev/null | sed 's/.*failures="\([0-9]*\)".*/\1/' | awk '{s+=$1} END {print s}') + ERRORS=$(grep -h "errors=" ${{ inputs.report-path }} 2>/dev/null | sed 's/.*errors="\([0-9]*\)".*/\1/' | awk '{s+=$1} END {print s}') + SKIPPED=$(grep -h "skipped=" ${{ inputs.report-path }} 2>/dev/null | sed 's/.*skipped="\([0-9]*\)".*/\1/' | awk '{s+=$1} END {print s}') + + TESTS_RUN=${TESTS_RUN:-0} + FAILURES=${FAILURES:-0} + ERRORS=${ERRORS:-0} + SKIPPED=${SKIPPED:-0} + PASSED=$((TESTS_RUN - FAILURES - ERRORS - SKIPPED)) + + if [ "$FAILURES" -eq 0 ] && [ "$ERRORS" -eq 0 ]; then + echo "### βœ… All tests passed!" >> $GITHUB_STEP_SUMMARY + else + echo "### ❌ Some tests failed" >> $GITHUB_STEP_SUMMARY + fi + + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Metric | Count |" >> $GITHUB_STEP_SUMMARY + echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY + echo "| βœ… Passed | $PASSED |" >> $GITHUB_STEP_SUMMARY + echo "| ❌ Failed | $FAILURES |" >> $GITHUB_STEP_SUMMARY + echo "| πŸ’₯ Errors | $ERRORS |" >> $GITHUB_STEP_SUMMARY + echo "| ⏭️ Skipped | $SKIPPED |" >> $GITHUB_STEP_SUMMARY + echo "| πŸ“Š Total | $TESTS_RUN |" >> $GITHUB_STEP_SUMMARY + + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Test Classes" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Class | Tests | Passed | Failed | Errors | Time |" >> $GITHUB_STEP_SUMMARY + echo "|-------|-------|--------|--------|--------|------|" >> $GITHUB_STEP_SUMMARY + + for file in ${{ inputs.report-path }}; do + if [ -f "$file" ]; then + CLASS=$(basename "$file" .xml | sed 's/TEST-//') + T=$(grep -o 'tests="[0-9]*"' "$file" | head -1 | sed 's/[^0-9]//g') + F=$(grep -o 'failures="[0-9]*"' "$file" | head -1 | sed 's/[^0-9]//g') + E=$(grep -o 'errors="[0-9]*"' "$file" | head -1 | sed 's/[^0-9]//g') + TIME=$(grep -o 'time="[0-9.]*"' "$file" | head -1 | sed 's/[^0-9.]//g') + P=$((T - F - E)) + + STATUS="βœ…" + if [ "${F:-0}" -gt 0 ] || [ "${E:-0}" -gt 0 ]; then + STATUS="❌" + fi + + echo "| $STATUS $CLASS | ${T:-0} | ${P:-0} | ${F:-0} | ${E:-0} | ${TIME:-0}s |" >> $GITHUB_STEP_SUMMARY + fi + done + else + echo "⚠️ No test reports found at ${{ inputs.report-path }}" >> $GITHUB_STEP_SUMMARY + fi + + - name: Generate Coverage Summary + shell: bash + run: | + JACOCO_XML="${{ inputs.jacoco-path }}" + + if [ -f "$JACOCO_XML" ]; then + echo "" >> $GITHUB_STEP_SUMMARY + echo "## πŸ“Š Code Coverage" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # JaCoCo XML may be on a single line - split it for parsing + # Extract report-level counters (last occurrence of each type before ) + extract_counter() { + local type=$1 + local field=$2 + # Split XML on > to get one tag per line, find counter, extract value + sed 's/>/>\n/g' "$JACOCO_XML" | grep "> $GITHUB_STEP_SUMMARY + echo "|--------|---------|--------|----------|" >> $GITHUB_STEP_SUMMARY + echo "| πŸ“ Instructions | ${INSTR_COVERED:-0} | ${INSTR_MISSED:-0} | ${INSTR_PCT}% |" >> $GITHUB_STEP_SUMMARY + echo "| 🌿 Branches | ${BRANCH_COVERED:-0} | ${BRANCH_MISSED:-0} | ${BRANCH_PCT}% |" >> $GITHUB_STEP_SUMMARY + echo "| πŸ“ Lines | ${LINE_COVERED:-0} | ${LINE_MISSED:-0} | ${LINE_PCT}% |" >> $GITHUB_STEP_SUMMARY + echo "| πŸ”§ Methods | ${METHOD_COVERED:-0} | ${METHOD_MISSED:-0} | ${METHOD_PCT}% |" >> $GITHUB_STEP_SUMMARY + echo "| πŸ“¦ Classes | ${CLASS_COVERED:-0} | ${CLASS_MISSED:-0} | ${CLASS_PCT}% |" >> $GITHUB_STEP_SUMMARY + else + echo "" >> $GITHUB_STEP_SUMMARY + echo "## πŸ“Š Code Coverage" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "⚠️ No JaCoCo report found at $JACOCO_XML" >> $GITHUB_STEP_SUMMARY + fi diff --git a/.github/agents/agentic-workflows.agent.md b/.github/agents/agentic-workflows.agent.md new file mode 100644 index 000000000..3cf66a3b2 --- /dev/null +++ b/.github/agents/agentic-workflows.agent.md @@ -0,0 +1,168 @@ +--- +name: Agentic Workflows +description: GitHub Agentic Workflows (gh-aw) - Create, debug, and upgrade AI-powered workflows with intelligent prompt routing +infer: false +--- + +# GitHub Agentic Workflows Agent + +This agent helps you work with **GitHub Agentic Workflows (gh-aw)**, a CLI extension for creating AI-powered workflows in natural language using markdown files. + +## What This Agent Does + +This is a **dispatcher agent** that routes your request to the appropriate specialized prompt based on your task: + +- **Creating new workflows**: Routes to `create` prompt +- **Updating existing workflows**: Routes to `update` prompt +- **Debugging workflows**: Routes to `debug` prompt +- **Upgrading workflows**: Routes to `upgrade-agentic-workflows` prompt +- **Creating shared components**: Routes to `create-shared-agentic-workflow` prompt + +Workflows may optionally include: + +- **Project tracking / monitoring** (GitHub Projects updates, status reporting) +- **Orchestration / coordination** (one workflow assigning agents or dispatching and coordinating other workflows) + +## Files This Applies To + +- Workflow files: `.github/workflows/*.md` and `.github/workflows/**/*.md` +- Workflow lock files: `.github/workflows/*.lock.yml` +- Shared components: `.github/workflows/shared/*.md` +- Configuration: https://github.com/github/gh-aw/blob/v0.42.17/.github/aw/github-agentic-workflows.md + +## Problems This Solves + +- **Workflow Creation**: Design secure, validated agentic workflows with proper triggers, tools, and permissions +- **Workflow Debugging**: Analyze logs, identify missing tools, investigate failures, and fix configuration issues +- **Version Upgrades**: Migrate workflows to new gh-aw versions, apply codemods, fix breaking changes +- **Component Design**: Create reusable shared workflow components that wrap MCP servers + +## How to Use + +When you interact with this agent, it will: + +1. **Understand your intent** - Determine what kind of task you're trying to accomplish +2. **Route to the right prompt** - Load the specialized prompt file for your task +3. **Execute the task** - Follow the detailed instructions in the loaded prompt + +## Available Prompts + +### Create New Workflow +**Load when**: User wants to create a new workflow from scratch, add automation, or design a workflow that doesn't exist yet + +**Prompt file**: https://github.com/github/gh-aw/blob/v0.42.17/.github/aw/create-agentic-workflow.md + +**Use cases**: +- "Create a workflow that triages issues" +- "I need a workflow to label pull requests" +- "Design a weekly research automation" + +### Update Existing Workflow +**Load when**: User wants to modify, improve, or refactor an existing workflow + +**Prompt file**: https://github.com/github/gh-aw/blob/v0.42.17/.github/aw/update-agentic-workflow.md + +**Use cases**: +- "Add web-fetch tool to the issue-classifier workflow" +- "Update the PR reviewer to use discussions instead of issues" +- "Improve the prompt for the weekly-research workflow" + +### Debug Workflow +**Load when**: User needs to investigate, audit, debug, or understand a workflow, troubleshoot issues, analyze logs, or fix errors + +**Prompt file**: https://github.com/github/gh-aw/blob/v0.42.17/.github/aw/debug-agentic-workflow.md + +**Use cases**: +- "Why is this workflow failing?" +- "Analyze the logs for workflow X" +- "Investigate missing tool calls in run #12345" + +### Upgrade Agentic Workflows +**Load when**: User wants to upgrade workflows to a new gh-aw version or fix deprecations + +**Prompt file**: https://github.com/github/gh-aw/blob/v0.42.17/.github/aw/upgrade-agentic-workflows.md + +**Use cases**: +- "Upgrade all workflows to the latest version" +- "Fix deprecated fields in workflows" +- "Apply breaking changes from the new release" + +### Create Shared Agentic Workflow +**Load when**: User wants to create a reusable workflow component or wrap an MCP server + +**Prompt file**: https://github.com/github/gh-aw/blob/v0.42.17/.github/aw/create-shared-agentic-workflow.md + +**Use cases**: +- "Create a shared component for Notion integration" +- "Wrap the Slack MCP server as a reusable component" +- "Design a shared workflow for database queries" + +### Orchestration and Delegation + +**Load when**: Creating or updating workflows that coordinate multiple agents or dispatch work to other workflows + +**Prompt file**: https://github.com/github/gh-aw/blob/v0.42.17/.github/aw/orchestration.md + +**Use cases**: +- Assigning work to AI coding agents +- Dispatching specialized worker workflows +- Using correlation IDs for tracking +- Orchestration design patterns + +### GitHub Projects Integration + +**Load when**: Creating or updating workflows that manage GitHub Projects v2 + +**Prompt file**: https://github.com/github/gh-aw/blob/v0.42.17/.github/aw/projects.md + +**Use cases**: +- Tracking items and fields with update-project +- Posting periodic run summaries +- Creating new projects +- Projects v2 authentication and configuration + +## Instructions + +When a user interacts with you: + +1. **Identify the task type** from the user's request +2. **Load the appropriate prompt** from the GitHub repository URLs listed above +3. **Follow the loaded prompt's instructions** exactly +4. **If uncertain**, ask clarifying questions to determine the right prompt + +## Quick Reference + +```bash +# Initialize repository for agentic workflows +gh aw init + +# Compile workflows +gh aw compile [workflow-name] + +# Debug workflow runs +gh aw logs [workflow-name] +gh aw audit + +# Upgrade workflows +gh aw fix --write +gh aw compile --validate +``` + +## Key Features of gh-aw + +- **Natural Language Workflows**: Write workflows in markdown with YAML frontmatter +- **AI Engine Support**: Copilot, Claude, Codex, or custom engines +- **MCP Server Integration**: Connect to Model Context Protocol servers for tools +- **Safe Outputs**: Structured communication between AI and GitHub API +- **Strict Mode**: Security-first validation and sandboxing +- **Shared Components**: Reusable workflow building blocks +- **Repo Memory**: Persistent git-backed storage for agents +- **Sandboxed Execution**: All workflows run in the Agent Workflow Firewall (AWF) sandbox, enabling full `bash` and `edit` tools by default + +## Important Notes + +- Always reference the instructions file at https://github.com/github/gh-aw/blob/v0.42.17/.github/aw/github-agentic-workflows.md for complete documentation +- Use the MCP tool `agentic-workflows` when running in GitHub Copilot Cloud +- Workflows must be compiled to `.lock.yml` files before running in GitHub Actions +- **Bash tools are enabled by default** - Don't restrict bash commands unnecessarily since workflows are sandboxed by the AWF +- Follow security best practices: minimal permissions, explicit network access, no template injection diff --git a/.github/aw/actions-lock.json b/.github/aw/actions-lock.json new file mode 100644 index 000000000..d81867b96 --- /dev/null +++ b/.github/aw/actions-lock.json @@ -0,0 +1,24 @@ +{ + "entries": { + "actions/github-script@v8": { + "repo": "actions/github-script", + "version": "v8", + "sha": "ed597411d8f924073f98dfc5c65a23a2325f34cd" + }, + "actions/setup-java@v4": { + "repo": "actions/setup-java", + "version": "v4", + "sha": "c1e323688fd81a25caa38c78aa6df2d33d3e20d9" + }, + "actions/setup-node@v4": { + "repo": "actions/setup-node", + "version": "v4", + "sha": "49933ea5288caeca8642d1e84afbd3f7d6820020" + }, + "github/gh-aw/actions/setup@v0.51.6": { + "repo": "github/gh-aw/actions/setup", + "version": "v0.51.6", + "sha": "33cd6c7f1fee588654ef19def2e6a4174be66197" + } + } +} diff --git a/.github/badges/jacoco.svg b/.github/badges/jacoco.svg new file mode 100644 index 000000000..8ef59f460 --- /dev/null +++ b/.github/badges/jacoco.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + coverage + coverage + 87.5% + 87.5% + + diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 000000000..7112a3f51 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,306 @@ +# Copilot Instructions for copilot-sdk-java + +A Java SDK for programmatic control of GitHub Copilot CLI. This is a community-driven port of the official .NET SDK, targeting Java 17+. + +## About These Instructions + +These instructions guide GitHub Copilot when assisting with this repository. They cover: + +- **Tech Stack**: Java 17+, Maven, Jackson for JSON, JUnit for testing +- **Purpose**: Provide a Java SDK for programmatic control of GitHub Copilot CLI +- **Architecture**: JSON-RPC client communicating with Copilot CLI over stdio +- **Key Goals**: Maintain parity with upstream .NET SDK while following Java idioms + +## Build & Test Commands + +```bash +# Build and run all tests +mvn clean verify + +# Run a single test class +mvn test -Dtest=CopilotClientTest + +# Run a single test method +mvn test -Dtest=ToolsTest#testToolInvocation + +# Format code (required before commit) +mvn spotless:apply + +# Check formatting only +mvn spotless:check + +# Build without tests +mvn clean package -DskipTests + +# Run tests with debug logging +mvn test -Pdebug +``` + +### Running Tests from AI Agents / Copilot + +When running tests to verify changes, **always use `mvn verify` without `-q` and without piping through `grep`**. The full output is needed to diagnose failures. Do NOT use commands like: + +```bash +# BAD - hides critical failure details, often requires a second run +mvn verify -q 2>&1 | grep -E 'Tests run:|BUILD' +mvn verify 2>&1 | grep -E 'Tests run.*in com|BUILD|test failure' +``` + +Instead, use one of these approaches: + +```bash +# GOOD - run tests with full output (preferred for investigating failures) +mvn verify + +# GOOD - run tests showing just the summary and result using Maven's built-in log level +mvn verify -B --fail-at-end 2>&1 | tail -30 + +# GOOD - run a single test class when debugging a specific test +mvn test -Dtest=CopilotClientTest +``` + +**Interpreting results:** +- `BUILD SUCCESS` at the end means all tests passed. No further investigation needed. +- `BUILD FAILURE` means something failed. The lines immediately above `BUILD FAILURE` will contain the relevant error information. Look for `[ERROR]` lines near the bottom of the output. +- The Surefire summary line `Tests run: X, Failures: Y, Errors: Z, Skipped: W` appears near the end and gives the counts. +- **Do NOT run tests a second time** just to get different output formatting. One run with full output is sufficient. + +## Architecture + +### Core Components + +- **CopilotClient** - Main entry point. Manages connection to Copilot CLI server via JSON-RPC over stdio. Spawns CLI process or connects to existing server. +- **CopilotSession** - Represents a conversation session. Handles event subscriptions, tool registration, permissions, and message sending. +- **JsonRpcClient** - Low-level JSON-RPC protocol implementation using Jackson for serialization. + +### Package Structure + +- `com.github.copilot.sdk` - Core classes (CopilotClient, CopilotSession, JsonRpcClient) +- `com.github.copilot.sdk.json` - DTOs, request/response types, handler interfaces (SessionConfig, MessageOptions, ToolDefinition, etc.) +- `com.github.copilot.sdk.events` - Event types for session streaming (AssistantMessageEvent, SessionIdleEvent, ToolExecutionStartEvent, etc.) + +### Test Infrastructure + +Tests use the official copilot-sdk test harness from `https://github.com/github/copilot-sdk`. The harness is automatically cloned during `generate-test-resources` phase to `target/copilot-sdk/`. + +- **E2ETestContext** - Manages test environment with CapiProxy for deterministic API responses +- **CapiProxy** - Node.js-based replaying proxy using YAML snapshots from `test/snapshots/` +- Test snapshots are stored in the upstream repo's `test/snapshots/` directory + +## Key Conventions + +### Upstream Merging + +This SDK tracks the official .NET implementation at `github/copilot-sdk`. The `.lastmerge` file contains the last merged upstream commit hash. Use the `agentic-merge-upstream` skill (see `.github/prompts/agentic-merge-upstream.prompt.md`) to port changes. + +When porting from .NET: +- Adapt to Java idioms, don't copy C# patterns directly +- Convert `async/await` β†’ `CompletableFuture` +- Convert C# properties β†’ Java getters/setters or fluent setters +- Use Jackson for JSON (`ObjectMapper`, `@JsonProperty`) + +### Code Style + +- 4-space indentation (enforced by Spotless with Eclipse formatter) +- Fluent setter pattern for configuration classes (e.g., `new SessionConfig().setModel("gpt-5").setTools(tools)`) +- Public APIs require Javadoc (enforced by Checkstyle, except `json` and `events` packages) +- Pre-commit hook runs `mvn spotless:check` - enable with: `git config core.hooksPath .githooks` + +### Handler Pattern + +Handlers use functional interfaces with `CompletableFuture` returns: + +```java +session.createSession(new SessionConfig() + .setOnPermissionRequest((request, invocation) -> + CompletableFuture.completedFuture(new PermissionRequestResult().setKind("allow"))) + .setOnUserInput((request, invocation) -> + CompletableFuture.completedFuture(new UserInputResponse().setResponse("user input"))) +); +``` + +### Event Handling + +Sessions emit typed events via `session.on()`: + +```java +session.on(AssistantMessageEvent.class, msg -> System.out.println(msg.getData().content())); +session.on(SessionIdleEvent.class, idle -> done.complete(null)); +``` + +### Sealed Event Hierarchy + +`AbstractSessionEvent` is a sealed class permitting specific event types. Use pattern matching: + +```java +switch (event) { + case AssistantMessageEvent msg -> handleMessage(msg); + case ToolExecutionStartEvent tool -> handleToolStart(tool); + case SessionIdleEvent idle -> handleIdle(); + default -> { } +} +``` + +### Tool Definition Pattern + +Custom 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")), + "required", List.of("location") + ), + invocation -> { + // Type-safe: invocation.getArgumentsAs(WeatherArgs.class) + // Or Map-based: invocation.getArguments().get("location") + return CompletableFuture.completedFuture(result); + } +); +``` + +## Testing Conventions + +### E2E Test Structure + +Tests extend the shared context pattern: + +```java +private static E2ETestContext ctx; + +@BeforeAll +static void setup() throws Exception { + ctx = E2ETestContext.create(); +} + +@AfterAll +static void teardown() throws Exception { + if (ctx != null) ctx.close(); +} + +@Test +void testFeature() throws Exception { + ctx.configureForTest("category", "test_name"); // Loads test/snapshots/category/test_name.yaml + try (CopilotClient client = ctx.createClient()) { + // Test logic + } +} +``` + +### Snapshot Naming + +Test method names are converted to lowercase snake_case for snapshot filenames to avoid case collisions on macOS/Windows. + +## JSON Serialization + +- Uses Jackson with `@JsonProperty` annotations +- `@JsonInclude(JsonInclude.Include.NON_NULL)` on DTOs to omit null fields +- `ObjectMapper` configured via `JsonRpcClient.getObjectMapper()` with: + - `JavaTimeModule` for date/time handling + - `FAIL_ON_UNKNOWN_PROPERTIES = false` for forward compatibility + +## Documentation + +- Site docs in `src/site/markdown/` (filtered for `${project.version}` substitution) +- Update `src/site/site.xml` when adding new documentation pages +- Javadoc required for public APIs except `json` and `events` packages (self-documenting DTOs) +- **Copilot CLI Version**: When updating the required Copilot CLI version in `README.md`, also update it in `src/site/markdown/index.md` to keep them in sync + +## Boundaries and Restrictions + +### What NOT to Modify + +- **DO NOT** edit `.github/agents/` directory - these contain instructions for other agents +- **DO NOT** modify `target/` directory - this contains build artifacts +- **DO NOT** edit `pom.xml` dependencies without careful consideration - this SDK has minimal dependencies by design +- **DO NOT** change the Jackson version without testing against all serialization patterns +- **DO NOT** modify test snapshots in `target/copilot-sdk/test/snapshots/` - these come from upstream +- **DO NOT** alter the Eclipse formatter configuration in `pom.xml` without team consensus +- **DO NOT** remove or skip Checkstyle or Spotless checks + +### Security Guidelines + +- **NEVER** commit secrets, API keys, tokens, or credentials to the repository +- **NEVER** commit `.env` files or any files containing sensitive configuration +- **NEVER** log sensitive data in code (API keys, tokens, user data) +- Always use `try-with-resources` for streams and readers to prevent resource leaks +- Always use `StandardCharsets.UTF_8` when creating InputStreamReader/OutputStreamWriter +- Review any new dependencies for known security vulnerabilities before adding them +- When handling user input in tools or handlers, consider injection risks + +## Dependency Management + +This SDK is designed to be **lightweight with minimal dependencies**: + +- Core dependencies: Jackson (JSON), JUnit (tests only) +- Before adding new dependencies: + 1. Consider if the functionality can be implemented without a new dependency + 2. Check if Jackson already provides the needed functionality + 3. Ensure the dependency is actively maintained and widely used + 4. Verify compatibility with Java 17+ + 5. Check for security vulnerabilities + 6. Get team approval for non-trivial additions + +## Commit and PR Guidelines + +### Commit Messages + +- Use clear, descriptive commit messages +- Start with a verb in present tense (e.g., "Add", "Fix", "Update", "Refactor") +- Keep the first line under 72 characters +- Add details in the body if needed +- Examples: + - `Add support for streaming responses` + - `Fix resource leak in JsonRpcClient` + - `Update documentation for tool handlers` + - `Refactor event handling to use sealed classes` + +### Pull Requests + +- Keep PRs focused and minimal - one feature/fix per PR +- Ensure all tests pass before requesting review +- Run `mvn spotless:apply` before committing +- Include tests for new functionality +- Update documentation if adding/changing public APIs +- Reference related issues using `#issue-number` +- For upstream merges, follow the `agentic-merge-upstream` skill workflow + +## Development Workflow + +1. **Setup**: Enable git hooks with `git config core.hooksPath .githooks` +2. **Branch**: Create feature branches from `main` +3. **Code**: Write code following the conventions above +4. **Format**: Run `mvn spotless:apply` to format code +5. **Test**: Run `mvn clean verify` to ensure all tests pass +6. **Commit**: Make focused commits with clear messages +7. **Push**: Push your branch and create a PR +8. **Review**: Address review feedback and iterate + +## Release Process + +The release process is automated via the `publish-maven.yml` GitHub Actions workflow. Key steps: + +1. **CHANGELOG Update**: The script `.github/scripts/release/update-changelog.sh` automatically: + - Converts the `## [Unreleased]` section to `## [version] - date` + - Creates a new empty `## [Unreleased]` section at the top + - Updates version comparison links at the bottom of CHANGELOG.md + - Injects the upstream SDK commit hash (from `.lastmerge`) as a `> **Upstream sync:**` blockquote in both the new `[Unreleased]` section and the released version section + +2. **Upstream Sync Tracking**: Each release records which commit from the official `github/copilot-sdk` it is synced to: + - The `.lastmerge` file is read during the release workflow + - The commit hash is injected into `CHANGELOG.md` under the release heading + - Format: `> **Upstream sync:** [\`github/copilot-sdk@SHORT_HASH\`](link-to-commit)` + +3. **Documentation Updates**: README.md and jbang-example.java are updated with the new version. + +4. **Maven Release**: Uses `maven-release-plugin` to: + - Update pom.xml version + - Create a git tag + - Deploy to Maven Central + +5. **Rollback**: If the release fails, the documentation commit is automatically reverted + +The workflow is triggered manually via workflow_dispatch with optional version parameters. diff --git a/.github/prompts/agentic-merge-upstream.prompt.md b/.github/prompts/agentic-merge-upstream.prompt.md index 04e36503c..c1d2c54a0 100644 --- a/.github/prompts/agentic-merge-upstream.prompt.md +++ b/.github/prompts/agentic-merge-upstream.prompt.md @@ -13,55 +13,79 @@ You are an expert Java developer tasked with porting changes from the official C Before making any changes, **read and understand the existing Java SDK implementation** to ensure new code integrates seamlessly. +## Utility Scripts + +The `.github/scripts/` directory contains helper scripts that automate the repeatable parts of this workflow. **Use these scripts instead of running the commands manually.** + +| Script | Purpose | +|---|---| +| `.github/scripts/upstream-sync/merge-upstream-start.sh` | Creates branch, updates CLI, clones upstream, reads `.lastmerge`, prints commit summary | +| `.github/scripts/upstream-sync/merge-upstream-diff.sh` | Detailed diff analysis grouped by area (`.NET src`, tests, snapshots, docs, etc.) | +| `.github/scripts/upstream-sync/merge-upstream-finish.sh` | Runs format + test + build, updates `.lastmerge`, commits, pushes branch | +| `.github/scripts/build/format-and-test.sh` | Standalone `spotless:apply` + `mvn clean verify` (useful during porting too) | + +All scripts write/read a `.merge-env` file (git-ignored) to share state (branch name, upstream dir, last-merge commit). + ## Workflow Overview -1. Clone upstream repository -2. Analyze diff since last merge -3. Apply changes to Java SDK -4. Test and fix issues -5. Update documentation -6. Leave changes uncommitted for review +1. Run `./.github/scripts/upstream-sync/merge-upstream-start.sh` (creates branch, clones upstream, shows summary) +2. Run `./.github/scripts/upstream-sync/merge-upstream-diff.sh` (analyze changes) +3. Update README with minimum CLI version requirement +4. Identify upstream changes to port +5. Apply changes to Java SDK (commit as you go) +6. Port/adjust tests from upstream changes +7. Run `./.github/scripts/build/format-and-test.sh` frequently while porting +8. Build the package +9. Update documentation (**required for every user-facing upstream change**) +10. Run `./.github/scripts/upstream-sync/merge-upstream-finish.sh` (final test + push) and finalize Pull Request (see note below about coding agent vs. manual workflow) +11. Perform final review before handing off --- -## Step 1: Clone Upstream Repository +## Step 1: Initialize Upstream Sync -Clone the official Copilot SDK repository into a temporary folder: +Run the start script to create a branch, update the CLI, clone the upstream repo, and see a summary of new commits: ```bash -UPSTREAM_REPO="https://github.com/github/copilot-sdk.git" -TEMP_DIR=$(mktemp -d) -git clone --depth=100 "$UPSTREAM_REPO" "$TEMP_DIR/copilot-sdk" +./.github/scripts/upstream-sync/merge-upstream-start.sh ``` -## Step 2: Read Last Merge Commit +This writes a `.merge-env` file used by the other scripts. It outputs: +- The branch name created +- The Copilot CLI version +- The upstream dir path +- A short log of upstream commits since `.lastmerge` + +## Step 2: Analyze Upstream Changes -Read the commit hash from `.lastmerge` file in the Java SDK root: +Run the diff script for a detailed breakdown by area: ```bash -LAST_MERGE_COMMIT=$(cat .lastmerge) -echo "Last merged commit: $LAST_MERGE_COMMIT" +./.github/scripts/upstream-sync/merge-upstream-diff.sh # stat only +./.github/scripts/upstream-sync/merge-upstream-diff.sh --full # full diffs ``` -## Step 3: Analyze Changes +The diff script groups changes into: .NET source, .NET tests, test snapshots, documentation, protocol/config, Go/Node.js/Python SDKs, and other files. + +## Step 3: Update README with CLI Version -Generate a diff between the last merged commit and HEAD of main: +After the start script runs, check the CLI version it printed (also saved in `.merge-env` as `CLI_VERSION`). Update the Requirements section in `README.md` and `src/site/markdown/index.md` to specify the minimum CLI version requirement. + +Commit this change before proceeding: ```bash -cd "$TEMP_DIR/copilot-sdk" -git fetch origin main -git log --oneline "$LAST_MERGE_COMMIT"..origin/main -git diff "$LAST_MERGE_COMMIT"..origin/main --stat +git add README.md src/site/markdown/index.md +git commit -m "Update Copilot CLI minimum version requirement" ``` -Focus on analyzing: +## Step 4: Identify Changes to Port + +Using the output from `merge-upstream-diff.sh`, focus on: - `dotnet/src/` - Primary reference implementation - `dotnet/test/` - Test cases to port - `docs/` - Documentation updates - `sdk-protocol-version.json` - Protocol version changes -## Step 4: Identify Changes to Port - For each change in the upstream diff, determine: 1. **New Features**: New methods, classes, or capabilities added to the SDK @@ -99,6 +123,23 @@ Before modifying any code: 4. **Preserve backward compatibility** - Existing API signatures should not break unless absolutely necessary 5. **When in doubt, match existing code** - Follow what's already in the Java SDK, not the upstream +### Commit Changes Incrementally + +**Important:** Commit your changes as you work, grouping related changes together: + +```bash +# After porting a feature or fix, commit with a descriptive message +git add +git commit -m "Port from upstream" + +# Example commits: +# git commit -m "Port new authentication flow from upstream" +# git commit -m "Add new message types from upstream protocol update" +# git commit -m "Port bug fix for session handling from upstream" +``` + +This creates a clear history of changes that can be reviewed in the Pull Request. + ### General Guidelines - **Naming Conventions**: Convert C# PascalCase to Java camelCase for methods/variables @@ -132,16 +173,68 @@ Follow the existing Java SDK patterns: - **Match the style of surrounding code** - Consistency with existing code is more important than upstream patterns - **Prefer existing abstractions** - If the Java SDK already solves a problem differently than .NET, keep the Java approach -## Step 6: Format and Run Tests +## Step 6: Port Tests + +After porting implementation changes, **always check for new or updated tests** in the upstream repository: -After applying changes, format the code and run the test suite: +### Check for New Tests ```bash -mvn spotless:apply -mvn clean test +cd "$TEMP_DIR/copilot-sdk" +git diff "$LAST_MERGE_COMMIT"..origin/main --stat -- dotnet/test/ +git diff "$LAST_MERGE_COMMIT"..origin/main --stat -- test/snapshots/ ``` -**Important:** Always run `mvn spotless:apply` before testing to ensure code formatting is consistent with project standards. +### Port Test Cases + +For each new or modified test file in `dotnet/test/`: + +1. **Create corresponding Java test class** in `src/test/java/com/github/copilot/sdk/` +2. **Follow existing test patterns** - Look at existing tests like `PermissionsTest.java` for structure +3. **Use the E2ETestContext** infrastructure for tests that need the test harness +4. **Match snapshot directory names** - Test snapshots in `test/snapshots/` must match the directory name used in `ctx.configureForTest()` + +### Test File Mapping + +| Upstream Test (.NET) | Java SDK Test | +|-----------------------------|--------------------------------------------------------| +| `dotnet/test/AskUserTests.cs` | `src/test/java/com/github/copilot/sdk/AskUserTest.java` | +| `dotnet/test/HooksTests.cs` | `src/test/java/com/github/copilot/sdk/HooksTest.java` | +| `dotnet/test/ClientTests.cs` | `src/test/java/com/github/copilot/sdk/CopilotClientTest.java` | +| `dotnet/test/*Tests.cs` | `src/test/java/com/github/copilot/sdk/*Test.java` | + +### Test Snapshot Compatibility + +New test snapshots are stored in `test/snapshots/` in the upstream repository. These snapshots are automatically cloned during the Maven build process. + +If tests fail with errors like `TypeError: Cannot read properties of undefined`, the test harness may not yet support the new RPC methods. In this case: + +1. **Mark tests as `@Disabled`** with a clear reason (e.g., `@Disabled("Requires test harness update with X support - see upstream PR #NNN")`) +2. **Document the dependency** in the test class Javadoc +3. **Enable tests later** once the harness is updated + +### Unit Tests vs E2E Tests + +- **Unit tests** (like auth option validation) can run without the test harness +- **E2E tests** require the test harness with matching snapshots + +Commit tests separately or together with their corresponding implementation changes. + +## Step 7: Format and Run Tests + +After applying changes, use the convenience script: + +```bash +./.github/scripts/build/format-and-test.sh # format + full verify +./.github/scripts/build/format-and-test.sh --debug # with debug logging +``` + +Or for quicker iteration during porting: + +```bash +./.github/scripts/build/format-and-test.sh --format-only # just spotless +./.github/scripts/build/format-and-test.sh --test-only # skip formatting +``` ### If Tests Fail @@ -158,7 +251,7 @@ mvn clean test - **Null handling**: Add null checks where C# had nullable types - **JSON serialization**: Verify Jackson annotations are correct -## Step 7: Build the Package +## Step 8: Build the Package Once tests pass, build the complete package: @@ -171,49 +264,167 @@ Verify: - No warnings (if possible) - JAR file is generated in `target/` -## Step 8: Update Documentation +## Step 9: Update Documentation + +**Documentation is critical for new features.** Every new feature ported from upstream must be documented before the merge is complete. +Review and complete this documentation checklist before proceeding to Step 10. +If you determine no docs changes are needed, document that decision and rationale in the PR body under a clear heading (for example, `Documentation Impact`). -Review and update documentation as needed: +### Documentation Checklist -1. **README.md**: Update if there are new features or API changes -2. **src/site/markdown/documentation.md**: Update detailed documentation -3. **Javadoc**: Add/update Javadoc comments for new/changed public APIs -4. **CHANGELOG**: (if exists) Add entry for the changes +For each new feature or significant change: -## Step 9: Update Last Merge Reference +1. **README.md**: Update the main README if there are user-facing changes +2. **src/site/markdown/index.md**: Update if requirements or quick start examples change +3. **src/site/markdown/documentation.md**: Add sections for new basic usage patterns +4. **src/site/markdown/advanced.md**: Add sections for new advanced features (tools, handlers, configurations) +5. **src/site/markdown/mcp.md**: Update if MCP-related changes are made +6. **Javadoc**: Add/update Javadoc comments for all new/changed public APIs +7. **src/site/site.xml**: Update if new documentation pages were added -Update the `.lastmerge` file with the new HEAD commit: +### Documentation Requirements for New Features + +When adding a new feature, ensure the documentation includes: + +- **What it does**: Clear explanation of the feature's purpose +- **How to use it**: Code example showing typical usage +- **API reference**: Link to relevant Javadoc +- **Configuration options**: All available settings/properties + +### Example: Documenting a New Handler + +If a new handler (like `UserInputHandler`, `PermissionHandler`) is added, create a section in `advanced.md`: + +```markdown +## Feature Name + +Brief description of what the feature does. + +\`\`\`java +var session = client.createSession( + new SessionConfig() + .setOnFeatureRequest((request, invocation) -> { + // Handle the request + return CompletableFuture.completedFuture(result); + }) +).get(); +\`\`\` + +Explain the request/response objects and their properties. + +See [FeatureHandler](apidocs/com/github/copilot/sdk/json/FeatureHandler.html) Javadoc for more details. +``` + +### Verify Documentation Consistency + +Ensure consistency across all documentation files: + +- Requirements section should match in `README.md` and `src/site/markdown/index.md` +- Code examples should use the same patterns and be tested +- Links to Javadoc should use correct paths (`apidocs/...`) + +## Step 10: Finish, Push, and Finalize Pull Request + +Run the finish script which updates `.lastmerge`, runs a final build, and pushes the branch: ```bash -cd "$TEMP_DIR/copilot-sdk" -NEW_COMMIT=$(git rev-parse origin/main) -echo "$NEW_COMMIT" > /.lastmerge +./.github/scripts/upstream-sync/merge-upstream-finish.sh # full format + test + push +./.github/scripts/upstream-sync/merge-upstream-finish.sh --skip-tests # if tests already passed +``` + +### PR Handling: Coding Agent vs. Manual Workflow + +**If running as a Copilot coding agent** (triggered via GitHub issue assignment by the weekly sync workflow), a pull request has **already been created automatically** for you. Do NOT create a new one. Just push your commits to the current branch β€” the existing PR will be updated. Add the `upstream-sync` label to the existing PR by running this command in a terminal: + +```bash +gh pr edit --add-label "upstream-sync" +``` + +> **No-changes scenario (coding agent only):** If after analyzing the upstream diff there are no relevant changes to port to the Java SDK, push an empty commit with a message explaining why no changes were needed, so the PR reflects the analysis outcome. The repository maintainer will close the PR and issue manually. + +**If running manually** (e.g., from VS Code via the reusable prompt), create the Pull Request using `gh` CLI or the GitHub MCP tool. Then add the label: + +```bash +gh pr create --base main --title "Merge upstream SDK changes (YYYY-MM-DD)" --body-file /dev/stdin <<< "$PR_BODY" +gh pr edit --add-label "upstream-sync" +``` + +The PR body should include: +1. **Title**: `Merge upstream SDK changes (YYYY-MM-DD)` +2. **Body** with: + - Summary of upstream commits analyzed (with count and commit range) + - Table of changes ported (commit hash + description) + - List of changes intentionally not ported (with reasons) + - Verification status (test count, build status) + +### PR Body Template + +```markdown +## Upstream Merge + +Ports changes from the official Copilot SDK ([github/copilot-sdk](https://github.com/github/copilot-sdk)) since last merge (``β†’``). + +### Upstream commits analyzed (N commits) + +- Brief description of each upstream change and whether it was ported or not + +### Changes ported + +| Commit | Description | +|---|---| +| `` | Description of change | + +### Not ported (intentionally) + +- **Feature name** β€” Reason why it wasn't ported + +### Verification + +- All **N tests pass** (`mvn clean test`) +- Package builds successfully (`mvn clean package -DskipTests`) +- Code formatted with Spotless ``` -## Step 10: Final Review (DO NOT COMMIT) +## Step 11: Final Review Before finishing: -1. Run `git status` to see all changed files -2. Run `git diff` to review all changes +1. Run `git log --oneline main..$BRANCH_NAME` to review all commits +2. Run `git diff main..$BRANCH_NAME --stat` to see a summary of all changes 3. Ensure no unintended changes were made 4. Verify code follows project conventions -5. **DO NOT COMMIT** - leave changes staged/unstaged for user review +5. Confirm the branch was pushed to remote +6. Confirm the Pull Request is ready (created or updated) and provide the PR URL to the user --- ## Checklist +- [ ] New branch created from `main` +- [ ] Copilot CLI updated to latest version +- [ ] README.md updated with minimum CLI version requirement - [ ] Upstream repository cloned - [ ] Diff analyzed between `.lastmerge` commit and HEAD - [ ] New features/fixes identified - [ ] Changes ported to Java SDK following conventions +- [ ] **New/updated tests ported from upstream** (check `dotnet/test/` and `test/snapshots/`) +- [ ] Tests marked `@Disabled` if harness doesn't support new features yet +- [ ] Changes committed incrementally with descriptive messages - [ ] `mvn test` passes - [ ] `mvn package` builds successfully -- [ ] Documentation updated +- [ ] **Documentation updated for new features:** + - [ ] `README.md` updated if user-facing changes + - [ ] `src/site/markdown/index.md` updated if requirements changed + - [ ] `src/site/markdown/documentation.md` updated for new basic usage + - [ ] `src/site/markdown/advanced.md` updated for new advanced features + - [ ] Javadoc added/updated for new public APIs +- [ ] If no documentation files were changed for user-facing upstream changes, PR body explicitly explains why documentation changes were not needed - [ ] `src/site/site.xml` updated if new documentation pages were added - [ ] `.lastmerge` file updated with new commit hash -- [ ] Changes left uncommitted for review +- [ ] Branch pushed to remote +- [ ] **Pull Request finalized** (coding agent: push to existing PR; manual: create via `mcp_github_create_pull_request`) +- [ ] **`upstream-sync` label added** to the PR via `mcp_github_add_issue_labels` +- [ ] PR URL provided to user --- @@ -226,4 +437,3 @@ Before finishing: - Uses JUnit 5 for testing - **Java SDK design decisions take precedence over upstream patterns** - **Adapt upstream changes to fit Java idioms, not the other way around** - diff --git a/.github/prompts/coding-agent-merge-instructions.md b/.github/prompts/coding-agent-merge-instructions.md new file mode 100644 index 000000000..1c18e1f5f --- /dev/null +++ b/.github/prompts/coding-agent-merge-instructions.md @@ -0,0 +1,19 @@ + + + +Follow the agentic-merge-upstream prompt at .github/prompts/agentic-merge-upstream.prompt.md +to port upstream changes to the Java SDK. + +Use the utility scripts in .github/scripts/ subfolders for initialization, diffing, formatting, and testing. +Commit changes incrementally. Update .lastmerge when done. + +IMPORTANT: A pull request has already been created automatically for you β€” do NOT create a new +one. Push your commits to the current branch, and the existing PR will be updated. + +Add the 'upstream-sync' label to the existing PR by running this command in a terminal: + + gh pr edit --add-label "upstream-sync" + +If after analyzing the upstream diff there are no relevant changes to port to the Java SDK, +push an empty commit with a message explaining why no changes were needed, so the PR reflects +the analysis outcome. The repository maintainer will close the PR and issue manually. diff --git a/.github/prompts/commit-as-pull-request.prompt.md b/.github/prompts/commit-as-pull-request.prompt.md new file mode 100644 index 000000000..91b33fabc --- /dev/null +++ b/.github/prompts/commit-as-pull-request.prompt.md @@ -0,0 +1,108 @@ +# Commit as Pull Request + +You are an automated assistant that takes the current uncommitted changes in the workspace, creates a branch, commits, pushes, opens a pull request, merges it, and syncs the local `main` branch. + +## Prerequisites + +- The workspace must be a git repository with a configured remote named `origin`. +- The project must be compiling successfully with the current changes (if applicable). +- The project must be building successfully with the current changes (if applicable). +- There must be uncommitted changes (staged or unstaged) in the working tree. +- The GitHub MCP tools must be available for creating and merging pull requests. + +## Helper Scripts + +The following scripts in `.github/scripts/ci/` automate the git operations for this workflow: + +| Script | Purpose | +|--------|---------| +| `parse-repo-info.sh` | Extracts `REPO_OWNER` and `REPO_NAME` from the git remote URL | +| `commit-and-push.sh` | Verifies changes, runs formatter, creates branch, commits, and pushes | +| `sync-after-merge.sh` | Syncs local `main` and deletes the feature branch | + +## Workflow + +Execute the following steps **in order**. Stop immediately if any step fails. + +### Step 1: Determine the repository owner and name + +```bash +eval "$(.github/scripts/ci/parse-repo-info.sh)" +# Sets: REPO_OWNER, REPO_NAME +``` + +### Step 2: Auto-detect branch name and define commit message + +Analyze the changed files using `git diff` (and `git diff --cached` for staged changes) to understand what was modified. Generate: + +- **Branch name**: A short, kebab-case branch name prefixed with an appropriate category (`fix/`, `feat/`, `docs/`, `refactor/`, `chore/`). Example: `fix/cliurl-auto-correct-usestdio`. +- **Commit message**: A clear, descriptive commit message following the project conventions: + - First line: imperative verb, under 72 characters (e.g., "Fix cliUrl to auto-correct useStdio") + - Body (if needed): explain *why* the change was made + +If the user has provided an explicit branch name or commit message, use those instead. + +### Step 3: Verify the project builds + +If the changes include Java source files, run the build to confirm the project compiles and tests pass: + +```bash +mvn clean verify +``` + +If only non-Java files changed (e.g., documentation, scripts, configuration), this step may be skipped. + +**Stop immediately if the build fails.** Do not proceed to commit broken code. + +### Step 4: Commit and push + +Runs the formatter (if applicable), creates the branch, stages all changes, commits, and pushes: + +```bash +.github/scripts/ci/commit-and-push.sh "" "" +# Outputs: BRANCH_NAME (may differ if suffix was appended) +``` + +Pass `--skip-format` as a third argument to skip `mvn spotless:apply` (e.g., when only non-Java files changed). + +### Step 5: Create a pull request + +Use the GitHub MCP `create_pull_request` tool with: + +- **owner** and **repo**: from Step 1 +- **title**: the first line of the commit message +- **head**: the branch name from Step 4 +- **base**: `main` (or the repository's default branch) +- **body**: A well-structured PR description including: + - **Summary**: What the change does and why + - **Changes**: Bullet list of files/areas modified + - **Testing**: How the changes were verified + +### Step 6: Merge the pull request + +Use the GitHub MCP `merge_pull_request` tool with: + +- **merge_method**: `squash` +- **commit_title**: ` (#)` + +### Step 7: Sync and clean up + +```bash +.github/scripts/ci/sync-after-merge.sh "" +``` + +## Error Handling + +- Branch name collisions are handled automatically by `commit-and-push.sh` (appends a numeric suffix). +- If the push fails due to authentication, the script exits with code 2 β€” inform the user and stop. +- If the PR creation fails, provide the error and stop. +- If the merge fails (e.g., merge conflicts, required checks), inform the user and leave the PR open. + +## Output + +After completion, provide a brief summary: + +1. Branch name +2. PR URL and number +3. Merge commit SHA +4. Confirmation that local `main` is up to date diff --git a/.github/prompts/documentation-coverage.prompt.md b/.github/prompts/documentation-coverage.prompt.md new file mode 100644 index 000000000..80284c6a9 --- /dev/null +++ b/.github/prompts/documentation-coverage.prompt.md @@ -0,0 +1,207 @@ +# Documentation Coverage Assessment + +You are an expert Java developer tasked with assessing whether the documentation in `src/site/markdown/` adequately covers the functionality implemented in the Java SDK. + +## Objective + +Analyze the Java SDK source code and compare it against the existing documentation to: +1. **Identify undocumented features** - Functionality in code that lacks documentation +2. **Find outdated documentation** - Docs that don't match current implementation +3. **Assess documentation quality** - Are documented features explained with examples? + +## Assessment Process + +### Step 1: Inventory Public API + +Extract all public classes, methods, and features from the SDK: + +```bash +# List all public classes in core package +grep -l "public class\|public interface\|public enum" src/main/java/com/github/copilot/sdk/*.java + +# List all public classes in json package (DTOs) +grep -l "public class" src/main/java/com/github/copilot/sdk/json/*.java + +# List all event types +ls src/main/java/com/github/copilot/sdk/events/ +``` + +### Step 2: Inventory Documentation + +List all documentation files: + +```bash +ls src/site/markdown/ +``` + +Read each documentation file to understand current coverage. + +### Step 3: Map Features to Documentation + +For each major feature area, determine if it's documented: + +#### CopilotClient Features + +Examine `CopilotClient.java` for public methods: + +```bash +grep "public.*(" src/main/java/com/github/copilot/sdk/CopilotClient.java | grep -v "@" +``` + +| Method | Purpose | Documented In | Status | +|--------|---------|---------------|--------| +| `start()` | Start the client | ? | βœ…/❌ | +| `stop()` | Stop the client | ? | βœ…/❌ | +| `createSession()` | Create a new session | ? | βœ…/❌ | +| `resumeSession()` | Resume existing session | ? | βœ…/❌ | +| `deleteSession()` | Delete a session | ? | βœ…/❌ | +| `listSessions()` | List all sessions | ? | βœ…/❌ | +| `listModels()` | List available models | ? | βœ…/❌ | +| `getStatus()` | Get client status | ? | βœ…/❌ | +| `getAuthStatus()` | Get auth status | ? | βœ…/❌ | +| `ping()` | Ping the server | ? | βœ…/❌ | + +#### CopilotSession Features + +Examine `CopilotSession.java` for public methods: + +```bash +grep "public.*(" src/main/java/com/github/copilot/sdk/CopilotSession.java | grep -v "@" +``` + +| Method | Purpose | Documented In | Status | +|--------|---------|---------------|--------| +| `send()` | Send a message | ? | βœ…/❌ | +| `sendAndWait()` | Send and wait for response | ? | βœ…/❌ | +| `on()` | Subscribe to events | ? | βœ…/❌ | +| `registerTools()` | Register custom tools | ? | βœ…/❌ | +| `getMessages()` | Get conversation history | ? | βœ…/❌ | +| `abort()` | Abort current operation | ? | βœ…/❌ | + +#### SessionConfig Options + +Examine `SessionConfig.java` for configurable options: + +```bash +grep "public.*set\|private.*;" src/main/java/com/github/copilot/sdk/json/SessionConfig.java +``` + +| Option | Purpose | Documented | Example Provided | +|--------|---------|:----------:|:----------------:| +| `model` | Model to use | βœ…/❌ | βœ…/❌ | +| `tools` | Custom tools | βœ…/❌ | βœ…/❌ | +| `systemMessage` | System prompt | βœ…/❌ | βœ…/❌ | +| `streaming` | Enable streaming | βœ…/❌ | βœ…/❌ | +| `mcpServers` | MCP integration | βœ…/❌ | βœ…/❌ | +| `hooks` | Session hooks | βœ…/❌ | βœ…/❌ | +| `infiniteSessions` | Long sessions | βœ…/❌ | βœ…/❌ | +| `skillDirectories` | Skills config | βœ…/❌ | βœ…/❌ | +| `customAgents` | Custom agents | βœ…/❌ | βœ…/❌ | + +#### Event Types + +Check which events are documented: + +```bash +grep "TYPE_MAP.put" src/main/java/com/github/copilot/sdk/events/SessionEventParser.java +``` + +| Event Type | Event Class | Documented | Example | +|------------|-------------|:----------:|:-------:| +| `session.start` | `SessionStartEvent` | βœ…/❌ | βœ…/❌ | +| `session.idle` | `SessionIdleEvent` | βœ…/❌ | βœ…/❌ | +| `assistant.message` | `AssistantMessageEvent` | βœ…/❌ | βœ…/❌ | +| ... | ... | ... | ... | + +#### Hooks + +Check `SessionHooks.java` for hook types: + +```bash +grep "private.*Handler" src/main/java/com/github/copilot/sdk/json/SessionHooks.java +``` + +| Hook | Handler Interface | Documented | Example | +|------|-------------------|:----------:|:-------:| +| Pre-tool use | `PreToolUseHandler` | βœ…/❌ | βœ…/❌ | +| Post-tool use | `PostToolUseHandler` | βœ…/❌ | βœ…/❌ | +| User prompt submitted | `UserPromptSubmittedHandler` | βœ…/❌ | βœ…/❌ | +| Session lifecycle | `SessionLifecycleHandler` | βœ…/❌ | βœ…/❌ | + +### Step 4: Check Documentation Quality + +For each documented feature, verify: + +1. **Accurate description** - Does it match current implementation? +2. **Code example** - Is there a working code snippet? +3. **Complete coverage** - Are all parameters/options explained? +4. **Up to date** - Does it reflect the current API? + +## Report Format + +### Documentation Coverage Summary + +| Category | Total Features | Documented | Coverage | +|----------|----------------|------------|----------| +| Client Methods | X | X | X% | +| Session Methods | X | X | X% | +| Config Options | X | X | X% | +| Events | X | X | X% | +| Hooks | X | X | X% | +| **Overall** | **X** | **X** | **X%** | + +### Undocumented Features + +Features that exist in code but have no documentation: + +| Feature | Location | Priority | Notes | +|---------|----------|----------|-------| +| Feature name | Class/method | High/Medium/Low | Why it matters | + +### Documentation Gaps + +Existing docs that need enhancement: + +| Document | Gap | Recommendation | +|----------|-----|----------------| +| `getting-started.md` | Missing X | Add section on X | +| `advanced.md` | Outdated API | Update to match code | + +### Missing Documentation Files + +Topics that warrant dedicated documentation: + +| Topic | Why Needed | Suggested File | +|-------|------------|----------------| +| Hooks | Complex feature | `hooks.md` | +| Error Handling | Common need | `error-handling.md` | + +### Recommendations + +#### High Priority +1. Document feature X - used frequently, no docs +2. Update docs for Y - API changed + +#### Medium Priority +1. Add examples for Z +2. Improve explanation of W + +#### Nice to Have +1. Add troubleshooting guide +2. Add FAQ section + +## Key Files + +### Source Code +- `src/main/java/com/github/copilot/sdk/CopilotClient.java` +- `src/main/java/com/github/copilot/sdk/CopilotSession.java` +- `src/main/java/com/github/copilot/sdk/json/SessionConfig.java` +- `src/main/java/com/github/copilot/sdk/json/SessionHooks.java` +- `src/main/java/com/github/copilot/sdk/events/SessionEventParser.java` + +### Documentation +- `src/site/markdown/index.md` - Landing page +- `src/site/markdown/getting-started.md` - Quick start guide +- `src/site/markdown/documentation.md` - API reference +- `src/site/markdown/advanced.md` - Advanced topics +- `src/site/markdown/mcp.md` - MCP integration diff --git a/.github/prompts/test-coverage-assessment.prompt.md b/.github/prompts/test-coverage-assessment.prompt.md new file mode 100644 index 000000000..c2dc3b0c4 --- /dev/null +++ b/.github/prompts/test-coverage-assessment.prompt.md @@ -0,0 +1,125 @@ +# Test Coverage Assessment + +You are an expert Java developer tasked with analyzing test coverage for this Java SDK. Your goal is to produce a comprehensive report of what is tested and what gaps exist. + +## Objective + +Analyze the test coverage of the SDK by examining: +1. **Event types** - All session events defined in `SessionEventParser` +2. **Hook types** - All hooks defined in `SessionHooks` +3. **Core functionality** - Session management, tools, permissions, etc. + +## Assessment Process + +### Step 1: Identify All Testable Components + +First, examine the source code to identify all components that should be tested: + +```bash +# List all event classes +ls src/main/java/com/github/copilot/sdk/events/ + +# Check the event type mapping in SessionEventParser +grep -n "TYPE_MAP.put" src/main/java/com/github/copilot/sdk/events/SessionEventParser.java +``` + +Extract the list of all registered event types from `SessionEventParser.java`. + +### Step 2: Identify All Hook Types + +Check `SessionHooks.java` for all available hook handlers: + +```bash +grep -E "private.*Handler" src/main/java/com/github/copilot/sdk/json/SessionHooks.java +``` + +### Step 3: Analyze Existing Tests + +Examine the test files to understand current coverage: + +```bash +# List all test files +ls src/test/java/com/github/copilot/sdk/ + +# Check for event-related tests +grep -r "import.*events\." src/test/java/com/github/copilot/sdk/ | grep -v "\.class" + +# Check for hook tests +grep -l "SessionHooks\|Hook" src/test/java/com/github/copilot/sdk/*.java +``` + +### Step 4: Categorize Test Coverage + +For each component, determine: +- **Unit Test Coverage**: Tests that verify JSON parsing/serialization without E2E flow +- **E2E Test Coverage**: Tests that verify the component works in a real session flow + +## Report Format + +Generate a comprehensive report in the following format: + +### Event Types Coverage + +| Event Type | Event Class | Unit Test | E2E Test | Notes | +|------------|-------------|:---------:|:--------:|-------| +| `session.start` | `SessionStartEvent` | βœ…/❌ | βœ…/❌ | Any notes | +| ... | ... | ... | ... | ... | + +### Hook Types Coverage + +| Hook Type | Handler Interface | Unit Test | E2E Test | Notes | +|-----------|-------------------|:---------:|:--------:|-------| +| `preToolUse` | `PreToolUseHandler` | βœ…/❌ | βœ…/❌ | Any notes | +| ... | ... | ... | ... | ... | + +### Coverage Summary + +| Category | Total | Unit Tested | E2E Tested | Coverage % | +|----------|-------|-------------|------------|------------| +| Events | X | X | X | X% | +| Hooks | X | X | X | X% | + +### Gaps Identified + +List components that lack tests: +1. **Missing Unit Tests**: Components without JSON parsing tests +2. **Missing E2E Tests**: Components not exercised in integration tests +3. **Partially Tested**: Components with incomplete test coverage + +### Recommendations + +Prioritized list of tests to add: +1. High priority: Critical paths without coverage +2. Medium priority: Common use cases without coverage +3. Low priority: Edge cases and rare events + +## Key Files to Examine + +- `src/main/java/com/github/copilot/sdk/events/SessionEventParser.java` - Event type registry +- `src/main/java/com/github/copilot/sdk/json/SessionHooks.java` - Hook definitions +- `src/main/java/com/github/copilot/sdk/CopilotSession.java` - Hook handling logic +- `src/test/java/com/github/copilot/sdk/SessionEventParserTest.java` - Event parsing tests +- `src/test/java/com/github/copilot/sdk/SessionEventsE2ETest.java` - Event E2E tests +- `src/test/java/com/github/copilot/sdk/HooksTest.java` - Hook tests +- `src/test/java/com/github/copilot/sdk/SessionEventHandlingTest.java` - Event handling tests + +## Verification + +After producing the report, verify by running: + +```bash +# Count total tests +mvn test 2>&1 | grep "Tests run:" + +# Run specific test categories +mvn test -Dtest=SessionEventParserTest +mvn test -Dtest=SessionEventsE2ETest +mvn test -Dtest=HooksTest +``` + +## Output + +Provide: +1. The complete coverage report in markdown table format +2. A prioritized list of recommended improvements +3. Optionally: Skeleton test code for missing high-priority tests diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..8993ca146 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,29 @@ + + +Resolves #ISSUE_NUMBER + +---- + +### Before the change? + + +* + +### After the change? + + +* + +### Pull request checklist +- [ ] Tests for the changes have been added (for bug fixes / features) +- [ ] Docs have been reviewed and added / updated if needed (for bug fixes / features) +- [ ] `mvn spotless:apply` has been run to format the code +- [ ] `mvn clean verify` passes locally + +### Does this introduce a breaking change? + + +- [ ] Yes +- [ ] No + +---- diff --git a/.github/release.yml b/.github/release.yml index 0146a4a5a..1203ddbec 100644 --- a/.github/release.yml +++ b/.github/release.yml @@ -2,6 +2,8 @@ # https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes changelog: + header: | + πŸ“‹ **Full Changelog**: See [CHANGELOG.md](https://github.com/copilot-community-sdk/copilot-sdk-java/blob/main/CHANGELOG.md) for detailed release notes. exclude: labels: - ignore-for-release diff --git a/.github/scripts/build/format-and-test.sh b/.github/scripts/build/format-and-test.sh new file mode 100755 index 000000000..c827813bb --- /dev/null +++ b/.github/scripts/build/format-and-test.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# ────────────────────────────────────────────────────────────── +# format-and-test.sh +# +# Convenience script that runs the full quality pipeline: +# 1. spotless:apply (auto-format code) +# 2. mvn clean verify (compile + test + checkstyle + spotbugs) +# +# Usage: ./.github/scripts/build/format-and-test.sh +# ./.github/scripts/build/format-and-test.sh --format-only +# ./.github/scripts/build/format-and-test.sh --test-only +# ./.github/scripts/build/format-and-test.sh --debug (uses -Pdebug) +# ────────────────────────────────────────────────────────────── +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)" +cd "$ROOT_DIR" + +FORMAT=true +TEST=true +MVN_PROFILE="" + +for arg in "$@"; do + case "$arg" in + --format-only) TEST=false ;; + --test-only) FORMAT=false ;; + --debug) MVN_PROFILE="-Pdebug" ;; + *) echo "Unknown option: $arg"; exit 1 ;; + esac +done + +if $FORMAT; then + echo "β–Έ Running Spotless (format)…" + mvn spotless:apply +fi + +if $TEST; then + echo "β–Έ Running mvn clean verify $MVN_PROFILE …" + mvn clean verify $MVN_PROFILE + echo "" + echo "βœ… All checks passed." +else + echo "βœ… Formatting complete." +fi diff --git a/.github/scripts/ci/commit-and-push.sh b/.github/scripts/ci/commit-and-push.sh new file mode 100755 index 000000000..65fcac51b --- /dev/null +++ b/.github/scripts/ci/commit-and-push.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +# Verify changes, optionally run the code formatter, create a branch, +# stage all changes, commit, and push to origin. +# +# Usage: +# ./commit-and-push.sh [--skip-format] +# +# Arguments: +# branch-name The branch to create (e.g., fix/my-change) +# commit-message The commit message (may include newlines via $'...' syntax) +# --skip-format Skip running mvn spotless:apply +# +# Exit codes: +# 0 Success +# 1 No changes to commit / general error +# 2 Push failed + +set -euo pipefail + +if [[ $# -lt 2 ]]; then + echo "Usage: $0 [--skip-format]" >&2 + exit 1 +fi + +branch_name="$1" +commit_message="$2" +skip_format="${3:-}" + +# -- Step 1: Verify there are changes to commit --------------------------------- +if [[ -z "$(git status --porcelain)" ]]; then + echo "ERROR: No uncommitted changes found. Nothing to commit." >&2 + exit 1 +fi + +echo "βœ“ Uncommitted changes detected." + +# -- Step 4: Run code formatter (if applicable) ---------------------------------- +if [[ "$skip_format" != "--skip-format" ]] && [[ -f pom.xml ]]; then + if grep -q 'spotless-maven-plugin' pom.xml 2>/dev/null; then + echo "Running Spotless formatter..." + mvn -q spotless:apply + echo "βœ“ Spotless formatting applied." + fi +fi + +# -- Step 5: Create branch, stage, and commit ------------------------------------ +# If the branch already exists locally, append a numeric suffix +actual_branch="$branch_name" +suffix=2 +while git show-ref --verify --quiet "refs/heads/$actual_branch" 2>/dev/null; do + actual_branch="${branch_name}-${suffix}" + suffix=$((suffix + 1)) +done + +git checkout -b "$actual_branch" +git add -A +git commit -m "$commit_message" + +echo "βœ“ Committed on branch '$actual_branch'." + +# -- Step 6: Push the branch ----------------------------------------------------- +if ! git push -u origin "$actual_branch" 2>&1; then + echo "ERROR: Push failed." >&2 + exit 2 +fi + +# Verify the push actually transferred the commit +remote_sha=$(git ls-remote origin "refs/heads/$actual_branch" | awk '{print $1}') +local_sha=$(git rev-parse HEAD) + +if [[ "$remote_sha" != "$local_sha" ]]; then + echo "WARNING: Remote SHA does not match local. Retrying push..." >&2 + git push -u origin "$actual_branch" 2>&1 || { echo "ERROR: Retry push failed." >&2; exit 2; } +fi + +echo "βœ“ Pushed to origin/$actual_branch." +echo "BRANCH_NAME=$actual_branch" diff --git a/.github/scripts/ci/create-issue-assigned-to-copilot.ts b/.github/scripts/ci/create-issue-assigned-to-copilot.ts new file mode 100644 index 000000000..28f02c6b1 --- /dev/null +++ b/.github/scripts/ci/create-issue-assigned-to-copilot.ts @@ -0,0 +1,135 @@ +import { Octokit } from '@octokit/rest'; + +const GITHUB_TOKEN = process.env.GITHUB_TOKEN; +const GITHUB_REPO_OWNER = process.env.GITHUB_REPO_OWNER; +const GITHUB_REPO_NAME = process.env.GITHUB_REPO_NAME; + +const GRAPHQL_FEATURES_HEADER = 'issues_copilot_assignment_api_support,coding_agent_model_selection'; + +/** + * Creates a GitHub issue and assigns it to the Copilot Coding Agent. + * Follows the official GitHub API docs: + * 1. Query suggestedActors to find copilot-swe-agent and get its node ID + * 2. Get the repository node ID + * 3. Create issue with assigneeIds + agentAssignment, including required GraphQL-Features header + * Returns the issue URL on success, null on failure. + */ +export async function createIssueWithCopilot(description: string): Promise { + if (!GITHUB_TOKEN || !GITHUB_REPO_OWNER || !GITHUB_REPO_NAME) { + return null; + } + + if (!description.trim()) { + return null; + } + + const octokit = new Octokit({ auth: GITHUB_TOKEN }); + + try { + // Step 1: Fetch repo ID and find copilot-swe-agent in suggestedActors + const repoInfo: any = await octokit.graphql(` + query($owner: String!, $name: String!) { + repository(owner: $owner, name: $name) { + id + suggestedActors(capabilities: [CAN_BE_ASSIGNED], first: 100) { + nodes { + login + __typename + ... on Bot { id } + ... on User { id } + } + } + } + } + `, { + owner: GITHUB_REPO_OWNER, + name: GITHUB_REPO_NAME, + headers: { + 'GraphQL-Features': GRAPHQL_FEATURES_HEADER, + }, + }); + + const repoId = repoInfo?.repository?.id; + if (!repoId) { + console.error('Could not fetch repository ID'); + return null; + } + + const copilotBot = repoInfo.repository.suggestedActors.nodes.find( + (node: any) => node.login === 'copilot-swe-agent' + ); + + let botId: string; + if (copilotBot) { + botId = copilotBot.id; + console.log(`Found Copilot bot: login=${copilotBot.login}, id=${botId}, type=${copilotBot.__typename}`); + } else { + // Fallback: the GITHUB_TOKEN in Actions may lack permission to see suggestedActors. + // Use the known node ID for copilot-swe-agent. + botId = 'BOT_kgDOC9w8XQ'; + console.log(`copilot-swe-agent not found in suggestedActors, using known bot ID: ${botId}`); + } + + const title = description.split('\n')[0].slice(0, 100); + + // Step 2: Create issue with agentAssignment and required GraphQL-Features header + const response: any = await octokit.graphql(` + mutation($repoId: ID!, $title: String!, $body: String!, $assigneeIds: [ID!]) { + createIssue(input: { + repositoryId: $repoId, + title: $title, + body: $body, + assigneeIds: $assigneeIds, + agentAssignment: { + targetRepositoryId: $repoId, + baseRef: "main", + customInstructions: "", + customAgent: "", + model: "" + } + }) { + issue { + number + title + url + assignees(first: 10) { nodes { login } } + } + } + } + `, { + repoId, + title, + body: description, + assigneeIds: [botId], + headers: { + 'GraphQL-Features': GRAPHQL_FEATURES_HEADER, + }, + }); + + const issue = response?.createIssue?.issue; + if (!issue) { + return null; + } + + console.log(`Assigned to: ${issue.assignees.nodes.map((a: any) => a.login).join(', ')}`); + return issue.url; + } catch (error) { + console.error('Error creating issue:', error); + return null; + } +} + +// CLI entry point +const description = process.argv[2]; +if (!description) { + console.error('Usage: npx tsx create-issue-assigned-to-copilot.py '); + process.exit(1); +} +createIssueWithCopilot(description).then((url) => { + if (url) { + console.log(`Issue created: ${url}`); + } else { + console.error('Failed to create issue'); + process.exit(1); + } +}); \ No newline at end of file diff --git a/.github/scripts/ci/parse-repo-info.sh b/.github/scripts/ci/parse-repo-info.sh new file mode 100755 index 000000000..3d89115c7 --- /dev/null +++ b/.github/scripts/ci/parse-repo-info.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# Parse the GitHub owner and repository name from the git remote URL. +# Outputs two lines: owner on the first, repo on the second. +# Handles both HTTPS and SSH remote URL formats. +# +# Usage: +# eval "$(./parse-repo-info.sh)" +# echo "Owner: $REPO_OWNER Repo: $REPO_NAME" + +set -euo pipefail + +remote_url=$(git remote get-url origin 2>/dev/null) || { + echo "ERROR: No git remote named 'origin' found." >&2 + exit 1 +} + +# Strip trailing .git if present +remote_url="${remote_url%.git}" + +if [[ "$remote_url" =~ github\.com[:/]([^/]+)/([^/]+)$ ]]; then + owner="${BASH_REMATCH[1]}" + repo="${BASH_REMATCH[2]}" +else + echo "ERROR: Could not parse owner/repo from remote URL: $remote_url" >&2 + exit 1 +fi + +echo "REPO_OWNER=$owner" +echo "REPO_NAME=$repo" diff --git a/.github/scripts/ci/sync-after-merge.sh b/.github/scripts/ci/sync-after-merge.sh new file mode 100755 index 000000000..94ec9cf8e --- /dev/null +++ b/.github/scripts/ci/sync-after-merge.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# After a PR has been merged, sync the local main branch and +# optionally delete the local feature branch. +# +# Usage: +# ./sync-after-merge.sh [branch-name] +# +# Arguments: +# branch-name The local branch to delete (optional). Skipped if omitted. + +set -euo pipefail + +branch_name="${1:-}" + +# -- Step 9: Sync local main ---------------------------------------------------- +git checkout main +git pull + +echo "βœ“ Local main is up to date." + +# -- Step 10: Clean up the local branch ------------------------------------------ +if [[ -n "$branch_name" ]]; then + if git show-ref --verify --quiet "refs/heads/$branch_name" 2>/dev/null; then + git branch -d "$branch_name" 2>/dev/null || git branch -D "$branch_name" + echo "βœ“ Deleted local branch '$branch_name'." + else + echo "Branch '$branch_name' does not exist locally (already cleaned up)." + fi +fi diff --git a/.github/scripts/generate-coverage-badge.sh b/.github/scripts/generate-coverage-badge.sh new file mode 100755 index 000000000..1d124d947 --- /dev/null +++ b/.github/scripts/generate-coverage-badge.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +# Generates an SVG coverage badge from a JaCoCo CSV report. +# +# Usage: generate-coverage-badge.sh [jacoco.csv] [output-dir] +# jacoco.csv - Path to JaCoCo CSV report (default: target/site/jacoco-coverage/jacoco.csv) +# output-dir - Directory for the badge SVG (default: .github/badges) +set -euo pipefail + +CSV="${1:-target/site/jacoco-coverage/jacoco.csv}" +BADGES_DIR="${2:-.github/badges}" + +if [ ! -f "$CSV" ]; then + echo "⚠️ No JaCoCo CSV report found at $CSV" + exit 0 +fi + +# Sum INSTRUCTION_MISSED and INSTRUCTION_COVERED across all rows (skip header) +read -r missed covered <<< "$(awk -F',' 'NR>1 { m+=$4; c+=$5 } END { print m, c }' "$CSV")" +total=$((missed + covered)) +if [ "$total" -eq 0 ]; then + pct="0" +else + pct=$(awk "BEGIN { printf \"%.1f\", ($covered / $total) * 100 }") + # Drop trailing .0 + pct=$(echo "$pct" | sed 's/\.0$//') +fi +echo "Coverage: ${pct}%" + +# Choose badge color based on coverage +color="#e05d44" # red <60 +if awk "BEGIN{exit!($pct>=100)}"; then color="#4c1" # bright green +elif awk "BEGIN{exit!($pct>=90)}"; then color="#97ca00" # green +elif awk "BEGIN{exit!($pct>=80)}"; then color="#a4a61d" # yellow-green +elif awk "BEGIN{exit!($pct>=70)}"; then color="#dfb317" # yellow +elif awk "BEGIN{exit!($pct>=60)}"; then color="#fe7d37" # orange +fi + +# Generate SVG badge +mkdir -p "$BADGES_DIR" +label="coverage" +value="${pct}%" +lw=62; vw=46; tw=$((lw + vw)) +cat > "${BADGES_DIR}/jacoco.svg" < + + + + + + + + + + + + ${label} + ${label} + ${value} + ${value} + + +EOF + +echo "Badge generated at ${BADGES_DIR}/jacoco.svg" diff --git a/.github/scripts/release/test-update-changelog.sh b/.github/scripts/release/test-update-changelog.sh new file mode 100755 index 000000000..ade87d6a9 --- /dev/null +++ b/.github/scripts/release/test-update-changelog.sh @@ -0,0 +1,203 @@ +#!/bin/bash +# Test script for update-changelog.sh + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +UPDATE_SCRIPT="${SCRIPT_DIR}/update-changelog.sh" +TEST_DIR="/tmp/changelog-test-$$" + +# Colors for output +GREEN='\033[0;32m' +RED='\033[0;31m' +NC='\033[0m' # No Color + +passed=0 +failed=0 + +# Setup test directory +mkdir -p "$TEST_DIR" + +# Cleanup on exit +cleanup() { + rm -rf "$TEST_DIR" +} +trap cleanup EXIT + +# Helper function to run a test +run_test() { + local test_name="$1" + local test_func="$2" + + echo -n "Testing: $test_name ... " + + if $test_func; then + echo -e "${GREEN}PASSED${NC}" + ((passed++)) + else + echo -e "${RED}FAILED${NC}" + ((failed++)) + fi +} + +# Test 1: Basic functionality - Replace Unreleased with version +test_basic_replace() { + local test_file="${TEST_DIR}/test1.md" + cat > "$test_file" << 'EOF' +# Changelog + +## [Unreleased] + +### Added +- New feature + +## [1.0.0] - 2026-01-01 + +### Added +- Initial release + +[1.0.0]: https://github.com/test/repo/releases/tag/1.0.0 +EOF + + # Run the script + CHANGELOG_FILE="$test_file" bash "$UPDATE_SCRIPT" 1.0.1 > /dev/null 2>&1 + + # Verify the changes + if grep -q "## \[Unreleased\]" "$test_file" && \ + grep -q "## \[1.0.1\] - $(date +%Y-%m-%d)" "$test_file" && \ + grep -q "\[Unreleased\]: https://github.com/test/repo/compare/v1.0.1...HEAD" "$test_file" && \ + grep -q "\[1.0.1\]: https://github.com/test/repo/compare/v1.0.0...v1.0.1" "$test_file"; then + return 0 + else + return 1 + fi +} + +# Test 2: Handle CHANGELOG without Unreleased link +test_no_unreleased_link() { + local test_file="${TEST_DIR}/test2.md" + cat > "$test_file" << 'EOF' +# Changelog + +## [Unreleased] + +### Added +- New feature + +## [1.0.0] - 2026-01-01 + +[1.0.0]: https://github.com/test/repo/releases/tag/1.0.0 +EOF + + CHANGELOG_FILE="$test_file" bash "$UPDATE_SCRIPT" 1.0.1 > /dev/null 2>&1 + + # Should add both Unreleased and version links + if grep -q "\[Unreleased\]: https://github.com/test/repo/compare/v1.0.1...HEAD" "$test_file" && \ + grep -q "\[1.0.1\]: https://github.com/test/repo/compare/v1.0.0...v1.0.1" "$test_file"; then + return 0 + else + return 1 + fi +} + +# Test 3: Preserve content structure +test_preserve_content() { + local test_file="${TEST_DIR}/test3.md" + cat > "$test_file" << 'EOF' +# Changelog + +## [Unreleased] + +### Added +- Feature A +- Feature B + +### Fixed +- Bug fix + +## [1.0.0] - 2026-01-01 + +[1.0.0]: https://github.com/test/repo/releases/tag/1.0.0 +EOF + + CHANGELOG_FILE="$test_file" bash "$UPDATE_SCRIPT" 1.0.1 > /dev/null 2>&1 + + # Verify content is preserved under the new version + if grep -A 6 "## \[1.0.1\]" "$test_file" | grep -q "Feature A" && \ + grep -A 6 "## \[1.0.1\]" "$test_file" | grep -q "Bug fix"; then + return 0 + else + return 1 + fi +} + +# Test 4: Error handling - no Unreleased section +test_no_unreleased_section() { + local test_file="${TEST_DIR}/test4.md" + cat > "$test_file" << 'EOF' +# Changelog + +## [1.0.0] - 2026-01-01 + +[1.0.0]: https://github.com/test/repo/releases/tag/1.0.0 +EOF + + # Should fail because there's no Unreleased section + if ! CHANGELOG_FILE="$test_file" bash "$UPDATE_SCRIPT" 1.0.1 > /dev/null 2>&1; then + return 0 + else + return 1 + fi +} + +# Test 5: Multiple version handling +test_multiple_versions() { + local test_file="${TEST_DIR}/test5.md" + cat > "$test_file" << 'EOF' +# Changelog + +## [Unreleased] + +### Added +- New feature + +## [1.0.1] - 2026-02-01 + +## [1.0.0] - 2026-01-01 + +[1.0.1]: https://github.com/test/repo/compare/v1.0.0...v1.0.1 +[1.0.0]: https://github.com/test/repo/releases/tag/1.0.0 +EOF + + CHANGELOG_FILE="$test_file" bash "$UPDATE_SCRIPT" 1.0.2 > /dev/null 2>&1 + + # Verify the new version is added and links are updated + if grep -q "## \[1.0.2\] - $(date +%Y-%m-%d)" "$test_file" && \ + grep -q "\[1.0.2\]: https://github.com/test/repo/compare/v1.0.1...v1.0.2" "$test_file"; then + return 0 + else + return 1 + fi +} + +# Run all tests +echo "Running CHANGELOG update script tests..." +echo "" + +run_test "Basic functionality - Replace Unreleased with version" test_basic_replace +run_test "Handle CHANGELOG without Unreleased link" test_no_unreleased_link +run_test "Preserve content structure" test_preserve_content +run_test "Error handling - no Unreleased section" test_no_unreleased_section +run_test "Multiple version handling" test_multiple_versions + +echo "" +echo "==========================================" +echo -e "Tests passed: ${GREEN}${passed}${NC}" +echo -e "Tests failed: ${RED}${failed}${NC}" +echo "==========================================" + +if [ $failed -eq 0 ]; then + exit 0 +else + exit 1 +fi diff --git a/.github/scripts/release/update-changelog.sh b/.github/scripts/release/update-changelog.sh new file mode 100755 index 000000000..6dfedbf1c --- /dev/null +++ b/.github/scripts/release/update-changelog.sh @@ -0,0 +1,121 @@ +#!/bin/bash +set -e + +# Script to update CHANGELOG.md during release process +# Usage: ./update-changelog.sh [upstream-hash] +# Example: ./update-changelog.sh 1.0.8 +# Example: ./update-changelog.sh 1.0.8 05e3c46c8c23130c9c064dc43d00ec78f7a75eab + +if [ -z "$1" ]; then + echo "Error: Version argument required" + echo "Usage: $0 [upstream-hash]" + exit 1 +fi + +VERSION="$1" +UPSTREAM_HASH="${2:-}" +CHANGELOG_FILE="${CHANGELOG_FILE:-CHANGELOG.md}" +RELEASE_DATE=$(date +%Y-%m-%d) + +echo "Updating CHANGELOG.md for version ${VERSION} (${RELEASE_DATE})" +if [ -n "$UPSTREAM_HASH" ]; then + echo " Upstream SDK sync: ${UPSTREAM_HASH:0:7}" +fi + +# Check if CHANGELOG.md exists +if [ ! -f "$CHANGELOG_FILE" ]; then + echo "Error: CHANGELOG.md not found" + exit 1 +fi + +# Check if there's an [Unreleased] section +if ! grep -q "## \[Unreleased\]" "$CHANGELOG_FILE"; then + echo "Error: No [Unreleased] section found in CHANGELOG.md" + exit 1 +fi + +# Create a temporary file +TEMP_FILE=$(mktemp) + +# Process the CHANGELOG +awk -v version="$VERSION" -v date="$RELEASE_DATE" -v upstream_hash="$UPSTREAM_HASH" ' +BEGIN { + unreleased_found = 0 + content_found = 0 + links_section = 0 + first_version_link = "" + repo_url = "" +} + +# Track if we are in the links section at the bottom +/^\[/ { + links_section = 1 +} + +# Capture the repository URL from the first version link +links_section && repo_url == "" && /^\[[0-9]+\.[0-9]+\.[0-9]+\]:/ { + match($0, /(https:\/\/github\.com\/[^\/]+\/[^\/]+)\//, arr) + if (arr[1] != "") { + repo_url = arr[1] + } +} + +# Replace [Unreleased] with the version and date +/^## \[Unreleased\]/ { + if (!unreleased_found) { + print "## [Unreleased]" + print "" + if (upstream_hash != "") { + short_hash = substr(upstream_hash, 1, 7) + print "> **Upstream sync:** [`github/copilot-sdk@" short_hash "`](https://github.com/github/copilot-sdk/commit/" upstream_hash ")" + print "" + } + print "## [" version "] - " date + if (upstream_hash != "") { + print "" + print "> **Upstream sync:** [`github/copilot-sdk@" short_hash "`](https://github.com/github/copilot-sdk/commit/" upstream_hash ")" + } + unreleased_found = 1 + skip_old_upstream = 1 + next + } +} + +# Skip the old upstream sync line and surrounding blank lines from the previous [Unreleased] section +skip_old_upstream && /^[[:space:]]*$/ { next } +skip_old_upstream && /^> \*\*Upstream sync:\*\*/ { next } +skip_old_upstream && !/^[[:space:]]*$/ && !/^> \*\*Upstream sync:\*\*/ { skip_old_upstream = 0 } + +# Capture the first version link to get the previous version +links_section && first_version_link == "" && /^\[[0-9]+\.[0-9]+\.[0-9]+\]:/ { + match($0, /\[([0-9]+\.[0-9]+\.[0-9]+)\]:/, arr) + if (arr[1] != "" && repo_url != "") { + first_version_link = arr[1] + # Insert Unreleased and new version links before first version link + print "[Unreleased]: " repo_url "/compare/v" version "...HEAD" + print "[" version "]: " repo_url "/compare/v" arr[1] "...v" version + } +} + +# Update existing [Unreleased] link if present +links_section && /^\[Unreleased\]:/ { + # Get the previous version and repo URL from the existing link + match($0, /(https:\/\/github\.com\/[^\/]+\/[^\/]+)\/compare\/v([0-9]+\.[0-9]+\.[0-9]+)\.\.\.HEAD/, arr) + if (arr[1] != "" && arr[2] != "") { + print "[Unreleased]: " arr[1] "/compare/v" version "...HEAD" + print "[" version "]: " arr[1] "/compare/v" arr[2] "...v" version + next + } +} + +# Print all other lines unchanged +{ print } +' "$CHANGELOG_FILE" > "$TEMP_FILE" + +# Replace the original file +mv "$TEMP_FILE" "$CHANGELOG_FILE" + +echo "βœ“ CHANGELOG.md updated successfully" +echo " - Added version ${VERSION} with date ${RELEASE_DATE}" +echo " - Created new [Unreleased] section" +echo " - Updated version comparison links" diff --git a/.github/scripts/upstream-sync/merge-upstream-diff.sh b/.github/scripts/upstream-sync/merge-upstream-diff.sh new file mode 100755 index 000000000..ee61b6ffc --- /dev/null +++ b/.github/scripts/upstream-sync/merge-upstream-diff.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +# ────────────────────────────────────────────────────────────── +# merge-upstream-diff.sh +# +# Generates a detailed diff analysis of upstream changes since +# the last merge, grouped by area of interest: +# β€’ .NET source (primary reference) +# β€’ .NET tests +# β€’ Test snapshots +# β€’ Documentation +# β€’ Protocol / config files +# +# Usage: ./.github/scripts/upstream-sync/merge-upstream-diff.sh [--full] +# --full Show actual diffs, not just stats +# +# Requires: .merge-env written by merge-upstream-start.sh +# ────────────────────────────────────────────────────────────── +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)" +ENV_FILE="$ROOT_DIR/.merge-env" + +if [[ ! -f "$ENV_FILE" ]]; then + echo "❌ $ENV_FILE not found. Run ./.github/scripts/upstream-sync/merge-upstream-start.sh first." + exit 1 +fi + +# shellcheck source=/dev/null +source "$ENV_FILE" + +SHOW_FULL=false +if [[ "${1:-}" == "--full" ]]; then + SHOW_FULL=true +fi + +cd "$UPSTREAM_DIR" +git fetch origin main 2>/dev/null + +RANGE="$LAST_MERGE_COMMIT..origin/main" + +echo "════════════════════════════════════════════════════════════" +echo " Upstream diff analysis: $RANGE" +echo "════════════════════════════════════════════════════════════" + +# ── Commit log ──────────────────────────────────────────────── +echo "" +echo "── Commit log ──" +git log --oneline --no-decorate "$RANGE" +echo "" + +# Helper to print a section +section() { + local title="$1"; shift + local paths=("$@") + + echo "── $title ──" + local stat + stat=$(git diff "$RANGE" --stat -- "${paths[@]}" 2>/dev/null || true) + if [[ -z "$stat" ]]; then + echo " (no changes)" + else + echo "$stat" + if $SHOW_FULL; then + echo "" + git diff "$RANGE" -- "${paths[@]}" 2>/dev/null || true + fi + fi + echo "" +} + +# ── Sections ────────────────────────────────────────────────── +section ".NET source (dotnet/src)" "dotnet/src/" +section ".NET tests (dotnet/test)" "dotnet/test/" +section "Test snapshots" "test/snapshots/" +section "Documentation (docs/)" "docs/" +section "Protocol & config" "sdk-protocol-version.json" "package.json" "justfile" +section "Go SDK" "go/" +section "Node.js SDK" "nodejs/" +section "Python SDK" "python/" +section "Other files" "README.md" "CONTRIBUTING.md" "SECURITY.md" "SUPPORT.md" + +echo "════════════════════════════════════════════════════════════" +echo " To see full diffs: $0 --full" +echo " To see a specific path:" +echo " cd $UPSTREAM_DIR && git diff $RANGE -- " +echo "════════════════════════════════════════════════════════════" diff --git a/.github/scripts/upstream-sync/merge-upstream-finish.sh b/.github/scripts/upstream-sync/merge-upstream-finish.sh new file mode 100755 index 000000000..1663ef259 --- /dev/null +++ b/.github/scripts/upstream-sync/merge-upstream-finish.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +# ────────────────────────────────────────────────────────────── +# merge-upstream-finish.sh +# +# Finalises an upstream merge: +# 1. Runs format + test + build (via format-and-test.sh) +# 2. Updates .lastmerge to upstream HEAD +# 3. Commits the .lastmerge update +# 4. Pushes the branch to origin +# +# Usage: ./.github/scripts/upstream-sync/merge-upstream-finish.sh +# ./.github/scripts/upstream-sync/merge-upstream-finish.sh --skip-tests +# +# Requires: .merge-env written by merge-upstream-start.sh +# ────────────────────────────────────────────────────────────── +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)" +ENV_FILE="$ROOT_DIR/.merge-env" + +if [[ ! -f "$ENV_FILE" ]]; then + echo "❌ $ENV_FILE not found. Run ./.github/scripts/upstream-sync/merge-upstream-start.sh first." + exit 1 +fi + +# shellcheck source=/dev/null +source "$ENV_FILE" + +SKIP_TESTS=false +if [[ "${1:-}" == "--skip-tests" ]]; then + SKIP_TESTS=true +fi + +cd "$ROOT_DIR" + +# ── 1. Format, test, build ─────────────────────────────────── +if $SKIP_TESTS; then + echo "β–Έ Formatting only (tests skipped)…" + mvn spotless:apply + mvn clean package -DskipTests +else + echo "β–Έ Running format + test + build…" + "$ROOT_DIR/.github/scripts/build/format-and-test.sh" +fi + +# ── 2. Update .lastmerge ───────────────────────────────────── +echo "β–Έ Updating .lastmerge…" +NEW_COMMIT=$(cd "$UPSTREAM_DIR" && git rev-parse origin/main) +echo "$NEW_COMMIT" > "$ROOT_DIR/.lastmerge" + +git add .lastmerge +git commit -m "Update .lastmerge to $NEW_COMMIT" + +# ── 3. Push branch ─────────────────────────────────────────── +echo "β–Έ Pushing branch $BRANCH_NAME to origin…" +git push -u origin "$BRANCH_NAME" + +echo "" +echo "βœ… Branch pushed. Next step:" +echo " Create a Pull Request (base: main, head: $BRANCH_NAME)" +echo "" +echo " Suggested title: Merge upstream SDK changes ($(date +%Y-%m-%d))" +echo " Don't forget to add the 'upstream-sync' label." diff --git a/.github/scripts/upstream-sync/merge-upstream-start.sh b/.github/scripts/upstream-sync/merge-upstream-start.sh new file mode 100755 index 000000000..755361cd1 --- /dev/null +++ b/.github/scripts/upstream-sync/merge-upstream-start.sh @@ -0,0 +1,88 @@ +#!/usr/bin/env bash +# ────────────────────────────────────────────────────────────── +# merge-upstream-start.sh +# +# Prepares the workspace for an upstream merge: +# 1. Creates a dated branch from main +# 2. Updates Copilot CLI and records the new version +# 3. Clones the upstream copilot-sdk repo into a temp dir +# 4. Reads .lastmerge and prints a short summary of new commits +# +# Usage: ./.github/scripts/upstream-sync/merge-upstream-start.sh +# Output: Exports UPSTREAM_DIR and LAST_MERGE_COMMIT to a +# .merge-env file so other scripts can source it. +# ────────────────────────────────────────────────────────────── +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)" +cd "$ROOT_DIR" + +UPSTREAM_REPO="https://github.com/github/copilot-sdk.git" +ENV_FILE="$ROOT_DIR/.merge-env" + +# ── 1. Create branch (or reuse existing) ───────────────────── +CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) + +if [[ "$CURRENT_BRANCH" != "main" ]]; then + # Already on a non-main branch (e.g., coding agent's auto-created PR branch). + # Stay on this branch β€” do not create a new one. + BRANCH_NAME="$CURRENT_BRANCH" + echo "β–Έ Already on branch '$BRANCH_NAME' β€” reusing it (coding agent mode)." + git pull origin main --no-edit 2>/dev/null || echo " (pull from main skipped or fast-forward not possible)" +else + echo "β–Έ Ensuring main is up to date…" + git pull origin main + + BRANCH_NAME="merge-upstream-$(date +%Y%m%d)" + echo "β–Έ Creating branch: $BRANCH_NAME" + git checkout -b "$BRANCH_NAME" +fi + +# ── 2. Update Copilot CLI ──────────────────────────────────── +echo "β–Έ Updating Copilot CLI…" +if command -v copilot &>/dev/null; then + copilot update || echo " (copilot update returned non-zero – check manually)" + CLI_VERSION=$(copilot --version | head -n 1 | awk '{print $NF}') + echo " Copilot CLI version: $CLI_VERSION" +else + echo " ⚠ 'copilot' command not found – skipping CLI update." + CLI_VERSION="UNKNOWN" +fi + +# ── 3. Clone upstream ──────────────────────────────────────── +TEMP_DIR=$(mktemp -d) +UPSTREAM_DIR="$TEMP_DIR/copilot-sdk" +echo "β–Έ Cloning upstream into $UPSTREAM_DIR …" +git clone --depth=200 "$UPSTREAM_REPO" "$UPSTREAM_DIR" + +# ── 4. Read last merge commit ──────────────────────────────── +if [[ ! -f "$ROOT_DIR/.lastmerge" ]]; then + echo "❌ .lastmerge file not found in repo root." + exit 1 +fi +LAST_MERGE_COMMIT=$(tr -d '[:space:]' < "$ROOT_DIR/.lastmerge") +echo "β–Έ Last merged upstream commit: $LAST_MERGE_COMMIT" + +# Quick summary +echo "" +echo "── Upstream commits since last merge ──" +(cd "$UPSTREAM_DIR" && git fetch origin main && \ + git log --oneline "$LAST_MERGE_COMMIT"..origin/main) || \ + echo " (could not generate log – the commit may have been rebased)" +echo "" + +# ── 5. Write env file for other scripts ────────────────────── +cat > "$ENV_FILE" < + + + + + + + + Copilot SDK for Java β€” Documentation + + + + + +
+
+
+

Copilot SDK for Java

+

An unofficial, community-driven SDK for building AI-powered tools with GitHub Copilot's agentic runtime.

+ +
+
+ +
+ +
+ ⚠️ Disclaimer: This is an unofficial, community-driven SDK and is not supported or endorsed by GitHub. Use at your own risk. +
+ +
+ πŸ’‘ Tip: We recommend using the Latest Release documentation unless you need features from an unreleased version. +
+ +
+

Available Versions

+
    + +
+ +
+ +
+
    + +
+
+
+ +
+
+
+ Latest stable release +
+
+
+ Development snapshot +
+
+
+ + + +
+ + + diff --git a/.github/templates/styles.css b/.github/templates/styles.css new file mode 100644 index 000000000..415058862 --- /dev/null +++ b/.github/templates/styles.css @@ -0,0 +1,436 @@ +/* ===== Reset & Base ===== */ +*, *::before, *::after { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; + line-height: 1.6; + color: #24292f; + background: #f6f8fa; + -webkit-font-smoothing: antialiased; +} + +.container { + max-width: 860px; + margin: 0 auto; + padding: 0 24px; +} + +a { + color: #0969da; + text-decoration: none; + transition: color 0.2s; +} + +a:hover { + color: #0550ae; +} + +/* ===== Hero Header ===== */ +.hero { + position: relative; + text-align: center; + padding: 80px 24px 48px; + overflow: hidden; +} + +.hero-bg { + position: absolute; + inset: 0; + background: + radial-gradient(ellipse 80% 60% at 50% -20%, rgba(102, 126, 234, 0.12), transparent), + radial-gradient(ellipse 60% 50% at 80% 50%, rgba(118, 75, 162, 0.06), transparent); + z-index: 0; +} + +.hero-content { + position: relative; + z-index: 1; +} + +.hero h1 { + font-size: clamp(2em, 5vw, 2.8em); + font-weight: 800; + line-height: 1.15; + margin-bottom: 16px; + color: #24292f; +} + +.gradient-text { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.hero-subtitle { + font-size: 1.1em; + color: #57606a; + margin-bottom: 32px; + max-width: 520px; + margin-left: auto; + margin-right: auto; +} + +/* ===== Buttons ===== */ +.header-buttons { + display: flex; + gap: 12px; + justify-content: center; + flex-wrap: wrap; +} + +.btn { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 12px 24px; + border-radius: 8px; + font-weight: 600; + font-size: 0.95em; + text-decoration: none; + transition: all 0.2s; + cursor: pointer; +} + +.btn-github { + background: #24292f; + color: #fff; +} + +.btn-github:hover { + color: #fff; + background: #32383f; + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(36, 41, 47, 0.25); +} + +.btn-github svg { + fill: #fff; +} + +.btn-maven { + background: linear-gradient(135deg, #667eea, #764ba2); + color: #fff; +} + +.btn-maven:hover { + color: #fff; + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(102, 126, 234, 0.35); +} + +.btn-maven svg { + fill: #fff; +} + +/* ===== Alert Boxes ===== */ +.alert { + padding: 16px 20px; + border-radius: 10px; + margin-bottom: 16px; + font-size: 0.95em; + line-height: 1.6; + border: 1px solid; +} + +.alert-warning { + background: #fff8c5; + border-color: #d4a72c; + color: #6a5300; +} + +.alert-info { + background: rgba(102, 126, 234, 0.06); + border-color: rgba(102, 126, 234, 0.2); + color: #4a5067; +} + +/* ===== Section ===== */ +.section { + padding: 0 0 48px; +} + +.section-title { + font-size: 1.4em; + font-weight: 700; + color: #24292f; + margin-bottom: 20px; +} + +/* ===== Version List ===== */ +.version-list { + list-style: none; + padding: 0; + display: flex; + flex-direction: column; + gap: 10px; +} + +.version-list li { + background: #fff; + border: 1px solid #d0d7de; + border-radius: 10px; + padding: 16px 20px; + display: flex; + align-items: center; + justify-content: space-between; + transition: all 0.2s; +} + +.version-list li:hover { + border-color: #0969da; + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(0, 0, 0, 0.06); +} + +.version-list li.latest-version { + background: linear-gradient(135deg, rgba(102, 126, 234, 0.04), rgba(118, 75, 162, 0.04)); + border-color: #667eea; +} + +.version-list li.latest-version:hover { + box-shadow: 0 6px 20px rgba(102, 126, 234, 0.15); +} + +.version-name { + color: #24292f; + font-size: 1.05em; + font-weight: 700; +} + +.version-links { + display: flex; + align-items: center; + gap: 16px; +} + +.doc-link { + font-size: 0.85em; + font-weight: 500; + color: #0969da; +} + +.doc-link:hover { + color: #0550ae; +} + +/* ===== Badges ===== */ +.badge { + display: inline-block; + padding: 4px 12px; + border-radius: 100px; + font-size: 0.75em; + font-weight: 600; + letter-spacing: 0.3px; + text-transform: uppercase; +} + +.badge.latest { + background: linear-gradient(135deg, #667eea, #764ba2); + color: #fff; +} + +.badge.snapshot { + background: rgba(212, 167, 44, 0.12); + border: 1px solid rgba(212, 167, 44, 0.3); + color: #7a5c00; +} + +/* ===== Collapsible ===== */ +.collapsible { + margin-top: 8px; +} + +.collapsible-toggle { + background: #fff; + border: 1px solid #d0d7de; + border-radius: 10px; + padding: 14px 20px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + font-size: 0.95em; + font-weight: 600; + color: #57606a; + transition: all 0.2s; +} + +.collapsible-toggle:hover { + border-color: #0969da; + color: #24292f; +} + +.collapsible-toggle::after { + content: 'β–Ά'; + font-size: 0.7em; + transition: transform 0.2s; + color: #8b949e; +} + +.collapsible-toggle.open::after { + transform: rotate(90deg); +} + +.collapsible-content { + display: none; + padding-top: 10px; +} + +.collapsible-content.open { + display: block; +} + +/* ===== Older Releases List ===== */ +.older-list { + list-style: none; + padding: 8px 0 0; +} + +.older-list li { + display: flex; + align-items: center; + justify-content: space-between; + padding: 8px 20px; + border-bottom: 1px solid #eaeef2; + font-size: 0.9em; +} + +.older-list li:last-child { + border-bottom: none; +} + +.older-list > li > span:first-child { + color: #24292f; + font-weight: 500; +} + +.older-links { + display: flex; + gap: 16px; +} + +.release-link { + font-size: 0.85em; + font-weight: 500; + color: #8b949e; +} + +.release-link:hover { + color: #0969da; +} + +/* ===== Legend ===== */ +.legend { + display: flex; + flex-wrap: wrap; + gap: 24px; + justify-content: center; + margin-top: 28px; + padding: 16px; + background: #fff; + border: 1px solid #d0d7de; + border-radius: 10px; +} + +.legend-item { + display: flex; + align-items: center; + gap: 8px; + font-size: 0.85em; + color: #57606a; +} + +.legend-color { + width: 14px; + height: 14px; + border-radius: 4px; +} + +.legend-color.latest { + background: linear-gradient(135deg, #667eea, #764ba2); +} + +.legend-color.snapshot { + background: rgba(212, 167, 44, 0.4); + border: 1px solid rgba(212, 167, 44, 0.5); +} + +/* ===== Footer ===== */ +.footer { + margin-top: 48px; + padding: 32px 0; + text-align: center; + border-top: 1px solid #d0d7de; +} + +.footer-links { + display: flex; + gap: 12px; + justify-content: center; + flex-wrap: wrap; + align-items: center; +} + +.footer-links a { + color: #57606a; + font-size: 0.88em; + font-weight: 500; + transition: color 0.2s; +} + +.footer-links a:hover { + color: #0969da; +} + +.footer-links .sep { + color: #d0d7de; +} + +.built-with { + margin-top: 16px; + font-size: 0.85em; + color: #8b949e; +} + +.built-with a { + color: #57606a; + font-weight: 600; +} + +.built-with a:hover { + color: #0969da; +} + +/* ===== Responsive ===== */ +@media (max-width: 640px) { + .hero { + padding: 60px 16px 36px; + } + + .header-buttons { + flex-direction: column; + align-items: center; + } + + .btn { + width: 100%; + max-width: 280px; + justify-content: center; + } + + .version-list li { + flex-direction: column; + align-items: flex-start; + gap: 8px; + } +} diff --git a/.github/templates/versions.html b/.github/templates/versions.html deleted file mode 100644 index 2926d7782..000000000 --- a/.github/templates/versions.html +++ /dev/null @@ -1,295 +0,0 @@ - - - - - - - - Documentation Versions - Copilot SDK for Java - - - -
- - -
- ⚠️ Disclaimer: This is an unofficial, community-driven SDK and is not supported or endorsed by GitHub. Use at your own risk. -
- -
- πŸ’‘ Tip: We recommend using the Latest Release documentation unless you need features from an unreleased version. -
- -

Available Versions

-
    - -
- -
- -
-
    - -
-
-
- -
-
-
- Latest stable release -
-
-
- Development snapshot -
-
- - -
- - diff --git a/.github/workflows/agentics-maintenance.yml b/.github/workflows/agentics-maintenance.yml new file mode 100644 index 000000000..bb7a31d5d --- /dev/null +++ b/.github/workflows/agentics-maintenance.yml @@ -0,0 +1,82 @@ +# +# ___ _ _ +# / _ \ | | (_) +# | |_| | __ _ ___ _ __ | |_ _ ___ +# | _ |/ _` |/ _ \ '_ \| __| |/ __| +# | | | | (_| | __/ | | | |_| | (__ +# \_| |_/\__, |\___|_| |_|\__|_|\___| +# __/ | +# _ _ |___/ +# | | | | / _| | +# | | | | ___ _ __ _ __| |_| | _____ ____ +# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| +# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ +# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ +# +# This file was automatically generated by pkg/workflow/maintenance_workflow.go (v0.51.6). DO NOT EDIT. +# +# To regenerate this workflow, run: +# gh aw compile +# Not all edits will cause changes to this file. +# +# For more information: https://github.github.com/gh-aw/introduction/overview/ +# +# Alternative regeneration methods: +# make recompile +# +# Or use the gh-aw CLI directly: +# ./gh-aw compile --validate --verbose +# +# The workflow is generated when any workflow uses the 'expires' field +# in create-discussions, create-issues, or create-pull-request safe-outputs configuration. +# Schedule frequency is automatically determined by the shortest expiration time. +# +name: Agentic Maintenance + +on: + schedule: + - cron: "37 0 * * *" # Daily (based on minimum expires: 6 days) + workflow_dispatch: + +permissions: {} + +jobs: + close-expired-entities: + if: ${{ !github.event.repository.fork }} + runs-on: ubuntu-slim + permissions: + discussions: write + issues: write + pull-requests: write + steps: + - name: Setup Scripts + uses: github/gh-aw/actions/setup@33cd6c7f1fee588654ef19def2e6a4174be66197 # v0.51.6 + with: + destination: /opt/gh-aw/actions + + - name: Close expired discussions + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/close_expired_discussions.cjs'); + await main(); + + - name: Close expired issues + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/close_expired_issues.cjs'); + await main(); + + - name: Close expired pull requests + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/close_expired_pull_requests.cjs'); + await main(); diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 5381c7816..5749900f6 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -1,20 +1,26 @@ name: "Build & Test" -env: - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - on: schedule: - # Run once a day at 00:00 UTC - - cron: '0 0 * * *' + # Run once a week on Sundays at 00:00 UTC + - cron: '0 0 * * 0' push: branches: [main] + paths-ignore: + - 'README.md' + - 'LICENSE' + - '.github/**' pull_request: + paths-ignore: + - 'README.md' + - 'LICENSE' + - '.github/**' workflow_dispatch: merge_group: permissions: - contents: read + contents: write + checks: write jobs: @@ -27,11 +33,14 @@ jobs: shell: bash steps: - uses: actions/checkout@v6 - - uses: ./.github/actions/setup-copilot + - uses: actions/setup-node@v6 + with: + node-version: 22 - uses: actions/setup-java@v5 with: java-version: "17" distribution: "temurin" + cache: "maven" - name: Run spotless check run: | @@ -42,11 +51,55 @@ jobs: fi echo "βœ… spotless:check passed" - - name: Build SDK - run: mvn compile + - name: Build SDK and clone test harness + run: mvn test-compile + + - name: Verify Javadoc generation + run: mvn javadoc:javadoc -q + + - name: Install Copilot CLI from cloned SDK + id: setup-copilot + run: | + # Install dependencies in the cloned SDK's nodejs directory + # This ensures we use the same CLI version as the test harness expects + cd target/copilot-sdk/nodejs + npm ci --ignore-scripts + echo "path=$(pwd)/node_modules/@github/copilot/index.js" >> $GITHUB_OUTPUT + + - name: Verify CLI works + run: node ${{ steps.setup-copilot.outputs.path }} --version - name: Run Java SDK tests env: + CI: "true" COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - COPILOT_CLI_PATH: ${{ steps.cli-path.outputs.path }} + COPILOT_CLI_PATH: ${{ steps.setup-copilot.outputs.path }} run: mvn verify + + - name: Upload test results for site generation + if: success() && github.ref == 'refs/heads/main' + uses: actions/upload-artifact@v6 + with: + name: test-results-for-site + path: | + target/jacoco-test-results/sdk-tests.exec + target/surefire-reports/ + retention-days: 1 + + - name: Generate and commit JaCoCo badge + if: success() && github.ref == 'refs/heads/main' + run: | + .github/scripts/generate-coverage-badge.sh + + # Commit if changed + if [[ $(git status --porcelain .github/badges/) ]]; then + git config --global user.name 'github-actions[bot]' + git config --global user.email '41898282+github-actions[bot]@users.noreply.github.com' + git add .github/badges/ + git commit -m "Update JaCoCo coverage badge" + git push + fi + + - name: Generate Test Report Summary + if: always() + uses: ./.github/actions/test-report diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml new file mode 100644 index 000000000..8d2964558 --- /dev/null +++ b/.github/workflows/copilot-setup-steps.yml @@ -0,0 +1,53 @@ +name: "Copilot Setup Steps" + +# This workflow configures the environment for GitHub Copilot Agent with gh-aw MCP server +on: + workflow_dispatch: + push: + paths: + - .github/workflows/copilot-setup-steps.yml + +jobs: + # The job MUST be called 'copilot-setup-steps' to be recognized by GitHub Copilot Agent + copilot-setup-steps: + runs-on: ubuntu-latest + + # Set minimal permissions for setup steps + # Copilot Agent receives its own token with appropriate permissions + permissions: + contents: read + + steps: + # Clone the repository + - name: Checkout repository + uses: actions/checkout@v6 + + # Install GitHub CLI and gh-aw extension for Copilot Agent interaction + - name: Install gh-aw extension + uses: github/gh-aw/actions/setup-cli@v0.42.17 + with: + version: v0.42.17 + + # Setup Node.js + - uses: actions/setup-node@v6 + with: + node-version: 22 + + # Set up JDK 17 + - name: Set up JDK 17 + uses: actions/setup-java@v5 + with: + java-version: '17' + distribution: 'temurin' + cache: 'maven' + + # Verify installations + - name: Verify tool installations + run: | + echo "=== Verifying installations ===" + node --version + npm --version + java -version + gh --version + gh aw version + echo "βœ… All tools installed successfully" diff --git a/.github/workflows/deploy-site.yml b/.github/workflows/deploy-site.yml index 941b8bfc4..7c0c42903 100644 --- a/.github/workflows/deploy-site.yml +++ b/.github/workflows/deploy-site.yml @@ -1,14 +1,18 @@ # Workflow for deploying versioned documentation to GitHub Pages name: Deploy Documentation +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: - # Runs on pushes targeting the default branch (publishes to /snapshot/) - push: - branches: ["main"] - paths: - - 'pom.xml' - - 'src/**' - - '.github/**' + # Runs after Build & Test succeeds on main (publishes to /snapshot/) + workflow_run: + workflows: ["Build & Test"] + types: [completed] + branches: [main] # Runs on release publish (publishes to /latest/ and /vX.Y.Z/) release: @@ -32,6 +36,7 @@ on: # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages permissions: + actions: read # Required to download artifacts from Build & Test workflow contents: write pages: write id-token: write @@ -43,6 +48,8 @@ concurrency: jobs: build-and-deploy: + # Skip if triggered by workflow_run that failed + if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest environment: name: github-pages @@ -58,6 +65,7 @@ jobs: with: java-version: '17' distribution: 'temurin' + cache: 'maven' - name: Checkout gh-pages branch uses: actions/checkout@v6 @@ -110,10 +118,37 @@ jobs: echo "is_release=false" >> $GITHUB_OUTPUT fi + - name: Download test results from Build & Test + if: steps.tags.outputs.is_release == 'false' && inputs.rebuild_all_versions != true && github.event_name == 'workflow_run' + uses: actions/download-artifact@v4 + with: + name: test-results-for-site + path: /tmp/test-results + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + continue-on-error: true + - name: Build snapshot documentation (main branch) if: steps.tags.outputs.is_release == 'false' && inputs.rebuild_all_versions != true run: | - ./mvnw clean site -DskipTests -Dcheckstyle.skip=true + # Compile sources (needed for javadoc and other reports) + ./mvnw clean compile -DskipTests -Dcheckstyle.skip=true + + # Restore test results from Build & Test (for JaCoCo + Surefire reports) + # upload-artifact strips the common ancestor (target/), so files are at root + if [ -d "/tmp/test-results/surefire-reports" ]; then + mkdir -p target/surefire-reports + cp -r /tmp/test-results/surefire-reports/* target/surefire-reports/ + echo "Surefire reports restored" + fi + if [ -f "/tmp/test-results/jacoco-test-results/sdk-tests.exec" ]; then + mkdir -p target/jacoco-test-results + cp /tmp/test-results/jacoco-test-results/sdk-tests.exec target/jacoco-test-results/ + echo "JaCoCo exec restored" + fi + + # Generate site (report plugins pick up the restored data) + ./mvnw site -DskipTests -Dcheckstyle.skip=true rm -rf "site/snapshot" mkdir -p "site/snapshot" @@ -180,12 +215,15 @@ jobs: - name: Copy version index page run: | - cp .github/templates/versions.html site/index.html + cp .github/templates/index.html site/index.html + cp .github/templates/styles.css site/styles.css - name: Update version list from git tags run: | cd site + REPO_URL="https://github.com/copilot-community-sdk/copilot-sdk-java" + # Get versions from git tags (already sorted by version, descending) VERSIONS=$(git -C .. tag -l | grep -E '^v?[0-9]+\.[0-9]+' | sed 's/^v//' | sort -Vr) HAS_SNAPSHOT=$([ -d "snapshot" ] && echo "true" || echo "false") @@ -197,17 +235,17 @@ jobs: # Add snapshot if exists (goes to current versions) if [ "$HAS_SNAPSHOT" = "true" ]; then - CURRENT_HTML+='
  • Development (main branch)snapshot
  • ' + CURRENT_HTML+='
  • Development (main branch)documentation β†—changelog β†—snapshot
  • ' fi # Add versioned releases from tags (first one is latest, rest go to older) for v in $VERSIONS; do if [ -d "$v" ]; then if [ "$IS_FIRST_VERSION" = "true" ]; then - CURRENT_HTML+="
  • Version $vlatest
  • " + CURRENT_HTML+='
  • Version '"$v"'documentation β†—release notes β†—latest
  • ' IS_FIRST_VERSION="false" else - OLDER_HTML+="
  • Version $v
  • " + OLDER_HTML+='
  • '"$v"'documentation β†—release notes β†—
  • ' fi fi done @@ -216,6 +254,16 @@ jobs: sed -i "s||$CURRENT_HTML|" index.html sed -i "s||$OLDER_HTML|" index.html + - name: Overlay custom JaCoCo CSS + run: | + cd site + for dir in */jacoco/jacoco-resources; do + if [ -d "$dir" ]; then + cp ../src/site/jacoco-resources/report.css "$dir/report.css" + echo "Overlaid JaCoCo CSS in $dir" + fi + done + - name: Deploy to GitHub Pages run: | cd site diff --git a/.github/workflows/publish-maven.yml b/.github/workflows/publish-maven.yml index 336b45bde..5596cbd11 100644 --- a/.github/workflows/publish-maven.yml +++ b/.github/workflows/publish-maven.yml @@ -1,6 +1,9 @@ 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: @@ -52,11 +55,12 @@ jobs: with: java-version: "17" distribution: "temurin" + cache: "maven" server-id: central server-username: MAVEN_USERNAME server-password: MAVEN_PASSWORD - gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} - gpg-passphrase: MAVEN_GPG_PASSPHRASE + gpg-private-key: ${{ secrets.GPG_SECRET_KEY }} + gpg-passphrase: GPG_PASSPHRASE - name: Determine versions id: versions @@ -96,6 +100,15 @@ jobs: run: | VERSION="${{ steps.versions.outputs.release_version }}" + # Read the upstream SDK commit hash that this release is synced to + UPSTREAM_HASH=$(cat .lastmerge) + UPSTREAM_SHORT="${UPSTREAM_HASH:0:7}" + UPSTREAM_URL="https://github.com/github/copilot-sdk/commit/${UPSTREAM_HASH}" + echo "Upstream SDK sync: ${UPSTREAM_SHORT} (${UPSTREAM_URL})" + + # Update CHANGELOG.md with release version and upstream sync hash + ./.github/scripts/release/update-changelog.sh "${VERSION}" "${UPSTREAM_HASH}" + # Update version in README.md sed -i "s|[0-9]*\.[0-9]*\.[0-9]*|${VERSION}|g" README.md sed -i "s|copilot-sdk:[0-9]*\.[0-9]*\.[0-9]*|copilot-sdk:${VERSION}|g" README.md @@ -103,8 +116,13 @@ jobs: # Update version in jbang-example.java sed -i "s|copilot-sdk:[0-9]*\.[0-9]*\.[0-9]*|copilot-sdk:${VERSION}|g" jbang-example.java + # Update version in cookbook files (Maven will filter ${project.version} during site generation, + # but we also need the actual version for direct JBang usage) + find src/site/markdown/cookbook -name "*.md" -type f -exec \ + sed -i "s|\${project.version}|${VERSION}|g" {} \; + # Commit the documentation changes before release:prepare (requires clean working directory) - git add README.md jbang-example.java + 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 @@ -123,7 +141,7 @@ jobs: env: MAVEN_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} MAVEN_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} - MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - name: Perform Release and Deploy to Maven Central run: | @@ -133,7 +151,7 @@ jobs: env: MAVEN_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} MAVEN_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} - MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - name: Rollback documentation commit on failure if: failure() && steps.update-docs.outputs.docs_commit_sha != '' @@ -159,16 +177,28 @@ jobs: VERSION="${{ needs.publish-maven.outputs.version }}" GROUP_ID="io.github.copilot-community-sdk" ARTIFACT_ID="copilot-sdk" + 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 describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "") + PREV_TAG=$(git tag --list 'v*' --sort=-version:refname \ + | grep -E '^v[0-9]+\.[0-9]+\.[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=("v${VERSION}") + GH_ARGS=("${CURRENT_TAG}") GH_ARGS+=("--title" "Copilot Java SDK ${VERSION}") GH_ARGS+=("--notes" "${RELEASE_NOTES}") GH_ARGS+=("--generate-notes") @@ -183,10 +213,21 @@ jobs: 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: | diff --git a/.github/workflows/weekly-upstream-sync.lock.yml b/.github/workflows/weekly-upstream-sync.lock.yml new file mode 100644 index 000000000..cfd1342fa --- /dev/null +++ b/.github/workflows/weekly-upstream-sync.lock.yml @@ -0,0 +1,1239 @@ +# +# ___ _ _ +# / _ \ | | (_) +# | |_| | __ _ ___ _ __ | |_ _ ___ +# | _ |/ _` |/ _ \ '_ \| __| |/ __| +# | | | | (_| | __/ | | | |_| | (__ +# \_| |_/\__, |\___|_| |_|\__|_|\___| +# __/ | +# _ _ |___/ +# | | | | / _| | +# | | | | ___ _ __ _ __| |_| | _____ ____ +# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| +# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ +# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ +# +# This file was automatically generated by gh-aw (v0.51.6). DO NOT EDIT. +# +# To update this file, edit the corresponding .md file and run: +# gh aw compile +# Not all edits will cause changes to this file. +# +# For more information: https://github.github.com/gh-aw/introduction/overview/ +# +# Weekly upstream sync workflow. Checks for new commits in the official +# Copilot SDK (github/copilot-sdk) and assigns to Copilot to port changes. +# +# gh-aw-metadata: {"schema_version":"v1","frontmatter_hash":"fc14b09206c7aeafcd52c843adce996a1c14cf15875f9b647ef71f631b3b296e","compiler_version":"v0.51.6"} + +name: "Weekly Upstream Sync Agentic Workflow" +"on": + schedule: + - cron: "39 8 * * 2" + # Friendly format: weekly (scattered) + workflow_dispatch: + +permissions: {} + +concurrency: + group: "gh-aw-${{ github.workflow }}" + +run-name: "Weekly Upstream Sync Agentic Workflow" + +jobs: + activation: + runs-on: ubuntu-slim + permissions: + contents: read + outputs: + comment_id: "" + comment_repo: "" + model: ${{ steps.generate_aw_info.outputs.model }} + secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} + steps: + - name: Setup Scripts + uses: github/gh-aw/actions/setup@33cd6c7f1fee588654ef19def2e6a4174be66197 # v0.51.6 + with: + destination: /opt/gh-aw/actions + - name: Generate agentic run info + id: generate_aw_info + env: + GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" + GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || '' }} + GH_AW_INFO_VERSION: "" + GH_AW_INFO_AGENT_VERSION: "0.0.420" + GH_AW_INFO_CLI_VERSION: "v0.51.6" + GH_AW_INFO_WORKFLOW_NAME: "Weekly Upstream Sync Agentic Workflow" + GH_AW_INFO_EXPERIMENTAL: "false" + GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" + GH_AW_INFO_STAGED: "false" + GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","github"]' + GH_AW_INFO_FIREWALL_ENABLED: "true" + GH_AW_INFO_AWF_VERSION: "v0.23.0" + GH_AW_INFO_AWMG_VERSION: "" + GH_AW_INFO_FIREWALL_TYPE: "squid" + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { main } = require('/opt/gh-aw/actions/generate_aw_info.cjs'); + await main(core, context); + - name: Validate COPILOT_GITHUB_TOKEN secret + id: validate-secret + run: /opt/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default + env: + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + - name: Checkout .github and .agents folders + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + sparse-checkout: | + .github + .agents + sparse-checkout-cone-mode: true + fetch-depth: 1 + persist-credentials: false + - name: Check workflow file timestamps + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_WORKFLOW_FILE: "weekly-upstream-sync.lock.yml" + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/check_workflow_timestamp_api.cjs'); + await main(); + - name: Create prompt with built-in context + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + run: | + bash /opt/gh-aw/actions/create_prompt_first.sh + { + cat << 'GH_AW_PROMPT_EOF' + + GH_AW_PROMPT_EOF + cat "/opt/gh-aw/prompts/xpia.md" + cat "/opt/gh-aw/prompts/temp_folder_prompt.md" + cat "/opt/gh-aw/prompts/markdown.md" + cat "/opt/gh-aw/prompts/safe_outputs_prompt.md" + cat << 'GH_AW_PROMPT_EOF' + + Tools: add_comment, create_issue, close_issue, assign_to_agent, missing_tool, missing_data, noop + + + The following GitHub context information is available for this workflow: + {{#if __GH_AW_GITHUB_ACTOR__ }} + - **actor**: __GH_AW_GITHUB_ACTOR__ + {{/if}} + {{#if __GH_AW_GITHUB_REPOSITORY__ }} + - **repository**: __GH_AW_GITHUB_REPOSITORY__ + {{/if}} + {{#if __GH_AW_GITHUB_WORKSPACE__ }} + - **workspace**: __GH_AW_GITHUB_WORKSPACE__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }} + - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }} + - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }} + - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }} + - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__ + {{/if}} + {{#if __GH_AW_GITHUB_RUN_ID__ }} + - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ + {{/if}} + + + GH_AW_PROMPT_EOF + cat << 'GH_AW_PROMPT_EOF' + + GH_AW_PROMPT_EOF + cat << 'GH_AW_PROMPT_EOF' + {{#runtime-import .github/workflows/weekly-upstream-sync.md}} + GH_AW_PROMPT_EOF + } > "$GH_AW_PROMPT" + - name: Interpolate variables and render templates + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/interpolate_prompt.cjs'); + await main(); + - name: Substitute placeholders + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + + const substitutePlaceholders = require('/opt/gh-aw/actions/substitute_placeholders.cjs'); + + // Call the substitution function + return await substitutePlaceholders({ + file: process.env.GH_AW_PROMPT, + substitutions: { + GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, + GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID, + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER, + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER, + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER, + GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, + GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, + GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE + } + }); + - name: Validate prompt placeholders + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + run: bash /opt/gh-aw/actions/validate_prompt_placeholders.sh + - name: Print prompt + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + run: bash /opt/gh-aw/actions/print_prompt_summary.sh + - name: Upload activation artifact + if: success() + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + with: + name: activation + path: | + /tmp/gh-aw/aw_info.json + /tmp/gh-aw/aw-prompts/prompt.txt + retention-days: 1 + + agent: + needs: activation + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + issues: read + concurrency: + group: "gh-aw-copilot-${{ github.workflow }}" + env: + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + GH_AW_ASSETS_ALLOWED_EXTS: "" + GH_AW_ASSETS_BRANCH: "" + GH_AW_ASSETS_MAX_SIZE_KB: 0 + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + GH_AW_SAFE_OUTPUTS: /opt/gh-aw/safeoutputs/outputs.jsonl + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: /opt/gh-aw/safeoutputs/config.json + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: /opt/gh-aw/safeoutputs/tools.json + GH_AW_WORKFLOW_ID_SANITIZED: weeklyupstreamsync + outputs: + checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} + detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} + detection_success: ${{ steps.detection_conclusion.outputs.success }} + has_patch: ${{ steps.collect_output.outputs.has_patch }} + model: ${{ needs.activation.outputs.model }} + output: ${{ steps.collect_output.outputs.output }} + output_types: ${{ steps.collect_output.outputs.output_types }} + steps: + - name: Setup Scripts + uses: github/gh-aw/actions/setup@33cd6c7f1fee588654ef19def2e6a4174be66197 # v0.51.6 + with: + destination: /opt/gh-aw/actions + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Create gh-aw temp directory + run: bash /opt/gh-aw/actions/create_gh_aw_tmp_dir.sh + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Checkout PR branch + id: checkout-pr + if: | + (github.event.pull_request) || (github.event.issue.pull_request) + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/checkout_pr_branch.cjs'); + await main(); + - name: Install GitHub Copilot CLI + run: /opt/gh-aw/actions/install_copilot_cli.sh 0.0.420 + - name: Install awf binary + run: bash /opt/gh-aw/actions/install_awf_binary.sh v0.23.0 + - name: Determine automatic lockdown mode for GitHub MCP Server + id: determine-automatic-lockdown + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + with: + script: | + const determineAutomaticLockdown = require('/opt/gh-aw/actions/determine_automatic_lockdown.cjs'); + await determineAutomaticLockdown(github, context, core); + - name: Download container images + run: bash /opt/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.23.0 ghcr.io/github/gh-aw-firewall/api-proxy:0.23.0 ghcr.io/github/gh-aw-firewall/squid:0.23.0 ghcr.io/github/gh-aw-mcpg:v0.1.6 ghcr.io/github/github-mcp-server:v0.31.0 node:lts-alpine + - name: Write Safe Outputs Config + run: | + mkdir -p /opt/gh-aw/safeoutputs + mkdir -p /tmp/gh-aw/safeoutputs + mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs + cat > /opt/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF' + {"add_comment":{"max":10,"target":"*"},"assign_to_agent":{"default_agent":"copilot","max":1,"target":"*"},"close_issue":{"max":10,"required_labels":["upstream-sync"],"target":"*"},"create_issue":{"expires":144,"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1}} + GH_AW_SAFE_OUTPUTS_CONFIG_EOF + cat > /opt/gh-aw/safeoutputs/tools.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_EOF' + [ + { + "description": "Create a new GitHub issue for tracking bugs, feature requests, or tasks. Use this for actionable work items that need assignment, labeling, and status tracking. For reports, announcements, or status updates that don't require task tracking, use create_discussion instead. CONSTRAINTS: Maximum 1 issue(s) can be created. Title will be prefixed with \"[upstream-sync] \". Labels [upstream-sync] will be automatically added. Assignees [copilot] will be automatically assigned.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "body": { + "description": "Detailed issue description in Markdown. Do NOT repeat the title as a heading since it already appears as the issue's h1. Include context, reproduction steps, or acceptance criteria as appropriate.", + "type": "string" + }, + "labels": { + "description": "Labels to categorize the issue (e.g., 'bug', 'enhancement'). Labels must exist in the repository.", + "items": { + "type": "string" + }, + "type": "array" + }, + "parent": { + "description": "Parent issue number for creating sub-issues. This is the numeric ID from the GitHub URL (e.g., 42 in github.com/owner/repo/issues/42). Can also be a temporary_id (e.g., 'aw_abc123', 'aw_Test123') from a previously created issue in the same workflow run.", + "type": [ + "number", + "string" + ] + }, + "temporary_id": { + "description": "Unique temporary identifier for referencing this issue before it's created. Format: 'aw_' followed by 3 to 8 alphanumeric characters (e.g., 'aw_abc1', 'aw_Test123'). Use '#aw_ID' in body text to reference other issues by their temporary_id; these are replaced with actual issue numbers after creation.", + "pattern": "^aw_[A-Za-z0-9]{3,8}$", + "type": "string" + }, + "title": { + "description": "Concise issue title summarizing the bug, feature, or task. The title appears as the main heading, so keep it brief and descriptive.", + "type": "string" + } + }, + "required": [ + "title", + "body" + ], + "type": "object" + }, + "name": "create_issue" + }, + { + "description": "Close a GitHub issue with a closing comment. You can and should always add a comment when closing an issue to explain the action or provide context. This tool is ONLY for closing issues - use update_issue if you need to change the title, body, labels, or other metadata without closing. Use close_issue when work is complete, the issue is no longer relevant, or it's a duplicate. The closing comment should explain the resolution or reason for closing. If the issue is already closed, a comment will still be posted. CONSTRAINTS: Maximum 10 issue(s) can be closed. Target: *.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "body": { + "description": "Closing comment explaining why the issue is being closed and summarizing any resolution, workaround, or conclusion.", + "type": "string" + }, + "issue_number": { + "description": "Issue number to close. This is the numeric ID from the GitHub URL (e.g., 901 in github.com/owner/repo/issues/901). If omitted, closes the issue that triggered this workflow (requires an issue event trigger).", + "type": [ + "number", + "string" + ] + } + }, + "required": [ + "body" + ], + "type": "object" + }, + "name": "close_issue" + }, + { + "description": "Add a comment to an existing GitHub issue, pull request, or discussion. Use this to provide feedback, answer questions, or add information to an existing conversation. For creating new items, use create_issue, create_discussion, or create_pull_request instead. IMPORTANT: Comments are subject to validation constraints enforced by the MCP server - maximum 65536 characters for the complete comment (including footer which is added automatically), 10 mentions (@username), and 50 links. Exceeding these limits will result in an immediate error with specific guidance. NOTE: By default, this tool requires discussions:write permission. If your GitHub App lacks Discussions permission, set 'discussions: false' in the workflow's safe-outputs.add-comment configuration to exclude this permission. CONSTRAINTS: Maximum 10 comment(s) can be added. Target: *.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "body": { + "description": "The comment text in Markdown format. This is the 'body' field - do not use 'comment_body' or other variations. Provide helpful, relevant information that adds value to the conversation. CONSTRAINTS: The complete comment (your body text + automatically added footer) must not exceed 65536 characters total. Maximum 10 mentions (@username), maximum 50 links (http/https URLs). A footer (~200-500 characters) is automatically appended with workflow attribution, so leave adequate space. If these limits are exceeded, the tool call will fail with a detailed error message indicating which constraint was violated.", + "type": "string" + }, + "item_number": { + "description": "The issue, pull request, or discussion number to comment on. This is the numeric ID from the GitHub URL (e.g., 123 in github.com/owner/repo/issues/123). If omitted, the tool auto-targets the issue, PR, or discussion that triggered this workflow. Auto-targeting only works for issue, pull_request, discussion, and comment event triggers β€” it does NOT work for schedule, workflow_dispatch, push, or workflow_run triggers. For those trigger types, always provide item_number explicitly, or the comment will be silently discarded.", + "type": "number" + } + }, + "required": [ + "body" + ], + "type": "object" + }, + "name": "add_comment" + }, + { + "description": "Assign the GitHub Copilot coding agent to work on an issue or pull request. The agent will analyze the issue/PR and attempt to implement a solution, creating a pull request when complete. Use this to delegate coding tasks to Copilot. Example usage: assign_to_agent(issue_number=123, agent=\"copilot\") or assign_to_agent(pull_number=456, agent=\"copilot\", pull_request_repo=\"owner/repo\") CONSTRAINTS: Maximum 1 issue(s) can be assigned to agent.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "agent": { + "description": "Agent identifier to assign. Defaults to 'copilot' (the Copilot coding agent) if not specified.", + "type": "string" + }, + "issue_number": { + "description": "Issue number to assign the Copilot coding agent to. This is the numeric ID from the GitHub URL (e.g., 234 in github.com/owner/repo/issues/234). Can also be a temporary_id (e.g., 'aw_abc123', 'aw_Test123') from an issue created earlier in the same workflow run. The issue should contain clear, actionable requirements. Either issue_number or pull_number must be provided, but not both.", + "type": [ + "number", + "string" + ] + }, + "pull_number": { + "description": "Pull request number to assign the Copilot coding agent to. This is the numeric ID from the GitHub URL (e.g., 456 in github.com/owner/repo/pull/456). Either issue_number or pull_number must be provided, but not both.", + "type": [ + "number", + "string" + ] + }, + "pull_request_repo": { + "description": "Target repository where the pull request should be created, in 'owner/repo' format. If omitted, the PR will be created in the same repository as the issue. This allows issues and code to live in different repositories. The global pull-request-repo configuration (if set) is automatically allowed; additional repositories must be listed in allowed-pull-request-repos.", + "type": "string" + } + }, + "type": "object" + }, + "name": "assign_to_agent" + }, + { + "description": "Report that a tool or capability needed to complete the task is not available, or share any information you deem important about missing functionality or limitations. Use this when you cannot accomplish what was requested because the required functionality is missing or access is restricted.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "alternatives": { + "description": "Any workarounds, manual steps, or alternative approaches the user could take (max 256 characters).", + "type": "string" + }, + "reason": { + "description": "Explanation of why this tool is needed or what information you want to share about the limitation (max 256 characters).", + "type": "string" + }, + "tool": { + "description": "Optional: Name or description of the missing tool or capability (max 128 characters). Be specific about what functionality is needed.", + "type": "string" + } + }, + "required": [ + "reason" + ], + "type": "object" + }, + "name": "missing_tool" + }, + { + "description": "Log a transparency message when no significant actions are needed. Use this to confirm workflow completion and provide visibility when analysis is complete but no changes or outputs are required (e.g., 'No issues found', 'All checks passed'). This ensures the workflow produces human-visible output even when no other actions are taken.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "message": { + "description": "Status or completion message to log. Should explain what was analyzed and the outcome (e.g., 'Code review complete - no issues found', 'Analysis complete - all tests passing').", + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "name": "noop" + }, + { + "description": "Report that data or information needed to complete the task is not available. Use this when you cannot accomplish what was requested because required data, context, or information is missing.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "alternatives": { + "description": "Any workarounds, manual steps, or alternative approaches the user could take (max 256 characters).", + "type": "string" + }, + "context": { + "description": "Additional context about the missing data or where it should come from (max 256 characters).", + "type": "string" + }, + "data_type": { + "description": "Type or description of the missing data or information (max 128 characters). Be specific about what data is needed.", + "type": "string" + }, + "reason": { + "description": "Explanation of why this data is needed to complete the task (max 256 characters).", + "type": "string" + } + }, + "required": [], + "type": "object" + }, + "name": "missing_data" + } + ] + GH_AW_SAFE_OUTPUTS_TOOLS_EOF + cat > /opt/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_EOF' + { + "add_comment": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "item_number": { + "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 + } + } + }, + "assign_to_agent": { + "defaultMax": 1, + "fields": { + "agent": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, + "issue_number": { + "issueNumberOrTemporaryId": true + }, + "pull_number": { + "optionalPositiveInteger": true + }, + "pull_request_repo": { + "type": "string", + "maxLength": 256 + }, + "repo": { + "type": "string", + "maxLength": 256 + } + }, + "customValidation": "requiresOneOf:issue_number,pull_number" + }, + "close_issue": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "issue_number": { + "optionalPositiveInteger": true + }, + "repo": { + "type": "string", + "maxLength": 256 + } + } + }, + "create_issue": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "labels": { + "type": "array", + "itemType": "string", + "itemSanitize": true, + "itemMaxLength": 128 + }, + "parent": { + "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 + }, + "temporary_id": { + "type": "string" + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "missing_data": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "context": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "data_type": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, + "reason": { + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, + "missing_tool": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 512 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "tool": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "noop": { + "defaultMax": 1, + "fields": { + "message": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + } + } + } + } + GH_AW_SAFE_OUTPUTS_VALIDATION_EOF + - name: Generate Safe Outputs MCP Server Config + id: safe-outputs-config + run: | + # Generate a secure random API key (360 bits of entropy, 40+ chars) + # Mask immediately to prevent timing vulnerabilities + API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${API_KEY}" + + PORT=3001 + + # Set outputs for next steps + { + echo "safe_outputs_api_key=${API_KEY}" + echo "safe_outputs_port=${PORT}" + } >> "$GITHUB_OUTPUT" + + echo "Safe Outputs MCP server will run on port ${PORT}" + + - name: Start Safe Outputs MCP HTTP Server + id: safe-outputs-start + env: + DEBUG: '*' + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: /opt/gh-aw/safeoutputs/tools.json + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: /opt/gh-aw/safeoutputs/config.json + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + run: | + # Environment variables are set above to prevent template injection + export DEBUG + export GH_AW_SAFE_OUTPUTS_PORT + export GH_AW_SAFE_OUTPUTS_API_KEY + export GH_AW_SAFE_OUTPUTS_TOOLS_PATH + export GH_AW_SAFE_OUTPUTS_CONFIG_PATH + export GH_AW_MCP_LOG_DIR + + bash /opt/gh-aw/actions/start_safe_outputs_server.sh + + - name: Start MCP Gateway + id: start-mcp-gateway + env: + GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} + GITHUB_MCP_LOCKDOWN: ${{ steps.determine-automatic-lockdown.outputs.lockdown == 'true' && '1' || '0' }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + run: | + set -eo pipefail + mkdir -p /tmp/gh-aw/mcp-config + + # Export gateway environment variables for MCP config and gateway script + export MCP_GATEWAY_PORT="80" + export MCP_GATEWAY_DOMAIN="host.docker.internal" + MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${MCP_GATEWAY_API_KEY}" + export MCP_GATEWAY_API_KEY + export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads" + mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}" + export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288" + export DEBUG="*" + + export GH_AW_ENGINE="copilot" + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.1.6' + + mkdir -p /home/runner/.copilot + cat << GH_AW_MCP_CONFIG_EOF | bash /opt/gh-aw/actions/start_mcp_gateway.sh + { + "mcpServers": { + "github": { + "type": "stdio", + "container": "ghcr.io/github/github-mcp-server:v0.31.0", + "env": { + "GITHUB_LOCKDOWN_MODE": "$GITHUB_MCP_LOCKDOWN", + "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", + "GITHUB_READ_ONLY": "1", + "GITHUB_TOOLSETS": "context,repos,issues" + } + }, + "safeoutputs": { + "type": "http", + "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", + "headers": { + "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + } + } + }, + "gateway": { + "port": $MCP_GATEWAY_PORT, + "domain": "${MCP_GATEWAY_DOMAIN}", + "apiKey": "${MCP_GATEWAY_API_KEY}", + "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" + } + } + GH_AW_MCP_CONFIG_EOF + - name: Download activation artifact + uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8 + with: + name: activation + path: /tmp/gh-aw + - name: Clean git credentials + run: bash /opt/gh-aw/actions/clean_git_credentials.sh + - name: Execute GitHub Copilot CLI + id: agentic_execution + # Copilot CLI tool arguments (sorted): + timeout-minutes: 20 + run: | + set -o pipefail + # shellcheck disable=SC1003 + sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --allow-domains "*.githubusercontent.com,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com" --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --image-tag 0.23.0 --skip-pull --enable-api-proxy \ + -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --add-dir "${GITHUB_WORKSPACE}" --disable-builtin-mcps --allow-all-tools --allow-all-paths --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"${GH_AW_MODEL_AGENT_COPILOT:+ --model "$GH_AW_MODEL_AGENT_COPILOT"}' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + env: + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json + GH_AW_MODEL_AGENT_COPILOT: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || '' }} + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_STEP_SUMMARY: ${{ env.GITHUB_STEP_SUMMARY }} + GITHUB_WORKSPACE: ${{ github.workspace }} + XDG_CONFIG_HOME: /home/runner + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Copy Copilot session state files to logs + if: always() + continue-on-error: true + run: | + # Copy Copilot session state files to logs folder for artifact collection + # This ensures they are in /tmp/gh-aw/ where secret redaction can scan them + SESSION_STATE_DIR="$HOME/.copilot/session-state" + LOGS_DIR="/tmp/gh-aw/sandbox/agent/logs" + + if [ -d "$SESSION_STATE_DIR" ]; then + echo "Copying Copilot session state files from $SESSION_STATE_DIR to $LOGS_DIR" + mkdir -p "$LOGS_DIR" + cp -v "$SESSION_STATE_DIR"/*.jsonl "$LOGS_DIR/" 2>/dev/null || true + echo "Session state files copied successfully" + else + echo "No session-state directory found at $SESSION_STATE_DIR" + fi + - name: Stop MCP Gateway + if: always() + continue-on-error: true + env: + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }} + run: | + bash /opt/gh-aw/actions/stop_mcp_gateway.sh "$GATEWAY_PID" + - name: Redact secrets in logs + if: always() + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/redact_secrets.cjs'); + await main(); + env: + GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' + SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Upload Safe Outputs + if: always() + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + with: + name: safe-output + path: ${{ env.GH_AW_SAFE_OUTPUTS }} + if-no-files-found: warn + - name: Ingest agent output + id: collect_output + if: always() + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} + GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/collect_ndjson_output.cjs'); + await main(); + - name: Upload sanitized agent output + if: always() && env.GH_AW_AGENT_OUTPUT + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + with: + name: agent-output + path: ${{ env.GH_AW_AGENT_OUTPUT }} + if-no-files-found: warn + - name: Upload engine output files + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + with: + name: agent_outputs + path: | + /tmp/gh-aw/sandbox/agent/logs/ + /tmp/gh-aw/redacted-urls.log + if-no-files-found: ignore + - name: Parse agent logs for step summary + if: always() + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/ + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/parse_copilot_log.cjs'); + await main(); + - name: Parse MCP Gateway logs for step summary + if: always() + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/parse_mcp_gateway_log.cjs'); + await main(); + - name: Print firewall logs + if: always() + continue-on-error: true + env: + AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs + run: | + # Fix permissions on firewall logs so they can be uploaded as artifacts + # AWF runs with sudo, creating files owned by root + sudo chmod -R a+r /tmp/gh-aw/sandbox/firewall/logs 2>/dev/null || true + # Only run awf logs summary if awf command exists (it may not be installed if workflow failed before install step) + if command -v awf &> /dev/null; then + awf logs summary | tee -a "$GITHUB_STEP_SUMMARY" + else + echo 'AWF binary not installed, skipping firewall log summary' + fi + - name: Upload agent artifacts + if: always() + continue-on-error: true + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + with: + name: agent-artifacts + path: | + /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/sandbox/firewall/logs/ + /tmp/gh-aw/agent-stdio.log + /tmp/gh-aw/agent/ + if-no-files-found: ignore + # --- Threat Detection (inline) --- + - name: Check if detection needed + id: detection_guard + if: always() + env: + OUTPUT_TYPES: ${{ steps.collect_output.outputs.output_types }} + HAS_PATCH: ${{ steps.collect_output.outputs.has_patch }} + run: | + if [[ -n "$OUTPUT_TYPES" || "$HAS_PATCH" == "true" ]]; then + echo "run_detection=true" >> "$GITHUB_OUTPUT" + echo "Detection will run: output_types=$OUTPUT_TYPES, has_patch=$HAS_PATCH" + else + echo "run_detection=false" >> "$GITHUB_OUTPUT" + echo "Detection skipped: no agent outputs or patches to analyze" + fi + - name: Clear MCP configuration for detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + rm -f /tmp/gh-aw/mcp-config/mcp-servers.json + rm -f /home/runner/.copilot/mcp-config.json + rm -f "$GITHUB_WORKSPACE/.gemini/settings.json" + - name: Prepare threat detection files + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + mkdir -p /tmp/gh-aw/threat-detection/aw-prompts + cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true + cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true + for f in /tmp/gh-aw/aw-*.patch; do + [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true + done + echo "Prepared threat detection files:" + ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true + - name: Setup threat detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + WORKFLOW_NAME: "Weekly Upstream Sync Agentic Workflow" + WORKFLOW_DESCRIPTION: "Weekly upstream sync workflow. Checks for new commits in the official\nCopilot SDK (github/copilot-sdk) and assigns to Copilot to port changes." + HAS_PATCH: ${{ steps.collect_output.outputs.has_patch }} + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/setup_threat_detection.cjs'); + await main(); + - name: Ensure threat-detection directory and log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + mkdir -p /tmp/gh-aw/threat-detection + touch /tmp/gh-aw/threat-detection/detection.log + - name: Execute GitHub Copilot CLI + if: always() && steps.detection_guard.outputs.run_detection == 'true' + id: detection_agentic_execution + # Copilot CLI tool arguments (sorted): + # --allow-tool shell(cat) + # --allow-tool shell(grep) + # --allow-tool shell(head) + # --allow-tool shell(jq) + # --allow-tool shell(ls) + # --allow-tool shell(tail) + # --allow-tool shell(wc) + timeout-minutes: 20 + run: | + set -o pipefail + # shellcheck disable=SC1003 + sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --allow-domains "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,raw.githubusercontent.com,registry.npmjs.org,telemetry.enterprise.githubcopilot.com" --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --image-tag 0.23.0 --skip-pull --enable-api-proxy \ + -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --add-dir "${GITHUB_WORKSPACE}" --disable-builtin-mcps --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(jq)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(wc)'\'' --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"${GH_AW_MODEL_DETECTION_COPILOT:+ --model "$GH_AW_MODEL_DETECTION_COPILOT"}' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + env: + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + GH_AW_MODEL_DETECTION_COPILOT: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || '' }} + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_STEP_SUMMARY: ${{ env.GITHUB_STEP_SUMMARY }} + GITHUB_WORKSPACE: ${{ github.workspace }} + XDG_CONFIG_HOME: /home/runner + - name: Parse threat detection results + id: parse_detection_results + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/parse_threat_detection_results.cjs'); + await main(); + - name: Upload threat detection log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + with: + name: threat-detection.log + path: /tmp/gh-aw/threat-detection/detection.log + if-no-files-found: ignore + - name: Set detection conclusion + id: detection_conclusion + if: always() + env: + RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} + DETECTION_SUCCESS: ${{ steps.parse_detection_results.outputs.success }} + run: | + if [[ "$RUN_DETECTION" != "true" ]]; then + echo "conclusion=skipped" >> "$GITHUB_OUTPUT" + echo "success=true" >> "$GITHUB_OUTPUT" + echo "Detection was not needed, marking as skipped" + elif [[ "$DETECTION_SUCCESS" == "true" ]]; then + echo "conclusion=success" >> "$GITHUB_OUTPUT" + echo "success=true" >> "$GITHUB_OUTPUT" + echo "Detection passed successfully" + else + echo "conclusion=failure" >> "$GITHUB_OUTPUT" + echo "success=false" >> "$GITHUB_OUTPUT" + echo "Detection found issues" + fi + + conclusion: + needs: + - activation + - agent + - safe_outputs + if: (always()) && (needs.agent.result != 'skipped') + runs-on: ubuntu-slim + permissions: + contents: read + discussions: write + issues: write + pull-requests: write + outputs: + noop_message: ${{ steps.noop.outputs.noop_message }} + tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} + total_count: ${{ steps.missing_tool.outputs.total_count }} + steps: + - name: Setup Scripts + uses: github/gh-aw/actions/setup@33cd6c7f1fee588654ef19def2e6a4174be66197 # v0.51.6 + with: + destination: /opt/gh-aw/actions + - name: Download agent output artifact + continue-on-error: true + uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8 + with: + name: agent-output + path: /tmp/gh-aw/safeoutputs/ + - name: Setup agent output environment variable + run: | + mkdir -p /tmp/gh-aw/safeoutputs/ + find "/tmp/gh-aw/safeoutputs/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV" + - name: Process No-Op Messages + id: noop + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_NOOP_MAX: "1" + GH_AW_WORKFLOW_NAME: "Weekly Upstream Sync Agentic Workflow" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/noop.cjs'); + await main(); + - name: Record Missing Tool + id: missing_tool + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Weekly Upstream Sync Agentic Workflow" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/missing_tool.cjs'); + await main(); + - name: Handle Agent Failure + id: handle_agent_failure + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Weekly Upstream Sync Agentic Workflow" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_WORKFLOW_ID: "weekly-upstream-sync" + GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} + GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} + GH_AW_ASSIGNMENT_ERRORS: ${{ needs.safe_outputs.outputs.assign_to_agent_assignment_errors }} + GH_AW_ASSIGNMENT_ERROR_COUNT: ${{ needs.safe_outputs.outputs.assign_to_agent_assignment_error_count }} + GH_AW_GROUP_REPORTS: "false" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/handle_agent_failure.cjs'); + await main(); + - name: Handle No-Op Message + id: handle_noop_message + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Weekly Upstream Sync Agentic Workflow" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_NOOP_MESSAGE: ${{ steps.noop.outputs.noop_message }} + GH_AW_NOOP_REPORT_AS_ISSUE: "false" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/handle_noop_message.cjs'); + await main(); + + safe_outputs: + needs: agent + if: ((!cancelled()) && (needs.agent.result != 'skipped')) && (needs.agent.outputs.detection_success == 'true') + runs-on: ubuntu-slim + permissions: + contents: read + discussions: write + issues: write + pull-requests: write + timeout-minutes: 15 + env: + GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/${{ github.workflow }}" + GH_AW_ENGINE_ID: "copilot" + GH_AW_WORKFLOW_ID: "weekly-upstream-sync" + GH_AW_WORKFLOW_NAME: "Weekly Upstream Sync Agentic Workflow" + outputs: + assign_to_agent_assigned: ${{ steps.assign_to_agent.outputs.assigned }} + assign_to_agent_assignment_error_count: ${{ steps.assign_to_agent.outputs.assignment_error_count }} + assign_to_agent_assignment_errors: ${{ steps.assign_to_agent.outputs.assignment_errors }} + code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} + code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} + comment_id: ${{ steps.process_safe_outputs.outputs.comment_id }} + comment_url: ${{ steps.process_safe_outputs.outputs.comment_url }} + create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }} + create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }} + created_issue_number: ${{ steps.process_safe_outputs.outputs.created_issue_number }} + created_issue_url: ${{ steps.process_safe_outputs.outputs.created_issue_url }} + process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }} + process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} + steps: + - name: Setup Scripts + uses: github/gh-aw/actions/setup@33cd6c7f1fee588654ef19def2e6a4174be66197 # v0.51.6 + with: + destination: /opt/gh-aw/actions + - name: Download agent output artifact + continue-on-error: true + uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8 + with: + name: agent-output + path: /tmp/gh-aw/safeoutputs/ + - name: Setup agent output environment variable + run: | + mkdir -p /tmp/gh-aw/safeoutputs/ + find "/tmp/gh-aw/safeoutputs/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV" + - name: Process Safe Outputs + id: process_safe_outputs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":10,\"target\":\"*\"},\"close_issue\":{\"max\":10,\"required_labels\":[\"upstream-sync\"],\"target\":\"*\"},\"create_issue\":{\"assignees\":[\"copilot\"],\"expires\":144,\"labels\":[\"upstream-sync\"],\"max\":1,\"title_prefix\":\"[upstream-sync] \"},\"missing_data\":{},\"missing_tool\":{}}" + GH_AW_ASSIGN_COPILOT: "true" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/safe_output_handler_manager.cjs'); + await main(); + - name: Assign Copilot to created issues + if: steps.process_safe_outputs.outputs.issues_to_assign_copilot != '' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_ISSUES_TO_ASSIGN_COPILOT: ${{ steps.process_safe_outputs.outputs.issues_to_assign_copilot }} + with: + github-token: ${{ secrets.GH_AW_AGENT_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/assign_copilot_to_created_issues.cjs'); + await main(); + - name: Assign to agent + id: assign_to_agent + if: ((!cancelled()) && (needs.agent.result != 'skipped')) && (contains(needs.agent.outputs.output_types, 'assign_to_agent')) + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_AGENT_MAX_COUNT: 1 + GH_AW_AGENT_DEFAULT: "copilot" + GH_AW_AGENT_DEFAULT_MODEL: "claude-opus-4.6" + GH_AW_AGENT_TARGET: "*" + GH_AW_TEMPORARY_ID_MAP: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} + with: + github-token: ${{ secrets.GH_AW_AGENT_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/assign_to_agent.cjs'); + await main(); + - name: Upload safe output items manifest + if: always() + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + with: + name: safe-output-items + path: /tmp/safe-output-items.jsonl + if-no-files-found: warn + diff --git a/.github/workflows/weekly-upstream-sync.md b/.github/workflows/weekly-upstream-sync.md new file mode 100644 index 000000000..641f29301 --- /dev/null +++ b/.github/workflows/weekly-upstream-sync.md @@ -0,0 +1,151 @@ +--- +description: | + Weekly upstream sync workflow. Checks for new commits in the official + Copilot SDK (github/copilot-sdk) and assigns to Copilot to port changes. + +on: + schedule: weekly + workflow_dispatch: + +permissions: + contents: read + actions: read + issues: read + +network: + allowed: + - defaults + - github + +tools: + github: + toolsets: [context, repos, issues] + +safe-outputs: + create-issue: + title-prefix: "[upstream-sync] " + assignees: [copilot] + labels: [upstream-sync] + expires: 6 + close-issue: + required-labels: [upstream-sync] + target: "*" + max: 10 + add-comment: + target: "*" + max: 10 + assign-to-agent: + name: "copilot" + model: "claude-opus-4.6" + target: "*" + noop: + report-as-issue: false +--- +# Weekly Upstream Sync Agentic Workflow +This document describes the `weekly-upstream-sync.yml` GitHub Actions workflow, which automates the detection of new changes in the official [Copilot SDK](https://github.com/github/copilot-sdk) and delegates the merge work to the Copilot coding agent. + +## Overview + +The workflow runs on a **weekly schedule** (every Monday at 10:00 UTC) and can also be triggered manually. It does **not** perform the actual merge β€” instead, it detects upstream changes and creates a GitHub issue assigned to `copilot`, instructing the agent to follow the [agentic-merge-upstream](../prompts/agentic-merge-upstream.prompt.md) prompt to port the changes. + +The agent must also create the Pull Request with the label `upstream-sync`. This allows the workflow to track the merge progress and avoid creating duplicate issues if the agent is still working on a previous sync. + +## Trigger + +| Trigger | Schedule | +|---|---| +| `schedule` | Every Monday at 10:00 UTC (`0 10 * * 1`) | +| `workflow_dispatch` | Manual trigger from the Actions tab | + +## Workflow Steps + +### 1. Checkout repository + +Checks out the repo to read the `.lastmerge` file, which contains the SHA of the last upstream commit that was merged into the Java SDK. + +### 2. Check for upstream changes + +- Reads the last merged commit hash from `.lastmerge` +- Clones the upstream `github/copilot-sdk` repository +- Compares `.lastmerge` against upstream `HEAD` +- If they match: sets `has_changes=false` +- If they differ: counts new commits, generates a summary (up to 20 most recent), and sets outputs (`commit_count`, `upstream_head`, `last_merge`, `summary`) + +### 3. Close previous upstream-sync issues (when changes found) + +**Condition:** `has_changes == true` + +Before creating a new issue, closes any existing open issues with the `upstream-sync` label. This prevents stale issues from accumulating when previous sync attempts were incomplete or superseded. Each closed issue receives a comment explaining it was superseded. + +### 4. Close stale upstream-sync issues (when no changes found) + +**Condition:** `has_changes == false` + +If the upstream is already up to date, closes any lingering open `upstream-sync` issues with a comment noting that no changes were detected. This handles the case where a previous issue was created but the changes were merged manually (updating `.lastmerge`) before the agent completed. + +### 5. Create issue and assign to Copilot + +**Condition:** `has_changes == true` + +Creates a new GitHub issue with: + +- **Title:** `Upstream sync: N new commits (YYYY-MM-DD)` +- **Label:** `upstream-sync` +- **Assignee:** `copilot` +- **Body:** Contains commit count, commit range links, a summary of recent commits, and a link to the merge prompt + +The Copilot coding agent picks up the issue, creates a branch and PR, then follows the merge prompt to port the changes. + +### 6. Summary + +Writes a GitHub Actions step summary with: + +- Whether changes were detected +- Commit count and range +- Recent upstream commits +- Link to the created issue (if any) + +## Flow Diagram + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Schedule / Manual β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Read .lastmerge β”‚ +β”‚ Clone upstream SDK β”‚ +β”‚ Compare commits β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β”Œβ”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β” + β”‚ β”‚ + changes? no changes + β”‚ β”‚ + β–Ό β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Close oldβ”‚ β”‚ Close stale β”‚ +β”‚ issues β”‚ β”‚ issues β”‚ +β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Create issue assigned to β”‚ +β”‚ copilot β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Agent follows prompt to β”‚ +β”‚ port changes β†’ PR β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +## Related Files + +| File | Purpose | +|---|---| +| `.lastmerge` | Stores the SHA of the last merged upstream commit | +| [agentic-merge-upstream.prompt.md](../prompts/agentic-merge-upstream.prompt.md) | Detailed instructions the Copilot agent follows to port changes | +| `.github/scripts/upstream-sync/` | Helper scripts used by the merge prompt | diff --git a/.github/workflows/weekly-upstream-sync.yml b/.github/workflows/weekly-upstream-sync.yml new file mode 100644 index 000000000..f1549c5bb --- /dev/null +++ b/.github/workflows/weekly-upstream-sync.yml @@ -0,0 +1,178 @@ +name: "Weekly Upstream Sync" + +on: + schedule: + # Every Monday at 10:00 UTC (5:00 AM EST / 6:00 AM EDT) + - cron: '0 10 * * 1' + workflow_dispatch: + +permissions: + contents: write + issues: write + pull-requests: write + +env: + # Used for `gh` CLI operations to create/close/comment issues. Must be a PAT with repo permissions because the default + GH_AW_AGENT_TOKEN: ${{ secrets.GH_AW_AGENT_TOKEN }} + GH_TOKEN: ${{ secrets.GH_AW_AGENT_TOKEN }} + +jobs: + + check-and-sync: + name: "Check upstream & trigger Copilot merge" + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Check for upstream changes + id: check + run: | + LAST_MERGE=$(cat .lastmerge) + echo "Last merged commit: $LAST_MERGE" + + git clone --quiet https://github.com/github/copilot-sdk.git /tmp/upstream + cd /tmp/upstream + + UPSTREAM_HEAD=$(git rev-parse HEAD) + echo "Upstream HEAD: $UPSTREAM_HEAD" + + echo "last_merge=$LAST_MERGE" >> "$GITHUB_OUTPUT" + + if [ "$LAST_MERGE" = "$UPSTREAM_HEAD" ]; then + echo "No new upstream changes since last merge." + echo "has_changes=false" >> "$GITHUB_OUTPUT" + else + COMMIT_COUNT=$(git rev-list --count "$LAST_MERGE".."$UPSTREAM_HEAD") + echo "Found $COMMIT_COUNT new upstream commits." + echo "has_changes=true" >> "$GITHUB_OUTPUT" + echo "commit_count=$COMMIT_COUNT" >> "$GITHUB_OUTPUT" + echo "upstream_head=$UPSTREAM_HEAD" >> "$GITHUB_OUTPUT" + + # Generate a short summary of changes + SUMMARY=$(git log --oneline "$LAST_MERGE".."$UPSTREAM_HEAD" | head -20) + { + echo "summary<> "$GITHUB_OUTPUT" + fi + + - name: Close previous upstream-sync issues + if: steps.check.outputs.has_changes == 'true' + run: | + # Find all open issues with the upstream-sync label + OPEN_ISSUES=$(gh issue list \ + --repo "${{ github.repository }}" \ + --label "upstream-sync" \ + --state open \ + --json number \ + --jq '.[].number') + + for ISSUE_NUM in $OPEN_ISSUES; do + echo "Closing superseded issue #${ISSUE_NUM}" + gh issue comment "$ISSUE_NUM" \ + --repo "${{ github.repository }}" \ + --body "Superseded by a newer upstream sync issue. Closing this one." + gh issue close "$ISSUE_NUM" \ + --repo "${{ github.repository }}" \ + --reason "not planned" + done + + - name: Close stale upstream-sync issues (no changes) + if: steps.check.outputs.has_changes == 'false' + run: | + OPEN_ISSUES=$(gh issue list \ + --repo "${{ github.repository }}" \ + --label "upstream-sync" \ + --state open \ + --json number \ + --jq '.[].number') + + for ISSUE_NUM in $OPEN_ISSUES; do + echo "Closing stale issue #${ISSUE_NUM} β€” upstream is up to date" + gh issue comment "$ISSUE_NUM" \ + --repo "${{ github.repository }}" \ + --body "No new upstream changes detected. The Java SDK is up to date. Closing." + gh issue close "$ISSUE_NUM" \ + --repo "${{ github.repository }}" \ + --reason "completed" + done + + - name: Create issue and assign to Copilot + id: create-issue + if: steps.check.outputs.has_changes == 'true' + run: | + COMMIT_COUNT="${{ steps.check.outputs.commit_count }}" + LAST_MERGE="${{ steps.check.outputs.last_merge }}" + UPSTREAM_HEAD="${{ steps.check.outputs.upstream_head }}" + SUMMARY="${{ steps.check.outputs.summary }}" + DATE=$(date -u +"%Y-%m-%d") + REPO="${{ github.repository }}" + + BODY="## Automated Upstream Sync + + There are **${COMMIT_COUNT}** new commits in the [official Copilot SDK](https://github.com/github/copilot-sdk) since the last merge. + + - **Last merged commit:** [\`${LAST_MERGE}\`](https://github.com/github/copilot-sdk/commit/${LAST_MERGE}) + - **Upstream HEAD:** [\`${UPSTREAM_HEAD}\`](https://github.com/github/copilot-sdk/commit/${UPSTREAM_HEAD}) + + ### Recent upstream commits + + \`\`\` + ${SUMMARY} + \`\`\` + + ### Instructions + + Follow the [agentic-merge-upstream](.github/prompts/agentic-merge-upstream.prompt.md) prompt to port these changes to the Java SDK." + + # Create the issue and assign to Copilot coding agent + ISSUE_URL=$(gh issue create \ + --repo "$REPO" \ + --title "Upstream sync: ${COMMIT_COUNT} new commits (${DATE})" \ + --body "$BODY" \ + --label "upstream-sync" \ + --assignee "copilot-swe-agent") + + echo "issue_url=$ISSUE_URL" >> "$GITHUB_OUTPUT" + echo "βœ… Issue created and assigned to Copilot coding agent: $ISSUE_URL" + + - name: Summary + if: always() + run: | + HAS_CHANGES="${{ steps.check.outputs.has_changes }}" + COMMIT_COUNT="${{ steps.check.outputs.commit_count }}" + LAST_MERGE="${{ steps.check.outputs.last_merge }}" + UPSTREAM_HEAD="${{ steps.check.outputs.upstream_head }}" + ISSUE_URL="${{ steps.create-issue.outputs.issue_url }}" + + { + echo "## Weekly Upstream Sync" + echo "" + if [ "$HAS_CHANGES" = "true" ]; then + echo "### βœ… New upstream changes detected" + echo "" + echo "| | |" + echo "|---|---|" + echo "| **New commits** | ${COMMIT_COUNT} |" + echo "| **Last merged** | \`${LAST_MERGE:0:12}\` |" + echo "| **Upstream HEAD** | \`${UPSTREAM_HEAD:0:12}\` |" + echo "" + echo "An issue has been created and assigned to the Copilot coding agent: " + echo " -> ${ISSUE_URL}" + echo "" + echo "### Recent upstream commits" + echo "" + echo '```' + echo "${{ steps.check.outputs.summary }}" + echo '```' + else + echo "### ⏭️ No changes" + echo "" + echo "The Java SDK is already up to date with the upstream Copilot SDK." + echo "" + echo "**Last merged commit:** \`${LAST_MERGE:0:12}\`" + fi + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.gitignore b/.gitignore index 0b5a49d0a..f6b0681d3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,7 @@ .DS_Store target +examples-test/ +.merge-env +blog-copilotsdk/ +.claude/worktrees +smoke-test diff --git a/.lastmerge b/.lastmerge index a85c3cb80..c5649a512 100644 --- a/.lastmerge +++ b/.lastmerge @@ -1 +1 @@ -f902b76032287f9d48a24ebc382ec61c09e3bd36 \ No newline at end of file +062b61c8aa63b9b5d45fa1d7b01723e6660ffa83 diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..9be6c3347 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,21 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "java", + "name": "Debug JUnit Tests (with FINE logging)", + "request": "launch", + "mainClass": "", + "projectName": "copilot-sdk", + "vmArgs": "-Djava.util.logging.config.file=${workspaceFolder}/src/test/resources/logging-debug.properties -Dcopilot.sdk.dir=${workspaceFolder}/target/copilot-sdk -Dcopilot.tests.dir=${workspaceFolder}/target/copilot-sdk/test" + }, + { + "type": "java", + "name": "Debug Current Test File", + "request": "launch", + "mainClass": "", + "projectName": "copilot-sdk", + "vmArgs": "-Djava.util.logging.config.file=${workspaceFolder}/src/test/resources/logging-debug.properties -Dcopilot.sdk.dir=${workspaceFolder}/target/copilot-sdk -Dcopilot.tests.dir=${workspaceFolder}/target/copilot-sdk/test" + } + ] +} diff --git a/.vscode/mcp.json b/.vscode/mcp.json new file mode 100644 index 000000000..6699af564 --- /dev/null +++ b/.vscode/mcp.json @@ -0,0 +1,12 @@ +{ + "servers": { + "github-agentic-workflows": { + "command": "gh", + "args": [ + "aw", + "mcp-server" + ], + "cwd": "${workspaceFolder}" + } + } +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..85dfb5748 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,10 @@ +{ + "java.test.config": { + "vmArgs": [ + "-Djava.util.logging.config.file=${workspaceFolder}/src/test/resources/logging-debug.properties", + "-Dcopilot.sdk.dir=${workspaceFolder}/target/copilot-sdk", + "-Dcopilot.tests.dir=${workspaceFolder}/target/copilot-sdk/test" + ] + }, + "java.compile.nullAnalysis.mode": "automatic" +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..1404ba336 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,451 @@ +# Changelog + +All notable changes to the 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] + +> **Upstream sync:** [`github/copilot-sdk@062b61c`](https://github.com/github/copilot-sdk/commit/062b61c8aa63b9b5d45fa1d7b01723e6660ffa83) + +### Notice + +- **This repository is now archived.** The official GitHub Copilot SDK for Java is maintained at [github/copilot-sdk-java](https://github.com/github/copilot-sdk-java). No further changes or releases will be made in this repository. + +## [1.0.11] - 2026-03-12 + +> **Upstream 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 (upstream: [`e478657`](https://github.com/github/copilot-sdk/commit/e478657)) +- `SessionConfig.setAgent(String)` β€” pre-selects a custom agent by name when creating a session (upstream: [`7766b1a`](https://github.com/github/copilot-sdk/commit/7766b1a)) +- `ResumeSessionConfig.setAgent(String)` β€” pre-selects a custom agent by name when resuming a session (upstream: [`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 (upstream: [`4125fe7`](https://github.com/github/copilot-sdk/commit/4125fe7)) +- `ResumeSessionConfig.setOnEvent(Consumer)` β€” registers an event handler before the `session.resume` RPC is issued (upstream: [`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`) (upstream: [`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 (upstream: [`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 (upstream: [`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 (upstream: [`1653812`](https://github.com/github/copilot-sdk/commit/1653812)) + +## [1.0.10] - 2026-03-03 + +> **Upstream 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 (upstream: [`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 (upstream: [`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 (upstream: [`f843c80`](https://github.com/github/copilot-sdk/commit/f843c80)) +- `CopilotSession.listAgents()` β€” lists custom agents available for selection (upstream: [`9d998fb`](https://github.com/github/copilot-sdk/commit/9d998fb)) +- `CopilotSession.getCurrentAgent()` β€” gets the currently selected custom agent (upstream: [`9d998fb`](https://github.com/github/copilot-sdk/commit/9d998fb)) +- `CopilotSession.selectAgent(String)` β€” selects a custom agent for the session (upstream: [`9d998fb`](https://github.com/github/copilot-sdk/commit/9d998fb)) +- `CopilotSession.deselectAgent()` β€” deselects the current custom agent (upstream: [`9d998fb`](https://github.com/github/copilot-sdk/commit/9d998fb)) +- `CopilotSession.compact()` β€” triggers immediate session context compaction (upstream: [`9d998fb`](https://github.com/github/copilot-sdk/commit/9d998fb)) +- `AgentInfo` β€” new JSON type representing a custom agent with `name`, `displayName`, and `description` (upstream: [`9d998fb`](https://github.com/github/copilot-sdk/commit/9d998fb)) +- New event types: `SessionTaskCompleteEvent` (`session.task_complete`), `AssistantStreamingDeltaEvent` (`assistant.streaming_delta`), `SubagentDeselectedEvent` (`subagent.deselected`) (upstream: 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 (upstream: [`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 (upstream: [`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 (upstream: [`279f6c4`](https://github.com/github/copilot-sdk/commit/279f6c4)) +- **Breaking:** The no-arg `createSession()` and `resumeSession(String)` overloads were removed (upstream: [`279f6c4`](https://github.com/github/copilot-sdk/commit/279f6c4)) +- `AssistantMessageDeltaEvent` data: `totalResponseSizeBytes` field moved to new `AssistantStreamingDeltaEvent` (upstream: various) + +### Fixed + +- Permission checks now also apply to SDK-registered custom tools, invoking the `onPermissionRequest` handler with `kind="custom-tool"` before executing tools (upstream: [`388f2f3`](https://github.com/github/copilot-sdk/commit/388f2f3)) + +## [1.0.9] - 2026-02-16 + +> **Upstream 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 + +> **Upstream 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 upstream 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 upstream SDK changes (commit 87ff5510) +- Added agentic-merge-upstream Claude skill for tracking upstream 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 upstream 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 upstream 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 upstream 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 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/copilot-community-sdk/copilot-sdk-java/compare/v1.0.11...HEAD +[1.0.11]: https://github.com/copilot-community-sdk/copilot-sdk-java/compare/v1.0.10...v1.0.11 +[Unreleased]: https://github.com/copilot-community-sdk/copilot-sdk-java/compare/v1.0.11...HEAD +[1.0.11]: https://github.com/copilot-community-sdk/copilot-sdk-java/compare/v1.0.10...v1.0.11 +[1.0.10]: https://github.com/copilot-community-sdk/copilot-sdk-java/compare/v1.0.9...v1.0.10 +[Unreleased]: https://github.com/copilot-community-sdk/copilot-sdk-java/compare/v1.0.11...HEAD +[1.0.11]: https://github.com/copilot-community-sdk/copilot-sdk-java/compare/v1.0.10...v1.0.11 +[1.0.10]: https://github.com/copilot-community-sdk/copilot-sdk-java/compare/v1.0.9...v1.0.10 +[1.0.9]: https://github.com/copilot-community-sdk/copilot-sdk-java/compare/v1.0.8...v1.0.9 +[1.0.8]: https://github.com/copilot-community-sdk/copilot-sdk-java/compare/v1.0.7...v1.0.8 +[1.0.7]: https://github.com/copilot-community-sdk/copilot-sdk-java/compare/v1.0.6...v1.0.7 +[1.0.6]: https://github.com/copilot-community-sdk/copilot-sdk-java/compare/v1.0.5...v1.0.6 +[1.0.5]: https://github.com/copilot-community-sdk/copilot-sdk-java/compare/v1.0.4...v1.0.5 +[1.0.4]: https://github.com/copilot-community-sdk/copilot-sdk-java/compare/v1.0.3...v1.0.4 +[1.0.3]: https://github.com/copilot-community-sdk/copilot-sdk-java/compare/v1.0.2...v1.0.3 +[1.0.2]: https://github.com/copilot-community-sdk/copilot-sdk-java/compare/v1.0.1...v1.0.2 +[1.0.1]: https://github.com/copilot-community-sdk/copilot-sdk-java/compare/1.0.0...v1.0.1 +[1.0.0]: https://github.com/copilot-community-sdk/copilot-sdk-java/releases/tag/1.0.0 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..c75895245 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,74 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery and unwelcome sexual attention or + advance +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic + address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at . All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..fe7c9ec09 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,67 @@ +## Contributing + +[fork]: https://github.com/github/copilot-sdk-java/fork +[pr]: https://github.com/github/copilot-sdk-java/compare + +Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great. + +This repository contains the **Copilot SDK for Java**, the official Java variant of the official [GitHub Copilot SDK](https://github.com/github/copilot-sdk). For issues or features related to the upstream SDK, please contribute there instead. + +Contributions to this project are [released](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license) to the public under the [project's open source license](LICENSE). + +Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms. + +## What kinds of contributions we're looking for + +We'd love your help with: + + * Fixing any bugs in the existing feature set + * Making the SDK more idiomatic and nice to use for Java developers + * Improving documentation + +If you have ideas for entirely new features, please post an issue or start a discussion. We're very open to new features but need to make sure they align with the direction of the upstream [Copilot SDK](https://github.com/github/copilot-sdk) and can be maintained in sync. Note that this repo periodically merges upstream changes β€” see the [README](README.md#agentic-upstream-merge-and-sync) for details on how that works. + +## Prerequisites for running and testing code + +1. Install [Java 17+](https://openjdk.org/) (JDK) +1. Install [Maven 3.9+](https://maven.apache.org/download.cgi) (or use the included `mvnw` wrapper) +1. Install [Node.js](https://nodejs.org/) (v18+) β€” required for the E2E test harness + +## Submitting a pull request + +1. [Fork][fork] and clone the repository +1. Enable git hooks: `git config core.hooksPath .githooks` +1. Make sure the tests pass on your machine: `mvn clean verify` +1. Make sure formatting passes: `mvn spotless:check` +1. Create a new branch: `git checkout -b my-branch-name` +1. Make your change, add tests, and make sure the tests and linter still pass +1. Push to your fork and [submit a pull request][pr] +1. Pat yourself on the back and wait for your pull request to be reviewed and merged. + +### Running tests and linters + +```bash +# Build and run all tests +mvn clean verify + +# Run a single test class +mvn test -Dtest=CopilotClientTest + +# Format code (required before commit) +mvn spotless:apply + +# Check formatting only +mvn spotless:check +``` + +Here are a few things you can do that will increase the likelihood of your pull request being accepted: + +- Write tests. +- Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests. +- Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). + +## Resources + +- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/) +- [Using Pull Requests](https://help.github.com/articles/about-pull-requests/) +- [GitHub Help](https://help.github.com) diff --git a/LICENSE b/LICENSE index 4fa9fd93f..28a50fa22 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2026 Bruno Borges and the Copilot Community SDK contributors +Copyright GitHub, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 8bd479f3a..d4b0a54ab 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,46 @@ # Copilot SDK for Java +> 🚨 **This repository is archived.** The official GitHub Copilot SDK for Java is now maintained at **[github/copilot-sdk-java](https://github.com/github/copilot-sdk-java)**. No further changes or releases will be made here. Please use the official SDK going forward. + [![Build](https://github.com/copilot-community-sdk/copilot-sdk-java/actions/workflows/build-test.yml/badge.svg)](https://github.com/copilot-community-sdk/copilot-sdk-java/actions/workflows/build-test.yml) [![Site](https://github.com/copilot-community-sdk/copilot-sdk-java/actions/workflows/deploy-site.yml/badge.svg)](https://github.com/copilot-community-sdk/copilot-sdk-java/actions/workflows/deploy-site.yml) -[![Maven Central](https://img.shields.io/maven-central/v/io.github.copilot-community-sdk/copilot-sdk)](https://central.sonatype.com/artifact/io.github.copilot-community-sdk/copilot-sdk) -[![Java 17+](https://img.shields.io/badge/Java-17%2B-blue)](https://openjdk.org/) +[![Coverage](.github/badges/jacoco.svg)](https://copilot-community-sdk.github.io/copilot-sdk-java/snapshot/jacoco/index.html) +[![Documentation](https://img.shields.io/badge/docs-online-brightgreen)](https://copilot-community-sdk.github.io/copilot-sdk-java/) +[![Java 17+](https://img.shields.io/badge/Java-17%2B-blue?logo=openjdk&logoColor=white)](https://openjdk.org/) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -> ⚠️ **Disclaimer:** This is an **unofficial, community-driven SDK** and is **not supported or endorsed by GitHub**. This SDK may change in breaking ways. Use at your own risk. +#### Latest release +[![GitHub Release Date](https://img.shields.io/github/release-date/copilot-community-sdk/copilot-sdk-java)](https://github.com/copilot-community-sdk/copilot-sdk-java/releases) +[![GitHub Release](https://img.shields.io/github/v/release/copilot-community-sdk/copilot-sdk-java)](https://github.com/copilot-community-sdk/copilot-sdk-java/releases) +[![Maven Central](https://img.shields.io/maven-central/v/io.github.copilot-community-sdk/copilot-sdk)](https://central.sonatype.com/artifact/io.github.copilot-community-sdk/copilot-sdk) +[![Documentation](https://img.shields.io/badge/docs-latest-brightgreen)](https://copilot-community-sdk.github.io/copilot-sdk-java/latest/) +[![Javadoc](https://javadoc.io/badge2/io.github.copilot-community-sdk/copilot-sdk/javadoc.svg?q=1)](https://javadoc.io/doc/io.github.copilot-community-sdk/copilot-sdk/latest/index.html) + +## Background 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 +- GitHub Copilot CLI 0.0.411-1 or later installed and in PATH (or provide custom `cliPath`) + ### Maven ```xml io.github.copilot-community-sdk copilot-sdk - 1.0.4 + 1.0.11 ``` ### Gradle ```groovy -implementation 'io.github.copilot-community-sdk:copilot-sdk:1.0.4' +implementation 'io.github.copilot-community-sdk:copilot-sdk:1.0.11' ``` ## Quick Start @@ -36,45 +51,61 @@ import com.github.copilot.sdk.events.*; import com.github.copilot.sdk.json.*; import java.util.concurrent.CompletableFuture; -public class Example { +public 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().setModel("claude-sonnet-4.5") - ).get(); - - var done = new CompletableFuture(); - session.on(evt -> { - if (evt instanceof AssistantMessageEvent msg) { - System.out.println(msg.getData().getContent()); - } else if (evt instanceof SessionIdleEvent) { - done.complete(null); - } + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL).setModel("claude-sonnet-4.5")).get(); + + // Handle assistant message events + session.on(AssistantMessageEvent.class, msg -> { + System.out.println(msg.getData().content()); + }); + + // 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: " + (int) data.currentTokens()); + System.out.println("Token limit: " + (int) data.tokenLimit()); + System.out.println("Messages count: " + (int) data.messagesLength()); }); - session.send(new MessageOptions().setPrompt("What is 2+2?")).get(); - done.get(); + // Send a message + var completable = session.sendAndWait(new MessageOptions().setPrompt("What is 2+2?")); + // and wait for completion + completable.get(); } } } ``` +## Try it with JBang + +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/copilot-community-sdk/copilot-sdk-java/blob/latest/jbang-example.java +``` + ## Documentation πŸ“š **[Full Documentation](https://copilot-community-sdk.github.io/copilot-sdk-java/)** β€” Complete API reference, advanced usage examples, and guides. ### Quick Links -- [Getting Started](https://copilot-community-sdk.github.io/copilot-sdk-java/documentation.html) -- [Javadoc API Reference](https://copilot-community-sdk.github.io/copilot-sdk-java/apidocs/) -- [MCP Servers Integration](https://copilot-community-sdk.github.io/copilot-sdk-java/mcp.html) - -## Requirements - -- Java 17 or later -- GitHub Copilot CLI installed and in PATH (or provide custom `cliPath`) +- [Getting Started](https://copilot-community-sdk.github.io/copilot-sdk-java/latest/documentation.html) +- [Javadoc API Reference](https://copilot-community-sdk.github.io/copilot-sdk-java/latest/apidocs/) +- [MCP Servers Integration](https://copilot-community-sdk.github.io/copilot-sdk-java/latest/mcp.html) +- [Cookbook](src/site/markdown/cookbook/) β€” Practical recipes for common use cases ## Projects Using This SDK @@ -84,10 +115,26 @@ public class Example { > 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 upstream 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](CONTRIBUTING.md) for details. +### Agentic Upstream 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 upstream merge process is automated with AI assistance: + +**Weekly automated sync** β€” A [scheduled GitHub Actions workflow](.github/workflows/weekly-upstream-sync.yml) runs every Monday at 5 AM ET. It checks for new upstream commits since the last merge (tracked in [`.lastmerge`](.lastmerge)), and if changes are found, creates an issue labeled `upstream-sync` and assigns it to the GitHub Copilot coding agent. Any previously open `upstream-sync` issues are automatically closed. + +**Reusable prompt** β€” The merge workflow is defined in [`agentic-merge-upstream.prompt.md`](.github/prompts/agentic-merge-upstream.prompt.md). It can be triggered manually from: +- **VS Code Copilot Chat** β€” type `/agentic-merge-upstream` +- **GitHub Copilot CLI** β€” use `copilot` CLI with the same skill reference + ### Development Setup ```bash @@ -104,6 +151,28 @@ 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](LICENSE) for details. + +## Acknowledgement + +- Initially developed with Copilot and [Bruno Borges](https://www.linkedin.com/in/brunocborges/). + +## Star History + +[![Star History Chart](https://api.star-history.com/svg?repos=copilot-community-sdk/copilot-sdk-java&type=Date)](https://www.star-history.com/#copilot-community-sdk/copilot-sdk-java&Date) + +⭐ Drop a star if you find this useful! diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..ef183e6fc --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,31 @@ +Thanks for helping make GitHub safe for everyone. + +# Security + +GitHub takes the security of our software products and services seriously, including all of the open source code repositories managed through our GitHub organizations, such as [GitHub](https://github.com/GitHub). + +Even though [open source repositories are outside of the scope of our bug bounty program](https://bounty.github.com/index.html#scope) and therefore not eligible for bounty rewards, we will ensure that your finding gets passed along to the appropriate maintainers for remediation. + +## Reporting Security Issues + +If you believe you have found a security vulnerability in any GitHub-owned repository, please report it to us through coordinated disclosure. + +**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** + +Instead, please send an email to opensource-security[@]github.com. + +Please include as much of the information listed below as you can to help us better understand and resolve the issue: + +- The type of issue (e.g., buffer overflow, SQL injection, or cross-site scripting) +- Full paths of source file(s) related to the manifestation of the issue +- The location of the affected source code (tag/branch/commit or direct URL) +- Any special configuration required to reproduce the issue +- Step-by-step instructions to reproduce the issue +- Proof-of-concept or exploit code (if possible) +- Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +## Policy + +See [GitHub's Safe Harbor Policy](https://docs.github.com/en/site-policy/security-policies/github-bug-bounty-program-legal-safe-harbor#1-safe-harbor-terms) diff --git a/SUPPORT.md b/SUPPORT.md new file mode 100644 index 000000000..980b3dce8 --- /dev/null +++ b/SUPPORT.md @@ -0,0 +1,13 @@ +# Support + +## How to file issues and get help + +This project uses GitHub issues to track bugs and feature requests. Please search the existing issues before filing new issues to avoid duplicates. For new issues, file your bug or feature request as a new issue. + +For help or questions about using this project, please file an issue. + +**Copilot SDK for Java** is under active development and maintained by the community. We will do our best to respond to support, feature requests, and community questions in a timely manner. + +## GitHub Support Policy + +Support for this project is limited to the resources listed above. diff --git a/checkstyle.xml b/config/checkstyle/checkstyle.xml similarity index 100% rename from checkstyle.xml rename to config/checkstyle/checkstyle.xml diff --git a/config/spotbugs/spotbugs-exclude.xml b/config/spotbugs/spotbugs-exclude.xml new file mode 100644 index 000000000..7f3b068cb --- /dev/null +++ b/config/spotbugs/spotbugs-exclude.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + diff --git a/docs/CUSTOM_SITE_CSS.md b/docs/CUSTOM_SITE_CSS.md new file mode 100644 index 000000000..9181118f1 --- /dev/null +++ b/docs/CUSTOM_SITE_CSS.md @@ -0,0 +1,187 @@ +# Custom Site CSS + +This project applies custom CSS themes to both the **Maven-generated documentation site** and the **JaCoCo coverage reports** so they share a consistent visual identity: light backgrounds, GitHub-inspired colors, purple gradient accents, and rounded card-style containers. + +## Architecture Overview + +``` +src/site/ +β”œβ”€β”€ resources/ +β”‚ β”œβ”€β”€ css/ +β”‚ β”‚ └── site.css ← Maven site theme (loaded automatically by Fluido) +β”‚ └── images/ +β”‚ └── github-copilot.jpg ← Copilot logo for "Powered By" sidebar +β”œβ”€β”€ jacoco-resources/ +β”‚ └── report.css ← JaCoCo report theme (overlaid after generation) +└── site.xml ← Fluido skin configuration + +.github/ +β”œβ”€β”€ templates/ +β”‚ β”œβ”€β”€ index.html ← Version picker page template +β”‚ └── styles.css ← Version picker CSS +└── workflows/ + └── deploy-site.yml ← Deploys site + overlays JaCoCo CSS +``` + +## Maven Site Theme (`src/site/resources/css/site.css`) + +### How It Works + +The Maven Fluido Skin 2.1.0 (configured in `src/site/site.xml`) automatically loads `css/site.css` from the site resources directory. No additional configuration is needed β€” placing the file at `src/site/resources/css/site.css` is sufficient. + +### Design Choices + +| Element | Style | +|---------|-------| +| **Navbar** | Dark (`#24292f`) with no gradient or border, subtle box shadow | +| **Sidebar** | White card with rounded corners (`border-radius: 10px`), 1px border | +| **Active nav item** | Purple gradient (`#667eea β†’ #764ba2`) | +| **Links** | GitHub-blue (`#0969da`), hover darkens to `#0550ae` | +| **Code blocks** | Light gray background (`#eef1f6`) to distinguish from the white page | +| **Inline code** | Tinted purple background (`rgba(102, 126, 234, 0.1)`) | +| **Tables** | Rounded borders, light header, subtle row hover | +| **Sections** | White card with border and padding (nested sections are transparent) | +| **Alerts** | Rounded with 4px left accent border, color-coded (info/warning/danger/success) | +| **Typography** | System font stack (`-apple-system, BlinkMacSystemFont, 'Segoe UI', ...`) | + +### GitHub Ribbon Override + +The Fluido skin renders a "Fork me on GitHub" ribbon using a `::after` pseudo-element with `content: attr(data-ribbon)`. We override this to say "View on GitHub" with a purple gradient background: + +```css +.github-fork-ribbon:before { + background-color: #667eea !important; + background-image: linear-gradient(135deg, #667eea, #764ba2) !important; +} + +.github-fork-ribbon:after { + content: 'View on GitHub' !important; +} +``` + +> **Note:** Both `background-color` and `background-image` with `!important` are required because Fluido injects an inline `