From d56c5d1e5495c83e2752a79d78e1f10ca95f278b Mon Sep 17 00:00:00 2001 From: Soham Banerjee Date: Sun, 22 Mar 2026 22:38:53 +0530 Subject: [PATCH 01/12] feat: add automated Intel build and release workflow Add GitHub Actions workflow to automatically build and release Intel builds: - Daily cron job checks for new Codex.dmg versions - Downloads and verifies latest DMG from OpenAI - Builds Intel (x64) version using make build - Creates tagged releases with version extraction - Uploads Codex.app.zip asset to GitHub Includes helper scripts: - scripts/extract-app-version.sh: Extracts version from Codex.app - scripts/get-last-dmg-hash.sh: Gets DMG hash from last release Updates README.md with automated release documentation including: - How the automated system works - Instructions for end users to download releases - Benefits and troubleshooting information - Retains manual build instructions as fallback Closes #2 Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/automated-release.yml | 218 ++++++++++++++++++++++++ README.md | 50 ++++++ scripts/extract-app-version.sh | 63 +++++++ scripts/get-last-dmg-hash.sh | 41 +++++ 4 files changed, 372 insertions(+) create mode 100644 .github/workflows/automated-release.yml create mode 100755 scripts/extract-app-version.sh create mode 100755 scripts/get-last-dmg-hash.sh diff --git a/.github/workflows/automated-release.yml b/.github/workflows/automated-release.yml new file mode 100644 index 0000000..eb2dac0 --- /dev/null +++ b/.github/workflows/automated-release.yml @@ -0,0 +1,218 @@ +name: Automated Intel Release + +# Controls when the workflow will run +on: + schedule: + # Run daily at midnight UTC + - cron: '0 0 * * *' + # Allow manual trigger + workflow_dispatch: + +# Permissions needed for creating releases +permissions: + contents: write + actions: write + +jobs: + check-and-build: + # Don't run on forks - only main repository + if: github.repository == 'soham2008xyz/codex-rebuilder' + runs-on: macos-15-intel + + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Need full history for tag checking + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'npm' + + - name: Cache Codex CLI + id: cache-codex-cli + uses: actions/cache@v4 + with: + path: ~/.npm + key: npm-cache-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }} + + - name: Install Codex CLI + run: | + echo "Installing/Updating @openai/codex CLI..." + npm install -g @openai/codex + + - name: Get Last Deployed DMG Hash + id: get-last-hash + run: | + # Use helper script to get last hash + LAST_DMG_HASH=$(./scripts/get-last-dmg-hash.sh) + echo "Found last DMG hash: $LAST_DMG_HASH" + echo "last_dmg_hash=$LAST_DMG_HASH" >> $GITHUB_OUTPUT + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Cache Codex.dmg + id: cache-dmg + uses: actions/cache@v4 + with: + path: ~/Codex.dmg + key: dmg-cache-${{ hashFiles('~/Codex.dmg') }} + + - name: Download Latest Codex.dmg + id: download + run: | + echo "Downloading latest Codex.dmg..." + curl -L -o ~/Codex.dmg "https://persistent.oaistatic.com/codex-app-prod/Codex.dmg" + + # Calculate SHA256 hash + DMG_HASH=$(shasum -a 256 ~/Codex.dmg | cut -d' ' -f1) + echo "Downloaded DMG SHA256: $DMG_HASH" + echo "dmg_hash=$DMG_HASH" >> $GITHUB_OUTPUT + + - name: Check If New Version + id: check-version + run: | + LAST_HASH="${{ steps.get-last-hash.outputs.last_dmg_hash }}" + CURRENT_HASH="${{ steps.download.outputs.dmg_hash }}" + + echo "Last deployed hash: $LAST_HASH" + echo "Current hash: $CURRENT_HASH" + + if [[ "$LAST_HASH" == "none" ]] || [[ "$LAST_HASH" != "$CURRENT_HASH" ]]; then + echo "New version detected or first run. Proceeding with build." + echo "is_new=true" >> $GITHUB_OUTPUT + else + echo "No changes detected. Skipping build." + echo "is_new=false" >> $GITHUB_OUTPUT + fi + + - name: Setup Build Environment + if: steps.check-version.outputs.is_new == 'true' + run: | + echo "Setting up build environment..." + # Copy cached/downloaded DMG to build directory + cp ~/Codex.dmg ./Codex.dmg + + - name: Build Intel Release + id: build + if: steps.check-version.outputs.is_new == 'true' + run: | + echo "Starting Intel build..." + make build + + if [[ ! -d "Codex.app" ]]; then + echo "Build failed - Codex.app not found" + exit 1 + fi + + - name: Extract Version Information + id: extract-version + if: steps.check-version.outputs.is_new == 'true' + run: | + # Mount the DMG to extract version info + hdiutil attach -noverify -nobrowse -mountpoint /Volumes/CodexTemp ./Codex.dmg + + # Extract version from Info.plist + CODEX_VERSION=$(defaults read /Volumes/CodexTemp/Codex.app/Contents/Info.plist CFBundleShortVersionString 2>/dev/null || echo "unknown") + ELECTRON_VERSION=$(defaults read /Volume/CodexTemp/Codex.app/Contents/Info.plist CFBundleExecutable 2>/dev/null || echo "unknown") + + hdiutil detach /Volumes/CodexTemp + + # Clean version string + CODEX_VERSION=${CODEX_VERSION// /-} + CODEX_VERSION="${CODEX_VERSION//[^a-zA-Z0-9.-]/}" + + if [[ "$CODEX_VERSION" == "unknown" ]] || [[ -z "$CODEX_VERSION" ]]; then + # Fallback to date-based version + CODEX_VERSION=$(date +"%Y.%m.%d") + fi + + echo "Codex version: $CODEX_VERSION" + echo "version=$CODEX_VERSION" >> $GITHUB_OUTPUT + + - name: Create Release + id: create-release + if: steps.check-version.outputs.is_new == 'true' + run: | + VERSION="${{ steps.extract-version.outputs.version }}" + DMG_HASH="${{ steps.download.outputs.dmg_hash }}" + + # Clean version for tag + TAG_VERSION="v${VERSION}-intel" + echo "Creating release: $TAG_VERSION" + + # Create release notes + RELEASE_BODY=""" + ## Codex Intel Build v${VERSION} + + This is an Intel (x64) build of the official Codex Desktop App. + + ### Build Information + - **Source**: OpenAI Codex DMG + - **DMG SHA256**: ${DMG_HASH} + - **Architecture**: Intel x64 + - **Build Date**: $(date -u +"%Y-%m-%d %H:%M UTC") + + ### Notes + - Auto-generated from automated workflow + - Built on macOS Intel runner + - Includes `--no-sandbox` wrapper for tool compatibility + + ### Installation + + 1. Download the **Codex.app.zip** asset below + 2. Extract the zip file + 3. Move `Codex.app` to `/Applications/` + 4. If you see "App is damaged": + ```bash + xattr -cr /Applications/Codex.app + ``` + + ### Troubleshooting + + See [README.md](https://github.com/soham2008xyz/codex-rebuilder) for troubleshooting guides. + """ + + # Create release + gh release create "$TAG_VERSION" \ + --title "Intel Build - v${VERSION}" \ + --notes "$RELEASE_BODY" \ + --verify-tag=false + + - name: Upload Release Asset + if: steps.check-version.outputs.is_new == 'true' + run: | + TAG_VERSION="v${{ steps.extract-version.outputs.version }}-intel" + + # Zip the app + echo "Creating zip archive..." + zip -r Codex.app.zip Codex.app -x "*.DS_Store" -x "*/.git*" + + # Upload to release + echo "Uploading release asset..." + gh release upload "$TAG_VERSION" Codex.app.zip#Codex.app.zip \ + --clobber + + - name: Generate Summary + run: | + if [[ "${{ steps.check-version.outputs.is_new }}" == "true" ]]; then + echo "## ✅ New Intel Release Created" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Release**: [${{ steps.extract-version.outputs.version }}-intel](https://github.com/soham2008xyz/codex-rebuilder/releases/tag/v${{ steps.extract-version.outputs.version }}-intel)" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**DMG Hash**: ${{ steps.download.outputs.dmg_hash }}" >> $GITHUB_STEP_SUMMARY + else + echo "## â„šī¸ No new version detected" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "The current Codex DMG matches the last deployed version. No new build was created." >> $GITHUB_STEP_SUMMARY + fi + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Cleanup + if: steps.check-version.outputs.is_new == 'true' + run: | + rm -f ~/Codex.dmg ./Codex.dmg Codex.app.zip + make clean || true diff --git a/README.md b/README.md index bcf9d17..c9c7ed9 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,56 @@ To update: 3. If the Codex CLI also updated, run `npm update -g @openai/codex`. 4. Run `node rebuild_codex.js --clean` to ensure a fresh build with the new files. +## Automated Releases (GitHub Actions) 🤖 + +This repository includes an **automated build system** that creates new Intel releases automatically when OpenAI releases a new version. + +### How It Works + +A GitHub Actions workflow runs daily to: +- Check for new official Codex.dmg releases +- Download and verify the new DMG +- Build an Intel (x64) version using the existing process +- Create a tagged GitHub release with the built app + +### Using Automated Releases + +Instead of building manually, you can now **watch for releases** and download directly: + +**For End Users:** + +1. Go to the [Releases page](https://github.com/soham2008xyz/codex-rebuilder/releases) +2. Download the **Codex.app.zip** from the latest release (tagged `vX.Y.Z-intel`) +3. Extract and move `Codex.app` to `/Applications/` +4. Remove quarantine attributes if needed: + ```bash + xattr -cr /Applications/Codex.app + ``` +5. Launch Codex from Applications + +**Benefits:** +- ✅ No need to build locally +- ✅ Get updates within hours of official release +- ✅ Consistent, reproducible builds +- ✅ Direct download from GitHub (fast and reliable) + +**Manual Builds Still Supported:** + +You can still build manually if you prefer: +- Fork the repo and customize the build +- Use `bash build.sh` locally +- Follow manual update instructions below + +## Updates + +**Note:** This is a manual port. Auto-updates will **not** work. + +To update: +1. Download the new `Codex.dmg` from OpenAI. +2. Replace the old `Codex.dmg` in this folder. +3. If the Codex CLI also updated, run `npm update -g @openai/codex`. +4. Run `node rebuild_codex.js --clean` to ensure a fresh build with the new files. + ## Security Note The built app launches with the `--no-sandbox` Electron flag via a wrapper script at `Contents/MacOS/Codex`. This disables Chromium's internal process sandbox, which is necessary to allow tools like **Playwright** to spawn browser subprocesses from within the integrated terminal. diff --git a/scripts/extract-app-version.sh b/scripts/extract-app-version.sh new file mode 100755 index 0000000..22d3c3b --- /dev/null +++ b/scripts/extract-app-version.sh @@ -0,0 +1,63 @@ +#!/bin/bash +# +# Script: extract-app-version.sh +# Description: Extracts version information from Codex.app +# +# Usage: ./scripts/extract-app-version.sh +# +# Requirements: macOS, hdiutil, defaults +# +# Output: JSON with version information +# Example: {"version": "0.8.0", "electron": "40.0.0", "build_date": "2025-03-22"} +# + +set -e + +DMG_PATH="${1:-Codex.dmg}" + +if [[ ! -f "$DMG_PATH" ]]; then + echo "Error: DMG file not found at: $DMG_PATH" >&2 + exit 1 +fi + +# Create temporary mount point +MOUNT_POINT="/tmp/codex_extract_$$" +mkdir -p "$MOUNT_POINT" + +# Mount the DMG (noverify for speed, nobrowse to avoid Finder) +hdiutil attach -noverify -nobrowse -mountpoint "$MOUNT_POINT" "$DMG_PATH" > /dev/null 2>&1 + +# Check if mount was successful +if [[ ! -d "$MOUNT_POINT/Codex.app" ]]; then + echo "Error: Failed to mount DMG or Codex.app not found" >&2 + hdiutil detach "$MOUNT_POINT" > /dev/null 2>&1 || true + rmdir "$MOUNT_POINT" 2>/dev/null || true + exit 1 +fi + +# Extract version from Info.plist +CODEX_VERSION=$(defaults read "$MOUNT_POINT/Codex.app/Contents/Info.plist" CFBundleShortVersionString 2>/dev/null || echo "unknown") +ELECTRON_VERSION=$(defaults read "$MOUNT_POINT/Codex.app/Contents/Info.plist" CFBundleVersion 2>/dev/null || echo "unknown") + +# Clean up +hdiutil detach "$MOUNT_POINT" > /dev/null 2>&1 +rmdir "$MOUNT_POINT" 2>/dev/null || true + +# Sanitize version strings +CODEX_VERSION="${CODEX_VERSION//[^a-zA-Z0-9.-]/}" +ELECTRON_VERSION="${ELECTRON_VERSION//[^a-zA-Z0-9.-]/}" + +# Fallback to date-based if extraction failed +if [[ "$CODEX_VERSION" == "unknown" ]] || [[ -z "$CODEX_VERSION" ]]; then + CODEX_VERSION=$(date +"%Y.%m.%d") +fi + +# Output JSON +BUILD_DATE=$(date +"%Y-%m-%d") +cat < or "none" +# +# Example: ./scripts/get-last-dmg-hash.sh soham2008xyz codex-rebuilder +# + +set -e + +# Get repository info from arguments or default to current repo +REPO_OWNER="${1:-${GITHUB_REPOSITORY_OWNER}}" +REPO_NAME="${2:-${GITHUB_REPOSITORY##*/}}" +REPO="${REPO_OWNER}/${REPO_NAME}" + +# Check if gh CLI is available +if ! command -v gh &> /dev/null; then + echo "none" + exit 0 +fi + +# Get the latest Intel release +LATEST_RELEASE=$(gh release list --limit 20 --json tagName,body --jq ".[] | select(.tagName | test(\"-intel$$\")) | .tagName" 2>/dev/null | head -n1 || echo "") + +if [[ -z "$LATEST_RELEASE" ]]; then + echo "none" + exit 0 +fi + +# Extract DMG hash from release notes +DMG_HASH=$(gh release view "$LATEST_RELEASE" --json body --jq '.body' 2>/dev/null | grep -i "DMG SHA256" | grep -oE '[a-f0-9]{64}' || echo "") + +if [[ -n "$DMG_HASH" ]]; then + echo "$DMG_HASH" +else + echo "none" +fi \ No newline at end of file From 3a2b31a645d3dde6edd1b8835c138959671d794c Mon Sep 17 00:00:00 2001 From: Soham Banerjee Date: Sun, 22 Mar 2026 22:40:23 +0530 Subject: [PATCH 02/12] feat: add git-commit skill for conventional commits Add git-commit skill to standardize commit creation across all AI agent directories. The skill provides automated conventional commit message generation with diff analysis and intelligent staging capabilities. Changes include: - Add git-commit skill implementation in .agents/skills/git-commit/ - Create symlinks for compatibility across .agent, .claude, .junie, .vibe - Add skills-lock.json for skill metadata tracking The skill analyzes diffs to auto-detect commit type, scope, and generates appropriate conventional commit messages following best practices. Co-Authored-By: Claude Sonnet 4.6 --- .agent/skills/git-commit | 1 + .agents/skills/git-commit/SKILL.md | 124 +++++++++++++++++++++++++++++ .claude/skills/git-commit | 1 + .junie/skills/git-commit | 1 + .vibe/skills/git-commit | 1 + skills-lock.json | 10 +++ 6 files changed, 138 insertions(+) create mode 120000 .agent/skills/git-commit create mode 100644 .agents/skills/git-commit/SKILL.md create mode 120000 .claude/skills/git-commit create mode 120000 .junie/skills/git-commit create mode 120000 .vibe/skills/git-commit create mode 100644 skills-lock.json diff --git a/.agent/skills/git-commit b/.agent/skills/git-commit new file mode 120000 index 0000000..d766236 --- /dev/null +++ b/.agent/skills/git-commit @@ -0,0 +1 @@ +../../.agents/skills/git-commit \ No newline at end of file diff --git a/.agents/skills/git-commit/SKILL.md b/.agents/skills/git-commit/SKILL.md new file mode 100644 index 0000000..c35f13b --- /dev/null +++ b/.agents/skills/git-commit/SKILL.md @@ -0,0 +1,124 @@ +--- +name: git-commit +description: 'Execute git commit with conventional commit message analysis, intelligent staging, and message generation. Use when user asks to commit changes, create a git commit, or mentions "/commit". Supports: (1) Auto-detecting type and scope from changes, (2) Generating conventional commit messages from diff, (3) Interactive commit with optional type/scope/description overrides, (4) Intelligent file staging for logical grouping' +license: MIT +allowed-tools: Bash +--- + +# Git Commit with Conventional Commits + +## Overview + +Create standardized, semantic git commits using the Conventional Commits specification. Analyze the actual diff to determine appropriate type, scope, and message. + +## Conventional Commit Format + +``` +[optional scope]: + +[optional body] + +[optional footer(s)] +``` + +## Commit Types + +| Type | Purpose | +| ---------- | ------------------------------ | +| `feat` | New feature | +| `fix` | Bug fix | +| `docs` | Documentation only | +| `style` | Formatting/style (no logic) | +| `refactor` | Code refactor (no feature/fix) | +| `perf` | Performance improvement | +| `test` | Add/update tests | +| `build` | Build system/dependencies | +| `ci` | CI/config changes | +| `chore` | Maintenance/misc | +| `revert` | Revert commit | + +## Breaking Changes + +``` +# Exclamation mark after type/scope +feat!: remove deprecated endpoint + +# BREAKING CHANGE footer +feat: allow config to extend other configs + +BREAKING CHANGE: `extends` key behavior changed +``` + +## Workflow + +### 1. Analyze Diff + +```bash +# If files are staged, use staged diff +git diff --staged + +# If nothing staged, use working tree diff +git diff + +# Also check status +git status --porcelain +``` + +### 2. Stage Files (if needed) + +If nothing is staged or you want to group changes differently: + +```bash +# Stage specific files +git add path/to/file1 path/to/file2 + +# Stage by pattern +git add *.test.* +git add src/components/* + +# Interactive staging +git add -p +``` + +**Never commit secrets** (.env, credentials.json, private keys). + +### 3. Generate Commit Message + +Analyze the diff to determine: + +- **Type**: What kind of change is this? +- **Scope**: What area/module is affected? +- **Description**: One-line summary of what changed (present tense, imperative mood, <72 chars) + +### 4. Execute Commit + +```bash +# Single line +git commit -m "[scope]: " + +# Multi-line with body/footer +git commit -m "$(cat <<'EOF' +[scope]: + + + + +EOF +)" +``` + +## Best Practices + +- One logical change per commit +- Present tense: "add" not "added" +- Imperative mood: "fix bug" not "fixes bug" +- Reference issues: `Closes #123`, `Refs #456` +- Keep description under 72 characters + +## Git Safety Protocol + +- NEVER update git config +- NEVER run destructive commands (--force, hard reset) without explicit request +- NEVER skip hooks (--no-verify) unless user asks +- NEVER force push to main/master +- If commit fails due to hooks, fix and create NEW commit (don't amend) diff --git a/.claude/skills/git-commit b/.claude/skills/git-commit new file mode 120000 index 0000000..d766236 --- /dev/null +++ b/.claude/skills/git-commit @@ -0,0 +1 @@ +../../.agents/skills/git-commit \ No newline at end of file diff --git a/.junie/skills/git-commit b/.junie/skills/git-commit new file mode 120000 index 0000000..d766236 --- /dev/null +++ b/.junie/skills/git-commit @@ -0,0 +1 @@ +../../.agents/skills/git-commit \ No newline at end of file diff --git a/.vibe/skills/git-commit b/.vibe/skills/git-commit new file mode 120000 index 0000000..d766236 --- /dev/null +++ b/.vibe/skills/git-commit @@ -0,0 +1 @@ +../../.agents/skills/git-commit \ No newline at end of file diff --git a/skills-lock.json b/skills-lock.json new file mode 100644 index 0000000..a571e0d --- /dev/null +++ b/skills-lock.json @@ -0,0 +1,10 @@ +{ + "version": 1, + "skills": { + "git-commit": { + "source": "github/awesome-copilot", + "sourceType": "github", + "computedHash": "2607fc60629b82b257136dd2a7a373f0a4466c0b49df7746d845d59313c99b21" + } + } +} From 7f28abb9bd4b96932c0af16a817958943f2224ea Mon Sep 17 00:00:00 2001 From: Soham Banerjee Date: Sun, 22 Mar 2026 22:50:40 +0530 Subject: [PATCH 03/12] chore(skills): install github-issues skill across agent directories Add github-issues skill to multiple agent directories (.agent, .agents, .claude, .junie, .vibe) and update skills-lock.json accordingly. Co-Authored-By: Claude Sonnet 4.6 --- .agent/skills/github-issues | 1 + .agents/skills/github-issues/SKILL.md | 201 +++++++++++++ .../github-issues/references/dependencies.md | 71 +++++ .../skills/github-issues/references/images.md | 116 ++++++++ .../github-issues/references/issue-fields.md | 191 ++++++++++++ .../github-issues/references/issue-types.md | 72 +++++ .../github-issues/references/projects.md | 273 ++++++++++++++++++ .../skills/github-issues/references/search.md | 231 +++++++++++++++ .../github-issues/references/sub-issues.md | 137 +++++++++ .../github-issues/references/templates.md | 90 ++++++ .claude/skills/github-issues | 1 + .junie/skills/github-issues | 1 + .vibe/skills/github-issues | 1 + skills-lock.json | 5 + 14 files changed, 1391 insertions(+) create mode 120000 .agent/skills/github-issues create mode 100644 .agents/skills/github-issues/SKILL.md create mode 100644 .agents/skills/github-issues/references/dependencies.md create mode 100644 .agents/skills/github-issues/references/images.md create mode 100644 .agents/skills/github-issues/references/issue-fields.md create mode 100644 .agents/skills/github-issues/references/issue-types.md create mode 100644 .agents/skills/github-issues/references/projects.md create mode 100644 .agents/skills/github-issues/references/search.md create mode 100644 .agents/skills/github-issues/references/sub-issues.md create mode 100644 .agents/skills/github-issues/references/templates.md create mode 120000 .claude/skills/github-issues create mode 120000 .junie/skills/github-issues create mode 120000 .vibe/skills/github-issues diff --git a/.agent/skills/github-issues b/.agent/skills/github-issues new file mode 120000 index 0000000..4749164 --- /dev/null +++ b/.agent/skills/github-issues @@ -0,0 +1 @@ +../../.agents/skills/github-issues \ No newline at end of file diff --git a/.agents/skills/github-issues/SKILL.md b/.agents/skills/github-issues/SKILL.md new file mode 100644 index 0000000..4619bac --- /dev/null +++ b/.agents/skills/github-issues/SKILL.md @@ -0,0 +1,201 @@ +--- +name: github-issues +description: 'Create, update, and manage GitHub issues using MCP tools. Use this skill when users want to create bug reports, feature requests, or task issues, update existing issues, add labels/assignees/milestones, set issue fields (dates, priority, custom fields), set issue types, manage issue workflows, link issues, add dependencies, or track blocked-by/blocking relationships. Triggers on requests like "create an issue", "file a bug", "request a feature", "update issue X", "set the priority", "set the start date", "link issues", "add dependency", "blocked by", "blocking", or any GitHub issue management task.' +--- + +# GitHub Issues + +Manage GitHub issues using the `@modelcontextprotocol/server-github` MCP server. + +## Available Tools + +### MCP Tools (read operations) + +| Tool | Purpose | +|------|---------| +| `mcp__github__issue_read` | Read issue details, sub-issues, comments, labels (methods: get, get_comments, get_sub_issues, get_labels) | +| `mcp__github__list_issues` | List and filter repository issues by state, labels, date | +| `mcp__github__search_issues` | Search issues across repos using GitHub search syntax | +| `mcp__github__projects_list` | List projects, project fields, project items, status updates | +| `mcp__github__projects_get` | Get details of a project, field, item, or status update | +| `mcp__github__projects_write` | Add/update/delete project items, create status updates | + +### CLI / REST API (write operations) + +The MCP server does not currently support creating, updating, or commenting on issues. Use `gh api` for these operations. + +| Operation | Command | +|-----------|---------| +| Create issue | `gh api repos/{owner}/{repo}/issues -X POST -f title=... -f body=...` | +| Update issue | `gh api repos/{owner}/{repo}/issues/{number} -X PATCH -f title=... -f state=...` | +| Add comment | `gh api repos/{owner}/{repo}/issues/{number}/comments -X POST -f body=...` | +| Close issue | `gh api repos/{owner}/{repo}/issues/{number} -X PATCH -f state=closed` | +| Set issue type | Include `-f type=Bug` in the create call (REST API only, not supported by `gh issue create` CLI) | + +**Note:** `gh issue create` works for basic issue creation but does **not** support the `--type` flag. Use `gh api` when you need to set issue types. + +## Workflow + +1. **Determine action**: Create, update, or query? +2. **Gather context**: Get repo info, existing labels, milestones if needed +3. **Structure content**: Use appropriate template from [references/templates.md](references/templates.md) +4. **Execute**: Use MCP tools for reads, `gh api` for writes +5. **Confirm**: Report the issue URL to user + +## Creating Issues + +Use `gh api` to create issues. This supports all parameters including issue types. + +```bash +gh api repos/{owner}/{repo}/issues \ + -X POST \ + -f title="Issue title" \ + -f body="Issue body in markdown" \ + -f type="Bug" \ + --jq '{number, html_url}' +``` + +### Optional Parameters + +Add any of these flags to the `gh api` call: + +``` +-f type="Bug" # Issue type (Bug, Feature, Task, Epic, etc.) +-f labels[]="bug" # Labels (repeat for multiple) +-f assignees[]="username" # Assignees (repeat for multiple) +-f milestone=1 # Milestone number +``` + +**Issue types** are organization-level metadata. To discover available types, use: +```bash +gh api graphql -f query='{ organization(login: "ORG") { issueTypes(first: 10) { nodes { name } } } }' --jq '.data.organization.issueTypes.nodes[].name' +``` + +**Prefer issue types over labels for categorization.** When issue types are available (e.g., Bug, Feature, Task), use the `type` parameter instead of applying equivalent labels like `bug` or `enhancement`. Issue types are the canonical way to categorize issues on GitHub. Only fall back to labels when the org has no issue types configured. + +### Title Guidelines + +- Be specific and actionable +- Keep under 72 characters +- When issue types are set, don't add redundant prefixes like `[Bug]` +- Examples: + - `Login fails with SSO enabled` (with type=Bug) + - `Add dark mode support` (with type=Feature) + - `Add unit tests for auth module` (with type=Task) + +### Body Structure + +Always use the templates in [references/templates.md](references/templates.md). Choose based on issue type: + +| User Request | Template | +|--------------|----------| +| Bug, error, broken, not working | Bug Report | +| Feature, enhancement, add, new | Feature Request | +| Task, chore, refactor, update | Task | + +## Updating Issues + +Use `gh api` with PATCH: + +```bash +gh api repos/{owner}/{repo}/issues/{number} \ + -X PATCH \ + -f state=closed \ + -f title="Updated title" \ + --jq '{number, html_url}' +``` + +Only include fields you want to change. Available fields: `title`, `body`, `state` (open/closed), `labels`, `assignees`, `milestone`. + +## Examples + +### Example 1: Bug Report + +**User**: "Create a bug issue - the login page crashes when using SSO" + +**Action**: +```bash +gh api repos/github/awesome-copilot/issues \ + -X POST \ + -f title="Login page crashes when using SSO" \ + -f type="Bug" \ + -f body="## Description +The login page crashes when users attempt to authenticate using SSO. + +## Steps to Reproduce +1. Navigate to login page +2. Click 'Sign in with SSO' +3. Page crashes + +## Expected Behavior +SSO authentication should complete and redirect to dashboard. + +## Actual Behavior +Page becomes unresponsive and displays error." \ + --jq '{number, html_url}' +``` + +### Example 2: Feature Request + +**User**: "Create a feature request for dark mode with high priority" + +**Action**: +```bash +gh api repos/github/awesome-copilot/issues \ + -X POST \ + -f title="Add dark mode support" \ + -f type="Feature" \ + -f labels[]="high-priority" \ + -f body="## Summary +Add dark mode theme option for improved user experience and accessibility. + +## Motivation +- Reduces eye strain in low-light environments +- Increasingly expected by users + +## Proposed Solution +Implement theme toggle with system preference detection. + +## Acceptance Criteria +- [ ] Toggle switch in settings +- [ ] Persists user preference +- [ ] Respects system preference by default" \ + --jq '{number, html_url}' +``` + +## Common Labels + +Use these standard labels when applicable: + +| Label | Use For | +|-------|---------| +| `bug` | Something isn't working | +| `enhancement` | New feature or improvement | +| `documentation` | Documentation updates | +| `good first issue` | Good for newcomers | +| `help wanted` | Extra attention needed | +| `question` | Further information requested | +| `wontfix` | Will not be addressed | +| `duplicate` | Already exists | +| `high-priority` | Urgent issues | + +## Tips + +- Always confirm the repository context before creating issues +- Ask for missing critical information rather than guessing +- Link related issues when known: `Related to #123` +- For updates, fetch current issue first to preserve unchanged fields + +## Extended Capabilities + +The following features require REST or GraphQL APIs beyond the basic MCP tools. Each is documented in its own reference file so the agent only loads the knowledge it needs. + +| Capability | When to use | Reference | +|------------|-------------|-----------| +| Advanced search | Complex queries with boolean logic, date ranges, cross-repo search, issue field filters (`field.name:value`) | [references/search.md](references/search.md) | +| Sub-issues & parent issues | Breaking work into hierarchical tasks | [references/sub-issues.md](references/sub-issues.md) | +| Issue dependencies | Tracking blocked-by / blocking relationships | [references/dependencies.md](references/dependencies.md) | +| Issue types (advanced) | GraphQL operations beyond MCP `list_issue_types` / `type` param | [references/issue-types.md](references/issue-types.md) | +| Projects V2 | Project boards, progress reports, field management | [references/projects.md](references/projects.md) | +| Issue fields | Custom metadata: dates, priority, text, numbers (private preview) | [references/issue-fields.md](references/issue-fields.md) | +| Images in issues | Embedding images in issue bodies and comments via CLI | [references/images.md](references/images.md) | diff --git a/.agents/skills/github-issues/references/dependencies.md b/.agents/skills/github-issues/references/dependencies.md new file mode 100644 index 0000000..6ad7562 --- /dev/null +++ b/.agents/skills/github-issues/references/dependencies.md @@ -0,0 +1,71 @@ +# Issue Dependencies (Blocked By / Blocking) + +Dependencies let you mark that an issue is blocked by another issue. This creates a formal dependency relationship visible in the UI and trackable via API. No MCP tools exist for dependencies; use REST or GraphQL directly. + +## Using REST API + +**List issues blocking this issue:** +``` +GET /repos/{owner}/{repo}/issues/{issue_number}/dependencies/blocked_by +``` + +**Add a blocking dependency:** +``` +POST /repos/{owner}/{repo}/issues/{issue_number}/dependencies/blocked_by +Body: { "issue_id": 12345 } +``` + +The `issue_id` is the numeric issue **ID** (not the issue number). + +**Remove a blocking dependency:** +``` +DELETE /repos/{owner}/{repo}/issues/{issue_number}/dependencies/blocked_by/{issue_id} +``` + +## Using GraphQL + +**Read dependencies:** +```graphql +{ + repository(owner: "OWNER", name: "REPO") { + issue(number: 123) { + blockedBy(first: 10) { nodes { number title state } } + blocking(first: 10) { nodes { number title state } } + issueDependenciesSummary { blockedBy blocking totalBlockedBy totalBlocking } + } + } +} +``` + +**Add a dependency:** +```graphql +mutation { + addBlockedBy(input: { + issueId: "BLOCKED_ISSUE_NODE_ID" + blockingIssueId: "BLOCKING_ISSUE_NODE_ID" + }) { + blockingIssue { number title } + } +} +``` + +**Remove a dependency:** +```graphql +mutation { + removeBlockedBy(input: { + issueId: "BLOCKED_ISSUE_NODE_ID" + blockingIssueId: "BLOCKING_ISSUE_NODE_ID" + }) { + blockingIssue { number title } + } +} +``` + +## Tracked issues (read-only) + +Task-list tracking relationships are available via GraphQL as read-only fields: + +- `trackedIssues(first: N)` - issues tracked in this issue's task list +- `trackedInIssues(first: N)` - issues whose task lists reference this issue + +These are set automatically when issues are referenced in task lists (`- [ ] #123`). There are no mutations to manage them. diff --git a/.agents/skills/github-issues/references/images.md b/.agents/skills/github-issues/references/images.md new file mode 100644 index 0000000..f6dec63 --- /dev/null +++ b/.agents/skills/github-issues/references/images.md @@ -0,0 +1,116 @@ +# Images in Issues and Comments + +How to embed images in GitHub issue bodies and comments programmatically via the CLI. + +## Methods (ranked by reliability) + +### 1. GitHub Contents API (recommended for private repos) + +Push image files to a branch in the same repo, then reference them with a URL that works for authenticated viewers. + +**Step 1: Create a branch** + +```bash +# Get the SHA of the default branch +SHA=$(gh api repos/{owner}/{repo}/git/ref/heads/main --jq '.object.sha') + +# Create a new branch +gh api repos/{owner}/{repo}/git/refs -X POST \ + -f ref="refs/heads/{username}/images" \ + -f sha="$SHA" +``` + +**Step 2: Upload images via Contents API** + +```bash +# Base64-encode the image and upload +BASE64=$(base64 -i /path/to/image.png) + +gh api repos/{owner}/{repo}/contents/docs/images/my-image.png \ + -X PUT \ + -f message="Add image" \ + -f content="$BASE64" \ + -f branch="{username}/images" \ + --jq '.content.path' +``` + +Repeat for each image. The Contents API creates a commit per file. + +**Step 3: Reference in markdown** + +```markdown +![Description](https://github.com/{owner}/{repo}/raw/{username}/images/docs/images/my-image.png) +``` + +> **Important:** Use `github.com/{owner}/{repo}/raw/{branch}/{path}` format, NOT `raw.githubusercontent.com`. The `raw.githubusercontent.com` URLs return 404 for private repos. The `github.com/.../raw/...` format works because the browser sends auth cookies when the viewer is logged in and has repo access. + +**Pros:** Works for any repo the viewer has access to, images live in version control, no expiration. +**Cons:** Creates commits, viewers must be authenticated, images won't render in email notifications or for users without repo access. + +### 2. Gist hosting (public images only) + +Upload images as files in a gist. Only works for images you're comfortable making public. + +```bash +# Create a gist with a placeholder file +gh gist create --public -f description.md <<< "Image hosting gist" + +# Note: gh gist edit does NOT support binary files. +# You must use the API to add binary content to gists. +``` + +> **Limitation:** Gists don't support binary file uploads via the CLI. You'd need to base64-encode and store as text, which won't render as images. Not recommended. + +### 3. Browser upload (most reliable rendering) + +The most reliable way to get permanent image URLs is through the GitHub web UI: + +1. Open the issue/comment in a browser +2. Drag-drop or paste the image into the comment editor +3. GitHub generates a permanent `https://github.com/user-attachments/assets/{UUID}` URL +4. These URLs work for anyone, even without repo access, and render in email notifications + +> **Why the API can't do this:** GitHub's `upload/policies/assets` endpoint requires a browser session (CSRF token + cookies). It returns an HTML error page when called with API tokens. There is no public API for generating `user-attachments` URLs. + +## Taking screenshots programmatically + +Use `puppeteer-core` with local Chrome to screenshot HTML mockups: + +```javascript +const puppeteer = require('puppeteer-core'); + +const browser = await puppeteer.launch({ + executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', + defaultViewport: { width: 900, height: 600, deviceScaleFactor: 2 } +}); + +const page = await browser.newPage(); +await page.setContent(htmlString); + +// Screenshot specific elements +const elements = await page.$$('.section'); +for (let i = 0; i < elements.length; i++) { + await elements[i].screenshot({ path: `mockup-${i + 1}.png` }); +} + +await browser.close(); +``` + +> **Note:** MCP Playwright may not connect to localhost due to network isolation. Use puppeteer-core with a local Chrome installation instead. + +## Quick reference + +| Method | Private repos | Permanent | No auth needed | API-only | +|--------|:---:|:---:|:---:|:---:| +| Contents API + `github.com/raw/` | ✅ | ✅ | ❌ | ✅ | +| Browser drag-drop (`user-attachments`) | ✅ | ✅ | ✅ | ❌ | +| `raw.githubusercontent.com` | ❌ (404) | ✅ | ❌ | ✅ | +| Gist | Public only | ✅ | ✅ | ❌ (no binary) | + +## Common pitfalls + +- **`raw.githubusercontent.com` returns 404 for private repos** even with a valid token in the URL. GitHub's CDN does not pass auth headers through. +- **API download URLs are temporary.** URLs returned by `gh api repos/.../contents/...` with `download_url` include a token that expires. +- **`upload/policies/assets` requires a browser session.** Do not attempt to call this endpoint from the CLI. +- **Base64 encoding for large files** can hit API payload limits. The Contents API has a ~100MB file size limit but practical limits are lower for base64-encoded payloads. +- **Email notifications** will not render images that require authentication. If email readability matters, use the browser upload method. diff --git a/.agents/skills/github-issues/references/issue-fields.md b/.agents/skills/github-issues/references/issue-fields.md new file mode 100644 index 0000000..4ab668c --- /dev/null +++ b/.agents/skills/github-issues/references/issue-fields.md @@ -0,0 +1,191 @@ +# Issue Fields (GraphQL, Private Preview) + +> **Private preview:** Issue fields are currently in private preview. Request access at https://github.com/orgs/community/discussions/175366 + +Issue fields are custom metadata (dates, text, numbers, single-select) defined at the organization level and set per-issue. They are separate from labels, milestones, and assignees. Common examples: Start Date, Target Date, Priority, Impact, Effort. + +**Important:** All issue field queries and mutations require the `GraphQL-Features: issue_fields` HTTP header. Without it, the fields are not visible in the schema. + +**Prefer issue fields over project fields.** When you need to set metadata like dates, priority, or status on an issue, use issue fields (which live on the issue itself) rather than project fields (which live on a project item). Issue fields travel with the issue across projects and views, while project fields are scoped to a single project. Only use project fields when issue fields are not available or when the field is project-specific (e.g., sprint iterations). + +## Discovering available fields + +Fields are defined at the org level. List them before trying to set values: + +```graphql +# Header: GraphQL-Features: issue_fields +{ + organization(login: "OWNER") { + issueFields(first: 30) { + nodes { + __typename + ... on IssueFieldDate { id name } + ... on IssueFieldText { id name } + ... on IssueFieldNumber { id name } + ... on IssueFieldSingleSelect { id name options { id name color } } + } + } + } +} +``` + +Field types: `IssueFieldDate`, `IssueFieldText`, `IssueFieldNumber`, `IssueFieldSingleSelect`. + +For single-select fields, you need the option `id` (not the name) to set values. + +## Reading field values on an issue + +```graphql +# Header: GraphQL-Features: issue_fields +{ + repository(owner: "OWNER", name: "REPO") { + issue(number: 123) { + issueFieldValues(first: 20) { + nodes { + __typename + ... on IssueFieldDateValue { + value + field { ... on IssueFieldDate { id name } } + } + ... on IssueFieldTextValue { + value + field { ... on IssueFieldText { id name } } + } + ... on IssueFieldNumberValue { + value + field { ... on IssueFieldNumber { id name } } + } + ... on IssueFieldSingleSelectValue { + name + color + field { ... on IssueFieldSingleSelect { id name } } + } + } + } + } + } +} +``` + +## Setting field values + +Use `setIssueFieldValue` to set one or more fields at once. You need the issue's node ID and the field IDs from the discovery query above. + +```graphql +# Header: GraphQL-Features: issue_fields +mutation { + setIssueFieldValue(input: { + issueId: "ISSUE_NODE_ID" + issueFields: [ + { fieldId: "IFD_xxx", dateValue: "2026-04-15" } + { fieldId: "IFT_xxx", textValue: "some text" } + { fieldId: "IFN_xxx", numberValue: 3.0 } + { fieldId: "IFSS_xxx", singleSelectOptionId: "OPTION_ID" } + ] + }) { + issue { id title } + } +} +``` + +Each entry in `issueFields` takes a `fieldId` plus exactly one value parameter: + +| Field type | Value parameter | Format | +|-----------|----------------|--------| +| Date | `dateValue` | ISO 8601 date string, e.g. `"2026-04-15"` | +| Text | `textValue` | String | +| Number | `numberValue` | Float | +| Single select | `singleSelectOptionId` | ID from the field's `options` list | + +To clear a field value, set `delete: true` instead of a value parameter. + +## Workflow for setting fields + +1. **Discover fields** - query the org's `issueFields` to get field IDs and option IDs +2. **Get the issue node ID** - from `repository.issue.id` +3. **Set values** - call `setIssueFieldValue` with the issue node ID and field entries +4. **Batch when possible** - multiple fields can be set in a single mutation call + +## Example: Set dates and priority on an issue + +```bash +gh api graphql \ + -H "GraphQL-Features: issue_fields" \ + -f query=' +mutation { + setIssueFieldValue(input: { + issueId: "I_kwDOxxx" + issueFields: [ + { fieldId: "IFD_startDate", dateValue: "2026-04-01" } + { fieldId: "IFD_targetDate", dateValue: "2026-04-30" } + { fieldId: "IFSS_priority", singleSelectOptionId: "OPTION_P1" } + ] + }) { + issue { id title } + } +}' +``` + +## Searching by field values + +### GraphQL bulk query (recommended) + +The most reliable way to find issues by field value is to fetch issues via GraphQL and filter by `issueFieldValues`. The search qualifier syntax (`field.name:value`) is not yet reliable across all environments. + +```bash +# Find all open P1 issues in a repo +gh api graphql -H "GraphQL-Features: issue_fields" -f query=' +{ + repository(owner: "OWNER", name: "REPO") { + issues(first: 100, states: OPEN) { + nodes { + number + title + updatedAt + assignees(first: 3) { nodes { login } } + issueFieldValues(first: 10) { + nodes { + __typename + ... on IssueFieldSingleSelectValue { + name + field { ... on IssueFieldSingleSelect { name } } + } + } + } + } + } + } +}' --jq ' + [.data.repository.issues.nodes[] | + select(.issueFieldValues.nodes[] | + select(.field.name == "Priority" and .name == "P1") + ) | + {number, title, updatedAt, assignees: [.assignees.nodes[].login]} + ]' +``` + +**Schema notes for `IssueFieldSingleSelectValue`:** +- The selected option's display text is in `.name` (not `.value`) +- Also available: `.color`, `.description`, `.id` +- The parent field reference is in `.field` (use inline fragment to get the field name) + +### Search qualifier syntax (experimental) + +Issue fields may also be searchable using dot notation in search queries. This requires `advanced_search=true` on REST or `ISSUE_ADVANCED` search type on GraphQL, but results are inconsistent and may return 0 results even when matching issues exist. + +``` +field.priority:P0 # Single-select equals value +field.target-date:>=2026-04-01 # Date comparison +has:field.priority # Has any value set +no:field.priority # Has no value set +``` + +Field names use the **slug** (lowercase, hyphens for spaces). For example, "Target Date" becomes `target-date`. + +```bash +# REST API (may not return results in all environments) +gh api "search/issues?q=repo:owner/repo+field.priority:P0+is:open&advanced_search=true" \ + --jq '.items[] | "#\(.number): \(.title)"' +``` + +> **Warning:** The colon notation (`field:Priority:P1`) is silently ignored. If using search qualifiers, always use dot notation (`field.priority:P1`). However, the GraphQL bulk query approach above is more reliable. See [search.md](search.md) for the full search guide. diff --git a/.agents/skills/github-issues/references/issue-types.md b/.agents/skills/github-issues/references/issue-types.md new file mode 100644 index 0000000..f605d7b --- /dev/null +++ b/.agents/skills/github-issues/references/issue-types.md @@ -0,0 +1,72 @@ +# Issue Types (Advanced GraphQL) + +Issue types (Bug, Feature, Task, Epic, etc.) are defined at the **organization** level and inherited by repositories. They categorize issues beyond labels. + +For basic usage, the MCP tools handle issue types natively. Call `mcp__github__list_issue_types` to discover types, and pass `type: "Bug"` to `mcp__github__create_issue` or `mcp__github__update_issue`. This reference covers advanced GraphQL operations. + +## GraphQL Feature Header + +All GraphQL issue type operations require the `GraphQL-Features: issue_types` HTTP header. + +## List types (org or repo level) + +```graphql +# Header: GraphQL-Features: issue_types +{ + organization(login: "OWNER") { + issueTypes(first: 20) { + nodes { id name color description isEnabled } + } + } +} +``` + +Types can also be listed per-repo via `repository.issueTypes` or looked up by name via `repository.issueType(name: "Bug")`. + +## Read an issue's type + +```graphql +# Header: GraphQL-Features: issue_types +{ + repository(owner: "OWNER", name: "REPO") { + issue(number: 123) { + issueType { id name color } + } + } +} +``` + +## Set type on an existing issue + +```graphql +# Header: GraphQL-Features: issue_types +mutation { + updateIssueIssueType(input: { + issueId: "ISSUE_NODE_ID" + issueTypeId: "IT_xxx" + }) { + issue { id issueType { name } } + } +} +``` + +## Create issue with type + +```graphql +# Header: GraphQL-Features: issue_types +mutation { + createIssue(input: { + repositoryId: "REPO_NODE_ID" + title: "Fix login bug" + issueTypeId: "IT_xxx" + }) { + issue { id number issueType { name } } + } +} +``` + +To clear the type, set `issueTypeId` to `null`. + +## Available colors + +`GRAY`, `BLUE`, `GREEN`, `YELLOW`, `ORANGE`, `RED`, `PINK`, `PURPLE` diff --git a/.agents/skills/github-issues/references/projects.md b/.agents/skills/github-issues/references/projects.md new file mode 100644 index 0000000..e373b4e --- /dev/null +++ b/.agents/skills/github-issues/references/projects.md @@ -0,0 +1,273 @@ +# Projects V2 + +GitHub Projects V2 is managed via GraphQL. The MCP server provides three tools that wrap the GraphQL API, so you typically don't need raw GraphQL. + +## Using MCP tools (preferred) + +**List projects:** +Call `mcp__github__projects_list` with `method: "list_projects"`, `owner`, and `owner_type` ("user" or "organization"). + +**List project fields:** +Call `mcp__github__projects_list` with `method: "list_project_fields"` and `project_number`. + +**List project items:** +Call `mcp__github__projects_list` with `method: "list_project_items"` and `project_number`. + +**Add an issue/PR to a project:** +Call `mcp__github__projects_write` with `method: "add_project_item"`, `project_id` (node ID), and `content_id` (issue/PR node ID). + +**Update a project item field value:** +Call `mcp__github__projects_write` with `method: "update_project_item"`, `project_id`, `item_id`, `field_id`, and `value` (object with one of: `text`, `number`, `date`, `singleSelectOptionId`, `iterationId`). + +**Delete a project item:** +Call `mcp__github__projects_write` with `method: "delete_project_item"`, `project_id`, and `item_id`. + +## Workflow for project operations + +1. **Find the project** — see [Finding a project by name](#finding-a-project-by-name) below +2. **Discover fields** - use `projects_list` with `list_project_fields` to get field IDs and option IDs +3. **Find items** - use `projects_list` with `list_project_items` to get item IDs +4. **Mutate** - use `projects_write` to add, update, or delete items + +## Finding a project by name + +> **âš ī¸ Known issue:** `projectsV2(query: "â€Ļ")` does keyword search, not exact name match, and returns results sorted by recency. Common words like "issue" or "bug" return hundreds of false positives. The actual project may be buried dozens of pages deep. + +Use this priority order: + +### 1. Direct lookup (if you know the number) +```bash +gh api graphql -f query='{ + organization(login: "ORG") { + projectV2(number: 42) { id title } + } +}' --jq '.data.organization.projectV2' +``` + +### 2. Reverse lookup from a known issue (most reliable) +If the user mentions an issue, epic, or milestone that's in the project, query that issue's `projectItems` to discover the project: + +```bash +gh api graphql -f query='{ + repository(owner: "OWNER", name: "REPO") { + issue(number: 123) { + projectItems(first: 10) { + nodes { + id + project { number title id } + } + } + } + } +}' --jq '.data.repository.issue.projectItems.nodes[] | {number: .project.number, title: .project.title, id: .project.id}' +``` + +This is the most reliable approach for large orgs where name search fails. + +### 3. GraphQL name search with client-side filtering (fallback) +Query a large page and filter client-side for an exact title match: + +```bash +gh api graphql -f query='{ + organization(login: "ORG") { + projectsV2(first: 100, query: "search term") { + nodes { number title id } + } + } +}' --jq '.data.organization.projectsV2.nodes[] | select(.title | test("(?i)^exact name$"))' +``` + +If this returns nothing, paginate with `after` cursor or broaden the regex. Results are sorted by recency so older projects require pagination. + +### 4. MCP tool (small orgs only) +Call `mcp__github__projects_list` with `method: "list_projects"`. This works well for orgs with <50 projects but has no name filter, so you must scan all results. + +## Project discovery for progress reports + +When a user asks for a progress update on a project (e.g., "Give me a progress update for Project X"), follow this workflow: + +1. **Find the project** — use the [finding a project](#finding-a-project-by-name) strategies above. Ask the user for a known issue number if name search fails. + +2. **Discover fields** - call `projects_list` with `list_project_fields` to find the Status field (its options tell you the workflow stages) and any Iteration field (to scope to the current sprint). + +3. **Get all items** - call `projects_list` with `list_project_items`. For large projects (100+ items), paginate through all pages. Each item includes its field values (status, iteration, assignees). + +4. **Build the report** - group items by Status field value and count them. For iteration-based projects, filter to the current iteration first. Present a breakdown like: + + ``` + Project: Issue Fields (Iteration 42, Mar 2-8) + 15 actionable items: + 🎉 Done: 4 (27%) + In Review: 3 + In Progress: 3 + Ready: 2 + Blocked: 2 + ``` + +5. **Add context** - if items have sub-issues, include `subIssuesSummary` counts. If items have dependencies, note blocked items and what blocks them. + +## OAuth Scope Requirements + +| Operation | Required scope | +|-----------|---------------| +| Read projects, fields, items | `read:project` | +| Add/update/delete items, change field values | `project` | + +**Common pitfall:** The default `gh auth` token often only has `read:project`. Mutations will fail with `INSUFFICIENT_SCOPES`. To add the write scope: + +```bash +gh auth refresh -h github.com -s project +``` + +This triggers a browser-based OAuth flow. You must complete it before mutations will work. + +## Finding an Issue's Project Item ID + +When you know the issue but need its project item ID (e.g., to update its Status), query from the issue side: + +```bash +gh api graphql -f query=' +{ + repository(owner: "OWNER", name: "REPO") { + issue(number: 123) { + projectItems(first: 5) { + nodes { + id + project { title number } + fieldValues(first: 10) { + nodes { + ... on ProjectV2ItemFieldSingleSelectValue { + name + field { ... on ProjectV2SingleSelectField { name } } + } + } + } + } + } + } + } +}' --jq '.data.repository.issue.projectItems.nodes' +``` + +This returns the item ID, project info, and current field values in one query. + +## Using GraphQL via gh api (recommended) + +Use `gh api graphql` to run GraphQL queries and mutations. This is more reliable than MCP tools for write operations. + +**Find a project and its Status field options:** +```bash +gh api graphql -f query=' +{ + organization(login: "ORG") { + projectV2(number: 5) { + id + title + field(name: "Status") { + ... on ProjectV2SingleSelectField { + id + options { id name } + } + } + } + } +}' --jq '.data.organization.projectV2' +``` + +**List all fields (including iterations):** +```bash +gh api graphql -f query=' +{ + node(id: "PROJECT_ID") { + ... on ProjectV2 { + fields(first: 20) { + nodes { + ... on ProjectV2Field { id name } + ... on ProjectV2SingleSelectField { id name options { id name } } + ... on ProjectV2IterationField { id name configuration { iterations { id startDate } } } + } + } + } + } +}' --jq '.data.node.fields.nodes' +``` + +**Update a field value (e.g., set Status to "In Progress"):** +```bash +gh api graphql -f query=' +mutation { + updateProjectV2ItemFieldValue(input: { + projectId: "PROJECT_ID" + itemId: "ITEM_ID" + fieldId: "FIELD_ID" + value: { singleSelectOptionId: "OPTION_ID" } + }) { + projectV2Item { id } + } +}' +``` + +Value accepts one of: `text`, `number`, `date`, `singleSelectOptionId`, `iterationId`. + +**Add an item:** +```bash +gh api graphql -f query=' +mutation { + addProjectV2ItemById(input: { + projectId: "PROJECT_ID" + contentId: "ISSUE_OR_PR_NODE_ID" + }) { + item { id } + } +}' +``` + +**Delete an item:** +```bash +gh api graphql -f query=' +mutation { + deleteProjectV2Item(input: { + projectId: "PROJECT_ID" + itemId: "ITEM_ID" + }) { + deletedItemId + } +}' +``` + +## End-to-End Example: Set Issue Status to "In Progress" + +```bash +# 1. Get the issue's project item ID, project ID, and current status +gh api graphql -f query='{ + repository(owner: "github", name: "planning-tracking") { + issue(number: 2574) { + projectItems(first: 1) { + nodes { id project { id title } } + } + } + } +}' --jq '.data.repository.issue.projectItems.nodes[0]' + +# 2. Get the Status field ID and "In Progress" option ID +gh api graphql -f query='{ + node(id: "PROJECT_ID") { + ... on ProjectV2 { + field(name: "Status") { + ... on ProjectV2SingleSelectField { id options { id name } } + } + } + } +}' --jq '.data.node.field' + +# 3. Update the status +gh api graphql -f query='mutation { + updateProjectV2ItemFieldValue(input: { + projectId: "PROJECT_ID" + itemId: "ITEM_ID" + fieldId: "FIELD_ID" + value: { singleSelectOptionId: "IN_PROGRESS_OPTION_ID" } + }) { projectV2Item { id } } +}' +``` +``` diff --git a/.agents/skills/github-issues/references/search.md b/.agents/skills/github-issues/references/search.md new file mode 100644 index 0000000..9e08efa --- /dev/null +++ b/.agents/skills/github-issues/references/search.md @@ -0,0 +1,231 @@ +# Advanced Issue Search + +The `search_issues` MCP tool uses GitHub's issue search query format for cross-repo searches, supporting implicit-AND queries, date ranges, and metadata filters (but not explicit OR/NOT operators). + +## When to Use Search vs List vs Advanced Search + +There are three ways to find issues, each with different capabilities: + +| Capability | `list_issues` (MCP) | `search_issues` (MCP) | Advanced search (`gh api`) | +|-----------|---------------------|----------------------|---------------------------| +| **Scope** | Single repo only | Cross-repo, cross-org | Cross-repo, cross-org | +| **Issue field filters** (`field.priority:P0`) | No | No | **Yes** (dot notation) | +| **Issue type filter** (`type:Bug`) | No | Yes | Yes | +| **Boolean logic** (AND/OR/NOT, nesting) | No | Yes (implicit AND only) | **Yes** (explicit AND/OR/NOT) | +| **Label/state/date filters** | Yes | Yes | Yes | +| **Assignee/author/mentions** | No | Yes | Yes | +| **Negation** (`-label:x`, `no:label`) | No | Yes | Yes | +| **Text search** (title/body/comments) | No | Yes | Yes | +| **`since` filter** | Yes | No | No | +| **Result limit** | No cap (paginate all) | 1,000 max | 1,000 max | +| **How to call** | MCP tool directly | MCP tool directly | `gh api` with `advanced_search=true` | + +**Decision guide:** +- **Single repo, simple filters (state, labels, recent updates):** use `list_issues` +- **Cross-repo, text search, author/assignee, issue types:** use `search_issues` +- **Issue field values (Priority, dates, custom fields) or complex boolean logic:** use `gh api` with `advanced_search=true` + +## Query Syntax + +The `query` parameter is a string of search terms and qualifiers. A space between terms is implicit AND. + +### Scoping + +``` +repo:owner/repo # Single repo (auto-added if you pass owner+repo params) +org:github # All repos in an org +user:octocat # All repos owned by user +in:title # Search only in title +in:body # Search only in body +in:comments # Search only in comments +``` + +### State & Close Reason + +``` +is:open # Open issues (auto-added: is:issue) +is:closed # Closed issues +reason:completed # Closed as completed +reason:"not planned" # Closed as not planned +``` + +### People + +``` +author:username # Created by +assignee:username # Assigned to +mentions:username # Mentions user +commenter:username # Has comment from +involves:username # Author OR assignee OR mentioned OR commenter +author:@me # Current authenticated user +team:org/team # Team mentioned +``` + +### Labels, Milestones, Projects, Types + +``` +label:"bug" # Has label (quote multi-word labels) +label:bug label:priority # Has BOTH labels (AND) +label:bug,enhancement # Has EITHER label (OR) +-label:wontfix # Does NOT have label +milestone:"v2.0" # In milestone +project:github/57 # In project board +type:"Bug" # Issue type +``` + +### Missing Metadata + +``` +no:label # No labels assigned +no:milestone # No milestone +no:assignee # Unassigned +no:project # Not in any project +``` + +### Dates + +All date qualifiers support `>`, `<`, `>=`, `<=`, and range (`..`) operators with ISO 8601 format: + +``` +created:>2026-01-01 # Created after Jan 1 +updated:>=2026-03-01 # Updated since Mar 1 +closed:2026-01-01..2026-02-01 # Closed in January +created:<2026-01-01 # Created before Jan 1 +``` + +### Linked Content + +``` +linked:pr # Issue has a linked PR +-linked:pr # Issues not yet linked to any PR +linked:issue # PR is linked to an issue +``` + +### Numeric Filters + +``` +comments:>10 # More than 10 comments +comments:0 # No comments +interactions:>100 # Reactions + comments > 100 +reactions:>50 # More than 50 reactions +``` + +### Boolean Logic & Nesting + +Use `AND`, `OR`, and parentheses (up to 5 levels deep, max 5 operators): + +``` +label:bug AND assignee:octocat +assignee:octocat OR assignee:hubot +(type:"Bug" AND label:P1) OR (type:"Feature" AND label:P1) +-author:app/dependabot # Exclude bot issues +``` + +A space between terms without an explicit operator is treated as AND. + +## Common Query Patterns + +**Unassigned bugs:** +``` +repo:owner/repo type:"Bug" no:assignee is:open +``` + +**Issues closed this week:** +``` +repo:owner/repo is:closed closed:>=2026-03-01 +``` + +**Stale open issues (no updates in 90 days):** +``` +repo:owner/repo is:open updated:<2026-01-01 +``` + +**Open issues without a linked PR (needs work):** +``` +repo:owner/repo is:open -linked:pr +``` + +**Issues I'm involved in across an org:** +``` +org:github involves:@me is:open +``` + +**High-activity issues:** +``` +repo:owner/repo is:open comments:>20 +``` + +**Issues by type and priority label:** +``` +repo:owner/repo type:"Epic" label:P1 is:open +``` + +## Issue Field Search + +> **Reliability warning:** The `field.name:value` search qualifier syntax is experimental and may return 0 results even when matching issues exist. For reliable filtering by field values, use the GraphQL bulk query approach documented in [issue-fields.md](issue-fields.md#searching-by-field-values). + +Issue fields can theoretically be searched via the `field.name:value` qualifier using **advanced search mode**. This works in the web UI but results from the API are inconsistent. + +### REST API + +Add `advanced_search=true` as a query parameter: + +```bash +gh api "search/issues?q=org:github+field.priority:P0+type:Epic+is:open&advanced_search=true" \ + --jq '.items[] | "#\(.number): \(.title)"' +``` + +### GraphQL + +Use `type: ISSUE_ADVANCED` instead of `type: ISSUE`: + +```graphql +{ + search(query: "org:github field.priority:P0 type:Epic is:open", type: ISSUE_ADVANCED, first: 10) { + issueCount + nodes { + ... on Issue { number title } + } + } +} +``` + +### Issue Field Qualifiers + +The syntax uses **dot notation** with the field's slug name (lowercase, hyphens for spaces): + +``` +field.priority:P0 # Single-select field equals value +field.priority:P1 # Different option value +field.target-date:>=2026-04-01 # Date comparison +has:field.priority # Has any value set +no:field.priority # Has no value set +``` + +**MCP limitation:** The `search_issues` MCP tool does not pass `advanced_search=true`. You must use `gh api` directly for issue field searches. + +### Common Field Search Patterns + +**P0 epics across an org:** +``` +org:github field.priority:P0 type:Epic is:open +``` + +**Issues with a target date this quarter:** +``` +org:github field.target-date:>=2026-04-01 field.target-date:<=2026-06-30 is:open +``` + +**Open bugs missing priority:** +``` +org:github no:field.priority type:Bug is:open +``` + +## Limitations + +- Query text: max **256 characters** (excluding operators/qualifiers) +- Boolean operators: max **5** AND/OR/NOT per query +- Results: max **1,000** total (use `list_issues` if you need all issues) +- Repo scan: searches up to **4,000** matching repositories +- Rate limit: **30 requests/minute** for authenticated search +- Issue field search requires `advanced_search=true` (REST) or `ISSUE_ADVANCED` (GraphQL); not available through MCP `search_issues` diff --git a/.agents/skills/github-issues/references/sub-issues.md b/.agents/skills/github-issues/references/sub-issues.md new file mode 100644 index 0000000..aac288e --- /dev/null +++ b/.agents/skills/github-issues/references/sub-issues.md @@ -0,0 +1,137 @@ +# Sub-Issues and Parent Issues + +Sub-issues let you break down work into hierarchical tasks. Each parent issue can have up to 100 sub-issues, nested up to 8 levels deep. Sub-issues can span repositories within the same owner. + +## Recommended Workflow + +The simplest way to create a sub-issue is **two steps**: create the issue, then link it. + +```bash +# Step 1: Create the issue and capture its numeric ID +ISSUE_ID=$(gh api repos/{owner}/{repo}/issues \ + -X POST \ + -f title="Sub-task title" \ + -f body="Description" \ + --jq '.id') + +# Step 2: Link it as a sub-issue of the parent +# IMPORTANT: sub_issue_id must be an integer. Use --input (not -f) to send JSON. +echo "{\"sub_issue_id\": $ISSUE_ID}" | gh api repos/{owner}/{repo}/issues/{parent_number}/sub_issues -X POST --input - +``` + +**Why `--input` instead of `-f`?** The `gh api -f` flag sends all values as strings, but the API requires `sub_issue_id` as an integer. Using `-f sub_issue_id=12345` will return a 422 error. + +Alternatively, use GraphQL `createIssue` with `parentIssueId` to do it in one step (see GraphQL section below). + +## Using MCP tools + +**List sub-issues:** +Call `mcp__github__issue_read` with `method: "get_sub_issues"`, `owner`, `repo`, and `issue_number`. + +**Create an issue as a sub-issue:** +There is no MCP tool for creating sub-issues directly. Use the workflow above or GraphQL. + +## Using REST API + +**List sub-issues:** +```bash +gh api repos/{owner}/{repo}/issues/{issue_number}/sub_issues +``` + +**Get parent issue:** +```bash +gh api repos/{owner}/{repo}/issues/{issue_number}/parent +``` + +**Add an existing issue as a sub-issue:** +```bash +# sub_issue_id is the numeric issue ID (not the issue number) +# Get it from the .id field when creating or fetching an issue +echo '{"sub_issue_id": 12345}' | gh api repos/{owner}/{repo}/issues/{parent_number}/sub_issues -X POST --input - +``` + +To move a sub-issue that already has a parent, add `"replace_parent": true` to the JSON body. + +**Remove a sub-issue:** +```bash +echo '{"sub_issue_id": 12345}' | gh api repos/{owner}/{repo}/issues/{parent_number}/sub_issue -X DELETE --input - +``` + +**Reprioritize a sub-issue:** +```bash +echo '{"sub_issue_id": 6, "after_id": 5}' | gh api repos/{owner}/{repo}/issues/{parent_number}/sub_issues/priority -X PATCH --input - +``` + +Use `after_id` or `before_id` to position the sub-issue relative to another. + +## Using GraphQL + +**Read parent and sub-issues:** +```graphql +{ + repository(owner: "OWNER", name: "REPO") { + issue(number: 123) { + parent { number title } + subIssues(first: 50) { + nodes { number title state } + } + subIssuesSummary { total completed percentCompleted } + } + } +} +``` + +**Add a sub-issue:** +```graphql +mutation { + addSubIssue(input: { + issueId: "PARENT_NODE_ID" + subIssueId: "CHILD_NODE_ID" + }) { + issue { id } + subIssue { id number title } + } +} +``` + +You can also use `subIssueUrl` instead of `subIssueId` (pass the issue's HTML URL). Add `replaceParent: true` to move a sub-issue from another parent. + +**Create an issue directly as a sub-issue:** +```graphql +mutation { + createIssue(input: { + repositoryId: "REPO_NODE_ID" + title: "Implement login validation" + parentIssueId: "PARENT_NODE_ID" + }) { + issue { id number } + } +} +``` + +**Remove a sub-issue:** +```graphql +mutation { + removeSubIssue(input: { + issueId: "PARENT_NODE_ID" + subIssueId: "CHILD_NODE_ID" + }) { + issue { id } + } +} +``` + +**Reprioritize a sub-issue:** +```graphql +mutation { + reprioritizeSubIssue(input: { + issueId: "PARENT_NODE_ID" + subIssueId: "CHILD_NODE_ID" + afterId: "OTHER_CHILD_NODE_ID" + }) { + issue { id } + } +} +``` + +Use `afterId` or `beforeId` to position relative to another sub-issue. diff --git a/.agents/skills/github-issues/references/templates.md b/.agents/skills/github-issues/references/templates.md new file mode 100644 index 0000000..c05b408 --- /dev/null +++ b/.agents/skills/github-issues/references/templates.md @@ -0,0 +1,90 @@ +# Issue Templates + +Copy and customize these templates for issue bodies. + +## Bug Report Template + +```markdown +## Description +[Clear description of the bug] + +## Steps to Reproduce +1. [First step] +2. [Second step] +3. [And so on...] + +## Expected Behavior +[What should happen] + +## Actual Behavior +[What actually happens] + +## Environment +- Browser: [e.g., Chrome 120] +- OS: [e.g., macOS 14.0] +- Version: [e.g., v1.2.3] + +## Screenshots/Logs +[If applicable] + +## Additional Context +[Any other relevant information] +``` + +## Feature Request Template + +```markdown +## Summary +[One-line description of the feature] + +## Motivation +[Why is this feature needed? What problem does it solve?] + +## Proposed Solution +[How should this feature work?] + +## Acceptance Criteria +- [ ] [Criterion 1] +- [ ] [Criterion 2] +- [ ] [Criterion 3] + +## Alternatives Considered +[Other approaches considered and why they weren't chosen] + +## Additional Context +[Mockups, examples, or related issues] +``` + +## Task Template + +```markdown +## Objective +[What needs to be accomplished] + +## Details +[Detailed description of the work] + +## Checklist +- [ ] [Subtask 1] +- [ ] [Subtask 2] +- [ ] [Subtask 3] + +## Dependencies +[Any blockers or related work] + +## Notes +[Additional context or considerations] +``` + +## Minimal Template + +For simple issues: + +```markdown +## Description +[What and why] + +## Tasks +- [ ] [Task 1] +- [ ] [Task 2] +``` diff --git a/.claude/skills/github-issues b/.claude/skills/github-issues new file mode 120000 index 0000000..4749164 --- /dev/null +++ b/.claude/skills/github-issues @@ -0,0 +1 @@ +../../.agents/skills/github-issues \ No newline at end of file diff --git a/.junie/skills/github-issues b/.junie/skills/github-issues new file mode 120000 index 0000000..4749164 --- /dev/null +++ b/.junie/skills/github-issues @@ -0,0 +1 @@ +../../.agents/skills/github-issues \ No newline at end of file diff --git a/.vibe/skills/github-issues b/.vibe/skills/github-issues new file mode 120000 index 0000000..4749164 --- /dev/null +++ b/.vibe/skills/github-issues @@ -0,0 +1 @@ +../../.agents/skills/github-issues \ No newline at end of file diff --git a/skills-lock.json b/skills-lock.json index a571e0d..6bf050e 100644 --- a/skills-lock.json +++ b/skills-lock.json @@ -5,6 +5,11 @@ "source": "github/awesome-copilot", "sourceType": "github", "computedHash": "2607fc60629b82b257136dd2a7a373f0a4466c0b49df7746d845d59313c99b21" + }, + "github-issues": { + "source": "github/awesome-copilot", + "sourceType": "github", + "computedHash": "3a752d99ddc3178f443fe242d490800398b2b42030a9d221b298b9486d90cc87" } } } From f20b659db2616c9b60c87835e923842918667208 Mon Sep 17 00:00:00 2001 From: Soham Banerjee Date: Sun, 22 Mar 2026 22:55:32 +0530 Subject: [PATCH 04/12] chore(skills): install GitHub CLI skill across agent directories Adds gh-cli skill documentation and symlinks in multiple agent configurations (.agent, .agents, .claude, .junie, .vibe). Updates skills-lock.json to track the new skill with computed hash. Co-Authored-By: Claude Sonnet 4.6 --- .agent/skills/gh-cli | 1 + .agents/skills/gh-cli/SKILL.md | 2187 ++++++++++++++++++++++++++++++++ .claude/skills/gh-cli | 1 + .junie/skills/gh-cli | 1 + .vibe/skills/gh-cli | 1 + skills-lock.json | 5 + 6 files changed, 2196 insertions(+) create mode 120000 .agent/skills/gh-cli create mode 100644 .agents/skills/gh-cli/SKILL.md create mode 120000 .claude/skills/gh-cli create mode 120000 .junie/skills/gh-cli create mode 120000 .vibe/skills/gh-cli diff --git a/.agent/skills/gh-cli b/.agent/skills/gh-cli new file mode 120000 index 0000000..0026ee7 --- /dev/null +++ b/.agent/skills/gh-cli @@ -0,0 +1 @@ +../../.agents/skills/gh-cli \ No newline at end of file diff --git a/.agents/skills/gh-cli/SKILL.md b/.agents/skills/gh-cli/SKILL.md new file mode 100644 index 0000000..6756f0b --- /dev/null +++ b/.agents/skills/gh-cli/SKILL.md @@ -0,0 +1,2187 @@ +--- +name: gh-cli +description: GitHub CLI (gh) comprehensive reference for repositories, issues, pull requests, Actions, projects, releases, gists, codespaces, organizations, extensions, and all GitHub operations from the command line. +--- + +# GitHub CLI (gh) + +Comprehensive reference for GitHub CLI (gh) - work seamlessly with GitHub from the command line. + +**Version:** 2.85.0 (current as of January 2026) + +## Prerequisites + +### Installation + +```bash +# macOS +brew install gh + +# Linux +curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg +echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null +sudo apt update +sudo apt install gh + +# Windows +winget install --id GitHub.cli + +# Verify installation +gh --version +``` + +### Authentication + +```bash +# Interactive login (default: github.com) +gh auth login + +# Login with specific hostname +gh auth login --hostname enterprise.internal + +# Login with token +gh auth login --with-token < mytoken.txt + +# Check authentication status +gh auth status + +# Switch accounts +gh auth switch --hostname github.com --user username + +# Logout +gh auth logout --hostname github.com --user username +``` + +### Setup Git Integration + +```bash +# Configure git to use gh as credential helper +gh auth setup-git + +# View active token +gh auth token + +# Refresh authentication scopes +gh auth refresh --scopes write:org,read:public_key +``` + +## CLI Structure + +``` +gh # Root command +├── auth # Authentication +│ ├── login +│ ├── logout +│ ├── refresh +│ ├── setup-git +│ ├── status +│ ├── switch +│ └── token +├── browse # Open in browser +├── codespace # GitHub Codespaces +│ ├── code +│ ├── cp +│ ├── create +│ ├── delete +│ ├── edit +│ ├── jupyter +│ ├── list +│ ├── logs +│ ├── ports +│ ├── rebuild +│ ├── ssh +│ ├── stop +│ └── view +├── gist # Gists +│ ├── clone +│ ├── create +│ ├── delete +│ ├── edit +│ ├── list +│ ├── rename +│ └── view +├── issue # Issues +│ ├── create +│ ├── list +│ ├── status +│ ├── close +│ ├── comment +│ ├── delete +│ ├── develop +│ ├── edit +│ ├── lock +│ ├── pin +│ ├── reopen +│ ├── transfer +│ ├── unlock +│ └── view +├── org # Organizations +│ └── list +├── pr # Pull Requests +│ ├── create +│ ├── list +│ ├── status +│ ├── checkout +│ ├── checks +│ ├── close +│ ├── comment +│ ├── diff +│ ├── edit +│ ├── lock +│ ├── merge +│ ├── ready +│ ├── reopen +│ ├── revert +│ ├── review +│ ├── unlock +│ ├── update-branch +│ └── view +├── project # Projects +│ ├── close +│ ├── copy +│ ├── create +│ ├── delete +│ ├── edit +│ ├── field-create +│ ├── field-delete +│ ├── field-list +│ ├── item-add +│ ├── item-archive +│ ├── item-create +│ ├── item-delete +│ ├── item-edit +│ ├── item-list +│ ├── link +│ ├── list +│ ├── mark-template +│ ├── unlink +│ └── view +├── release # Releases +│ ├── create +│ ├── list +│ ├── delete +│ ├── delete-asset +│ ├── download +│ ├── edit +│ ├── upload +│ ├── verify +│ ├── verify-asset +│ └── view +├── repo # Repositories +│ ├── create +│ ├── list +│ ├── archive +│ ├── autolink +│ ├── clone +│ ├── delete +│ ├── deploy-key +│ ├── edit +│ ├── fork +│ ├── gitignore +│ ├── license +│ ├── rename +│ ├── set-default +│ ├── sync +│ ├── unarchive +│ └── view +├── cache # Actions caches +│ ├── delete +│ └── list +├── run # Workflow runs +│ ├── cancel +│ ├── delete +│ ├── download +│ ├── list +│ ├── rerun +│ ├── view +│ └── watch +├── workflow # Workflows +│ ├── disable +│ ├── enable +│ ├── list +│ ├── run +│ └── view +├── agent-task # Agent tasks +├── alias # Command aliases +│ ├── delete +│ ├── import +│ ├── list +│ └── set +├── api # API requests +├── attestation # Artifact attestations +│ ├── download +│ ├── trusted-root +│ └── verify +├── completion # Shell completion +├── config # Configuration +│ ├── clear-cache +│ ├── get +│ ├── list +│ └── set +├── extension # Extensions +│ ├── browse +│ ├── create +│ ├── exec +│ ├── install +│ ├── list +│ ├── remove +│ ├── search +│ └── upgrade +├── gpg-key # GPG keys +│ ├── add +│ ├── delete +│ └── list +├── label # Labels +│ ├── clone +│ ├── create +│ ├── delete +│ ├── edit +│ └── list +├── preview # Preview features +├── ruleset # Rulesets +│ ├── check +│ ├── list +│ └── view +├── search # Search +│ ├── code +│ ├── commits +│ ├── issues +│ ├── prs +│ └── repos +├── secret # Secrets +│ ├── delete +│ ├── list +│ └── set +├── ssh-key # SSH keys +│ ├── add +│ ├── delete +│ └── list +├── status # Status overview +└── variable # Variables + ├── delete + ├── get + ├── list + └── set +``` + +## Configuration + +### Global Configuration + +```bash +# List all configuration +gh config list + +# Get specific configuration value +gh config list git_protocol +gh config get editor + +# Set configuration value +gh config set editor vim +gh config set git_protocol ssh +gh config set prompt disabled +gh config set pager "less -R" + +# Clear configuration cache +gh config clear-cache +``` + +### Environment Variables + +```bash +# GitHub token (for automation) +export GH_TOKEN=ghp_xxxxxxxxxxxx + +# GitHub hostname +export GH_HOST=github.com + +# Disable prompts +export GH_PROMPT_DISABLED=true + +# Custom editor +export GH_EDITOR=vim + +# Custom pager +export GH_PAGER=less + +# HTTP timeout +export GH_TIMEOUT=30 + +# Custom repository (override default) +export GH_REPO=owner/repo + +# Custom git protocol +export GH_ENTERPRISE_HOSTNAME=hostname +``` + +## Authentication (gh auth) + +### Login + +```bash +# Interactive login +gh auth login + +# Web-based authentication +gh auth login --web + +# With clipboard for OAuth code +gh auth login --web --clipboard + +# With specific git protocol +gh auth login --git-protocol ssh + +# With custom hostname (GitHub Enterprise) +gh auth login --hostname enterprise.internal + +# Login with token from stdin +gh auth login --with-token < token.txt + +# Insecure storage (plain text) +gh auth login --insecure-storage +``` + +### Status + +```bash +# Show all authentication status +gh auth status + +# Show active account only +gh auth status --active + +# Show specific hostname +gh auth status --hostname github.com + +# Show token in output +gh auth status --show-token + +# JSON output +gh auth status --json hosts + +# Filter with jq +gh auth status --json hosts --jq '.hosts | add' +``` + +### Switch Accounts + +```bash +# Interactive switch +gh auth switch + +# Switch to specific user/host +gh auth switch --hostname github.com --user monalisa +``` + +### Token + +```bash +# Print authentication token +gh auth token + +# Token for specific host/user +gh auth token --hostname github.com --user monalisa +``` + +### Refresh + +```bash +# Refresh credentials +gh auth refresh + +# Add scopes +gh auth refresh --scopes write:org,read:public_key + +# Remove scopes +gh auth refresh --remove-scopes delete_repo + +# Reset to default scopes +gh auth refresh --reset-scopes + +# With clipboard +gh auth refresh --clipboard +``` + +### Setup Git + +```bash +# Setup git credential helper +gh auth setup-git + +# Setup for specific host +gh auth setup-git --hostname enterprise.internal + +# Force setup even if host not known +gh auth setup-git --hostname enterprise.internal --force +``` + +## Browse (gh browse) + +```bash +# Open repository in browser +gh browse + +# Open specific path +gh browse script/ +gh browse main.go:312 + +# Open issue or PR +gh browse 123 + +# Open commit +gh browse 77507cd94ccafcf568f8560cfecde965fcfa63 + +# Open with specific branch +gh browse main.go --branch bug-fix + +# Open different repository +gh browse --repo owner/repo + +# Open specific pages +gh browse --actions # Actions tab +gh browse --projects # Projects tab +gh browse --releases # Releases tab +gh browse --settings # Settings page +gh browse --wiki # Wiki page + +# Print URL instead of opening +gh browse --no-browser +``` + +## Repositories (gh repo) + +### Create Repository + +```bash +# Create new repository +gh repo create my-repo + +# Create with description +gh repo create my-repo --description "My awesome project" + +# Create public repository +gh repo create my-repo --public + +# Create private repository +gh repo create my-repo --private + +# Create with homepage +gh repo create my-repo --homepage https://example.com + +# Create with license +gh repo create my-repo --license mit + +# Create with gitignore +gh repo create my-repo --gitignore python + +# Initialize as template repository +gh repo create my-repo --template + +# Create repository in organization +gh repo create org/my-repo + +# Create without cloning locally +gh repo create my-repo --source=. + +# Disable issues +gh repo create my-repo --disable-issues + +# Disable wiki +gh repo create my-repo --disable-wiki +``` + +### Clone Repository + +```bash +# Clone repository +gh repo clone owner/repo + +# Clone to specific directory +gh repo clone owner/repo my-directory + +# Clone with different branch +gh repo clone owner/repo --branch develop +``` + +### List Repositories + +```bash +# List all repositories +gh repo list + +# List repositories for owner +gh repo list owner + +# Limit results +gh repo list --limit 50 + +# Public repositories only +gh repo list --public + +# Source repositories only (not forks) +gh repo list --source + +# JSON output +gh repo list --json name,visibility,owner + +# Table output +gh repo list --limit 100 | tail -n +2 + +# Filter with jq +gh repo list --json name --jq '.[].name' +``` + +### View Repository + +```bash +# View repository details +gh repo view + +# View specific repository +gh repo view owner/repo + +# JSON output +gh repo view --json name,description,defaultBranchRef + +# View in browser +gh repo view --web +``` + +### Edit Repository + +```bash +# Edit description +gh repo edit --description "New description" + +# Set homepage +gh repo edit --homepage https://example.com + +# Change visibility +gh repo edit --visibility private +gh repo edit --visibility public + +# Enable/disable features +gh repo edit --enable-issues +gh repo edit --disable-issues +gh repo edit --enable-wiki +gh repo edit --disable-wiki +gh repo edit --enable-projects +gh repo edit --disable-projects + +# Set default branch +gh repo edit --default-branch main + +# Rename repository +gh repo rename new-name + +# Archive repository +gh repo archive +gh repo unarchive +``` + +### Delete Repository + +```bash +# Delete repository +gh repo delete owner/repo + +# Confirm without prompt +gh repo delete owner/repo --yes +``` + +### Fork Repository + +```bash +# Fork repository +gh repo fork owner/repo + +# Fork to organization +gh repo fork owner/repo --org org-name + +# Clone after forking +gh repo fork owner/repo --clone + +# Remote name for fork +gh repo fork owner/repo --remote-name upstream +``` + +### Sync Fork + +```bash +# Sync fork with upstream +gh repo sync + +# Sync specific branch +gh repo sync --branch feature + +# Force sync +gh repo sync --force +``` + +### Set Default Repository + +```bash +# Set default repository for current directory +gh repo set-default + +# Set default explicitly +gh repo set-default owner/repo + +# Unset default +gh repo set-default --unset +``` + +### Repository Autolinks + +```bash +# List autolinks +gh repo autolink list + +# Add autolink +gh repo autolink add \ + --key-prefix JIRA- \ + --url-template https://jira.example.com/browse/ + +# Delete autolink +gh repo autolink delete 12345 +``` + +### Repository Deploy Keys + +```bash +# List deploy keys +gh repo deploy-key list + +# Add deploy key +gh repo deploy-key add ~/.ssh/id_rsa.pub \ + --title "Production server" \ + --read-only + +# Delete deploy key +gh repo deploy-key delete 12345 +``` + +### Gitignore and License + +```bash +# View gitignore template +gh repo gitignore + +# View license template +gh repo license mit + +# License with full name +gh repo license mit --fullname "John Doe" +``` + +## Issues (gh issue) + +### Create Issue + +```bash +# Create issue interactively +gh issue create + +# Create with title +gh issue create --title "Bug: Login not working" + +# Create with title and body +gh issue create \ + --title "Bug: Login not working" \ + --body "Steps to reproduce..." + +# Create with body from file +gh issue create --body-file issue.md + +# Create with labels +gh issue create --title "Fix bug" --labels bug,high-priority + +# Create with assignees +gh issue create --title "Fix bug" --assignee user1,user2 + +# Create in specific repository +gh issue create --repo owner/repo --title "Issue title" + +# Create issue from web +gh issue create --web +``` + +### List Issues + +```bash +# List all open issues +gh issue list + +# List all issues (including closed) +gh issue list --state all + +# List closed issues +gh issue list --state closed + +# Limit results +gh issue list --limit 50 + +# Filter by assignee +gh issue list --assignee username +gh issue list --assignee @me + +# Filter by labels +gh issue list --labels bug,enhancement + +# Filter by milestone +gh issue list --milestone "v1.0" + +# Search/filter +gh issue list --search "is:open is:issue label:bug" + +# JSON output +gh issue list --json number,title,state,author + +# Table view +gh issue list --json number,title,labels --jq '.[] | [.number, .title, .labels[].name] | @tsv' + +# Show comments count +gh issue list --json number,title,comments --jq '.[] | [.number, .title, .comments]' + +# Sort by +gh issue list --sort created --order desc +``` + +### View Issue + +```bash +# View issue +gh issue view 123 + +# View with comments +gh issue view 123 --comments + +# View in browser +gh issue view 123 --web + +# JSON output +gh issue view 123 --json title,body,state,labels,comments + +# View specific fields +gh issue view 123 --json title --jq '.title' +``` + +### Edit Issue + +```bash +# Edit interactively +gh issue edit 123 + +# Edit title +gh issue edit 123 --title "New title" + +# Edit body +gh issue edit 123 --body "New description" + +# Add labels +gh issue edit 123 --add-label bug,high-priority + +# Remove labels +gh issue edit 123 --remove-label stale + +# Add assignees +gh issue edit 123 --add-assignee user1,user2 + +# Remove assignees +gh issue edit 123 --remove-assignee user1 + +# Set milestone +gh issue edit 123 --milestone "v1.0" +``` + +### Close/Reopen Issue + +```bash +# Close issue +gh issue close 123 + +# Close with comment +gh issue close 123 --comment "Fixed in PR #456" + +# Reopen issue +gh issue reopen 123 +``` + +### Comment on Issue + +```bash +# Add comment +gh issue comment 123 --body "This looks good!" + +# Edit comment +gh issue comment 123 --edit 456789 --body "Updated comment" + +# Delete comment +gh issue comment 123 --delete 456789 +``` + +### Issue Status + +```bash +# Show issue status summary +gh issue status + +# Status for specific repository +gh issue status --repo owner/repo +``` + +### Pin/Unpin Issues + +```bash +# Pin issue (pinned to repo dashboard) +gh issue pin 123 + +# Unpin issue +gh issue unpin 123 +``` + +### Lock/Unlock Issue + +```bash +# Lock conversation +gh issue lock 123 + +# Lock with reason +gh issue lock 123 --reason off-topic + +# Unlock +gh issue unlock 123 +``` + +### Transfer Issue + +```bash +# Transfer to another repository +gh issue transfer 123 --repo owner/new-repo +``` + +### Delete Issue + +```bash +# Delete issue +gh issue delete 123 + +# Confirm without prompt +gh issue delete 123 --yes +``` + +### Develop Issue (Draft PR) + +```bash +# Create draft PR from issue +gh issue develop 123 + +# Create in specific branch +gh issue develop 123 --branch fix/issue-123 + +# Create with base branch +gh issue develop 123 --base main +``` + +## Pull Requests (gh pr) + +### Create Pull Request + +```bash +# Create PR interactively +gh pr create + +# Create with title +gh pr create --title "Feature: Add new functionality" + +# Create with title and body +gh pr create \ + --title "Feature: Add new functionality" \ + --body "This PR adds..." + +# Fill body from template +gh pr create --body-file .github/PULL_REQUEST_TEMPLATE.md + +# Set base branch +gh pr create --base main + +# Set head branch (default: current branch) +gh pr create --head feature-branch + +# Create draft PR +gh pr create --draft + +# Add assignees +gh pr create --assignee user1,user2 + +# Add reviewers +gh pr create --reviewer user1,user2 + +# Add labels +gh pr create --labels enhancement,feature + +# Link to issue +gh pr create --issue 123 + +# Create in specific repository +gh pr create --repo owner/repo + +# Open in browser after creation +gh pr create --web +``` + +### List Pull Requests + +```bash +# List open PRs +gh pr list + +# List all PRs +gh pr list --state all + +# List merged PRs +gh pr list --state merged + +# List closed (not merged) PRs +gh pr list --state closed + +# Filter by head branch +gh pr list --head feature-branch + +# Filter by base branch +gh pr list --base main + +# Filter by author +gh pr list --author username +gh pr list --author @me + +# Filter by assignee +gh pr list --assignee username + +# Filter by labels +gh pr list --labels bug,enhancement + +# Limit results +gh pr list --limit 50 + +# Search +gh pr list --search "is:open is:pr label:review-required" + +# JSON output +gh pr list --json number,title,state,author,headRefName + +# Show check status +gh pr list --json number,title,statusCheckRollup --jq '.[] | [.number, .title, .statusCheckRollup[]?.status]' + +# Sort by +gh pr list --sort created --order desc +``` + +### View Pull Request + +```bash +# View PR +gh pr view 123 + +# View with comments +gh pr view 123 --comments + +# View in browser +gh pr view 123 --web + +# JSON output +gh pr view 123 --json title,body,state,author,commits,files + +# View diff +gh pr view 123 --json files --jq '.files[].path' + +# View with jq query +gh pr view 123 --json title,state --jq '"\(.title): \(.state)"' +``` + +### Checkout Pull Request + +```bash +# Checkout PR branch +gh pr checkout 123 + +# Checkout with specific branch name +gh pr checkout 123 --branch name-123 + +# Force checkout +gh pr checkout 123 --force +``` + +### Diff Pull Request + +```bash +# View PR diff +gh pr diff 123 + +# View diff with color +gh pr diff 123 --color always + +# Output to file +gh pr diff 123 > pr-123.patch + +# View diff of specific files +gh pr diff 123 --name-only +``` + +### Merge Pull Request + +```bash +# Merge PR +gh pr merge 123 + +# Merge with specific method +gh pr merge 123 --merge +gh pr merge 123 --squash +gh pr merge 123 --rebase + +# Delete branch after merge +gh pr merge 123 --delete-branch + +# Merge with comment +gh pr merge 123 --subject "Merge PR #123" --body "Merging feature" + +# Merge draft PR +gh pr merge 123 --admin + +# Force merge (skip checks) +gh pr merge 123 --admin +``` + +### Close Pull Request + +```bash +# Close PR (as draft, not merge) +gh pr close 123 + +# Close with comment +gh pr close 123 --comment "Closing due to..." +``` + +### Reopen Pull Request + +```bash +# Reopen closed PR +gh pr reopen 123 +``` + +### Edit Pull Request + +```bash +# Edit interactively +gh pr edit 123 + +# Edit title +gh pr edit 123 --title "New title" + +# Edit body +gh pr edit 123 --body "New description" + +# Add labels +gh pr edit 123 --add-label bug,enhancement + +# Remove labels +gh pr edit 123 --remove-label stale + +# Add assignees +gh pr edit 123 --add-assignee user1,user2 + +# Remove assignees +gh pr edit 123 --remove-assignee user1 + +# Add reviewers +gh pr edit 123 --add-reviewer user1,user2 + +# Remove reviewers +gh pr edit 123 --remove-reviewer user1 + +# Mark as ready for review +gh pr edit 123 --ready +``` + +### Ready for Review + +```bash +# Mark draft PR as ready +gh pr ready 123 +``` + +### Pull Request Checks + +```bash +# View PR checks +gh pr checks 123 + +# Watch checks in real-time +gh pr checks 123 --watch + +# Watch interval (seconds) +gh pr checks 123 --watch --interval 5 +``` + +### Comment on Pull Request + +```bash +# Add comment +gh pr comment 123 --body "Looks good!" + +# Comment on specific line +gh pr comment 123 --body "Fix this" \ + --repo owner/repo \ + --head-owner owner --head-branch feature + +# Edit comment +gh pr comment 123 --edit 456789 --body "Updated" + +# Delete comment +gh pr comment 123 --delete 456789 +``` + +### Review Pull Request + +```bash +# Review PR (opens editor) +gh pr review 123 + +# Approve PR +gh pr review 123 --approve --body "LGTM!" + +# Request changes +gh pr review 123 --request-changes \ + --body "Please fix these issues" + +# Comment on PR +gh pr review 123 --comment --body "Some thoughts..." + +# Dismiss review +gh pr review 123 --dismiss +``` + +### Update Branch + +```bash +# Update PR branch with latest base branch +gh pr update-branch 123 + +# Force update +gh pr update-branch 123 --force + +# Use merge strategy +gh pr update-branch 123 --merge +``` + +### Lock/Unlock Pull Request + +```bash +# Lock PR conversation +gh pr lock 123 + +# Lock with reason +gh pr lock 123 --reason off-topic + +# Unlock +gh pr unlock 123 +``` + +### Revert Pull Request + +```bash +# Revert merged PR +gh pr revert 123 + +# Revert with specific branch name +gh pr revert 123 --branch revert-pr-123 +``` + +### Pull Request Status + +```bash +# Show PR status summary +gh pr status + +# Status for specific repository +gh pr status --repo owner/repo +``` + +## GitHub Actions + +### Workflow Runs (gh run) + +```bash +# List workflow runs +gh run list + +# List for specific workflow +gh run list --workflow "ci.yml" + +# List for specific branch +gh run list --branch main + +# Limit results +gh run list --limit 20 + +# JSON output +gh run list --json databaseId,status,conclusion,headBranch + +# View run details +gh run view 123456789 + +# View run with verbose logs +gh run view 123456789 --log + +# View specific job +gh run view 123456789 --job 987654321 + +# View in browser +gh run view 123456789 --web + +# Watch run in real-time +gh run watch 123456789 + +# Watch with interval +gh run watch 123456789 --interval 5 + +# Rerun failed run +gh run rerun 123456789 + +# Rerun specific job +gh run rerun 123456789 --job 987654321 + +# Cancel run +gh run cancel 123456789 + +# Delete run +gh run delete 123456789 + +# Download run artifacts +gh run download 123456789 + +# Download specific artifact +gh run download 123456789 --name build + +# Download to directory +gh run download 123456789 --dir ./artifacts +``` + +### Workflows (gh workflow) + +```bash +# List workflows +gh workflow list + +# View workflow details +gh workflow view ci.yml + +# View workflow YAML +gh workflow view ci.yml --yaml + +# View in browser +gh workflow view ci.yml --web + +# Enable workflow +gh workflow enable ci.yml + +# Disable workflow +gh workflow disable ci.yml + +# Run workflow manually +gh workflow run ci.yml + +# Run with inputs +gh workflow run ci.yml \ + --raw-field \ + version="1.0.0" \ + environment="production" + +# Run from specific branch +gh workflow run ci.yml --ref develop +``` + +### Action Caches (gh cache) + +```bash +# List caches +gh cache list + +# List for specific branch +gh cache list --branch main + +# List with limit +gh cache list --limit 50 + +# Delete cache +gh cache delete 123456789 + +# Delete all caches +gh cache delete --all +``` + +### Action Secrets (gh secret) + +```bash +# List secrets +gh secret list + +# Set secret (prompts for value) +gh secret set MY_SECRET + +# Set secret from environment +echo "$MY_SECRET" | gh secret set MY_SECRET + +# Set secret for specific environment +gh secret set MY_SECRET --env production + +# Set secret for organization +gh secret set MY_SECRET --org orgname + +# Delete secret +gh secret delete MY_SECRET + +# Delete from environment +gh secret delete MY_SECRET --env production +``` + +### Action Variables (gh variable) + +```bash +# List variables +gh variable list + +# Set variable +gh variable set MY_VAR "some-value" + +# Set variable for environment +gh variable set MY_VAR "value" --env production + +# Set variable for organization +gh variable set MY_VAR "value" --org orgname + +# Get variable value +gh variable get MY_VAR + +# Delete variable +gh variable delete MY_VAR + +# Delete from environment +gh variable delete MY_VAR --env production +``` + +## Projects (gh project) + +```bash +# List projects +gh project list + +# List for owner +gh project list --owner owner + +# Open projects +gh project list --open + +# View project +gh project view 123 + +# View project items +gh project view 123 --format json + +# Create project +gh project create --title "My Project" + +# Create in organization +gh project create --title "Project" --org orgname + +# Create with readme +gh project create --title "Project" --readme "Description here" + +# Edit project +gh project edit 123 --title "New Title" + +# Delete project +gh project delete 123 + +# Close project +gh project close 123 + +# Copy project +gh project copy 123 --owner target-owner --title "Copy" + +# Mark template +gh project mark-template 123 + +# List fields +gh project field-list 123 + +# Create field +gh project field-create 123 --title "Status" --datatype single_select + +# Delete field +gh project field-delete 123 --id 456 + +# List items +gh project item-list 123 + +# Create item +gh project item-create 123 --title "New item" + +# Add item to project +gh project item-add 123 --owner-owner --repo repo --issue 456 + +# Edit item +gh project item-edit 123 --id 456 --title "Updated title" + +# Delete item +gh project item-delete 123 --id 456 + +# Archive item +gh project item-archive 123 --id 456 + +# Link items +gh project link 123 --id 456 --link-id 789 + +# Unlink items +gh project unlink 123 --id 456 --link-id 789 + +# View project in browser +gh project view 123 --web +``` + +## Releases (gh release) + +```bash +# List releases +gh release list + +# View latest release +gh release view + +# View specific release +gh release view v1.0.0 + +# View in browser +gh release view v1.0.0 --web + +# Create release +gh release create v1.0.0 \ + --notes "Release notes here" + +# Create release with notes from file +gh release create v1.0.0 --notes-file notes.md + +# Create release with target +gh release create v1.0.0 --target main + +# Create release as draft +gh release create v1.0.0 --draft + +# Create pre-release +gh release create v1.0.0 --prerelease + +# Create release with title +gh release create v1.0.0 --title "Version 1.0.0" + +# Upload asset to release +gh release upload v1.0.0 ./file.tar.gz + +# Upload multiple assets +gh release upload v1.0.0 ./file1.tar.gz ./file2.tar.gz + +# Upload with label (casing sensitive) +gh release upload v1.0.0 ./file.tar.gz --casing + +# Delete release +gh release delete v1.0.0 + +# Delete with cleanup tag +gh release delete v1.0.0 --yes + +# Delete specific asset +gh release delete-asset v1.0.0 file.tar.gz + +# Download release assets +gh release download v1.0.0 + +# Download specific asset +gh release download v1.0.0 --pattern "*.tar.gz" + +# Download to directory +gh release download v1.0.0 --dir ./downloads + +# Download archive (zip/tar) +gh release download v1.0.0 --archive zip + +# Edit release +gh release edit v1.0.0 --notes "Updated notes" + +# Verify release signature +gh release verify v1.0.0 + +# Verify specific asset +gh release verify-asset v1.0.0 file.tar.gz +``` + +## Gists (gh gist) + +```bash +# List gists +gh gist list + +# List all gists (including private) +gh gist list --public + +# Limit results +gh gist list --limit 20 + +# View gist +gh gist view abc123 + +# View gist files +gh gist view abc123 --files + +# Create gist +gh gist create script.py + +# Create gist with description +gh gist create script.py --desc "My script" + +# Create public gist +gh gist create script.py --public + +# Create multi-file gist +gh gist create file1.py file2.py + +# Create from stdin +echo "print('hello')" | gh gist create + +# Edit gist +gh gist edit abc123 + +# Delete gist +gh gist delete abc123 + +# Rename gist file +gh gist rename abc123 --filename old.py new.py + +# Clone gist +gh gist clone abc123 + +# Clone to directory +gh gist clone abc123 my-directory +``` + +## Codespaces (gh codespace) + +```bash +# List codespaces +gh codespace list + +# Create codespace +gh codespace create + +# Create with specific repository +gh codespace create --repo owner/repo + +# Create with branch +gh codespace create --branch develop + +# Create with specific machine +gh codespace create --machine premiumLinux + +# View codespace details +gh codespace view + +# SSH into codespace +gh codespace ssh + +# SSH with specific command +gh codespace ssh --command "cd /workspaces && ls" + +# Open codespace in browser +gh codespace code + +# Open in VS Code +gh codespace code --codec + +# Open with specific path +gh codespace code --path /workspaces/repo + +# Stop codespace +gh codespace stop + +# Delete codespace +gh codespace delete + +# View logs +gh codespace logs + +--tail 100 + +# View ports +gh codespace ports + +# Forward port +gh codespace cp 8080:8080 + +# Rebuild codespace +gh codespace rebuild + +# Edit codespace +gh codespace edit --machine standardLinux + +# Jupyter support +gh codespace jupyter + +# Copy files to/from codespace +gh codespace cp file.txt :/workspaces/file.txt +gh codespace cp :/workspaces/file.txt ./file.txt +``` + +## Organizations (gh org) + +```bash +# List organizations +gh org list + +# List for user +gh org list --user username + +# JSON output +gh org list --json login,name,description + +# View organization +gh org view orgname + +# View organization members +gh org view orgname --json members --jq '.members[] | .login' +``` + +## Search (gh search) + +```bash +# Search code +gh search code "TODO" + +# Search in specific repository +gh search code "TODO" --repo owner/repo + +# Search commits +gh search commits "fix bug" + +# Search issues +gh search issues "label:bug state:open" + +# Search PRs +gh search prs "is:open is:pr review:required" + +# Search repositories +gh search repos "stars:>1000 language:python" + +# Limit results +gh search repos "topic:api" --limit 50 + +# JSON output +gh search repos "stars:>100" --json name,description,stargazers + +# Order results +gh search repos "language:rust" --order desc --sort stars + +# Search with extensions +gh search code "import" --extension py + +# Web search (open in browser) +gh search prs "is:open" --web +``` + +## Labels (gh label) + +```bash +# List labels +gh label list + +# Create label +gh label create bug --color "d73a4a" --description "Something isn't working" + +# Create with hex color +gh label create enhancement --color "#a2eeef" + +# Edit label +gh label edit bug --name "bug-report" --color "ff0000" + +# Delete label +gh label delete bug + +# Clone labels from repository +gh label clone owner/repo + +# Clone to specific repository +gh label clone owner/repo --repo target/repo +``` + +## SSH Keys (gh ssh-key) + +```bash +# List SSH keys +gh ssh-key list + +# Add SSH key +gh ssh-key add ~/.ssh/id_rsa.pub --title "My laptop" + +# Add key with type +gh ssh-key add ~/.ssh/id_ed25519.pub --type "authentication" + +# Delete SSH key +gh ssh-key delete 12345 + +# Delete by title +gh ssh-key delete --title "My laptop" +``` + +## GPG Keys (gh gpg-key) + +```bash +# List GPG keys +gh gpg-key list + +# Add GPG key +gh gpg-key add ~/.ssh/id_rsa.pub + +# Delete GPG key +gh gpg-key delete 12345 + +# Delete by key ID +gh gpg-key delete ABCD1234 +``` + +## Status (gh status) + +```bash +# Show status overview +gh status + +# Status for specific repositories +gh status --repo owner/repo + +# JSON output +gh status --json +``` + +## Configuration (gh config) + +```bash +# List all config +gh config list + +# Get specific value +gh config get editor + +# Set value +gh config set editor vim + +# Set git protocol +gh config set git_protocol ssh + +# Clear cache +gh config clear-cache + +# Set prompt behavior +gh config set prompt disabled +gh config set prompt enabled +``` + +## Extensions (gh extension) + +```bash +# List installed extensions +gh extension list + +# Search extensions +gh extension search github + +# Install extension +gh extension install owner/extension-repo + +# Install from branch +gh extension install owner/extension-repo --branch develop + +# Upgrade extension +gh extension upgrade extension-name + +# Remove extension +gh extension remove extension-name + +# Create new extension +gh extension create my-extension + +# Browse extensions +gh extension browse + +# Execute extension command +gh extension exec my-extension --arg value +``` + +## Aliases (gh alias) + +```bash +# List aliases +gh alias list + +# Set alias +gh alias set prview 'pr view --web' + +# Set shell alias +gh alias set co 'pr checkout' --shell + +# Delete alias +gh alias delete prview + +# Import aliases +gh alias import ./aliases.sh +``` + +## API Requests (gh api) + +```bash +# Make API request +gh api /user + +# Request with method +gh api --method POST /repos/owner/repo/issues \ + --field title="Issue title" \ + --field body="Issue body" + +# Request with headers +gh api /user \ + --header "Accept: application/vnd.github.v3+json" + +# Request with pagination +gh api /user/repos --paginate + +# Raw output (no formatting) +gh api /user --raw + +# Include headers in output +gh api /user --include + +# Silent mode (no progress output) +gh api /user --silent + +# Input from file +gh api --input request.json + +# jq query on response +gh api /user --jq '.login' + +# Field from response +gh api /repos/owner/repo --jq '.stargazers_count' + +# GitHub Enterprise +gh api /user --hostname enterprise.internal + +# GraphQL query +gh api graphql \ + -f query=' + { + viewer { + login + repositories(first: 5) { + nodes { + name + } + } + } + }' +``` + +## Rulesets (gh ruleset) + +```bash +# List rulesets +gh ruleset list + +# View ruleset +gh ruleset view 123 + +# Check ruleset +gh ruleset check --branch feature + +# Check specific repository +gh ruleset check --repo owner/repo --branch main +``` + +## Attestations (gh attestation) + +```bash +# Download attestation +gh attestation download owner/repo \ + --artifact-id 123456 + +# Verify attestation +gh attestation verify owner/repo + +# Get trusted root +gh attestation trusted-root +``` + +## Completion (gh completion) + +```bash +# Generate shell completion +gh completion -s bash > ~/.gh-complete.bash +gh completion -s zsh > ~/.gh-complete.zsh +gh completion -s fish > ~/.gh-complete.fish +gh completion -s powershell > ~/.gh-complete.ps1 + +# Shell-specific instructions +gh completion --shell=bash +gh completion --shell=zsh +``` + +## Preview (gh preview) + +```bash +# List preview features +gh preview + +# Run preview script +gh preview prompter +``` + +## Agent Tasks (gh agent-task) + +```bash +# List agent tasks +gh agent-task list + +# View agent task +gh agent-task view 123 + +# Create agent task +gh agent-task create --description "My task" +``` + +## Global Flags + +| Flag | Description | +| -------------------------- | -------------------------------------- | +| `--help` / `-h` | Show help for command | +| `--version` | Show gh version | +| `--repo [HOST/]OWNER/REPO` | Select another repository | +| `--hostname HOST` | GitHub hostname | +| `--jq EXPRESSION` | Filter JSON output | +| `--json FIELDS` | Output JSON with specified fields | +| `--template STRING` | Format JSON using Go template | +| `--web` | Open in browser | +| `--paginate` | Make additional API calls | +| `--verbose` | Show verbose output | +| `--debug` | Show debug output | +| `--timeout SECONDS` | Maximum API request duration | +| `--cache CACHE` | Cache control (default, force, bypass) | + +## Output Formatting + +### JSON Output + +```bash +# Basic JSON +gh repo view --json name,description + +# Nested fields +gh repo view --json owner,name --jq '.owner.login + "/" + .name' + +# Array operations +gh pr list --json number,title --jq '.[] | select(.number > 100)' + +# Complex queries +gh issue list --json number,title,labels \ + --jq '.[] | {number, title: .title, tags: [.labels[].name]}' +``` + +### Template Output + +```bash +# Custom template +gh repo view \ + --template '{{.name}}: {{.description}}' + +# Multiline template +gh pr view 123 \ + --template 'Title: {{.title}} +Author: {{.author.login}} +State: {{.state}} +' +``` + +## Common Workflows + +### Create PR from Issue + +```bash +# Create branch from issue +gh issue develop 123 --branch feature/issue-123 + +# Make changes, commit, push +git add . +git commit -m "Fix issue #123" +git push + +# Create PR linking to issue +gh pr create --title "Fix #123" --body "Closes #123" +``` + +### Bulk Operations + +```bash +# Close multiple issues +gh issue list --search "label:stale" \ + --json number \ + --jq '.[].number' | \ + xargs -I {} gh issue close {} --comment "Closing as stale" + +# Add label to multiple PRs +gh pr list --search "review:required" \ + --json number \ + --jq '.[].number' | \ + xargs -I {} gh pr edit {} --add-label needs-review +``` + +### Repository Setup Workflow + +```bash +# Create repository with initial setup +gh repo create my-project --public \ + --description "My awesome project" \ + --clone \ + --gitignore python \ + --license mit + +cd my-project + +# Set up branches +git checkout -b develop +git push -u origin develop + +# Create labels +gh label create bug --color "d73a4a" --description "Bug report" +gh label create enhancement --color "a2eeef" --description "Feature request" +gh label create documentation --color "0075ca" --description "Documentation" +``` + +### CI/CD Workflow + +```bash +# Run workflow and wait +RUN_ID=$(gh workflow run ci.yml --ref main --jq '.databaseId') + +# Watch the run +gh run watch "$RUN_ID" + +# Download artifacts on completion +gh run download "$RUN_ID" --dir ./artifacts +``` + +### Fork Sync Workflow + +```bash +# Fork repository +gh repo fork original/repo --clone + +cd repo + +# Add upstream remote +git remote add upstream https://github.com/original/repo.git + +# Sync fork +gh repo sync + +# Or manual sync +git fetch upstream +git checkout main +git merge upstream/main +git push origin main +``` + +## Environment Setup + +### Shell Integration + +```bash +# Add to ~/.bashrc or ~/.zshrc +eval "$(gh completion -s bash)" # or zsh/fish + +# Create useful aliases +alias gs='gh status' +alias gpr='gh pr view --web' +alias gir='gh issue view --web' +alias gco='gh pr checkout' +``` + +### Git Configuration + +```bash +# Use gh as credential helper +gh auth setup-git + +# Set gh as default for repo operations +git config --global credential.helper 'gh !gh auth setup-git' + +# Or manually +git config --global credential.helper github +``` + +## Best Practices + +1. **Authentication**: Use environment variables for automation + + ```bash + export GH_TOKEN=$(gh auth token) + ``` + +2. **Default Repository**: Set default to avoid repetition + + ```bash + gh repo set-default owner/repo + ``` + +3. **JSON Parsing**: Use jq for complex data extraction + + ```bash + gh pr list --json number,title --jq '.[] | select(.title | contains("fix"))' + ``` + +4. **Pagination**: Use --paginate for large result sets + + ```bash + gh issue list --state all --paginate + ``` + +5. **Caching**: Use cache control for frequently accessed data + ```bash + gh api /user --cache force + ``` + +## Getting Help + +```bash +# General help +gh --help + +# Command help +gh pr --help +gh issue create --help + +# Help topics +gh help formatting +gh help environment +gh help exit-codes +gh help accessibility +``` + +## References + +- Official Manual: https://cli.github.com/manual/ +- GitHub Docs: https://docs.github.com/en/github-cli +- REST API: https://docs.github.com/en/rest +- GraphQL API: https://docs.github.com/en/graphql diff --git a/.claude/skills/gh-cli b/.claude/skills/gh-cli new file mode 120000 index 0000000..0026ee7 --- /dev/null +++ b/.claude/skills/gh-cli @@ -0,0 +1 @@ +../../.agents/skills/gh-cli \ No newline at end of file diff --git a/.junie/skills/gh-cli b/.junie/skills/gh-cli new file mode 120000 index 0000000..0026ee7 --- /dev/null +++ b/.junie/skills/gh-cli @@ -0,0 +1 @@ +../../.agents/skills/gh-cli \ No newline at end of file diff --git a/.vibe/skills/gh-cli b/.vibe/skills/gh-cli new file mode 120000 index 0000000..0026ee7 --- /dev/null +++ b/.vibe/skills/gh-cli @@ -0,0 +1 @@ +../../.agents/skills/gh-cli \ No newline at end of file diff --git a/skills-lock.json b/skills-lock.json index 6bf050e..3ce90f0 100644 --- a/skills-lock.json +++ b/skills-lock.json @@ -1,6 +1,11 @@ { "version": 1, "skills": { + "gh-cli": { + "source": "github/awesome-copilot", + "sourceType": "github", + "computedHash": "cbe644b0c6760ae2a1eaafa39133920d889331327065d92a131675a665273e56" + }, "git-commit": { "source": "github/awesome-copilot", "sourceType": "github", From f549939de03d6e4ff6b94a19d9b18dec4e7a818a Mon Sep 17 00:00:00 2001 From: Soham Banerjee Date: Mon, 23 Mar 2026 01:10:11 +0530 Subject: [PATCH 05/12] chore(skills): install git-flow-branch-creator skill across agent directories Co-Authored-By: Claude Sonnet 4.6 --- .agent/skills/git-flow-branch-creator | 1 + .../skills/git-flow-branch-creator/SKILL.md | 292 ++++++++++++++++++ .claude/skills/git-flow-branch-creator | 1 + .junie/skills/git-flow-branch-creator | 1 + .vibe/skills/git-flow-branch-creator | 1 + skills-lock.json | 5 + 6 files changed, 301 insertions(+) create mode 120000 .agent/skills/git-flow-branch-creator create mode 100644 .agents/skills/git-flow-branch-creator/SKILL.md create mode 120000 .claude/skills/git-flow-branch-creator create mode 120000 .junie/skills/git-flow-branch-creator create mode 120000 .vibe/skills/git-flow-branch-creator diff --git a/.agent/skills/git-flow-branch-creator b/.agent/skills/git-flow-branch-creator new file mode 120000 index 0000000..7370a43 --- /dev/null +++ b/.agent/skills/git-flow-branch-creator @@ -0,0 +1 @@ +../../.agents/skills/git-flow-branch-creator \ No newline at end of file diff --git a/.agents/skills/git-flow-branch-creator/SKILL.md b/.agents/skills/git-flow-branch-creator/SKILL.md new file mode 100644 index 0000000..ded80e2 --- /dev/null +++ b/.agents/skills/git-flow-branch-creator/SKILL.md @@ -0,0 +1,292 @@ +--- +name: git-flow-branch-creator +description: 'Intelligent Git Flow branch creator that analyzes git status/diff and creates appropriate branches following the nvie Git Flow branching model.' +--- + +### Instructions + +```xml + + Git Flow Branch Creator + This prompt analyzes your current git changes using git status and git diff (or git diff --cached), then intelligently determines the appropriate branch type according to the Git Flow branching model and creates a semantic branch name. + + Just run this prompt and Copilot will analyze your changes and create the appropriate Git Flow branch for you. + + +``` + +### Workflow + +**Follow these steps:** + +1. Run `git status` to review the current repository state and changed files. +2. Run `git diff` (for unstaged changes) or `git diff --cached` (for staged changes) to analyze the nature of changes. +3. Analyze the changes using the Git Flow Branch Analysis Framework below. +4. Determine the appropriate branch type based on the analysis. +5. Generate a semantic branch name following Git Flow conventions. +6. Create the branch and switch to it automatically. +7. Provide a summary of the analysis and next steps. + +### Git Flow Branch Analysis Framework + +```xml + + + + New features, enhancements, non-critical improvements + develop + develop + feature/descriptive-name or feature/ticket-number-description + + New functionality being added + UI/UX improvements + New API endpoints or methods + Database schema additions (non-breaking) + New configuration options + Performance improvements (non-critical) + + + + + Release preparation, version bumps, final testing + develop + develop AND master + release-X.Y.Z + + Version number changes + Build configuration updates + Documentation finalization + Minor bug fixes before release + Release notes updates + Dependency version locks + + + + + Critical production bug fixes requiring immediate deployment + master + develop AND master + hotfix-X.Y.Z or hotfix/critical-issue-description + + Security vulnerability fixes + Critical production bugs + Data corruption fixes + Service outage resolution + Emergency configuration changes + + + + +``` + +### Branch Naming Conventions + +```xml + + + feature/[ticket-number-]descriptive-name + + feature/user-authentication + feature/PROJ-123-shopping-cart + feature/api-rate-limiting + feature/dashboard-redesign + + + + + release-X.Y.Z + + release-1.2.0 + release-2.1.0 + release-1.0.0 + + + + + hotfix-X.Y.Z OR hotfix/critical-description + + hotfix-1.2.1 + hotfix/security-patch + hotfix/payment-gateway-fix + hotfix-2.1.1 + + + +``` + +### Analysis Process + +```xml + + + Change Nature Analysis + Examine the types of files modified and the nature of changes + + Look at file extensions, directory structure, and purpose + Determine if changes are additive, corrective, or preparatory + Assess if changes address critical issues or are developmental + + + + + Git Flow Classification + Map the changes to appropriate Git Flow branch type + + Are these critical fixes for production issues? + Consider hotfix branch + + Are these release preparation changes (version bumps, final tweaks)? + Consider release branch + Default to feature branch + + + + + + Branch Name Generation + Create semantic, descriptive branch name + + Use lowercase with hyphens + Name should clearly indicate the purpose + Add ticket numbers or project context when available + Avoid overly long names + + + +``` + +### Edge Cases and Validation + +```xml + + + Changes include both features and bug fixes + Prioritize the most significant change type or suggest splitting into multiple branches + + + + No changes detected in git status/diff + Inform user and suggest checking git status or making changes first + + + + Already on a feature/hotfix/release branch + Analyze if new branch is needed or if current branch is appropriate + + + + Suggested branch name already exists + Append incremental suffix or suggest alternative name + + +``` + +### Examples + +```xml + + + Added new user registration API endpoint + New functionality, additive changes, not critical + feature + feature/user-registration-api + git checkout -b feature/user-registration-api develop + + + + Fixed critical security vulnerability in authentication + Security fix, critical for production, immediate deployment needed + hotfix + hotfix/auth-security-patch + git checkout -b hotfix/auth-security-patch master + + + + Updated version to 2.1.0 and finalized release notes + Release preparation, version bump, documentation + release + release-2.1.0 + git checkout -b release-2.1.0 develop + + + + Improved database query performance and updated caching + Performance improvement, non-critical enhancement + feature + feature/database-performance-optimization + git checkout -b feature/database-performance-optimization develop + + +``` + +### Validation Checklist + +```xml + + + Repository is in a clean state (no uncommitted changes that would conflict) + Current branch is appropriate starting point (develop for features/releases, master for hotfixes) + Remote repository is up to date + + + + Change analysis covers all modified files + Branch type selection follows Git Flow principles + Branch name is semantic and follows conventions + Edge cases are considered and handled + + + + Target branch (develop/master) exists and is accessible + Proposed branch name doesn't conflict with existing branches + User has appropriate permissions to create branches + + +``` + +### Final Execution + +```xml + + + Output of git status command + Relevant portions of git diff output + Detailed analysis of what changes represent + Explanation of why specific branch type was chosen + + + + git checkout -b [branch-name] [source-branch] + Verify branch creation and current branch status + Provide guidance on next actions (commit changes, push branch, etc.) + + + + Suggest 2-3 alternative branch names if primary suggestion isn't suitable + Allow user to specify different branch type if analysis seems incorrect + + +``` + +### Git Flow Reference + +```xml + + + Production-ready code, every commit is a release + Integration branch for features, latest development changes + + + + Branch from develop, merge back to develop + Branch from develop, merge to both develop and master + Branch from master, merge to both develop and master + + + + Always use --no-ff flag to preserve branch history + Tag releases on master branch + Delete branches after successful merge + + +``` diff --git a/.claude/skills/git-flow-branch-creator b/.claude/skills/git-flow-branch-creator new file mode 120000 index 0000000..7370a43 --- /dev/null +++ b/.claude/skills/git-flow-branch-creator @@ -0,0 +1 @@ +../../.agents/skills/git-flow-branch-creator \ No newline at end of file diff --git a/.junie/skills/git-flow-branch-creator b/.junie/skills/git-flow-branch-creator new file mode 120000 index 0000000..7370a43 --- /dev/null +++ b/.junie/skills/git-flow-branch-creator @@ -0,0 +1 @@ +../../.agents/skills/git-flow-branch-creator \ No newline at end of file diff --git a/.vibe/skills/git-flow-branch-creator b/.vibe/skills/git-flow-branch-creator new file mode 120000 index 0000000..7370a43 --- /dev/null +++ b/.vibe/skills/git-flow-branch-creator @@ -0,0 +1 @@ +../../.agents/skills/git-flow-branch-creator \ No newline at end of file diff --git a/skills-lock.json b/skills-lock.json index 3ce90f0..9fc86a3 100644 --- a/skills-lock.json +++ b/skills-lock.json @@ -11,6 +11,11 @@ "sourceType": "github", "computedHash": "2607fc60629b82b257136dd2a7a373f0a4466c0b49df7746d845d59313c99b21" }, + "git-flow-branch-creator": { + "source": "github/awesome-copilot", + "sourceType": "github", + "computedHash": "8add10a7fce31ef99856d36bffeb90f1f1130f5afd88b111417c257f1e5d39bd" + }, "github-issues": { "source": "github/awesome-copilot", "sourceType": "github", From bee33ef6d6b7a3f82ebecb36184e26a47f7cd157 Mon Sep 17 00:00:00 2001 From: Soham Banerjee Date: Mon, 23 Mar 2026 10:22:40 +0530 Subject: [PATCH 06/12] chore(skills): install gh-fix-ci skill across agent directories Install the GitHub PR checks fix skill in multiple agent directories: - .agent/skills/gh-fix-ci (symlink) - .agents/skills/gh-fix-ci/ (full skill with LICENSE, SKILL.md, agents config, assets, scripts) - .claude/skills/gh-fix-ci (symlink) - .junie/skills/gh-fix-ci (symlink) - .vibe/skills/gh-fix-ci (symlink) - Update skills-lock.json Co-Authored-By: Claude Sonnet 4.6 --- .agent/skills/gh-fix-ci | 1 + .agents/skills/gh-fix-ci/LICENSE.txt | 201 +++++++ .agents/skills/gh-fix-ci/SKILL.md | 69 +++ .agents/skills/gh-fix-ci/agents/openai.yaml | 6 + .../skills/gh-fix-ci/assets/github-small.svg | 3 + .agents/skills/gh-fix-ci/assets/github.png | Bin 0 -> 1838 bytes .../gh-fix-ci/scripts/inspect_pr_checks.py | 509 ++++++++++++++++++ .claude/skills/gh-fix-ci | 1 + .junie/skills/gh-fix-ci | 1 + .vibe/skills/gh-fix-ci | 1 + skills-lock.json | 5 + 11 files changed, 797 insertions(+) create mode 120000 .agent/skills/gh-fix-ci create mode 100644 .agents/skills/gh-fix-ci/LICENSE.txt create mode 100644 .agents/skills/gh-fix-ci/SKILL.md create mode 100644 .agents/skills/gh-fix-ci/agents/openai.yaml create mode 100644 .agents/skills/gh-fix-ci/assets/github-small.svg create mode 100644 .agents/skills/gh-fix-ci/assets/github.png create mode 100755 .agents/skills/gh-fix-ci/scripts/inspect_pr_checks.py create mode 120000 .claude/skills/gh-fix-ci create mode 120000 .junie/skills/gh-fix-ci create mode 120000 .vibe/skills/gh-fix-ci diff --git a/.agent/skills/gh-fix-ci b/.agent/skills/gh-fix-ci new file mode 120000 index 0000000..786e31a --- /dev/null +++ b/.agent/skills/gh-fix-ci @@ -0,0 +1 @@ +../../.agents/skills/gh-fix-ci \ No newline at end of file diff --git a/.agents/skills/gh-fix-ci/LICENSE.txt b/.agents/skills/gh-fix-ci/LICENSE.txt new file mode 100644 index 0000000..13e25df --- /dev/null +++ b/.agents/skills/gh-fix-ci/LICENSE.txt @@ -0,0 +1,201 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf of + any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don\'t include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/.agents/skills/gh-fix-ci/SKILL.md b/.agents/skills/gh-fix-ci/SKILL.md new file mode 100644 index 0000000..76bdeb6 --- /dev/null +++ b/.agents/skills/gh-fix-ci/SKILL.md @@ -0,0 +1,69 @@ +--- +name: "gh-fix-ci" +description: "Use when a user asks to debug or fix failing GitHub PR checks that run in GitHub Actions; use `gh` to inspect checks and logs, summarize failure context, draft a fix plan, and implement only after explicit approval. Treat external providers (for example Buildkite) as out of scope and report only the details URL." +--- + + +# Gh Pr Checks Plan Fix + +## Overview + +Use gh to locate failing PR checks, fetch GitHub Actions logs for actionable failures, summarize the failure snippet, then propose a fix plan and implement after explicit approval. +- If a plan-oriented skill (for example `create-plan`) is available, use it; otherwise draft a concise plan inline and request approval before implementing. + +Prereq: authenticate with the standard GitHub CLI once (for example, run `gh auth login`), then confirm with `gh auth status` (repo + workflow scopes are typically required). + +## Inputs + +- `repo`: path inside the repo (default `.`) +- `pr`: PR number or URL (optional; defaults to current branch PR) +- `gh` authentication for the repo host + +## Quick start + +- `python "/scripts/inspect_pr_checks.py" --repo "." --pr ""` +- Add `--json` if you want machine-friendly output for summarization. + +## Workflow + +1. Verify gh authentication. + - Run `gh auth status` in the repo. + - If unauthenticated, ask the user to run `gh auth login` (ensuring repo + workflow scopes) before proceeding. +2. Resolve the PR. + - Prefer the current branch PR: `gh pr view --json number,url`. + - If the user provides a PR number or URL, use that directly. +3. Inspect failing checks (GitHub Actions only). + - Preferred: run the bundled script (handles gh field drift and job-log fallbacks): + - `python "/scripts/inspect_pr_checks.py" --repo "." --pr ""` + - Add `--json` for machine-friendly output. + - Manual fallback: + - `gh pr checks --json name,state,bucket,link,startedAt,completedAt,workflow` + - If a field is rejected, rerun with the available fields reported by `gh`. + - For each failing check, extract the run id from `detailsUrl` and run: + - `gh run view --json name,workflowName,conclusion,status,url,event,headBranch,headSha` + - `gh run view --log` + - If the run log says it is still in progress, fetch job logs directly: + - `gh api "/repos///actions/jobs//logs" > ""` +4. Scope non-GitHub Actions checks. + - If `detailsUrl` is not a GitHub Actions run, label it as external and only report the URL. + - Do not attempt Buildkite or other providers; keep the workflow lean. +5. Summarize failures for the user. + - Provide the failing check name, run URL (if any), and a concise log snippet. + - Call out missing logs explicitly. +6. Create a plan. + - Use the `create-plan` skill to draft a concise plan and request approval. +7. Implement after approval. + - Apply the approved plan, summarize diffs/tests, and ask about opening a PR. +8. Recheck status. + - After changes, suggest re-running the relevant tests and `gh pr checks` to confirm. + +## Bundled Resources + +### scripts/inspect_pr_checks.py + +Fetch failing PR checks, pull GitHub Actions logs, and extract a failure snippet. Exits non-zero when failures remain so it can be used in automation. + +Usage examples: +- `python "/scripts/inspect_pr_checks.py" --repo "." --pr "123"` +- `python "/scripts/inspect_pr_checks.py" --repo "." --pr "https://github.com/org/repo/pull/123" --json` +- `python "/scripts/inspect_pr_checks.py" --repo "." --max-lines 200 --context 40` diff --git a/.agents/skills/gh-fix-ci/agents/openai.yaml b/.agents/skills/gh-fix-ci/agents/openai.yaml new file mode 100644 index 0000000..262bd70 --- /dev/null +++ b/.agents/skills/gh-fix-ci/agents/openai.yaml @@ -0,0 +1,6 @@ +interface: + display_name: "GitHub Fix CI" + short_description: "Debug failing GitHub Actions CI" + icon_small: "./assets/github-small.svg" + icon_large: "./assets/github.png" + default_prompt: "Inspect failing GitHub Actions checks in this repo, summarize root cause, and propose a focused fix plan." diff --git a/.agents/skills/gh-fix-ci/assets/github-small.svg b/.agents/skills/gh-fix-ci/assets/github-small.svg new file mode 100644 index 0000000..828e9d9 --- /dev/null +++ b/.agents/skills/gh-fix-ci/assets/github-small.svg @@ -0,0 +1,3 @@ + + + diff --git a/.agents/skills/gh-fix-ci/assets/github.png b/.agents/skills/gh-fix-ci/assets/github.png new file mode 100644 index 0000000000000000000000000000000000000000..e23dbe59099a594c06a46081ce48b82aa409b0e7 GIT binary patch literal 1838 zcmV+}2hsS6P)2#A0PhyV#70VIF~kifn7w^eg%YirL-chAh}v*xMV zq6%xyJk#$zy?_4s523;h{t6YYPziB{N{BO5LY$!z;tZ7#XQ+fYLnXu+Dk0ABcNiJI zzrVxB$H(Nw_w%r}wicF`m;e7dCb%Yq4S#)o4R?2U69V}D{rz3^9uTs%wH0=DcEaxN zZrI%1bWPG9Z@DZ;xVgCr_xJbV@$oU_0K^;~9!|a@MV{b>kf*1oaB*>wjf5r~92|tx z)6_@G59Hwl#r-r7fDUEfg~k4H9iyx z1ijjKVJj;uVSRl)cm-?gyS|utNQESXNVBxK!_+o@etyF9^YbL=8j>Im2x$`H4A#Wj zc=fHD@a(A~db17TglueV^!|Nn5a```z;l5dX%R{b4#C;kSz{uRpY`2n$m4KX14lC< zkRj2boJ3x2=pu#_Jzb)WCmEd{U##it>#Ona*=*_{ZIF75ZHKKdOoV9S4T-CWDx_Aj zzrQ~zz2ePCO0cm646&$5Ui>}p+2=i^(hqoVOX}W4h*UZNdfH2Mt`C-$mPEgg)S|ky zb$hi@M>;g_p0^ry7o;1@o>&aLnh?}U#e5VeL@OO&Y=NU@y|Hsf0~H|{W-W$pW{?1f zKnv|2i3ve6ER{dYLzaf+@UTakXB87-jYx4l#{%o&(G;`9gjl45$k?LpN`nB_lF8NB z5fLG=zg7#P<1x9txV#)7ZA=lH!X*$9Vv!Eg3PX1VYQO>NO0l#-sn2T zL@OmyjUpmM>vdo|#*{9Z1!QQYTdGw=glO%@BGlMPaB9$Yv1lH86Jjyu*pIQp;%#Li z#FFwLbuMoQtcnP+6qz7nj8a0Qg2hXe?wN=XYZ@Iw#?&Z>nzFXc zN^3#l?sp8kMfF&_!%Konr)5w8_9JUe9auX@ICYdmy(cTV_hOyepi%K+LbSEzX`r^e z&OeCb+aT%7iaF`ZX^EMxgu;jU6OW6F`v3@L46Rlgj=2>g0EP-`!rs-OApOZH)5R^Fzchbn&yDS1RiQBItZMy=vAd8L^CR*53EkdG?Gn}BOTJU zc^2;@1*}!iCSW5|Nnf%yAZ!A*p$$V1#>|mn=9yEf!lZp!*o3yLYuJ%R8p*-4TJ9gt zL-Izsu6bAxl6p?z5a=`aOM`5q#@kXW5g2-0QPkb+ATkKUNx*YSrD{?cb)=$8J!_G5 z_In>vx{%TlNI^(0$%x4Mp3+vX z5IBu-l0yt=OWm_CcBKtFK#hRc7^H=MTvSx52)r+f-!Vi=B+@d3dyHx#l~Y1R&0>&C zpHW)ypggHD)Ki?aJyhSAiw~BJ2}X?nY=SbS+dHqyiE1KHMH)Pej&k?WtMOx30UMS2 z^EoI5#6ij!>Xd GhResult: + process = subprocess.run( + ["gh", *args], + cwd=cwd, + text=True, + capture_output=True, + ) + return GhResult(process.returncode, process.stdout, process.stderr) + + +def run_gh_command_raw(args: Sequence[str], cwd: Path) -> tuple[int, bytes, str]: + process = subprocess.run( + ["gh", *args], + cwd=cwd, + capture_output=True, + ) + stderr = process.stderr.decode(errors="replace") + return process.returncode, process.stdout, stderr + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description=( + "Inspect failing GitHub PR checks, fetch GitHub Actions logs, and extract a " + "failure snippet." + ), + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + parser.add_argument("--repo", default=".", help="Path inside the target Git repository.") + parser.add_argument( + "--pr", default=None, help="PR number or URL (defaults to current branch PR)." + ) + parser.add_argument("--max-lines", type=int, default=DEFAULT_MAX_LINES) + parser.add_argument("--context", type=int, default=DEFAULT_CONTEXT_LINES) + parser.add_argument("--json", action="store_true", help="Emit JSON instead of text output.") + return parser.parse_args() + + +def main() -> int: + args = parse_args() + repo_root = find_git_root(Path(args.repo)) + if repo_root is None: + print("Error: not inside a Git repository.", file=sys.stderr) + return 1 + + if not ensure_gh_available(repo_root): + return 1 + + pr_value = resolve_pr(args.pr, repo_root) + if pr_value is None: + return 1 + + checks = fetch_checks(pr_value, repo_root) + if checks is None: + return 1 + + failing = [c for c in checks if is_failing(c)] + if not failing: + print(f"PR #{pr_value}: no failing checks detected.") + return 0 + + results = [] + for check in failing: + results.append( + analyze_check( + check, + repo_root=repo_root, + max_lines=max(1, args.max_lines), + context=max(1, args.context), + ) + ) + + if args.json: + print(json.dumps({"pr": pr_value, "results": results}, indent=2)) + else: + render_results(pr_value, results) + + return 1 + + +def find_git_root(start: Path) -> Path | None: + result = subprocess.run( + ["git", "rev-parse", "--show-toplevel"], + cwd=start, + text=True, + capture_output=True, + ) + if result.returncode != 0: + return None + return Path(result.stdout.strip()) + + +def ensure_gh_available(repo_root: Path) -> bool: + if which("gh") is None: + print("Error: gh is not installed or not on PATH.", file=sys.stderr) + return False + result = run_gh_command(["auth", "status"], cwd=repo_root) + if result.returncode == 0: + return True + message = (result.stderr or result.stdout or "").strip() + print(message or "Error: gh not authenticated.", file=sys.stderr) + return False + + +def resolve_pr(pr_value: str | None, repo_root: Path) -> str | None: + if pr_value: + return pr_value + result = run_gh_command(["pr", "view", "--json", "number"], cwd=repo_root) + if result.returncode != 0: + message = (result.stderr or result.stdout or "").strip() + print(message or "Error: unable to resolve PR.", file=sys.stderr) + return None + try: + data = json.loads(result.stdout or "{}") + except json.JSONDecodeError: + print("Error: unable to parse PR JSON.", file=sys.stderr) + return None + number = data.get("number") + if not number: + print("Error: no PR number found.", file=sys.stderr) + return None + return str(number) + + +def fetch_checks(pr_value: str, repo_root: Path) -> list[dict[str, Any]] | None: + primary_fields = ["name", "state", "conclusion", "detailsUrl", "startedAt", "completedAt"] + result = run_gh_command( + ["pr", "checks", pr_value, "--json", ",".join(primary_fields)], + cwd=repo_root, + ) + if result.returncode != 0: + message = "\n".join(filter(None, [result.stderr, result.stdout])).strip() + available_fields = parse_available_fields(message) + if available_fields: + fallback_fields = [ + "name", + "state", + "bucket", + "link", + "startedAt", + "completedAt", + "workflow", + ] + selected_fields = [field for field in fallback_fields if field in available_fields] + if not selected_fields: + print("Error: no usable fields available for gh pr checks.", file=sys.stderr) + return None + result = run_gh_command( + ["pr", "checks", pr_value, "--json", ",".join(selected_fields)], + cwd=repo_root, + ) + if result.returncode != 0: + message = (result.stderr or result.stdout or "").strip() + print(message or "Error: gh pr checks failed.", file=sys.stderr) + return None + else: + print(message or "Error: gh pr checks failed.", file=sys.stderr) + return None + try: + data = json.loads(result.stdout or "[]") + except json.JSONDecodeError: + print("Error: unable to parse checks JSON.", file=sys.stderr) + return None + if not isinstance(data, list): + print("Error: unexpected checks JSON shape.", file=sys.stderr) + return None + return data + + +def is_failing(check: dict[str, Any]) -> bool: + conclusion = normalize_field(check.get("conclusion")) + if conclusion in FAILURE_CONCLUSIONS: + return True + state = normalize_field(check.get("state") or check.get("status")) + if state in FAILURE_STATES: + return True + bucket = normalize_field(check.get("bucket")) + return bucket in FAILURE_BUCKETS + + +def analyze_check( + check: dict[str, Any], + repo_root: Path, + max_lines: int, + context: int, +) -> dict[str, Any]: + url = check.get("detailsUrl") or check.get("link") or "" + run_id = extract_run_id(url) + job_id = extract_job_id(url) + base: dict[str, Any] = { + "name": check.get("name", ""), + "detailsUrl": url, + "runId": run_id, + "jobId": job_id, + } + + if run_id is None: + base["status"] = "external" + base["note"] = "No GitHub Actions run id detected in detailsUrl." + return base + + metadata = fetch_run_metadata(run_id, repo_root) + log_text, log_error, log_status = fetch_check_log( + run_id=run_id, + job_id=job_id, + repo_root=repo_root, + ) + + if log_status == "pending": + base["status"] = "log_pending" + base["note"] = log_error or "Logs are not available yet." + if metadata: + base["run"] = metadata + return base + + if log_error: + base["status"] = "log_unavailable" + base["error"] = log_error + if metadata: + base["run"] = metadata + return base + + snippet = extract_failure_snippet(log_text, max_lines=max_lines, context=context) + base["status"] = "ok" + base["run"] = metadata or {} + base["logSnippet"] = snippet + base["logTail"] = tail_lines(log_text, max_lines) + return base + + +def extract_run_id(url: str) -> str | None: + if not url: + return None + for pattern in (r"/actions/runs/(\d+)", r"/runs/(\d+)"): + match = re.search(pattern, url) + if match: + return match.group(1) + return None + + +def extract_job_id(url: str) -> str | None: + if not url: + return None + match = re.search(r"/actions/runs/\d+/job/(\d+)", url) + if match: + return match.group(1) + match = re.search(r"/job/(\d+)", url) + if match: + return match.group(1) + return None + + +def fetch_run_metadata(run_id: str, repo_root: Path) -> dict[str, Any] | None: + fields = [ + "conclusion", + "status", + "workflowName", + "name", + "event", + "headBranch", + "headSha", + "url", + ] + result = run_gh_command(["run", "view", run_id, "--json", ",".join(fields)], cwd=repo_root) + if result.returncode != 0: + return None + try: + data = json.loads(result.stdout or "{}") + except json.JSONDecodeError: + return None + if not isinstance(data, dict): + return None + return data + + +def fetch_check_log( + run_id: str, + job_id: str | None, + repo_root: Path, +) -> tuple[str, str, str]: + log_text, log_error = fetch_run_log(run_id, repo_root) + if not log_error: + return log_text, "", "ok" + + if is_log_pending_message(log_error) and job_id: + job_log, job_error = fetch_job_log(job_id, repo_root) + if job_log: + return job_log, "", "ok" + if job_error and is_log_pending_message(job_error): + return "", job_error, "pending" + if job_error: + return "", job_error, "error" + return "", log_error, "pending" + + if is_log_pending_message(log_error): + return "", log_error, "pending" + + return "", log_error, "error" + + +def fetch_run_log(run_id: str, repo_root: Path) -> tuple[str, str]: + result = run_gh_command(["run", "view", run_id, "--log"], cwd=repo_root) + if result.returncode != 0: + error = (result.stderr or result.stdout or "").strip() + return "", error or "gh run view failed" + return result.stdout, "" + + +def fetch_job_log(job_id: str, repo_root: Path) -> tuple[str, str]: + repo_slug = fetch_repo_slug(repo_root) + if not repo_slug: + return "", "Error: unable to resolve repository name for job logs." + endpoint = f"/repos/{repo_slug}/actions/jobs/{job_id}/logs" + returncode, stdout_bytes, stderr = run_gh_command_raw(["api", endpoint], cwd=repo_root) + if returncode != 0: + message = (stderr or stdout_bytes.decode(errors="replace")).strip() + return "", message or "gh api job logs failed" + if is_zip_payload(stdout_bytes): + return "", "Job logs returned a zip archive; unable to parse." + return stdout_bytes.decode(errors="replace"), "" + + +def fetch_repo_slug(repo_root: Path) -> str | None: + result = run_gh_command(["repo", "view", "--json", "nameWithOwner"], cwd=repo_root) + if result.returncode != 0: + return None + try: + data = json.loads(result.stdout or "{}") + except json.JSONDecodeError: + return None + name_with_owner = data.get("nameWithOwner") + if not name_with_owner: + return None + return str(name_with_owner) + + +def normalize_field(value: Any) -> str: + if value is None: + return "" + return str(value).strip().lower() + + +def parse_available_fields(message: str) -> list[str]: + if "Available fields:" not in message: + return [] + fields: list[str] = [] + collecting = False + for line in message.splitlines(): + if "Available fields:" in line: + collecting = True + continue + if not collecting: + continue + field = line.strip() + if not field: + continue + fields.append(field) + return fields + + +def is_log_pending_message(message: str) -> bool: + lowered = message.lower() + return any(marker in lowered for marker in PENDING_LOG_MARKERS) + + +def is_zip_payload(payload: bytes) -> bool: + return payload.startswith(b"PK") + + +def extract_failure_snippet(log_text: str, max_lines: int, context: int) -> str: + lines = log_text.splitlines() + if not lines: + return "" + + marker_index = find_failure_index(lines) + if marker_index is None: + return "\n".join(lines[-max_lines:]) + + start = max(0, marker_index - context) + end = min(len(lines), marker_index + context) + window = lines[start:end] + if len(window) > max_lines: + window = window[-max_lines:] + return "\n".join(window) + + +def find_failure_index(lines: Sequence[str]) -> int | None: + for idx in range(len(lines) - 1, -1, -1): + lowered = lines[idx].lower() + if any(marker in lowered for marker in FAILURE_MARKERS): + return idx + return None + + +def tail_lines(text: str, max_lines: int) -> str: + if max_lines <= 0: + return "" + lines = text.splitlines() + return "\n".join(lines[-max_lines:]) + + +def render_results(pr_number: str, results: Iterable[dict[str, Any]]) -> None: + results_list = list(results) + print(f"PR #{pr_number}: {len(results_list)} failing checks analyzed.") + for result in results_list: + print("-" * 60) + print(f"Check: {result.get('name', '')}") + if result.get("detailsUrl"): + print(f"Details: {result['detailsUrl']}") + run_id = result.get("runId") + if run_id: + print(f"Run ID: {run_id}") + job_id = result.get("jobId") + if job_id: + print(f"Job ID: {job_id}") + status = result.get("status", "unknown") + print(f"Status: {status}") + + run_meta = result.get("run", {}) + if run_meta: + branch = run_meta.get("headBranch", "") + sha = (run_meta.get("headSha") or "")[:12] + workflow = run_meta.get("workflowName") or run_meta.get("name") or "" + conclusion = run_meta.get("conclusion") or run_meta.get("status") or "" + print(f"Workflow: {workflow} ({conclusion})") + if branch or sha: + print(f"Branch/SHA: {branch} {sha}") + if run_meta.get("url"): + print(f"Run URL: {run_meta['url']}") + + if result.get("note"): + print(f"Note: {result['note']}") + + if result.get("error"): + print(f"Error fetching logs: {result['error']}") + continue + + snippet = result.get("logSnippet") or "" + if snippet: + print("Failure snippet:") + print(indent_block(snippet, prefix=" ")) + else: + print("No snippet available.") + print("-" * 60) + + +def indent_block(text: str, prefix: str = " ") -> str: + return "\n".join(f"{prefix}{line}" for line in text.splitlines()) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/.claude/skills/gh-fix-ci b/.claude/skills/gh-fix-ci new file mode 120000 index 0000000..786e31a --- /dev/null +++ b/.claude/skills/gh-fix-ci @@ -0,0 +1 @@ +../../.agents/skills/gh-fix-ci \ No newline at end of file diff --git a/.junie/skills/gh-fix-ci b/.junie/skills/gh-fix-ci new file mode 120000 index 0000000..786e31a --- /dev/null +++ b/.junie/skills/gh-fix-ci @@ -0,0 +1 @@ +../../.agents/skills/gh-fix-ci \ No newline at end of file diff --git a/.vibe/skills/gh-fix-ci b/.vibe/skills/gh-fix-ci new file mode 120000 index 0000000..786e31a --- /dev/null +++ b/.vibe/skills/gh-fix-ci @@ -0,0 +1 @@ +../../.agents/skills/gh-fix-ci \ No newline at end of file diff --git a/skills-lock.json b/skills-lock.json index 9fc86a3..f2b1ac7 100644 --- a/skills-lock.json +++ b/skills-lock.json @@ -6,6 +6,11 @@ "sourceType": "github", "computedHash": "cbe644b0c6760ae2a1eaafa39133920d889331327065d92a131675a665273e56" }, + "gh-fix-ci": { + "source": "openai/skills", + "sourceType": "github", + "computedHash": "1b1ce4eeeae2bfc175d05f1b69e07b17dfa1fddf99a96c22b6dcae7e5fb4e550" + }, "git-commit": { "source": "github/awesome-copilot", "sourceType": "github", From 121514a9dde1a123ecc30a65ff4c34a22db0b13a Mon Sep 17 00:00:00 2001 From: Soham Banerjee Date: Sun, 29 Mar 2026 12:58:21 +0530 Subject: [PATCH 07/12] Update .github/workflows/automated-release.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/automated-release.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/automated-release.yml b/.github/workflows/automated-release.yml index eb2dac0..2dd0edd 100644 --- a/.github/workflows/automated-release.yml +++ b/.github/workflows/automated-release.yml @@ -57,17 +57,17 @@ jobs: id: cache-dmg uses: actions/cache@v4 with: - path: ~/Codex.dmg - key: dmg-cache-${{ hashFiles('~/Codex.dmg') }} + path: Codex.dmg + key: dmg-cache-${{ hashFiles('Codex.dmg') }} - name: Download Latest Codex.dmg id: download run: | echo "Downloading latest Codex.dmg..." - curl -L -o ~/Codex.dmg "https://persistent.oaistatic.com/codex-app-prod/Codex.dmg" + curl -L -o Codex.dmg "https://persistent.oaistatic.com/codex-app-prod/Codex.dmg" # Calculate SHA256 hash - DMG_HASH=$(shasum -a 256 ~/Codex.dmg | cut -d' ' -f1) + DMG_HASH=$(shasum -a 256 Codex.dmg | cut -d' ' -f1) echo "Downloaded DMG SHA256: $DMG_HASH" echo "dmg_hash=$DMG_HASH" >> $GITHUB_OUTPUT From dbe5fd4942f2aa3a6a68440b10b4b0755cd503c1 Mon Sep 17 00:00:00 2001 From: Soham Banerjee Date: Sun, 29 Mar 2026 12:58:33 +0530 Subject: [PATCH 08/12] Update .github/workflows/automated-release.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/automated-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/automated-release.yml b/.github/workflows/automated-release.yml index 2dd0edd..ca67e13 100644 --- a/.github/workflows/automated-release.yml +++ b/.github/workflows/automated-release.yml @@ -116,7 +116,7 @@ jobs: # Extract version from Info.plist CODEX_VERSION=$(defaults read /Volumes/CodexTemp/Codex.app/Contents/Info.plist CFBundleShortVersionString 2>/dev/null || echo "unknown") - ELECTRON_VERSION=$(defaults read /Volume/CodexTemp/Codex.app/Contents/Info.plist CFBundleExecutable 2>/dev/null || echo "unknown") + ELECTRON_VERSION=$(defaults read /Volumes/CodexTemp/Codex.app/Contents/Info.plist CFBundleVersion 2>/dev/null || echo "unknown") hdiutil detach /Volumes/CodexTemp From cf67e42b92c2fd1eb607889e239d0bc9e170884b Mon Sep 17 00:00:00 2001 From: Soham Banerjee Date: Sun, 29 Mar 2026 12:59:46 +0530 Subject: [PATCH 09/12] Update .github/workflows/automated-release.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/automated-release.yml | 41 +++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/.github/workflows/automated-release.yml b/.github/workflows/automated-release.yml index ca67e13..63d7218 100644 --- a/.github/workflows/automated-release.yml +++ b/.github/workflows/automated-release.yml @@ -63,10 +63,47 @@ jobs: - name: Download Latest Codex.dmg id: download run: | + set -euo pipefail + echo "Downloading latest Codex.dmg..." - curl -L -o Codex.dmg "https://persistent.oaistatic.com/codex-app-prod/Codex.dmg" + curl --fail --show-error --location --retry 5 --retry-delay 5 \ + -o ~/Codex.dmg "https://persistent.oaistatic.com/codex-app-prod/Codex.dmg" + + echo "Download complete. Verifying DMG contents..." + + # Attach the DMG and capture the mount point + MOUNT_DIR=$(hdiutil attach -nobrowse -readonly ~/Codex.dmg | awk '/\/Volumes\// {print $3; exit}') + if [ -z "${MOUNT_DIR:-}" ]; then + echo "Failed to determine DMG mount point." + exit 1 + fi + + echo "DMG mounted at: $MOUNT_DIR" + + # Find the first .app bundle inside the mounted volume + APP_PATH=$(find "$MOUNT_DIR" -maxdepth 1 -type d -name "*.app" | head -n 1) + if [ -z "${APP_PATH:-}" ]; then + echo "No .app bundle found in DMG." + hdiutil detach "$MOUNT_DIR" || true + exit 1 + fi + + echo "Found app bundle: $APP_PATH" + + # Verify code signature + echo "Verifying code signature with codesign..." + codesign --verify --deep --strict --verbose "$APP_PATH" + + # Verify Gatekeeper assessment + echo "Verifying app with spctl..." + spctl -a -vv "$APP_PATH" + + echo "Code signature and Gatekeeper verification succeeded." + + # Detach the DMG after verification + hdiutil detach "$MOUNT_DIR" - # Calculate SHA256 hash + # Calculate SHA256 hash only after successful verification DMG_HASH=$(shasum -a 256 Codex.dmg | cut -d' ' -f1) echo "Downloaded DMG SHA256: $DMG_HASH" echo "dmg_hash=$DMG_HASH" >> $GITHUB_OUTPUT From 7be6f36f067c512fb8afdd552c84eb4ac5f53682 Mon Sep 17 00:00:00 2001 From: Soham Banerjee Date: Sun, 29 Mar 2026 13:02:29 +0530 Subject: [PATCH 10/12] Update scripts/extract-app-version.sh Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- scripts/extract-app-version.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/extract-app-version.sh b/scripts/extract-app-version.sh index 22d3c3b..53d5854 100755 --- a/scripts/extract-app-version.sh +++ b/scripts/extract-app-version.sh @@ -21,8 +21,8 @@ if [[ ! -f "$DMG_PATH" ]]; then fi # Create temporary mount point -MOUNT_POINT="/tmp/codex_extract_$$" -mkdir -p "$MOUNT_POINT" +MOUNT_POINT=$(mktemp -d -t codex_extract) +trap 'hdiutil detach "$MOUNT_POINT" &>/dev/null || true; rmdir "$MOUNT_POINT" &>/dev/null || true' EXIT # Mount the DMG (noverify for speed, nobrowse to avoid Finder) hdiutil attach -noverify -nobrowse -mountpoint "$MOUNT_POINT" "$DMG_PATH" > /dev/null 2>&1 From 98182f7c54c404bde689625ec047080d96e1cf82 Mon Sep 17 00:00:00 2001 From: Soham Banerjee Date: Sun, 29 Mar 2026 13:02:40 +0530 Subject: [PATCH 11/12] Update .agents/skills/gh-cli/SKILL.md Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .agents/skills/gh-cli/SKILL.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.agents/skills/gh-cli/SKILL.md b/.agents/skills/gh-cli/SKILL.md index 6756f0b..d39280c 100644 --- a/.agents/skills/gh-cli/SKILL.md +++ b/.agents/skills/gh-cli/SKILL.md @@ -1615,9 +1615,7 @@ gh codespace stop gh codespace delete # View logs -gh codespace logs - ---tail 100 +gh codespace logs --tail 100 # View ports gh codespace ports From 4160c02f6e9f84db516bc626ff65a7b96b64e403 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 29 Mar 2026 07:36:05 +0000 Subject: [PATCH 12/12] fix: use notes-file for release body and add GH_TOKEN to release steps Agent-Logs-Url: https://github.com/soham2008xyz/codex-intel/sessions/5dca48d1-daed-4315-8c0b-7c38e349c7e8 Co-authored-by: soham2008xyz <7334295+soham2008xyz@users.noreply.github.com> --- .github/workflows/automated-release.yml | 68 +++++++++++++------------ 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/.github/workflows/automated-release.yml b/.github/workflows/automated-release.yml index 63d7218..48ced00 100644 --- a/.github/workflows/automated-release.yml +++ b/.github/workflows/automated-release.yml @@ -180,43 +180,45 @@ jobs: TAG_VERSION="v${VERSION}-intel" echo "Creating release: $TAG_VERSION" - # Create release notes - RELEASE_BODY=""" - ## Codex Intel Build v${VERSION} - - This is an Intel (x64) build of the official Codex Desktop App. - - ### Build Information - - **Source**: OpenAI Codex DMG - - **DMG SHA256**: ${DMG_HASH} - - **Architecture**: Intel x64 - - **Build Date**: $(date -u +"%Y-%m-%d %H:%M UTC") - - ### Notes - - Auto-generated from automated workflow - - Built on macOS Intel runner - - Includes `--no-sandbox` wrapper for tool compatibility - - ### Installation - - 1. Download the **Codex.app.zip** asset below - 2. Extract the zip file - 3. Move `Codex.app` to `/Applications/` - 4. If you see "App is damaged": - ```bash - xattr -cr /Applications/Codex.app - ``` - - ### Troubleshooting - - See [README.md](https://github.com/soham2008xyz/codex-rebuilder) for troubleshooting guides. - """ + # Write release notes to a temp file to avoid shell quoting issues + { + echo "## Codex Intel Build v${VERSION}" + echo "" + echo "This is an Intel (x64) build of the official Codex Desktop App." + echo "" + echo "### Build Information" + echo "- **Source**: OpenAI Codex DMG" + echo "- **DMG SHA256**: ${DMG_HASH}" + echo "- **Architecture**: Intel x64" + echo "- **Build Date**: $(date -u +"%Y-%m-%d %H:%M UTC")" + echo "" + echo "### Notes" + echo "- Auto-generated from automated workflow" + echo "- Built on macOS Intel runner" + echo "- Includes \`--no-sandbox\` wrapper for tool compatibility" + echo "" + echo "### Installation" + echo "" + echo "1. Download the **Codex.app.zip** asset below" + echo "2. Extract the zip file" + echo "3. Move \`Codex.app\` to \`/Applications/\`" + echo '4. If you see "App is damaged":' + echo ' ```bash' + echo " xattr -cr /Applications/Codex.app" + echo ' ```' + echo "" + echo "### Troubleshooting" + echo "" + echo "See [README.md](https://github.com/soham2008xyz/codex-rebuilder) for troubleshooting guides." + } > /tmp/release-notes.md # Create release gh release create "$TAG_VERSION" \ --title "Intel Build - v${VERSION}" \ - --notes "$RELEASE_BODY" \ + --notes-file /tmp/release-notes.md \ --verify-tag=false + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Upload Release Asset if: steps.check-version.outputs.is_new == 'true' @@ -231,6 +233,8 @@ jobs: echo "Uploading release asset..." gh release upload "$TAG_VERSION" Codex.app.zip#Codex.app.zip \ --clobber + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Generate Summary run: |