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/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..36881a0f5 --- /dev/null +++ b/.github/aw/actions-lock.json @@ -0,0 +1,19 @@ +{ + "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" + } + } +} diff --git a/.github/badges/jacoco.svg b/.github/badges/jacoco.svg new file mode 100644 index 000000000..388fa4643 --- /dev/null +++ b/.github/badges/jacoco.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + coverage + coverage + 91% + 91% + + diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index e3530baa8..bc28a29fb 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -36,6 +36,35 @@ mvn clean package -DskipTests 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 @@ -249,3 +278,29 @@ This SDK is designed to be **lightweight with minimal dependencies**: 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 d962373c3..8db8e8446 100644 --- a/.github/prompts/agentic-merge-upstream.prompt.md +++ b/.github/prompts/agentic-merge-upstream.prompt.md @@ -19,22 +19,22 @@ The `.github/scripts/` directory contains helper scripts that automate the repea | Script | Purpose | |---|---| -| `.github/scripts/merge-upstream-start.sh` | Creates branch, updates CLI, clones upstream, reads `.lastmerge`, prints commit summary | -| `.github/scripts/merge-upstream-diff.sh` | Detailed diff analysis grouped by area (`.NET src`, tests, snapshots, docs, etc.) | -| `.github/scripts/merge-upstream-finish.sh` | Runs format + test + build, updates `.lastmerge`, commits, pushes branch | -| `.github/scripts/format-and-test.sh` | Standalone `spotless:apply` + `mvn clean verify` (useful during porting too) | +| `.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. Run `./.github/scripts/merge-upstream-start.sh` (creates branch, clones upstream, shows summary) -2. Run `./.github/scripts/merge-upstream-diff.sh` (analyze changes) +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. Port changes to Java SDK (commit as you go) -5. Run `./.github/scripts/format-and-test.sh` frequently while porting +5. Run `./.github/scripts/build/format-and-test.sh` frequently while porting 6. Update documentation -7. Run `./.github/scripts/merge-upstream-finish.sh` (final test + push) +7. Run `./.github/scripts/upstream-sync/merge-upstream-finish.sh` (final test + push) 8. Finalize Pull Request (see note below about coding agent vs. manual workflow) --- @@ -44,7 +44,7 @@ All scripts write/read a `.merge-env` file (git-ignored) to share state (branch Run the start script to create a branch, update the CLI, clone the upstream repo, and see a summary of new commits: ```bash -./.github/scripts/merge-upstream-start.sh +./.github/scripts/upstream-sync/merge-upstream-start.sh ``` This writes a `.merge-env` file used by the other scripts. It outputs: @@ -56,8 +56,8 @@ This writes a `.merge-env` file used by the other scripts. It outputs: Then run the diff script for a detailed breakdown by area: ```bash -./.github/scripts/merge-upstream-diff.sh # stat only -./.github/scripts/merge-upstream-diff.sh --full # full diffs +./.github/scripts/upstream-sync/merge-upstream-diff.sh # stat only +./.github/scripts/upstream-sync/merge-upstream-diff.sh --full # full diffs ``` The diff script groups changes into: .NET source, .NET tests, test snapshots, documentation, protocol/config, Go/Node.js/Python SDKs, and other files. @@ -220,15 +220,15 @@ Commit tests separately or together with their corresponding implementation chan After applying changes, use the convenience script: ```bash -./.github/scripts/format-and-test.sh # format + full verify -./.github/scripts/format-and-test.sh --debug # with debug logging +./.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/format-and-test.sh --format-only # just spotless -./.github/scripts/format-and-test.sh --test-only # skip formatting +./.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 @@ -321,8 +321,8 @@ Ensure consistency across all documentation files: Run the finish script which updates `.lastmerge`, runs a final build, and pushes the branch: ```bash -./.github/scripts/merge-upstream-finish.sh # full format + test + push -./.github/scripts/merge-upstream-finish.sh --skip-tests # if tests already passed +./.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 diff --git a/.github/prompts/coding-agent-merge-instructions.md b/.github/prompts/coding-agent-merge-instructions.md index 429bb5382..1c18e1f5f 100644 --- a/.github/prompts/coding-agent-merge-instructions.md +++ b/.github/prompts/coding-agent-merge-instructions.md @@ -4,7 +4,7 @@ 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/ for initialization, diffing, formatting, and testing. +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 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/scripts/format-and-test.sh b/.github/scripts/build/format-and-test.sh similarity index 82% rename from .github/scripts/format-and-test.sh rename to .github/scripts/build/format-and-test.sh index 857e6ce36..6de3972f3 100755 --- a/.github/scripts/format-and-test.sh +++ b/.github/scripts/build/format-and-test.sh @@ -6,10 +6,10 @@ # 1. spotless:apply (auto-format code) # 2. mvn clean verify (compile + test + checkstyle + spotbugs) # -# Usage: ./.github/scripts/format-and-test.sh -# ./.github/scripts/format-and-test.sh --format-only -# ./.github/scripts/format-and-test.sh --test-only -# ./.github/scripts/format-and-test.sh --debug (uses -Pdebug) +# 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 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/create-issue-assigned-to-copilot.ts b/.github/scripts/ci/create-issue-assigned-to-copilot.ts similarity index 100% rename from .github/scripts/create-issue-assigned-to-copilot.ts rename to .github/scripts/ci/create-issue-assigned-to-copilot.ts 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/merge-upstream-diff.sh b/.github/scripts/upstream-sync/merge-upstream-diff.sh similarity index 93% rename from .github/scripts/merge-upstream-diff.sh rename to .github/scripts/upstream-sync/merge-upstream-diff.sh index 983cd7d0b..ee61b6ffc 100755 --- a/.github/scripts/merge-upstream-diff.sh +++ b/.github/scripts/upstream-sync/merge-upstream-diff.sh @@ -10,18 +10,18 @@ # • Documentation # • Protocol / config files # -# Usage: ./.github/scripts/merge-upstream-diff.sh [--full] +# 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)" +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/merge-upstream-start.sh first." + echo "❌ $ENV_FILE not found. Run ./.github/scripts/upstream-sync/merge-upstream-start.sh first." exit 1 fi diff --git a/.github/scripts/merge-upstream-finish.sh b/.github/scripts/upstream-sync/merge-upstream-finish.sh similarity index 85% rename from .github/scripts/merge-upstream-finish.sh rename to .github/scripts/upstream-sync/merge-upstream-finish.sh index 69ad51ed3..1663ef259 100755 --- a/.github/scripts/merge-upstream-finish.sh +++ b/.github/scripts/upstream-sync/merge-upstream-finish.sh @@ -8,18 +8,18 @@ # 3. Commits the .lastmerge update # 4. Pushes the branch to origin # -# Usage: ./.github/scripts/merge-upstream-finish.sh -# ./.github/scripts/merge-upstream-finish.sh --skip-tests +# 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)" +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/merge-upstream-start.sh first." + echo "❌ $ENV_FILE not found. Run ./.github/scripts/upstream-sync/merge-upstream-start.sh first." exit 1 fi @@ -40,7 +40,7 @@ if $SKIP_TESTS; then mvn clean package -DskipTests else echo "▸ Running format + test + build…" - "$ROOT_DIR/.github/scripts/format-and-test.sh" + "$ROOT_DIR/.github/scripts/build/format-and-test.sh" fi # ── 2. Update .lastmerge ───────────────────────────────────── diff --git a/.github/scripts/merge-upstream-start.sh b/.github/scripts/upstream-sync/merge-upstream-start.sh similarity index 92% rename from .github/scripts/merge-upstream-start.sh rename to .github/scripts/upstream-sync/merge-upstream-start.sh index f7b9da16f..755361cd1 100755 --- a/.github/scripts/merge-upstream-start.sh +++ b/.github/scripts/upstream-sync/merge-upstream-start.sh @@ -8,13 +8,13 @@ # 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/merge-upstream-start.sh +# 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)" +ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)" cd "$ROOT_DIR" UPSTREAM_REPO="https://github.com/github/copilot-sdk.git" @@ -83,6 +83,6 @@ EOF echo "▸ Env file written to $ENV_FILE (sourced by other merge scripts)." echo "" echo "✅ Ready. Next steps:" -echo " 1. Run ./.github/scripts/merge-upstream-diff.sh to see the full diff analysis." +echo " 1. Run ./.github/scripts/upstream-sync/merge-upstream-diff.sh to see the full diff analysis." echo " 2. Port changes to the Java SDK." -echo " 3. Run ./.github/scripts/merge-upstream-finish.sh when done." +echo " 3. Run ./.github/scripts/upstream-sync/merge-upstream-finish.sh when done." diff --git a/.github/skills/commit-as-pull-request/SKILL.md b/.github/skills/commit-as-pull-request/SKILL.md new file mode 100644 index 000000000..ebc614d7d --- /dev/null +++ b/.github/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](../../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/.github/skills/documentation-coverage/SKILL.md b/.github/skills/documentation-coverage/SKILL.md index 34eb72c02..7a02f2f8c 100644 --- a/.github/skills/documentation-coverage/SKILL.md +++ b/.github/skills/documentation-coverage/SKILL.md @@ -1,4 +1,3 @@ -```skill --- name: documentation-coverage description: Assess whether the documentation in src/site/markdown/ adequately covers the Java SDK functionality. @@ -6,4 +5,3 @@ license: MIT --- Follow instructions in the [documentation-coverage prompt](../../prompts/documentation-coverage.prompt.md) to analyze gaps between the SDK's implemented features and its documentation. -``` diff --git a/.github/templates/index.html b/.github/templates/index.html new file mode 100644 index 000000000..deda683ad --- /dev/null +++ b/.github/templates/index.html @@ -0,0 +1,86 @@ + + + + + + + + + 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/build-test.yml b/.github/workflows/build-test.yml index 2ed3ad968..5749900f6 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -2,39 +2,24 @@ name: "Build & Test" 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/prompts/**' - - '.github/skills/**' - - '.github/scripts/**' - - '.github/templates/**' - - '.github/dependabot.yml' - - '.github/release.yml' - - '.github/workflows/publish-maven.yml' - - '.github/workflows/weekly-upstream-sync.yml' - - '.github/workflows/copilot-issue.yml' + - '.github/**' pull_request: paths-ignore: - 'README.md' - 'LICENSE' - - '.github/prompts/**' - - '.github/skills/**' - - '.github/scripts/**' - - '.github/templates/**' - - '.github/dependabot.yml' - - '.github/release.yml' - - '.github/workflows/publish-maven.yml' - - '.github/workflows/weekly-upstream-sync.yml' + - '.github/**' workflow_dispatch: merge_group: permissions: - contents: read + contents: write checks: write jobs: @@ -101,6 +86,20 @@ jobs: 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-issue.yml b/.github/workflows/copilot-issue.yml deleted file mode 100644 index 51ee70395..000000000 --- a/.github/workflows/copilot-issue.yml +++ /dev/null @@ -1,69 +0,0 @@ -name: Create Issue for Copilot Coding Agent - -on: - workflow_dispatch: - inputs: - title: - description: 'Issue title' - required: false - type: string - push: - branches: [main] - paths: - - '.github/workflows/copilot-issue.yml' - - '.github/scripts/create-issue-assigned-to-copilot.ts' - -jobs: - create-issue: - runs-on: ubuntu-latest - permissions: - issues: write - steps: - - name: Create issue and assign to Copilot - env: - GH_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - run: | - TITLE="${{ inputs.title || 'Investigation needed from Copilot Coding Agent' }}" - gh api \ - --method POST \ - -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - /repos/${{ github.repository }}/issues \ - --input - <<< "$(jq -n \ - --arg title "$TITLE" \ - --arg repo "${{ github.repository }}" \ - '{ - title: $title, - agent_assignment: { - target_repo: $repo, - base_branch: "main", - custom_instructions: "", - custom_agent: "", - model: "" - } - }')" - - create-issue-graphql: - runs-on: ubuntu-latest - permissions: - issues: write - steps: - - name: Checkout repository - uses: actions/checkout@v6 - - - name: Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: '22' - - - name: Install dependencies - run: npm install @octokit/rest tsx - working-directory: .github/scripts - - - name: Create issue via GraphQL - env: - GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - GITHUB_REPO_OWNER: ${{ github.repository_owner }} - GITHUB_REPO_NAME: ${{ github.event.repository.name }} - run: npx tsx create-issue-assigned-to-copilot.ts "${{ inputs.title || 'Investigation needed from Copilot Coding Agent' }}" - working-directory: .github/scripts diff --git a/.github/workflows/copilot-setup-steps.txt b/.github/workflows/copilot-setup-steps.yml similarity index 59% rename from .github/workflows/copilot-setup-steps.txt rename to .github/workflows/copilot-setup-steps.yml index f92d7f699..8d2964558 100644 --- a/.github/workflows/copilot-setup-steps.txt +++ b/.github/workflows/copilot-setup-steps.yml @@ -1,31 +1,39 @@ 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 - pull_request: - 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 - issues: write - pull-requests: write steps: - - name: Checkout code + # 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: @@ -33,17 +41,6 @@ jobs: distribution: 'temurin' cache: 'maven' - - name: Verify Java version - run: java -version - - - name: Download dependencies - run: mvn dependency:go-offline -B - - # Install gh-aw extension for advanced GitHub CLI features - - name: Install gh-aw extension - run: | - curl -fsSL https://raw.githubusercontent.com/githubnext/gh-aw/refs/heads/main/install-gh-aw.sh | bash - # Verify installations - name: Verify tool installations run: | diff --git a/.github/workflows/deploy-site.yml b/.github/workflows/deploy-site.yml index 3bf29870b..7c0c42903 100644 --- a/.github/workflows/deploy-site.yml +++ b/.github/workflows/deploy-site.yml @@ -1,6 +1,12 @@ # 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 after Build & Test succeeds on main (publishes to /snapshot/) workflow_run: @@ -209,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") @@ -226,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 @@ -245,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 bc31ecedc..ea5ee4975 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: @@ -97,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 @@ -104,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 @@ -184,6 +201,14 @@ 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] diff --git a/.github/workflows/weekly-upstream-sync.lock.yml b/.github/workflows/weekly-upstream-sync.lock.yml new file mode 100644 index 000000000..163600e1e --- /dev/null +++ b/.github/workflows/weekly-upstream-sync.lock.yml @@ -0,0 +1,1187 @@ +# +# ___ _ _ +# / _ \ | | (_) +# | |_| | __ _ ___ _ __ | |_ _ ___ +# | _ |/ _` |/ _ \ '_ \| __| |/ __| +# | | | | (_| | __/ | | | |_| | (__ +# \_| |_/\__, |\___|_| |_|\__|_|\___| +# __/ | +# _ _ |___/ +# | | | | / _| | +# | | | | ___ _ __ _ __| |_| | _____ ____ +# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| +# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ +# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ +# +# This file was automatically generated by gh-aw (v0.43.2). DO NOT EDIT. +# +# To update this file, edit the corresponding .md file and run: +# gh aw compile +# For more information: https://github.com/github/gh-aw/blob/main/.github/aw/github-agentic-workflows.md +# +# Weekly upstream sync workflow. Checks for new commits in the official +# Copilot SDK (github/copilot-sdk) and assigns to Copilot to port changes. +# +# frontmatter-hash: 27b0f4284357c75008ce6e8d1d772fe9871a3548be6a6a4996125b6ca980cc2e + +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: "" + steps: + - name: Setup Scripts + uses: github/gh-aw/actions/setup@v0.43.2 + with: + destination: /opt/gh-aw/actions + - 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(); + + 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 + outputs: + checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} + has_patch: ${{ steps.collect_output.outputs.has_patch }} + model: ${{ steps.generate_aw_info.outputs.model }} + output: ${{ steps.collect_output.outputs.output }} + output_types: ${{ steps.collect_output.outputs.output_types }} + secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} + steps: + - name: Setup Scripts + uses: github/gh-aw/actions/setup@v0.43.2 + with: + destination: /opt/gh-aw/actions + - name: Checkout .github and .agents folders + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + sparse-checkout: | + .github + .agents + fetch-depth: 1 + 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]" + # 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 + 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: Generate agentic run info + id: generate_aw_info + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const fs = require('fs'); + + const awInfo = { + engine_id: "copilot", + engine_name: "GitHub Copilot CLI", + model: process.env.GH_AW_MODEL_AGENT_COPILOT || "", + version: "", + agent_version: "0.0.405", + cli_version: "v0.43.2", + workflow_name: "Weekly Upstream Sync Agentic Workflow", + experimental: false, + supports_tools_allowlist: true, + supports_http_transport: true, + run_id: context.runId, + run_number: context.runNumber, + run_attempt: process.env.GITHUB_RUN_ATTEMPT, + repository: context.repo.owner + '/' + context.repo.repo, + ref: context.ref, + sha: context.sha, + actor: context.actor, + event_name: context.eventName, + staged: false, + allowed_domains: ["defaults","github"], + firewall_enabled: true, + awf_version: "v0.13.12", + awmg_version: "", + steps: { + firewall: "squid" + }, + created_at: new Date().toISOString() + }; + + // Write to /tmp/gh-aw directory to avoid inclusion in PR + const tmpPath = '/tmp/gh-aw/aw_info.json'; + fs.writeFileSync(tmpPath, JSON.stringify(awInfo, null, 2)); + console.log('Generated aw_info.json at:', tmpPath); + console.log(JSON.stringify(awInfo, null, 2)); + + // Set model as output for reuse in other steps/jobs + core.setOutput('model', awInfo.model); + - 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: Install GitHub Copilot CLI + run: /opt/gh-aw/actions/install_copilot_cli.sh 0.0.405 + - name: Install awf binary + run: bash /opt/gh-aw/actions/install_awf_binary.sh v0.13.12 + - name: Determine automatic lockdown mode for GitHub MCP server + id: determine-automatic-lockdown + env: + TOKEN_CHECK: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + if: env.TOKEN_CHECK != '' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + 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.13.12 ghcr.io/github/gh-aw-firewall/squid:0.13.12 ghcr.io/github/gh-aw-mcpg:v0.0.113 ghcr.io/github/github-mcp-server:v0.30.3 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 << 'EOF' + {"add_comment":{"max":10,"target":"*"},"assign_to_agent":{"default_agent":"copilot","target":"*"},"close_issue":{"max":10,"required_labels":["upstream-sync"]},"create_issue":{"expires":144,"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1}} + EOF + cat > /opt/gh-aw/safeoutputs/tools.json << '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_abc123def456') 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 12 hex characters (e.g., 'aw_abc123def456'). Use '#aw_ID' in body text to reference other issues by their temporary_id; these are replaced with actual issue numbers after creation.", + "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. Use this 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. 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. 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.", + "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 will attempt to resolve the target from the current workflow context (triggering issue, PR, or discussion).", + "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\")", + "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 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_abc123def456') 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 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" + ] + } + }, + "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" + } + ] + EOF + cat > /opt/gh-aw/safeoutputs/validation.json << 'EOF' + { + "add_comment": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "item_number": { + "issueOrPRNumber": true + } + } + }, + "assign_to_agent": { + "defaultMax": 1, + "fields": { + "agent": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, + "issue_number": { + "issueNumberOrTemporaryId": true + }, + "pull_number": { + "optionalPositiveInteger": true + } + }, + "customValidation": "requiresOneOf:issue_number,pull_number" + }, + "close_issue": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "issue_number": { + "optionalPositiveInteger": true + } + } + }, + "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_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 + } + } + } + } + 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 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 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.0.113' + + mkdir -p /home/runner/.copilot + cat << MCPCONFIG_EOF | bash /opt/gh-aw/actions/start_mcp_gateway.sh + { + "mcpServers": { + "github": { + "type": "stdio", + "container": "ghcr.io/github/github-mcp-server:v0.30.3", + "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}" + } + } + MCPCONFIG_EOF + - name: Generate workflow overview + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { generateWorkflowOverview } = require('/opt/gh-aw/actions/generate_workflow_overview.cjs'); + await generateWorkflowOverview(core); + - 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 << 'PROMPT_EOF' > "$GH_AW_PROMPT" + + PROMPT_EOF + cat "/opt/gh-aw/prompts/temp_folder_prompt.md" >> "$GH_AW_PROMPT" + cat "/opt/gh-aw/prompts/markdown.md" >> "$GH_AW_PROMPT" + cat << 'PROMPT_EOF' >> "$GH_AW_PROMPT" + + GitHub API Access Instructions + + The gh CLI is NOT authenticated. Do NOT use gh commands for GitHub operations. + + + To create or modify GitHub resources (issues, discussions, pull requests, etc.), you MUST call the appropriate safe output tool. Simply writing content will NOT work - the workflow requires actual tool calls. + + Discover available tools from the safeoutputs MCP server. + + **Critical**: Tool calls write structured data that downstream jobs process. Without tool calls, follow-up actions will be skipped. + + **Note**: If you made no other safe output tool calls during this workflow execution, call the "noop" tool to provide a status message indicating completion or that no actions were needed. + + + + 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}} + + + PROMPT_EOF + cat << 'PROMPT_EOF' >> "$GH_AW_PROMPT" + + PROMPT_EOF + cat << 'PROMPT_EOF' >> "$GH_AW_PROMPT" + {{#runtime-import .github/workflows/weekly-upstream-sync.md}} + PROMPT_EOF + - 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 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: 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: 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: 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 + sudo -E awf --enable-chroot --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.13.12 --skip-pull \ + -- '/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 --share /tmp/gh-aw/sandbox/agent/logs/conversation.md --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"${GH_AW_MODEL_AGENT_COPILOT:+ --model "$GH_AW_MODEL_AGENT_COPILOT"}' \ + 2>&1 | tee /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_HEAD_REF: ${{ github.head_ref }} + GITHUB_REF_NAME: ${{ github.ref_name }} + 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]" + # 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@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + with: + name: safe-output + path: ${{ env.GH_AW_SAFE_OUTPUTS }} + if-no-files-found: warn + - name: Ingest agent output + id: collect_output + 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@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + with: + name: agent-output + path: ${{ env.GH_AW_AGENT_OUTPUT }} + if-no-files-found: warn + - name: Upload engine output files + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + 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 + awf logs summary | tee -a "$GITHUB_STEP_SUMMARY" + - name: Upload agent artifacts + if: always() + continue-on-error: true + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + with: + name: agent-artifacts + path: | + /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/aw_info.json + /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 + + conclusion: + needs: + - activation + - agent + - detection + - 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@v0.43.2 + with: + destination: /opt/gh-aw/actions + - name: Debug job inputs + env: + COMMENT_ID: ${{ needs.activation.outputs.comment_id }} + COMMENT_REPO: ${{ needs.activation.outputs.comment_repo }} + AGENT_OUTPUT_TYPES: ${{ needs.agent.outputs.output_types }} + AGENT_CONCLUSION: ${{ needs.agent.result }} + run: | + echo "Comment ID: $COMMENT_ID" + echo "Comment Repo: $COMMENT_REPO" + echo "Agent Output Types: $AGENT_OUTPUT_TYPES" + echo "Agent Conclusion: $AGENT_CONCLUSION" + - name: Download agent output artifact + continue-on-error: true + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + 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.agent.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 }} + 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(); + - name: Update reaction comment with completion status + id: conclusion + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} + GH_AW_COMMENT_REPO: ${{ needs.activation.outputs.comment_repo }} + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_WORKFLOW_NAME: "Weekly Upstream Sync Agentic Workflow" + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.result }} + 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/notify_comment_error.cjs'); + await main(); + + detection: + needs: agent + if: needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true' + runs-on: ubuntu-latest + permissions: {} + concurrency: + group: "gh-aw-copilot-${{ github.workflow }}" + timeout-minutes: 10 + outputs: + success: ${{ steps.parse_results.outputs.success }} + steps: + - name: Setup Scripts + uses: github/gh-aw/actions/setup@v0.43.2 + with: + destination: /opt/gh-aw/actions + - name: Download agent artifacts + continue-on-error: true + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + with: + name: agent-artifacts + path: /tmp/gh-aw/threat-detection/ + - name: Download agent output artifact + continue-on-error: true + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + with: + name: agent-output + path: /tmp/gh-aw/threat-detection/ + - name: Echo agent output types + env: + AGENT_OUTPUT_TYPES: ${{ needs.agent.outputs.output_types }} + run: | + echo "Agent output-types: $AGENT_OUTPUT_TYPES" + - name: Setup threat detection + 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: ${{ needs.agent.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 + run: | + mkdir -p /tmp/gh-aw/threat-detection + touch /tmp/gh-aw/threat-detection/detection.log + - 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: Install GitHub Copilot CLI + run: /opt/gh-aw/actions/install_copilot_cli.sh 0.0.405 + - name: Execute GitHub Copilot CLI + id: 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 + COPILOT_CLI_INSTRUCTION="$(cat /tmp/gh-aw/aw-prompts/prompt.txt)" + mkdir -p /tmp/ + mkdir -p /tmp/gh-aw/ + mkdir -p /tmp/gh-aw/agent/ + mkdir -p /tmp/gh-aw/sandbox/agent/logs/ + copilot --add-dir /tmp/ --add-dir /tmp/gh-aw/ --add-dir /tmp/gh-aw/agent/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --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)' --share /tmp/gh-aw/sandbox/agent/logs/conversation.md --prompt "$COPILOT_CLI_INSTRUCTION"${GH_AW_MODEL_DETECTION_COPILOT:+ --model "$GH_AW_MODEL_DETECTION_COPILOT"} 2>&1 | tee /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_HEAD_REF: ${{ github.head_ref }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_STEP_SUMMARY: ${{ env.GITHUB_STEP_SUMMARY }} + GITHUB_WORKSPACE: ${{ github.workspace }} + XDG_CONFIG_HOME: /home/runner + - name: Parse threat detection results + id: parse_results + 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() + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + with: + name: threat-detection.log + path: /tmp/gh-aw/threat-detection/detection.log + if-no-files-found: ignore + + safe_outputs: + needs: + - agent + - detection + if: ((!cancelled()) && (needs.agent.result != 'skipped')) && (needs.detection.outputs.success == 'true') + runs-on: ubuntu-slim + permissions: + contents: read + discussions: write + issues: write + pull-requests: write + timeout-minutes: 15 + env: + 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 }} + create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }} + create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }} + 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@v0.43.2 + with: + destination: /opt/gh-aw/actions + - name: Download agent output artifact + continue-on-error: true + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + 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_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_DEFAULT: "copilot" + 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(); + diff --git a/.github/workflows/weekly-upstream-sync.md b/.github/workflows/weekly-upstream-sync.md new file mode 100644 index 000000000..31a445df4 --- /dev/null +++ b/.github/workflows/weekly-upstream-sync.md @@ -0,0 +1,150 @@ +--- +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" + 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 index 37af04c23..f1549c5bb 100644 --- a/.github/workflows/weekly-upstream-sync.yml +++ b/.github/workflows/weekly-upstream-sync.yml @@ -11,6 +11,11 @@ permissions: 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: @@ -33,6 +38,8 @@ jobs: 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" @@ -42,7 +49,6 @@ jobs: echo "has_changes=true" >> "$GITHUB_OUTPUT" echo "commit_count=$COMMIT_COUNT" >> "$GITHUB_OUTPUT" echo "upstream_head=$UPSTREAM_HEAD" >> "$GITHUB_OUTPUT" - echo "last_merge=$LAST_MERGE" >> "$GITHUB_OUTPUT" # Generate a short summary of changes SUMMARY=$(git log --oneline "$LAST_MERGE".."$UPSTREAM_HEAD" | head -20) @@ -55,8 +61,6 @@ jobs: - name: Close previous upstream-sync issues if: steps.check.outputs.has_changes == 'true' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | # Find all open issues with the upstream-sync label OPEN_ISSUES=$(gh issue list \ @@ -76,10 +80,29 @@ jobs: --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' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | COMMIT_COUNT="${{ steps.check.outputs.commit_count }}" LAST_MERGE="${{ steps.check.outputs.last_merge }}" @@ -105,28 +128,16 @@ jobs: Follow the [agentic-merge-upstream](.github/prompts/agentic-merge-upstream.prompt.md) prompt to port these changes to the Java SDK." - # Build the issue JSON payload - ISSUE_PAYLOAD=$(jq -n \ - --arg title "Upstream sync: ${COMMIT_COUNT} new commits (${DATE})" \ - --arg body "$BODY" \ - '{ - title: $title, - body: $body, - labels: ["upstream-sync"] - }') - - # Create the issue - ISSUE_NUMBER=$(echo "$ISSUE_PAYLOAD" | \ - gh api \ - --method POST \ - -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - "/repos/${REPO}/issues" \ - --input - \ - --jq '.number') - - echo "Created issue #${ISSUE_NUMBER}" - echo "✅ Issue #${ISSUE_NUMBER} created and assigned to Copilot coding agent." + # 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() @@ -135,6 +146,7 @@ jobs: 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" @@ -148,7 +160,8 @@ jobs: 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 "An issue has been created and assigned to the Copilot coding agent: " + echo " -> ${ISSUE_URL}" echo "" echo "### Recent upstream commits" echo "" diff --git a/.lastmerge b/.lastmerge index 64a7ea328..6d9098bab 100644 --- a/.lastmerge +++ b/.lastmerge @@ -1 +1 @@ -05e3c46c8c23130c9c064dc43d00ec78f7a75eab +e40d57c86e18b495722adbf42045288c03924342 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 index 061d444e0..85dfb5748 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,5 +5,6 @@ "-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 index b195a7484..649255c77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,85 @@ 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/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +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@e40d57c`](https://github.com/github/copilot-sdk/commit/e40d57c86e18b495722adbf42045288c03924342) + +## [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 @@ -302,6 +376,9 @@ New types: `GetForegroundSessionResponse`, `SetForegroundSessionResponse` - Pre-commit hook for Spotless code formatting - Comprehensive API documentation +[Unreleased]: https://github.com/copilot-community-sdk/copilot-sdk-java/compare/v1.0.9...HEAD +[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 diff --git a/README.md b/README.md index b79b39c3e..e50b13b42 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,17 @@ [![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) +[![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)](https://openjdk.org/) +[![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) #### 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-online-brightgreen)](https://copilot-community-sdk.github.io/copilot-sdk-java/1.0.7/) -[![Javadoc](https://javadoc.io/badge2/io.github.copilot-community-sdk/copilot-sdk/javadoc.svg)](https://javadoc.io/doc/io.github.copilot-community-sdk/copilot-sdk/latest/index.html) +[![Documentation](https://img.shields.io/badge/docs-online-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) ## Overview @@ -23,7 +25,7 @@ Java SDK for programmatic control of GitHub Copilot CLI, enabling you to build A ### Requirements - Java 17 or later -- GitHub Copilot CLI 0.0.406 or later installed and in PATH (or provide custom `cliPath`) +- GitHub Copilot CLI 0.0.409 or later installed and in PATH (or provide custom `cliPath`) ### Maven @@ -31,14 +33,14 @@ Java SDK for programmatic control of GitHub Copilot CLI, enabling you to build A io.github.copilot-community-sdk copilot-sdk - 1.0.8 + 1.0.9 ``` ### Gradle ```groovy -implementation 'io.github.copilot-community-sdk:copilot-sdk:1.0.8' +implementation 'io.github.copilot-community-sdk:copilot-sdk:1.0.9' ``` ## Quick Start @@ -82,6 +84,18 @@ public class CopilotSDK { } ``` +## 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. @@ -91,6 +105,7 @@ public class CopilotSDK { - [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) +- [Cookbook](src/site/markdown/cookbook/) — Practical recipes for common use cases ## Projects Using This SDK @@ -100,6 +115,12 @@ public class CopilotSDK { > 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. @@ -114,6 +135,27 @@ This SDK tracks the official [Copilot SDK](https://github.com/github/copilot-sdk - **VS Code Copilot Chat** — type `/agentic-merge-upstream` - **GitHub Copilot CLI** — use `copilot` CLI with the same skill reference +### Automated Test Coverage Improvement + +The repository includes an [Agentic Workflow](https://github.com/github/gh-aw) that automatically identifies untested code and creates comprehensive tests to increase coverage. The workflow is triggered manually: + +**To run the workflow:** +1. Navigate to the repository on GitHub +2. Go to **Actions** → **Increase Test Coverage** workflow +3. Click "Run workflow" +4. Optionally specify: + - `target_package` — Focus on a specific package (e.g., `com.github.copilot.sdk.json`) + - `min_coverage_threshold` — Target coverage percentage (default: 80%) + +**What it does:** +- Analyzes current test coverage using JaCoCo +- Identifies methods, branches, and edge cases lacking tests +- Creates high-quality tests following repository patterns +- Groups related tests logically +- Creates a PR for human review with coverage metrics + +The workflow follows all repository conventions (Spotless formatting, existing test patterns, E2E test infrastructure) and ensures no production code is modified unnecessarily. + ### Development Setup ```bash @@ -138,4 +180,4 @@ MIT — see [LICENSE](LICENSE) for details. [![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! \ No newline at end of file +⭐ Drop a star if you find this useful! 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/spotbugs-exclude.xml b/config/spotbugs/spotbugs-exclude.xml similarity index 100% rename from spotbugs-exclude.xml rename to config/spotbugs/spotbugs-exclude.xml 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 `