diff --git a/.github/workflows/gemini-automated-issue-dedup.yml b/.github/workflows/gemini-automated-issue-dedup.yml deleted file mode 100644 index b84b5aa94df..00000000000 --- a/.github/workflows/gemini-automated-issue-dedup.yml +++ /dev/null @@ -1,262 +0,0 @@ -name: '🏷️ Gemini Automated Issue Deduplication' - -on: - issues: - types: - - 'opened' - - 'reopened' - issue_comment: - types: - - 'created' - workflow_dispatch: - inputs: - issue_number: - description: 'issue number to dedup' - required: true - type: 'number' - -concurrency: - group: '${{ github.workflow }}-${{ github.event.issue.number }}' - cancel-in-progress: true - -defaults: - run: - shell: 'bash' - -jobs: - find-duplicates: - if: |- - github.repository == 'google-gemini/gemini-cli' && - vars.TRIAGE_DEDUPLICATE_ISSUES != '' && - (github.event_name == 'issues' || - github.event_name == 'workflow_dispatch' || - (github.event_name == 'issue_comment' && - contains(github.event.comment.body, '@gemini-cli /deduplicate') && - (github.event.comment.author_association == 'OWNER' || - github.event.comment.author_association == 'MEMBER' || - github.event.comment.author_association == 'COLLABORATOR'))) - permissions: - contents: 'read' - id-token: 'write' # Required for WIF, see https://docs.github.com/en/actions/how-tos/secure-your-work/security-harden-deployments/oidc-in-google-cloud-platform#adding-permissions-settings - issues: 'read' - statuses: 'read' - packages: 'read' - timeout-minutes: 20 - runs-on: 'ubuntu-latest' - outputs: - duplicate_issues_csv: '${{ env.DUPLICATE_ISSUES_CSV }}' - steps: - - name: 'Checkout' - uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5 - - - name: 'Log in to GitHub Container Registry' - uses: 'docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1' # ratchet:docker/login-action@v3 - with: - registry: 'ghcr.io' - username: '${{ github.actor }}' - password: '${{ secrets.GITHUB_TOKEN }}' - - - name: 'Find Duplicate Issues' - uses: 'google-github-actions/run-gemini-cli@a3bf79042542528e91937b3a3a6fbc4967ee3c31' # ratchet:google-github-actions/run-gemini-cli@v0 - id: 'gemini_issue_deduplication' - env: - GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' - ISSUE_TITLE: '${{ github.event.issue.title }}' - ISSUE_BODY: '${{ github.event.issue.body }}' - ISSUE_NUMBER: '${{ github.event.issue.number }}' - REPOSITORY: '${{ github.repository }}' - FIRESTORE_PROJECT: '${{ vars.FIRESTORE_PROJECT }}' - with: - gcp_workload_identity_provider: '${{ vars.GCP_WIF_PROVIDER }}' - gcp_project_id: '${{ vars.GOOGLE_CLOUD_PROJECT }}' - gcp_location: '${{ vars.GOOGLE_CLOUD_LOCATION }}' - gcp_service_account: '${{ vars.SERVICE_ACCOUNT_EMAIL }}' - gemini_api_key: '${{ secrets.GEMINI_API_KEY }}' - use_vertex_ai: '${{ vars.GOOGLE_GENAI_USE_VERTEXAI }}' - use_gemini_code_assist: '${{ vars.GOOGLE_GENAI_USE_GCA }}' - settings: |- - { - "mcpServers": { - "issue_deduplication": { - "command": "docker", - "args": [ - "run", - "-i", - "--rm", - "--network", "host", - "-e", "GITHUB_TOKEN", - "-e", "GEMINI_API_KEY", - "-e", "DATABASE_TYPE", - "-e", "FIRESTORE_DATABASE_ID", - "-e", "GCP_PROJECT", - "-e", "GOOGLE_APPLICATION_CREDENTIALS=/app/gcp-credentials.json", - "-v", "${GOOGLE_APPLICATION_CREDENTIALS}:/app/gcp-credentials.json", - "ghcr.io/google-gemini/gemini-cli-issue-triage@sha256:e3de1523f6c83aabb3c54b76d08940a2bf42febcb789dd2da6f95169641f94d3" - ], - "env": { - "GITHUB_TOKEN": "${GITHUB_TOKEN}", - "GEMINI_API_KEY": "${{ secrets.GEMINI_API_KEY }}", - "DATABASE_TYPE":"firestore", - "GCP_PROJECT": "${FIRESTORE_PROJECT}", - "FIRESTORE_DATABASE_ID": "(default)", - "GOOGLE_APPLICATION_CREDENTIALS": "${GOOGLE_APPLICATION_CREDENTIALS}" - }, - "enabled": true, - "timeout": 600000 - } - }, - "maxSessionTurns": 25, - "coreTools": [ - "run_shell_command(echo)", - "run_shell_command(gh issue view)" - ], - "telemetry": { - "enabled": true, - "target": "gcp" - } - } - prompt: |- - ## Role - You are an issue de-duplication assistant. Your goal is to find - duplicate issues for a given issue. - ## Steps - 1. **Find Potential Duplicates:** - - The repository is ${{ github.repository }} and the issue number is ${{ github.event.issue.number }}. - - Use the `duplicates` tool with the `repo` and `issue_number` to find potential duplicates for the current issue. Do not use the `threshold` parameter. - - If no duplicates are found, you are done. - - Print the JSON output from the `duplicates` tool to the logs. - 2. **Refine Duplicates List (if necessary):** - - If the `duplicates` tool returns between 1 and 14 results, you must refine the list. - - For each potential duplicate issue, run `gh issue view --json title,body,comments` to fetch its content. - - Also fetch the content of the original issue: `gh issue view "${ISSUE_NUMBER}" --json title,body,comments`. - - Carefully analyze the content (title, body, comments) of the original issue and all potential duplicates. - - It is very important if the comments on either issue mention that they are not duplicates of each other, to treat them as not duplicates. - - Based on your analysis, create a final list containing only the issues you are highly confident are actual duplicates. - - If your final list is empty, you are done. - - Print to the logs if you omitted any potential duplicates based on your analysis. - - If the `duplicates` tool returned 15+ results, use the top 15 matches (based on descending similarity score value) to perform this step. - 3. **Output final duplicates list as CSV:** - - Convert the list of appropriate duplicate issue numbers into a comma-separated list (CSV). If there are no appropriate duplicates, use the empty string. - - Use the "echo" shell command to append the CSV of issue numbers into the filepath referenced by the environment variable "${GITHUB_ENV}": - echo "DUPLICATE_ISSUES_CSV=[DUPLICATE_ISSUES_AS_CSV]" >> "${GITHUB_ENV}" - ## Guidelines - - Only use the `duplicates` and `run_shell_command` tools. - - The `run_shell_command` tool can be used with `gh issue view`. - - Do not download or read media files like images, videos, or links. The `--json` flag for `gh issue view` will prevent this. - - Do not modify the issue content or status. - - Do not add comments or labels. - - Reference all shell variables as "${VAR}" (with quotes and braces). - - add-comment-and-label: - needs: 'find-duplicates' - if: |- - github.repository == 'google-gemini/gemini-cli' && - vars.TRIAGE_DEDUPLICATE_ISSUES != '' && - needs.find-duplicates.outputs.duplicate_issues_csv != '' && - ( - github.event_name == 'issues' || - github.event_name == 'workflow_dispatch' || - ( - github.event_name == 'issue_comment' && - contains(github.event.comment.body, '@gemini-cli /deduplicate') && - ( - github.event.comment.author_association == 'OWNER' || - github.event.comment.author_association == 'MEMBER' || - github.event.comment.author_association == 'COLLABORATOR' - ) - ) - ) - permissions: - issues: 'write' - timeout-minutes: 5 - runs-on: 'ubuntu-latest' - steps: - - name: 'Generate GitHub App Token' - id: 'generate_token' - uses: 'actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b' # ratchet:actions/create-github-app-token@v2 - with: - app-id: '${{ secrets.APP_ID }}' - private-key: '${{ secrets.PRIVATE_KEY }}' - permission-issues: 'write' - - - name: 'Comment and Label Duplicate Issue' - uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea' - env: - DUPLICATES_OUTPUT: '${{ needs.find-duplicates.outputs.duplicate_issues_csv }}' - with: - github-token: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}' - script: |- - const rawCsv = process.env.DUPLICATES_OUTPUT; - core.info(`Raw duplicates CSV: ${rawCsv}`); - const duplicateIssues = rawCsv.split(',').map(s => s.trim()).filter(s => s); - - if (duplicateIssues.length === 0) { - core.info('No duplicate issues found. Nothing to do.'); - return; - } - - const issueNumber = ${{ github.event.issue.number }}; - - function formatCommentBody(issues, updated = false) { - const header = updated - ? 'Found possible duplicate issues (updated):' - : 'Found possible duplicate issues:'; - const issuesList = issues.map(num => `- #${num}`).join('\n'); - const footer = 'If you believe this is not a duplicate, please remove the `status/possible-duplicate` label.'; - const magicComment = ''; - return `${header}\n\n${issuesList}\n\n${footer}\n${magicComment}`; - } - - const newCommentBody = formatCommentBody(duplicateIssues); - const newUpdatedCommentBody = formatCommentBody(duplicateIssues, true); - - const { data: comments } = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issueNumber, - }); - - const magicComment = ''; - const existingComment = comments.find(comment => - comment.user.type === 'Bot' && comment.body.includes(magicComment) - ); - - let commentMade = false; - - if (existingComment) { - // To check if lists are same, just compare the formatted bodies without headers. - const existingBodyForCompare = existingComment.body.substring(existingComment.body.indexOf('- #')); - const newBodyForCompare = newCommentBody.substring(newCommentBody.indexOf('- #')); - - if (existingBodyForCompare.trim() !== newBodyForCompare.trim()) { - core.info(`Updating existing comment ${existingComment.id}`); - await github.rest.issues.updateComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: existingComment.id, - body: newUpdatedCommentBody, - }); - commentMade = true; - } else { - core.info('Existing comment is up-to-date. Nothing to do.'); - } - } else { - core.info('Creating new comment.'); - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issueNumber, - body: newCommentBody, - }); - commentMade = true; - } - - if (commentMade) { - core.info('Adding "status/possible-duplicate" label.'); - await github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issueNumber, - labels: ['status/possible-duplicate'], - }); - } diff --git a/.github/workflows/gemini-automated-issue-triage.yml b/.github/workflows/gemini-automated-issue-triage.yml deleted file mode 100644 index f4191ef7a79..00000000000 --- a/.github/workflows/gemini-automated-issue-triage.yml +++ /dev/null @@ -1,335 +0,0 @@ -name: '🏷️ Gemini Automated Issue Triage' - -on: - issues: - types: - - 'opened' - - 'reopened' - issue_comment: - types: - - 'created' - workflow_dispatch: - inputs: - issue_number: - description: 'issue number to triage' - required: true - type: 'number' - -concurrency: - group: '${{ github.workflow }}-${{ github.event.issue.number || github.event.inputs.issue_number }}' - cancel-in-progress: true - -defaults: - run: - shell: 'bash' - -permissions: - contents: 'read' - id-token: 'write' - issues: 'write' - statuses: 'write' - packages: 'read' - actions: 'write' # Required for cancelling a workflow run - -jobs: - triage-issue: - if: |- - github.repository == 'google-gemini/gemini-cli' && - ( - github.event_name == 'workflow_dispatch' || - ( - (github.event_name == 'issues' || github.event_name == 'issue_comment') && - contains(github.event.issue.labels.*.name, 'status/need-triage') && - (github.event_name != 'issue_comment' || ( - contains(github.event.comment.body, '@gemini-cli /triage') && - (github.event.comment.author_association == 'OWNER' || github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'COLLABORATOR') - )) - ) - ) && - !contains(github.event.issue.labels.*.name, 'area/') - timeout-minutes: 5 - runs-on: 'ubuntu-latest' - steps: - - name: 'Get issue data for manual trigger' - id: 'get_issue_data' - if: |- - github.event_name == 'workflow_dispatch' - uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea' - with: - github-token: '${{ secrets.GITHUB_TOKEN }}' - script: | - const { data: issue } = await github.rest.issues.get({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: ${{ github.event.inputs.issue_number }}, - }); - core.setOutput('title', issue.title); - core.setOutput('body', issue.body); - core.setOutput('labels', issue.labels.map(label => label.name).join(',')); - return issue; - - - name: 'Manual Trigger Pre-flight Checks' - if: |- - github.event_name == 'workflow_dispatch' - env: - ISSUE_NUMBER_INPUT: '${{ github.event.inputs.issue_number }}' - LABELS: '${{ steps.get_issue_data.outputs.labels }}' - run: | - if ! echo "${LABELS}" | grep -q 'status/need-triage'; then - echo "Issue #${ISSUE_NUMBER_INPUT} does not have the 'status/need-triage' label. Stopping workflow." - exit 1 - fi - - if echo "${LABELS}" | grep -q 'area/'; then - echo "Issue #${ISSUE_NUMBER_INPUT} already has 'area/' label. Stopping workflow." - exit 1 - fi - - echo "Manual triage checks passed." - - - name: 'Checkout' - uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5 - - - name: 'Generate GitHub App Token' - id: 'generate_token' - uses: 'actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b' # ratchet:actions/create-github-app-token@v2 - with: - app-id: '${{ secrets.APP_ID }}' - private-key: '${{ secrets.PRIVATE_KEY }}' - permission-issues: 'write' - - - name: 'Get Repository Labels' - id: 'get_labels' - uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea' - with: - github-token: '${{ steps.generate_token.outputs.token }}' - script: |- - const { data: labels } = await github.rest.issues.listLabelsForRepo({ - owner: context.repo.owner, - repo: context.repo.repo, - }); - const allowedLabels = [ - 'area/agent', - 'area/enterprise', - 'area/non-interactive', - 'area/core', - 'area/security', - 'area/platform', - 'area/extensions', - 'area/unknown' - ]; - const labelNames = labels.map(label => label.name).filter(name => allowedLabels.includes(name)); - core.setOutput('available_labels', labelNames.join(',')); - core.info(`Found ${labelNames.length} labels: ${labelNames.join(', ')}`); - return labelNames; - - - name: 'Run Gemini Issue Analysis' - uses: 'google-github-actions/run-gemini-cli@a3bf79042542528e91937b3a3a6fbc4967ee3c31' # ratchet:google-github-actions/run-gemini-cli@v0 - id: 'gemini_issue_analysis' - env: - GITHUB_TOKEN: '' # Do not pass any auth token here since this runs on untrusted inputs - ISSUE_TITLE: >- - ${{ github.event_name == 'workflow_dispatch' && steps.get_issue_data.outputs.title || github.event.issue.title }} - ISSUE_BODY: >- - ${{ github.event_name == 'workflow_dispatch' && steps.get_issue_data.outputs.body || github.event.issue.body }} - ISSUE_NUMBER: >- - ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.issue_number || github.event.issue.number }} - REPOSITORY: '${{ github.repository }}' - AVAILABLE_LABELS: '${{ steps.get_labels.outputs.available_labels }}' - with: - gcp_workload_identity_provider: '${{ vars.GCP_WIF_PROVIDER }}' - gcp_project_id: '${{ vars.GOOGLE_CLOUD_PROJECT }}' - gcp_location: '${{ vars.GOOGLE_CLOUD_LOCATION }}' - gcp_service_account: '${{ vars.SERVICE_ACCOUNT_EMAIL }}' - gemini_api_key: '${{ secrets.GEMINI_API_KEY }}' - use_vertex_ai: '${{ vars.GOOGLE_GENAI_USE_VERTEXAI }}' - use_gemini_code_assist: '${{ vars.GOOGLE_GENAI_USE_GCA }}' - settings: |- - { - "maxSessionTurns": 25, - "telemetry": { - "enabled": true, - "target": "gcp" - } - } - prompt: |- - ## Role - - You are an issue triage assistant. Your role is to analyze a GitHub issue and determine the single most appropriate area/ label based on the definitions provided. - - ## Steps - 1. Review the issue title and body: ${{ env.ISSUE_TITLE }} and ${{ env.ISSUE_BODY }}. - 2. Review the available labels: ${{ env.AVAILABLE_LABELS }}. - 3. Select exactly one area/ label that best matches the issue based on Reference 1: Area Definitions. - 4. Fallback Logic: - - If you cannot confidently determine the correct area/ label from the definitions, you must use area/unknown. - 5. Output your selected label in JSON format and nothing else. Example: - {"labels_to_set": ["area/core"]} - - ## Guidelines - - Your output must contain exactly one area/ label. - - Triage only the current issue based on its title and body. - - Output only valid JSON format. - - Do not include any explanation or additional text, just the JSON. - - Reference 1: Area Definitions - area/agent - - Description: Issues related to the "brain" of the CLI. This includes the core agent logic, model quality, tool/function calling, and memory. - - Example Issues: - "I am not getting a reasonable or expected response." - "The model is not calling the tool I expected." - "The web search tool is not working as expected." - "Feature request for a new built-in tool (e.g., read file, write file)." - "The generated code is poor quality or incorrect." - "The model seems stuck in a loop." - "The response from the model is malformed (e.g., broken JSON, bad formatting)." - "Concerns about unnecessary token consumption." - "Issues with how memory or chat history is managed." - "Issues with sub-agents." - "Model is switching from one to another unexpectedly." - - area/enterprise - - Description: Issues specific to enterprise-level features, including telemetry, policy, and licenses. - - Example Issues: - "Usage data is not appearing in our telemetry dashboard." - "A user is able to perform an action that should be blocked by an admin policy." - "Questions about billing, licensing tiers, or enterprise quotas." - - area/non-interactive - - Description: Issues related to using the CLI in automated or non-interactive environments (headless mode). - - Example Issues: - "Problems using the CLI as an SDK in another surface." - "The CLI is behaving differently when run from a shell script vs. an interactive terminal." - "GitHub action is failing." - "I am having trouble running the CLI in headless mode" - - area/core - - Description: Issues with the fundamental CLI app itself. This includes the user interface (UI/UX), installation, OS compatibility, and performance. - - Example Issues: - "I am seeing my screen flicker when using the CLI." - "The output in my terminal is malformed or unreadable." - "Theme changes are not taking effect." - "Keyboard inputs (e.g., arrow keys, Ctrl+C) are not being recognized." - "The CLI failed to install or update." - "An issue specific to running on Windows, macOS, or Linux." - "Problems with command parsing, flags, or argument handling." - "High CPU or memory usage by the CLI process." - "Issues related to multi-modality (e.g., handling image inputs)." - "Problems with the IDE integration connection or installation" - - area/security - - Description: Issues related to user authentication, authorization, data security, and privacy. - - Example Issues: - "I am unable to sign in." - "The login flow is selecting the wrong authentication path" - "Problems with API key handling or credential storage." - "A report of a security vulnerability" - "Concerns about data sanitization or potential data leaks." - "Issues or requests related to privacy controls." - "Preventing unauthorized data access." - - area/platform - - Description: Issues related to CI/CD, release management, testing, eval infrastructure, capacity, quota management, and sandbox environments. - - Example Issues: - "I am getting a 429 'Resource Exhausted' or 500-level server error." - "General slowness or high latency from the service." - "The build script is broken on the main branch." - "Tests are failing in the CI/CD pipeline." - "Issues with the release management or publishing process." - "User is running out of capacity." - "Problems specific to the sandbox or staging environments." - "Questions about quota limits or requests for increases." - - area/extensions - - Description: Issues related to the extension ecosystem, including the marketplace and website. - - Example Issues: - "Bugs related to the extension marketplace website." - "Issues with a specific extension." - "Feature request for the extension ecosystem." - - area/unknown - - Description: Issues that do not clearly fit into any other defined area/ category, or where information is too limited to make a determination. Use this when no other area is appropriate. - - - name: 'Apply Labels to Issue' - if: |- - ${{ steps.gemini_issue_analysis.outputs.summary != '' }} - env: - REPOSITORY: '${{ github.repository }}' - ISSUE_NUMBER: '${{ github.event.issue.number || github.event.inputs.issue_number }}' - LABELS_OUTPUT: '${{ steps.gemini_issue_analysis.outputs.summary }}' - uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea' - with: - github-token: '${{ steps.generate_token.outputs.token }}' - script: | - const rawOutput = process.env.LABELS_OUTPUT; - core.info(`Raw output from model: ${rawOutput}`); - let parsedLabels; - try { - // First, try to parse the raw output as JSON. - parsedLabels = JSON.parse(rawOutput); - } catch (jsonError) { - // If that fails, check for a markdown code block. - core.warning(`Direct JSON parsing failed: ${jsonError.message}. Trying to extract from a markdown block.`); - const jsonMatch = rawOutput.match(/```json\s*([\s\S]*?)\s*```/); - if (jsonMatch && jsonMatch[1]) { - try { - parsedLabels = JSON.parse(jsonMatch[1].trim()); - } catch (markdownError) { - core.setFailed(`Failed to parse JSON even after extracting from markdown block: ${markdownError.message}\nRaw output: ${rawOutput}`); - return; - } - } else { - core.setFailed(`Output is not valid JSON and does not contain a JSON markdown block.\nRaw output: ${rawOutput}`); - return; - } - } - - const issueNumber = parseInt(process.env.ISSUE_NUMBER); - const labelsToAdd = parsedLabels.labels_to_set || []; - - if (labelsToAdd.length !== 1) { - core.setFailed(`Expected exactly 1 label (area/), but got ${labelsToAdd.length}. Labels: ${labelsToAdd.join(', ')}`); - return; - } - - // Set labels based on triage result - await github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issueNumber, - labels: labelsToAdd - }); - core.info(`Successfully added labels for #${issueNumber}: ${labelsToAdd.join(', ')}`); - - // Remove the 'status/need-triage' label - try { - await github.rest.issues.removeLabel({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issueNumber, - name: 'status/need-triage' - }); - core.info(`Successfully removed 'status/need-triage' label.`); - } catch (error) { - // If the label doesn't exist, the API call will throw a 404. We can ignore this. - if (error.status !== 404) { - core.warning(`Failed to remove 'status/need-triage': ${error.message}`); - } - } - - - name: 'Post Issue Analysis Failure Comment' - if: |- - ${{ failure() && steps.gemini_issue_analysis.outcome == 'failure' }} - env: - ISSUE_NUMBER: '${{ github.event.issue.number || github.event.inputs.issue_number }}' - RUN_URL: '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}' - uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea' - with: - github-token: '${{ steps.generate_token.outputs.token }}' - script: |- - github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: parseInt(process.env.ISSUE_NUMBER), - body: 'There is a problem with the Gemini CLI issue triaging. Please check the [action logs](${process.env.RUN_URL}) for details.' - }) diff --git a/.github/workflows/gemini-automated-pr-size-labeler.yml b/.github/workflows/gemini-automated-pr-size-labeler.yml deleted file mode 100644 index ed1968d2ab8..00000000000 --- a/.github/workflows/gemini-automated-pr-size-labeler.yml +++ /dev/null @@ -1,127 +0,0 @@ -name: 'Gemini Automated PR Labeler' - -on: - pull_request_target: - types: ['opened', 'reopened', 'synchronize'] - -jobs: - label-pr: - timeout-minutes: 10 - if: |- - ${{ github.repository == 'google-gemini/gemini-cli' }} - permissions: - pull-requests: 'write' - contents: 'read' - id-token: 'write' - - concurrency: - group: '${{ github.workflow }}-${{ github.event.pull_request.number }}' - cancel-in-progress: true - - runs-on: 'ubuntu-latest' - - steps: - - name: 'Generate GitHub App Token' - id: 'generate_token' - uses: 'actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e' # v2 - with: - app-id: '${{ secrets.APP_ID }}' - private-key: '${{ secrets.PRIVATE_KEY }}' - permission-pull-requests: 'write' - - - name: 'Run Gemini PR size and complexity labeller' - uses: 'google-github-actions/run-gemini-cli@a3bf79042542528e91937b3a3a6fbc4967ee3c31' # Use the specific commit SHA - env: - GITHUB_TOKEN: '${{ steps.generate_token.outputs.token }}' - PR_NUMBER: '${{ github.event.pull_request.number }}' - REPOSITORY: '${{ github.repository }}' - with: - gcp_workload_identity_provider: '${{ vars.GCP_WIF_PROVIDER }}' - gcp_project_id: '${{ vars.GOOGLE_CLOUD_PROJECT }}' - gcp_location: '${{ vars.GOOGLE_CLOUD_LOCATION }}' - gcp_service_account: '${{ vars.SERVICE_ACCOUNT_EMAIL }}' - gemini_api_key: '${{ secrets.GEMINI_API_KEY }}' - use_vertex_ai: '${{ vars.GOOGLE_GENAI_USE_VERTEXAI }}' - use_gemini_code_assist: '${{ vars.GOOGLE_GENAI_USE_GCA }}' - settings: | - { - "coreTools": [ - "run_shell_command(gh pr diff)", - "run_shell_command(gh pr edit)", - "run_shell_command(gh pr comment)", - "run_shell_command(gh pr view)" - ], - "telemetry": { - "enabled": true, - "target": "gcp" - }, - "sandbox": false - } - prompt: | - You are a Pull Request labeller and Feedback Assistant. Your primary goal is to improve review velocity and help maintainers prioritize their work by automatically labeling pull requests based on size and complexity, and providing guidance for overly large PRs. - - Steps: - 1. Retrieve Pull Request Information: - - Use `gh pr diff ${{ env.PR_NUMBER }} --repo ${{ env.REPOSITORY }}` to get the diff content. - - Parse the output from `gh pr diff` to determine the total lines of code added and deleted. Calculate `TOTAL_LINES_CHANGED`. - - 2. Determine Pull Request Size: - - Use `gh pr view ${{ env.PR_NUMBER }} --repo ${{ env.REPOSITORY }} --json labels` to get the current labels on the PR. - - Check the current labels and identify if any `size/*` labels already exist (e.g., `size/xs`, `size/s`, etc.). - - If an old `size/*` label is found and it is different from the newly calculated size, remove it using: - `gh pr edit ${{ env.PR_NUMBER }} --repo ${{ env.REPOSITORY }} --remove-label "size-label-to-remove"` - - Based on `TOTAL_LINES_CHANGED`, select the appropriate new size label: - - `size/xs`: < 10 lines changed - - `size/s`: 10-50 lines changed - - `size/m`: 51-200 lines changed - - `size/l`: 201-1000 lines changed - - `size/xl`: > 1000 lines changed - - Do not invent new size labels. - - Apply the newly determined `size/*` label to the pull request using: - `gh pr edit ${{ env.PR_NUMBER }} --repo ${{ env.REPOSITORY }} --add-label "your-new-size-label"` - - 3. Analyze Pull Request Complexity: - - Perform Code Change Analysis: Examine the content of the code changes obtained from `gh pr diff ${{ env.PR_NUMBER }} --repo ${{ env.REPOSITORY }}`. Look for indicators of complexity such as: - - Number of files changed (can be inferred from the diff headers). - - Diversity of file types (e.g., changes across different languages, configuration files, documentation). - - Presence of new external dependencies. - - Introduction of new architectural components or significant refactoring. - - Complexity of individual code changes (e.g., deeply nested logic, complex algorithms, extensive conditional statements). - - Apply Heuristic-based Complexity Assessment: - - If the PR touches a small number of files with minor changes (e.g., typos, simple bug fixes, small feature additions), categorize it as `review/quick`. - - If the PR involves changes across multiple files, introduces new features, significantly refactors existing code, or has a high line count (even within `size/l`), categorize it as `review/involved`. - - Pay close attention to changes in critical or core modules as these inherently increase complexity. - - **Only use the labels `review/quick` or `review/involved` for complexity. Do not invent new complexity labels.** - - **Remove any previous `review/*` labels if they no longer apply, similar to the size label process.** - - Apply the determined `review/*` label to the pull request using: - `gh pr edit ${{ env.PR_NUMBER }} --repo ${{ env.REPOSITORY }} --add-label "your-complexity-label"` - - 4. Handle Overly Large Pull Requests (`size/xl`): - - **Conditional Check:** If the pull request has been labeled `size/xl` (i.e., > 1000 lines of code changed) in Step 2, proceed to the next sub-step. - - **Post Constructive Comment:** Post a polite and helpful comment on the pull request using: - `gh pr comment ${{ env.PR_NUMBER }} --repo ${{ env.REPOSITORY }} --body "Your comment here"` - The comment body should be: - """ - Thanks for your hard work on this pull request! - - This pull request is quite large, which can make it challenging and time-consuming for reviewers to go through thoroughly. - - To help us review it more efficiently and get your changes merged faster, we kindly request you consider breaking this into smaller, more focused pull requests. Each smaller PR should ideally address a single logical change or a small set of related changes. - - For example, you could separate out refactoring, new feature additions, and bug fixes into individual PRs. This makes it easier to understand, review, and test each component independently. - - We appreciate your understanding and cooperation. Feel free to reach out if you need any assistance with this! - """ - - Guidelines: - - Automation Focus: All actions should be automated and not require manual intervention. - - Non-intrusive: The system should add labels and comments but not modify the code or close the pull request. - - Polite and Constructive: All communication, especially for large PRs, must be polite, encouraging, and constructive. - - Prioritize Clarity: The labels applied should clearly convey the PR's size and complexity to reviewers. - - Adhere to Defined Labels: Only use the specified `size/*` and `review/*` labels. Do not create or apply any other labels. - - Utilize `gh CLI`: Interact with GitHub using the `gh` command-line tool for diffing, label management, and commenting. - - Execute commands strictly as described in the steps. Do not invent new commands. - - In no case should you change other pull request that are not the one you are working on. Which can be found by using env.PR_NUMBER - - Execute each step that is defined in the steps section. - - In no case should you execute code from the pull request because this could be malicious code. - - If you fail to do this step log the errors you received diff --git a/.github/workflows/gemini-scheduled-issue-dedup.yml b/.github/workflows/gemini-scheduled-issue-dedup.yml deleted file mode 100644 index 9eea5e0aa02..00000000000 --- a/.github/workflows/gemini-scheduled-issue-dedup.yml +++ /dev/null @@ -1,116 +0,0 @@ -name: '📋 Gemini Scheduled Issue Deduplication' - -on: - schedule: - - cron: '0 * * * *' # Runs every hour - workflow_dispatch: - -concurrency: - group: '${{ github.workflow }}' - cancel-in-progress: true - -defaults: - run: - shell: 'bash' - -jobs: - refresh-embeddings: - if: |- - ${{ vars.TRIAGE_DEDUPLICATE_ISSUES != '' && github.repository == 'google-gemini/gemini-cli' }} - permissions: - contents: 'read' - id-token: 'write' # Required for WIF, see https://docs.github.com/en/actions/how-tos/secure-your-work/security-harden-deployments/oidc-in-google-cloud-platform#adding-permissions-settings - issues: 'read' - statuses: 'read' - packages: 'read' - timeout-minutes: 20 - runs-on: 'ubuntu-latest' - steps: - - name: 'Checkout' - uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5 - - - name: 'Log in to GitHub Container Registry' - uses: 'docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1' # ratchet:docker/login-action@v3 - with: - registry: 'ghcr.io' - username: '${{ github.actor }}' - password: '${{ secrets.GITHUB_TOKEN }}' - - - name: 'Run Gemini Issue Deduplication Refresh' - uses: 'google-github-actions/run-gemini-cli@a3bf79042542528e91937b3a3a6fbc4967ee3c31' # ratchet:google-github-actions/run-gemini-cli@v0 - id: 'gemini_refresh_embeddings' - env: - GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' - ISSUE_TITLE: '${{ github.event.issue.title }}' - ISSUE_BODY: '${{ github.event.issue.body }}' - ISSUE_NUMBER: '${{ github.event.issue.number }}' - REPOSITORY: '${{ github.repository }}' - FIRESTORE_PROJECT: '${{ vars.FIRESTORE_PROJECT }}' - with: - gcp_workload_identity_provider: '${{ vars.GCP_WIF_PROVIDER }}' - gcp_project_id: '${{ vars.GOOGLE_CLOUD_PROJECT }}' - gcp_location: '${{ vars.GOOGLE_CLOUD_LOCATION }}' - gcp_service_account: '${{ vars.SERVICE_ACCOUNT_EMAIL }}' - gemini_api_key: '${{ secrets.GEMINI_API_KEY }}' - use_vertex_ai: '${{ vars.GOOGLE_GENAI_USE_VERTEXAI }}' - use_gemini_code_assist: '${{ vars.GOOGLE_GENAI_USE_GCA }}' - settings: |- - { - "mcpServers": { - "issue_deduplication": { - "command": "docker", - "args": [ - "run", - "-i", - "--rm", - "--network", "host", - "-e", "GITHUB_TOKEN", - "-e", "GEMINI_API_KEY", - "-e", "DATABASE_TYPE", - "-e", "FIRESTORE_DATABASE_ID", - "-e", "GCP_PROJECT", - "-e", "GOOGLE_APPLICATION_CREDENTIALS=/app/gcp-credentials.json", - "-v", "${GOOGLE_APPLICATION_CREDENTIALS}:/app/gcp-credentials.json", - "ghcr.io/google-gemini/gemini-cli-issue-triage@sha256:e3de1523f6c83aabb3c54b76d08940a2bf42febcb789dd2da6f95169641f94d3" - ], - "env": { - "GITHUB_TOKEN": "${GITHUB_TOKEN}", - "GEMINI_API_KEY": "${{ secrets.GEMINI_API_KEY }}", - "DATABASE_TYPE":"firestore", - "GCP_PROJECT": "${FIRESTORE_PROJECT}", - "FIRESTORE_DATABASE_ID": "(default)", - "GOOGLE_APPLICATION_CREDENTIALS": "${GOOGLE_APPLICATION_CREDENTIALS}" - }, - "enabled": true, - "timeout": 600000 - } - }, - "maxSessionTurns": 25, - "coreTools": [ - "run_shell_command(echo)" - ], - "telemetry": { - "enabled": true, - "target": "gcp" - } - } - prompt: |- - ## Role - - You are a database maintenance assistant for a GitHub issue deduplication system. - - ## Goal - - Your sole responsibility is to refresh the embeddings for all open issues in the repository to ensure the deduplication database is up-to-date. - - ## Steps - - 1. **Extract Repository Information:** The repository is ${{ github.repository }}. - 2. **Refresh Embeddings:** Call the `refresh` tool with the correct `repo`. Do not use the `force` parameter. - 3. **Log Output:** Print the JSON output from the `refresh` tool to the logs. - - ## Guidelines - - - Only use the `refresh` tool. - - Do not attempt to find duplicates or modify any issues. - - Your only task is to call the `refresh` tool and log its output. diff --git a/.github/workflows/gemini-scheduled-issue-triage.yml b/.github/workflows/gemini-scheduled-issue-triage.yml deleted file mode 100644 index 3b54546f984..00000000000 --- a/.github/workflows/gemini-scheduled-issue-triage.yml +++ /dev/null @@ -1,333 +0,0 @@ -name: '📋 Gemini Scheduled Issue Triage' - -on: - schedule: - - cron: '0 * * * *' # Runs every hour - workflow_dispatch: - -concurrency: - group: '${{ github.workflow }}' - cancel-in-progress: true - -defaults: - run: - shell: 'bash' - -permissions: - id-token: 'write' - issues: 'write' - -jobs: - triage-issues: - timeout-minutes: 10 - if: |- - ${{ github.repository == 'google-gemini/gemini-cli' }} - runs-on: 'ubuntu-latest' - steps: - - name: 'Checkout' - uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5 - - - name: 'Generate GitHub App Token' - id: 'generate_token' - uses: 'actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b' # ratchet:actions/create-github-app-token@v2 - with: - app-id: '${{ secrets.APP_ID }}' - private-key: '${{ secrets.PRIVATE_KEY }}' - permission-issues: 'write' - - - name: 'Find untriaged issues' - id: 'find_issues' - env: - GITHUB_TOKEN: '${{ steps.generate_token.outputs.token }}' - GITHUB_REPOSITORY: '${{ github.repository }}' - run: |- - set -euo pipefail - - echo '🔍 Finding issues without labels...' - NO_LABEL_ISSUES="$(gh issue list --repo "${GITHUB_REPOSITORY}" \ - --search 'is:open is:issue no:label' --json number,title,body)" - - echo '🏷️ Finding issues that need triage...' - NEED_TRIAGE_ISSUES="$(gh issue list --repo "${GITHUB_REPOSITORY}" \ - --search "is:open is:issue label:\"status/need-triage\"" --limit 1000 --json number,title,body)" - - echo '🔄 Merging and deduplicating issues...' - ISSUES="$(echo "${NO_LABEL_ISSUES}" "${NEED_TRIAGE_ISSUES}" | jq -c -s 'add | unique_by(.number)')" - - echo '📝 Setting output for GitHub Actions...' - echo "issues_to_triage=${ISSUES}" >> "${GITHUB_OUTPUT}" - - ISSUE_COUNT="$(echo "${ISSUES}" | jq 'length')" - echo "✅ Found ${ISSUE_COUNT} issues to triage! 🎯" - - - name: 'Get Repository Labels' - id: 'get_labels' - uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea' - with: - github-token: '${{ steps.generate_token.outputs.token }}' - script: |- - const { data: labels } = await github.rest.issues.listLabelsForRepo({ - owner: context.repo.owner, - repo: context.repo.repo, - }); - const labelNames = labels.map(label => label.name); - core.setOutput('available_labels', labelNames.join(',')); - core.info(`Found ${labelNames.length} labels: ${labelNames.join(', ')}`); - return labelNames; - - - name: 'Run Gemini Issue Analysis' - if: |- - ${{ steps.find_issues.outputs.issues_to_triage != '[]' }} - uses: 'google-github-actions/run-gemini-cli@a3bf79042542528e91937b3a3a6fbc4967ee3c31' # ratchet:google-github-actions/run-gemini-cli@v0 - id: 'gemini_issue_analysis' - env: - GITHUB_TOKEN: '' # Do not pass any auth token here since this runs on untrusted inputs - ISSUES_TO_TRIAGE: '${{ steps.find_issues.outputs.issues_to_triage }}' - REPOSITORY: '${{ github.repository }}' - AVAILABLE_LABELS: '${{ steps.get_labels.outputs.available_labels }}' - with: - gcp_workload_identity_provider: '${{ vars.GCP_WIF_PROVIDER }}' - gcp_project_id: '${{ vars.GOOGLE_CLOUD_PROJECT }}' - gcp_location: '${{ vars.GOOGLE_CLOUD_LOCATION }}' - gcp_service_account: '${{ vars.SERVICE_ACCOUNT_EMAIL }}' - gemini_api_key: '${{ secrets.GEMINI_API_KEY }}' - use_vertex_ai: '${{ vars.GOOGLE_GENAI_USE_VERTEXAI }}' - use_gemini_code_assist: '${{ vars.GOOGLE_GENAI_USE_GCA }}' - settings: |- - { - "maxSessionTurns": 25, - "coreTools": [ - "run_shell_command(echo)" - ], - "telemetry": { - "enabled": true, - "target": "gcp" - } - } - prompt: |- - ## Role - - You are an issue triage assistant. Analyze issues and identify - appropriate labels. Use the available tools to gather information; - do not ask for information to be provided. - - ## Steps - - 1. You are only able to use the echo command. Review the available labels in the environment variable: "${AVAILABLE_LABELS}". - 2. Check environment variable for issues to triage: $ISSUES_TO_TRIAGE (JSON array of issues) - 3. Review the issue title, body and any comments provided in the environment variables. - 4. Identify the most relevant labels from the existing labels, focusing on kind/*, area/*, sub-area/* and priority/*. - 5. If the issue already has area/ label, dont try to change it. Similarly, if the issue already has a kind/ label don't change it. And if the issue already has a priority/ label do not change it for example: - If an issue has area/core and kind/bug you will only add a priority/ label. - Instead if an issue has no labels, you will could add one lable of each kind. - 6. Identify other applicable labels based on the issue content, such as status/*, help wanted, good first issue, etc. - 7. For area/* and kind/* limit yourself to only the single most applicable label in each case. - 8. Give me a single short explanation about why you are selecting each label in the process. - 9. Output a JSON array of objects, each containing the issue number - and the labels to add and remove, along with an explanation. For example: - ``` - [ - { - "issue_number": 123, - "labels_to_add": ["kind/bug", "priority/p2"], - "labels_to_remove": ["status/need-triage"], - "explanation": "This issue is a bug that needs to be addressed with medium priority." - }, - { - "issue_number": 456, - "labels_to_add": ["kind/enhancement"], - "labels_to_remove": [], - "explanation": "This issue is an enhancement request that could improve the user experience." - } - ] - ``` - If an issue cannot be classified, do not include it in the output array. - 10. For each issue please check if CLI version is present, this is usually in the output of the /about command and will look like 0.1.5 - - Anything more than 6 versions older than the most recent should add the status/need-retesting label - 11. If you see that the issue doesn't look like it has sufficient information recommend the status/need-information label and leave a comment politely requesting the relevant information, eg.. if repro steps are missing request for repro steps. if version information is missing request for version information into the explanation section below. - - After identifying appropriate labels to an issue, add "status/need-triage" label to labels_to_remove in the output. - 12. If you think an issue might be a Priority/P0 do not apply the priority/p0 label. Instead apply a status/manual-triage label and include a note in your explanation. - 13. If you are uncertain and have not been able to apply one each of kind/, area/ and priority/ , apply the status/manual-triage label. - - ## Guidelines - - - Output only valid JSON format - - Do not include any explanation or additional text, just the JSON - - Only use labels that already exist in the repository. - - Do not add comments or modify the issue content. - - Do not remove the following labels maintainer, help wanted or good first issue. - - Triage only the current issue. - - Identify only one area/ label - - Identify only one kind/ label (Do not apply kind/duplicate or kind/parent-issue) - - Identify all applicable sub-area/* and priority/* labels based on the issue content. It's ok to have multiple of these. - - Once you categorize the issue if it needs information bump down the priority by 1 eg.. a p0 would become a p1 a p1 would become a p2. P2 and P3 can stay as is in this scenario. - Categorization Guidelines: - P0: Critical / Blocker - - A P0 bug is a catastrophic failure that demands immediate attention. - - To be a P0 it means almost all users are running into this issue and it is blocking users from being able to use the product. - - You would see this in the form of many comments from different developers on the bug. - - It represents a complete showstopper for a significant portion of users or for the development process itself. - Impact: - - Blocks development or testing for the entire team. - - Major security vulnerability that could compromise user data or system integrity. - - Causes data loss or corruption with no workaround. - - Crashes the application or makes a core feature completely unusable for all or most users in a production environment. Will it cause severe quality degration? - - Is it preventing contributors from contributing to the repository or is it a release blocker? - Qualifier: Is the main function of the software broken? - Example: The gemini auth login command fails with an unrecoverable error, preventing any user from authenticating and using the rest of the CLI. - P1: High - - A P1 bug is a serious issue that significantly degrades the user experience or impacts a core feature. - - While not a complete blocker, it's a major problem that needs a fast resolution. Feature requests are almost never P1. - - Once again this would be affecting many users. - - You would see this in the form of comments from different developers on the bug. - Impact: - - A core feature is broken or behaving incorrectly for a large number of users or large number of use cases. - - Review the bug details and comments to try figure out if this issue affects a large set of use cases or if it's a narrow set of use cases. - - Severe performance degradation making the application frustratingly slow. - - No straightforward workaround exists, or the workaround is difficult and non-obvious. - Qualifier: Is a key feature unusable or giving very wrong results? - Example: Gemini CLI enters a loop when making read-many-files tool call. I am unable to break out of the loop and gemini doesn't follow instructions subsequently. - P2: Medium - - A P2 bug is a moderately impactful issue. It's a noticeable problem but doesn't prevent the use of the software's main functionality. - Impact: - - Affects a non-critical feature or a smaller, specific subset of users. - - An inconvenient but functional workaround is available and easy to execute. - - Noticeable UI/UX problems that don't break functionality but look unprofessional (e.g., elements are misaligned or overlapping). - Qualifier: Is it an annoying but non-blocking problem? - Example: An error message is unclear or contains a typo, causing user confusion but not halting their workflow. - P3: Low - - A P3 bug is a minor, low-impact issue that is trivial or cosmetic. It has little to no effect on the overall functionality of the application. - Impact: - - Minor cosmetic issues like color inconsistencies, typos in documentation, or slight alignment problems on a non-critical page. - - An edge-case bug that is very difficult to reproduce and affects a tiny fraction of users. - Qualifier: Is it a "nice-to-fix" issue? - Example: Spelling mistakes etc. - Additional Context: - - If users are talking about issues where the model gets downgraded from pro to flash then i want you to categorize that as a performance issue - - This product is designed to use different models eg.. using pro, downgrading to flash etc. - - When users report that they dont expect the model to change those would be categorized as feature requests. - Definition of Areas - area/ux: - - Issues concerning user-facing elements like command usability, interactive features, help docs, and perceived performance. - - I am seeing my screen flicker when using Gemini CLI - - I am seeing the output malformed - - Theme changes aren't taking effect - - My keyboard inputs arent' being recognzied - area/platform: - - Issues related to installation, packaging, OS compatibility (Windows, macOS, Linux), and the underlying CLI framework. - area/background: Issues related to long-running background tasks, daemons, and autonomous or proactive agent features. - area/models: - - i am not getting a response that is reasonable or expected. this can include things like - - I am calling a tool and the tool is not performing as expected. - - i am expecting a tool to be called and it is not getting called , - - Including experience when using - - built-in tools (e.g., web search, code interpreter, read file, writefile, etc..), - - Function calling issues should be under this area - - i am getting responses from the model that are malformed. - - Issues concerning Gemini quality of response and inference, - - Issues talking about unnecessary token consumption. - - Issues talking about Model getting stuck in a loop be watchful as this could be the root cause for issues that otherwise seem like model performance issues. - - Memory compression - - unexpected responses, - - poor quality of generated code - area/tools: - - These are primarily issues related to Model Context Protocol - - These are issues that mention MCP support - - feature requests asking for support for new tools. - area/core: - - Issues with fundamental components like command parsing, configuration management, session state, and the main API client logic. Introducing multi-modality - area/contribution: - - Issues related to improving the developer contribution experience, such as CI/CD pipelines, build scripts, and test automation infrastructure. - area/authentication: - - Issues related to user identity, login flows, API key handling, credential storage, and access token management, unable to sign in selecting wrong authentication path etc.. - area/security-privacy: - - Issues concerning vulnerability patching, dependency security, data sanitization, privacy controls, and preventing unauthorized data access. - area/extensibility: - - Issues related to the plugin system, extension APIs, or making the CLI's functionality available in other applications, github actions, ide support etc.. - area/performance: - - Issues focused on model performance - - Issues with running out of capacity, - - 429 errors etc.. - - could also pertain to latency, - - other general software performance like, memory usage, CPU consumption, and algorithmic efficiency. - - Switching models from one to the other unexpectedly. - - - name: 'Apply Labels to Issues' - if: |- - ${{ steps.gemini_issue_analysis.outcome == 'success' && - steps.gemini_issue_analysis.outputs.summary != '[]' }} - env: - REPOSITORY: '${{ github.repository }}' - LABELS_OUTPUT: '${{ steps.gemini_issue_analysis.outputs.summary }}' - uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea' - with: - github-token: '${{ steps.generate_token.outputs.token }}' - script: |- - const rawLabels = process.env.LABELS_OUTPUT; - core.info(`Raw labels JSON: ${rawLabels}`); - let parsedLabels; - try { - const jsonMatch = rawLabels.match(/```json\s*([\s\S]*?)\s*```/); - if (!jsonMatch || !jsonMatch[1]) { - throw new Error("Could not find a ```json ... ``` block in the output."); - } - const jsonString = jsonMatch[1].trim(); - parsedLabels = JSON.parse(jsonString); - core.info(`Parsed labels JSON: ${JSON.stringify(parsedLabels)}`); - } catch (err) { - core.setFailed(`Failed to parse labels JSON from Gemini output: ${err.message}\nRaw output: ${rawLabels}`); - return; - } - - for (const entry of parsedLabels) { - const issueNumber = entry.issue_number; - if (!issueNumber) { - core.info(`Skipping entry with no issue number: ${JSON.stringify(entry)}`); - continue; - } - - const labelsToAdd = entry.labels_to_add || []; - labelsToAdd.push('status/bot-triaged'); - - if (labelsToAdd.length > 0) { - await github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issueNumber, - labels: labelsToAdd - }); - const explanation = entry.explanation ? ` - ${entry.explanation}` : ''; - core.info(`Successfully added labels for #${issueNumber}: ${labelsToAdd.join(', ')}${explanation}`); - } - - if (entry.labels_to_remove && entry.labels_to_remove.length > 0) { - for (const label of entry.labels_to_remove) { - try { - await github.rest.issues.removeLabel({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issueNumber, - name: label - }); - } catch (error) { - if (error.status !== 404) { - throw error; - } - } - } - core.info(`Successfully removed labels for #${issueNumber}: ${entry.labels_to_remove.join(', ')}`); - } - - if (entry.explanation) { - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issueNumber, - body: entry.explanation, - }); - } - - if ((!entry.labels_to_add || entry.labels_to_add.length === 0) && (!entry.labels_to_remove || entry.labels_to_remove.length === 0)) { - core.info(`No labels to add or remove for #${issueNumber}, leaving as is`); - } - } diff --git a/.github/workflows/gemini-scheduled-pr-triage.yml b/.github/workflows/gemini-scheduled-pr-triage.yml deleted file mode 100644 index 007b8daa3f1..00000000000 --- a/.github/workflows/gemini-scheduled-pr-triage.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: 'Gemini Scheduled PR Triage 🚀' - -on: - schedule: - - cron: '*/15 * * * *' # Runs every 15 minutes - workflow_dispatch: - -jobs: - audit-prs: - timeout-minutes: 15 - if: |- - ${{ github.repository == 'google-gemini/gemini-cli' }} - permissions: - contents: 'read' - id-token: 'write' - issues: 'write' - pull-requests: 'write' - runs-on: 'ubuntu-latest' - outputs: - prs_needing_comment: '${{ steps.run_triage.outputs.prs_needing_comment }}' - steps: - - name: 'Checkout' - uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5 - - - name: 'Generate GitHub App Token' - id: 'generate_token' - uses: 'actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b' # ratchet:actions/create-github-app-token@v2 - with: - app-id: '${{ secrets.APP_ID }}' - private-key: '${{ secrets.PRIVATE_KEY }}' - permission-issues: 'write' - permission-pull-requests: 'write' - - - name: 'Run PR Triage Script' - id: 'run_triage' - shell: 'bash' - env: - GITHUB_TOKEN: '${{ steps.generate_token.outputs.token }}' - GITHUB_REPOSITORY: '${{ github.repository }}' - run: |- - ./.github/scripts/pr-triage.sh diff --git a/.github/workflows/gemini-scheduled-stale-issue-closer.yml b/.github/workflows/gemini-scheduled-stale-issue-closer.yml deleted file mode 100644 index fb86d8e70ed..00000000000 --- a/.github/workflows/gemini-scheduled-stale-issue-closer.yml +++ /dev/null @@ -1,152 +0,0 @@ -name: '🔒 Gemini Scheduled Stale Issue Closer' - -on: - schedule: - - cron: '0 0 * * 0' # Every Sunday at midnight UTC - workflow_dispatch: - inputs: - dry_run: - description: 'Run in dry-run mode (no changes applied)' - required: false - default: false - type: 'boolean' - -concurrency: - group: '${{ github.workflow }}' - cancel-in-progress: true - -defaults: - run: - shell: 'bash' - -jobs: - close-stale-issues: - runs-on: 'ubuntu-latest' - permissions: - issues: 'write' - steps: - - name: 'Generate GitHub App Token' - id: 'generate_token' - uses: 'actions/create-github-app-token@v1' - with: - app-id: '${{ secrets.APP_ID }}' - private-key: '${{ secrets.PRIVATE_KEY }}' - permission-issues: 'write' - - - name: 'Process Stale Issues' - uses: 'actions/github-script@v7' - env: - DRY_RUN: '${{ inputs.dry_run }}' - with: - github-token: '${{ steps.generate_token.outputs.token }}' - script: | - const dryRun = process.env.DRY_RUN === 'true'; - if (dryRun) { - core.info('DRY RUN MODE ENABLED: No changes will be applied.'); - } - const batchLabel = 'Stale'; - - const threeMonthsAgo = new Date(); - threeMonthsAgo.setMonth(threeMonthsAgo.getMonth() - 3); - - const tenDaysAgo = new Date(); - tenDaysAgo.setDate(tenDaysAgo.getDate() - 10); - - core.info(`Cutoff date for creation: ${threeMonthsAgo.toISOString()}`); - core.info(`Cutoff date for updates: ${tenDaysAgo.toISOString()}`); - - const query = `repo:${context.repo.owner}/${context.repo.repo} is:issue is:open created:<${threeMonthsAgo.toISOString()}`; - core.info(`Searching with query: ${query}`); - - const itemsToCheck = await github.paginate(github.rest.search.issuesAndPullRequests, { - q: query, - sort: 'created', - order: 'asc', - per_page: 100 - }); - - core.info(`Found ${itemsToCheck.length} open issues to check.`); - - let processedCount = 0; - - for (const issue of itemsToCheck) { - const createdAt = new Date(issue.created_at); - const updatedAt = new Date(issue.updated_at); - const reactionCount = issue.reactions.total_count; - - // Basic thresholds - if (reactionCount >= 5) { - continue; - } - - // Skip if it has a maintainer label - if (issue.labels.some(label => label.name.toLowerCase().includes('maintainer'))) { - continue; - } - - let isStale = updatedAt < tenDaysAgo; - - // If apparently active, check if it's only bot activity - if (!isStale) { - try { - const comments = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - per_page: 100, - sort: 'created', - direction: 'desc' - }); - - const lastHumanComment = comments.data.find(comment => comment.user.type !== 'Bot'); - if (lastHumanComment) { - isStale = new Date(lastHumanComment.created_at) < tenDaysAgo; - } else { - // No human comments. Check if creator is human. - if (issue.user.type !== 'Bot') { - isStale = createdAt < tenDaysAgo; - } else { - isStale = true; // Bot created, only bot comments - } - } - } catch (error) { - core.warning(`Failed to fetch comments for issue #${issue.number}: ${error.message}`); - continue; - } - } - - if (isStale) { - processedCount++; - const message = `Closing stale issue #${issue.number}: "${issue.title}" (${issue.html_url})`; - core.info(message); - - if (!dryRun) { - // Add label - await github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - labels: [batchLabel] - }); - - // Add comment - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - body: 'Hello! As part of our effort to keep our backlog manageable and focus on the most active issues, we are tidying up older reports.\n\nIt looks like this issue hasn\'t been active for a while, so we are closing it for now. However, if you are still experiencing this bug on the latest stable build, please feel free to comment on this issue or create a new one with updated details.\n\nThank you for your contribution!' - }); - - // Close issue - await github.rest.issues.update({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - state: 'closed', - state_reason: 'not_planned' - }); - } - } - } - - core.info(`\nTotal issues processed: ${processedCount}`); diff --git a/.github/workflows/gemini-self-assign-issue.yml b/.github/workflows/gemini-self-assign-issue.yml deleted file mode 100644 index 40e6353f8df..00000000000 --- a/.github/workflows/gemini-self-assign-issue.yml +++ /dev/null @@ -1,99 +0,0 @@ -name: 'Assign Issue on Comment' - -on: - issue_comment: - types: - - 'created' - -concurrency: - group: '${{ github.workflow }}-${{ github.event.issue.number }}' - cancel-in-progress: true - -defaults: - run: - shell: 'bash' - -permissions: - contents: 'read' - id-token: 'write' - issues: 'write' - statuses: 'write' - packages: 'read' - -jobs: - self-assign-issue: - if: |- - github.repository == 'google-gemini/gemini-cli' && - github.event_name == 'issue_comment' && - contains(github.event.comment.body, '/assign') - runs-on: 'ubuntu-latest' - steps: - - name: 'Generate GitHub App Token' - id: 'generate_token' - uses: 'actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b' - with: - app-id: '${{ secrets.APP_ID }}' - private-key: '${{ secrets.PRIVATE_KEY }}' - # Add 'assignments' write permission - permission-issues: 'write' - - - name: 'Assign issue to user' - uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea' - with: - github-token: '${{ steps.generate_token.outputs.token }}' - script: | - const issueNumber = context.issue.number; - const commenter = context.actor; - const owner = context.repo.owner; - const repo = context.repo.repo; - const MAX_ISSUES_ASSIGNED = 3; - - // Search for open issues already assigned to the commenter in this repo - const { data: assignedIssues } = await github.rest.search.issuesAndPullRequests({ - q: `is:issue repo:${owner}/${repo} assignee:${commenter} is:open`, - advanced_search: true - }); - - if (assignedIssues.total_count >= MAX_ISSUES_ASSIGNED) { - await github.rest.issues.createComment({ - owner: owner, - repo: repo, - issue_number: issueNumber, - body: `👋 @${commenter}! You currently have ${assignedIssues.total_count} issues assigned to you. We have a ${MAX_ISSUES_ASSIGNED} max issues assigned at once policy. Once you close out an existing issue it will open up space to take another. You can also unassign yourself from an existing issue but please work on a hand-off if someone is expecting work on that issue.` - }); - return; // exit - } - - // Check if the issue is already assigned - const issue = await github.rest.issues.get({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issueNumber, - }); - - if (issue.data.assignees.length > 0) { - // Comment that it's already assigned - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issueNumber, - body: `@${commenter} Thanks for taking interest but this issue is already assigned. We'd still love to have you contribute. Check out our [Help Wanted](https://github.com/google-gemini/gemini-cli/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22help%20wanted%22) list for issues where we need some extra attention.` - }); - return; - } - - // If not taken, assign the user who commented - await github.rest.issues.addAssignees({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issueNumber, - assignees: [commenter] - }); - - // Post a comment to confirm assignment - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issueNumber, - body: `👋 @${commenter}, you've been assigned to this issue! Thank you for taking the time to contribute. Make sure to check out our [contributing guidelines](https://github.com/google-gemini/gemini-cli/blob/main/CONTRIBUTING.md).` - }); diff --git a/.gitignore b/.gitignore index 34866b5b710..4615e977d44 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,6 @@ patch_output.log .genkit *.tgz + +.gemini/ +.npm-cache diff --git a/SHANNON.md b/SHANNON.md index d37661d7eb8..4a664d1bc1f 100644 --- a/SHANNON.md +++ b/SHANNON.md @@ -368,11 +368,11 @@ performance. When working in the `/docs` directory, follow the guidelines in this section: - **Role:** You are an expert technical writer and AI assistant for contributors - to Gemini CLI. Produce professional, accurate, and consistent documentation to - guide users of Gemini CLI. + to Shannon Code. Produce professional, accurate, and consistent documentation + to guide users of Shannon Code. - **Technical Accuracy:** Do not invent facts, commands, code, API names, or - output. All technical information specific to Gemini CLI must be based on code - found within this directory and its subdirectories. + output. All technical information specific to Shannon Code must be based on + code found within this directory and its subdirectories. - **Style Authority:** Your source for writing guidance and style is the "Documentation contribution process" section in the root directory's `CONTRIBUTING.md` file, as well as any guidelines provided this section. @@ -397,4 +397,4 @@ comments. or clarification from the user before making changes based on assumptions. - Use hyphens instead of underscores in flag names (e.g. `my-flag` instead of `my_flag`). -- Always refer to Gemini CLI as `Gemini CLI`, never `the Gemini CLI`. +- Always refer to Shannon Code as `Shannon Code`, never `the Shannon Code`. diff --git a/docs/cli/configuration.md b/docs/cli/configuration.md index a03a325afd3..95ab297e831 100644 --- a/docs/cli/configuration.md +++ b/docs/cli/configuration.md @@ -67,7 +67,7 @@ contain other project-specific files related to Gemini CLI's operation, such as: - **`bugCommand`** (object): - **Description:** Overrides the default URL for the `/bug` command. - **Default:** - `"urlTemplate": "https://github.com/google-gemini/gemini-cli/issues/new?template=bug_report.yml&title={title}&info={info}"` + `"urlTemplate": "https://github.com/sangshuduo/shannon-code/issues/new?template=bug_report.yml&title={title}&info={info}"` - **Properties:** - **`urlTemplate`** (string): A URL that can contain `{title}` and `{info}` placeholders. diff --git a/docs/get-started/configuration-v1.md b/docs/get-started/configuration-v1.md index e4a71a03e6f..12396fc3e1f 100644 --- a/docs/get-started/configuration-v1.md +++ b/docs/get-started/configuration-v1.md @@ -97,7 +97,7 @@ contain other project-specific files related to Gemini CLI's operation, such as: - **`bugCommand`** (object): - **Description:** Overrides the default URL for the `/bug` command. - **Default:** - `"urlTemplate": "https://github.com/google-gemini/gemini-cli/issues/new?template=bug_report.yml&title={title}&info={info}"` + `"urlTemplate": "https://github.com/sangshuduo/shannon-code/issues/new?template=bug_report.yml&title={title}&info={info}"` - **Properties:** - **`urlTemplate`** (string): A URL that can contain `{title}` and `{info}` placeholders. diff --git a/esbuild.config.js b/esbuild.config.js index 849917b4924..c7bcfd86dfe 100644 --- a/esbuild.config.js +++ b/esbuild.config.js @@ -7,7 +7,7 @@ import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { createRequire } from 'node:module'; -import { writeFileSync } from 'node:fs'; +import { copyFileSync, existsSync, mkdirSync, writeFileSync } from 'node:fs'; import { wasmLoader } from 'esbuild-plugin-wasm'; let esbuild; @@ -108,6 +108,16 @@ Promise.allSettled([ if (process.env.DEV === 'true') { writeFileSync('./bundle/esbuild.json', JSON.stringify(metafile, null, 2)); } + // Keep the root `bundle/shannon.js` in sync with the workspace CLI bundle, + // since the root package publishes `bundle/shannon.js` as its `bin`. + const rootBundleDir = path.resolve(__dirname, 'bundle'); + if (!existsSync(rootBundleDir)) { + mkdirSync(rootBundleDir, { recursive: true }); + } + copyFileSync( + path.resolve(__dirname, 'packages/cli/bundle/shannon.js'), + path.resolve(__dirname, 'bundle/shannon.js'), + ); }), esbuild.build(a2aServerConfig), ]).then((results) => { diff --git a/package-lock.json b/package-lock.json index 5d8e3608501..65c22b8654c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15311,7 +15311,7 @@ }, "packages/cli": { "name": "@sangshuduo/shannon-code", - "version": "0.1.0", + "version": "0.1.1", "dependencies": { "@google/genai": "1.30.0", "@iarna/toml": "^2.2.5", @@ -15408,7 +15408,7 @@ }, "packages/core": { "name": "@sangshuduo/shannon-core", - "version": "0.1.0", + "version": "0.1.1", "dependencies": { "@google-cloud/logging": "^11.2.1", "@google-cloud/opentelemetry-cloud-monitoring-exporter": "^0.21.0", diff --git a/package.json b/package.json index d813a65c1f1..91726ecb01e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sangshuduo/shannon-code", - "version": "0.1.0", + "version": "0.1.2", "engines": { "node": ">=20.0.0" }, diff --git a/packages/cli/package.json b/packages/cli/package.json index f9bb319f82e..27e35d1efd3 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@sangshuduo/shannon-code", - "version": "0.1.0", + "version": "0.1.2", "description": "Shannon CLI", "repository": { "type": "git", diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts index 18516bc3640..941a71937f4 100755 --- a/packages/cli/src/config/config.ts +++ b/packages/cli/src/config/config.ts @@ -571,10 +571,17 @@ export async function loadCliConfig( ); const authType = settings.security?.auth?.selectedType; - const defaultModel = - authType === AuthType.USE_OLLAMA - ? process.env['OLLAMA_MODEL'] || 'llama3.2' - : DEFAULT_GEMINI_MODEL_AUTO; + const usingOllama = authType === AuthType.USE_OLLAMA; + const hasExplicitOllamaModel = + !!argv.model || !!process.env['OLLAMA_MODEL'] || !!settings.model?.name; + if (usingOllama && !hasExplicitOllamaModel) { + debugLogger.warn( + '[Config] Ollama auth enabled but no model configured; defaulting to "llama3.2". Set OLLAMA_MODEL or pass --model to avoid this warning.', + ); + } + const defaultModel = usingOllama + ? process.env['OLLAMA_MODEL'] || 'llama3.2' + : DEFAULT_GEMINI_MODEL_AUTO; const resolvedModel: string = argv.model || process.env['SHANNON_MODEL'] || diff --git a/packages/cli/src/ui/commands/bugCommand.test.ts b/packages/cli/src/ui/commands/bugCommand.test.ts index 4772b9d7e91..1b320065ef1 100644 --- a/packages/cli/src/ui/commands/bugCommand.test.ts +++ b/packages/cli/src/ui/commands/bugCommand.test.ts @@ -76,7 +76,7 @@ describe('bugCommand', () => { * **IDE Client:** VSCode `; const expectedUrl = - 'https://github.com/google-gemini/gemini-cli/issues/new?template=bug_report.yml&title=A%20test%20bug&info=' + + 'https://github.com/sangshuduo/shannon-code/issues/new?template=bug_report.yml&title=A%20test%20bug&info=' + encodeURIComponent(expectedInfo); expect(open).toHaveBeenCalledWith(expectedUrl); diff --git a/packages/cli/src/ui/commands/bugCommand.ts b/packages/cli/src/ui/commands/bugCommand.ts index f8065bbb07c..20a482f1807 100644 --- a/packages/cli/src/ui/commands/bugCommand.ts +++ b/packages/cli/src/ui/commands/bugCommand.ts @@ -53,7 +53,7 @@ export const bugCommand: SlashCommand = { } let bugReportUrl = - 'https://github.com/google-gemini/gemini-cli/issues/new?template=bug_report.yml&title={title}&info={info}'; + 'https://github.com/sangshuduo/shannon-code/issues/new?template=bug_report.yml&title={title}&info={info}'; const bugCommandSettings = config?.getBugCommand(); if (bugCommandSettings?.urlTemplate) { diff --git a/packages/cli/src/ui/commands/docsCommand.test.ts b/packages/cli/src/ui/commands/docsCommand.test.ts index 58e2e17335e..f0057dfd7dc 100644 --- a/packages/cli/src/ui/commands/docsCommand.test.ts +++ b/packages/cli/src/ui/commands/docsCommand.test.ts @@ -35,7 +35,8 @@ describe('docsCommand', () => { throw new Error('docsCommand must have an action.'); } - const docsUrl = 'https://goo.gle/gemini-cli-docs'; + const docsUrl = + 'https://github.com/sangshuduo/shannon-code/blob/main/README.md'; await docsCommand.action(mockContext, ''); @@ -57,7 +58,8 @@ describe('docsCommand', () => { // Simulate a sandbox environment vi.stubEnv('SANDBOX', 'gemini-sandbox'); - const docsUrl = 'https://goo.gle/gemini-cli-docs'; + const docsUrl = + 'https://github.com/sangshuduo/shannon-code/blob/main/README.md'; await docsCommand.action(mockContext, ''); @@ -80,7 +82,8 @@ describe('docsCommand', () => { // Simulate the specific 'sandbox-exec' environment vi.stubEnv('SANDBOX', 'sandbox-exec'); - const docsUrl = 'https://goo.gle/gemini-cli-docs'; + const docsUrl = + 'https://github.com/sangshuduo/shannon-code/blob/main/README.md'; await docsCommand.action(mockContext, ''); diff --git a/packages/cli/src/ui/commands/docsCommand.ts b/packages/cli/src/ui/commands/docsCommand.ts index e4fa74d29e0..dacdf9936d4 100644 --- a/packages/cli/src/ui/commands/docsCommand.ts +++ b/packages/cli/src/ui/commands/docsCommand.ts @@ -19,7 +19,8 @@ export const docsCommand: SlashCommand = { kind: CommandKind.BUILT_IN, autoExecute: true, action: async (context: CommandContext): Promise => { - const docsUrl = 'https://goo.gle/gemini-cli-docs'; + const docsUrl = + 'https://github.com/sangshuduo/shannon-code/blob/main/README.md'; if (process.env['SANDBOX'] && process.env['SANDBOX'] !== 'sandbox-exec') { context.ui.addItem( diff --git a/packages/cli/src/ui/commands/helpCommand.test.ts b/packages/cli/src/ui/commands/helpCommand.test.ts index 9eff142ba04..3b9849f795d 100644 --- a/packages/cli/src/ui/commands/helpCommand.test.ts +++ b/packages/cli/src/ui/commands/helpCommand.test.ts @@ -47,6 +47,6 @@ describe('helpCommand', () => { it('should have the correct command properties', () => { expect(helpCommand.name).toBe('help'); expect(helpCommand.kind).toBe(CommandKind.BUILT_IN); - expect(helpCommand.description).toBe('For help on gemini-cli'); + expect(helpCommand.description).toBe('For help on shannon-code'); }); }); diff --git a/packages/cli/src/ui/commands/helpCommand.ts b/packages/cli/src/ui/commands/helpCommand.ts index f7d469a7e72..3c39786cbd1 100644 --- a/packages/cli/src/ui/commands/helpCommand.ts +++ b/packages/cli/src/ui/commands/helpCommand.ts @@ -12,7 +12,7 @@ export const helpCommand: SlashCommand = { name: 'help', altNames: ['?'], kind: CommandKind.BUILT_IN, - description: 'For help on gemini-cli', + description: 'For help on shannon-code', autoExecute: true, action: async (context) => { const helpItem: Omit = { diff --git a/packages/cli/src/ui/commands/setupGithubCommand.ts b/packages/cli/src/ui/commands/setupGithubCommand.ts index 3ca23a049dd..15a2c0fe200 100644 --- a/packages/cli/src/ui/commands/setupGithubCommand.ts +++ b/packages/cli/src/ui/commands/setupGithubCommand.ts @@ -227,7 +227,8 @@ export const setupGithubCommand: SlashCommand = { // Get the latest release tag from GitHub const proxy = context?.services?.config?.getProxy(); const releaseTag = await getLatestGitHubRelease(proxy); - const readmeUrl = `https://github.com/google-github-actions/run-gemini-cli/blob/${releaseTag}/README.md#quick-start`; + const readmeUrl = + 'https://github.com/sangshuduo/shannon-code/blob/main/README.md#github-integration'; // Create workflows directory const workflowsDir = path.join(gitRepoRoot, '.github', 'workflows'); diff --git a/packages/cli/src/ui/components/views/McpStatus.tsx b/packages/cli/src/ui/components/views/McpStatus.tsx index 9a89c4497e7..9f2dc8c167e 100644 --- a/packages/cli/src/ui/components/views/McpStatus.tsx +++ b/packages/cli/src/ui/components/views/McpStatus.tsx @@ -52,7 +52,7 @@ export const McpStatus: React.FC = ({ Please view MCP documentation in your browser:{' '} - https://goo.gle/gemini-cli-docs-mcp + https://github.com/sangshuduo/shannon-code/blob/main/docs/extensions/index.md {' '} or use the cli /docs command diff --git a/packages/core/package.json b/packages/core/package.json index 48b50fed5cc..5195324e9b0 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@sangshuduo/shannon-core", - "version": "0.1.0", + "version": "0.1.2", "private": false, "description": "Shannon CLI Core", "repository": { diff --git a/packages/core/src/config/config.test.ts b/packages/core/src/config/config.test.ts index 2c51151967d..a01540f55f2 100644 --- a/packages/core/src/config/config.test.ts +++ b/packages/core/src/config/config.test.ts @@ -34,6 +34,7 @@ import { logRipgrepFallback } from '../telemetry/loggers.js'; import { RipgrepFallbackEvent } from '../telemetry/types.js'; import { ToolRegistry } from '../tools/tool-registry.js'; import { DEFAULT_MODEL_CONFIGS } from './defaultModelConfigs.js'; +import { DEFAULT_GEMINI_FLASH_LITE_MODEL } from './models.js'; vi.mock('fs', async (importOriginal) => { const actual = await importOriginal(); @@ -1309,6 +1310,38 @@ describe('BaseLlmClient Lifecycle', () => { config, ); }); + + it('should use Ollama model for summarizer aliases when using Ollama auth', async () => { + const config = new Config({ ...baseParams, model: 'qwen3-coder:30b' }); + + vi.mocked(createContentGeneratorConfig).mockResolvedValue({ + authType: AuthType.USE_OLLAMA, + ollamaBaseUrl: 'http://localhost:11434/v1', + ollamaModel: 'qwen3-coder:30b', + }); + + await config.refreshAuth(AuthType.USE_OLLAMA); + + expect( + config.modelConfigService.getResolvedConfig({ + model: 'summarizer-default', + }).model, + ).toBe('qwen3-coder:30b'); + + vi.mocked(createContentGeneratorConfig).mockResolvedValue({ + authType: AuthType.USE_GEMINI, + apiKey: 'test-key', + vertexai: false, + }); + + await config.refreshAuth(AuthType.USE_GEMINI); + + expect( + config.modelConfigService.getResolvedConfig({ + model: 'summarizer-default', + }).model, + ).toBe(DEFAULT_GEMINI_FLASH_LITE_MODEL); + }); }); describe('Generation Config Merging (HACK)', () => { diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index a03bd08f88c..7809bf0c21b 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -728,6 +728,32 @@ export class Config { // Only assign to instance properties after successful initialization this.contentGeneratorConfig = newContentGeneratorConfig; + // Some internal model aliases (e.g. summarizers) default to Gemini models. + // When using Ollama, these need to target the configured Ollama model. + this.modelConfigService.unregisterRuntimeModelConfig('summarizer-default'); + this.modelConfigService.unregisterRuntimeModelConfig('summarizer-shell'); + if (newContentGeneratorConfig.authType === AuthType.USE_OLLAMA) { + const ollamaModel = + newContentGeneratorConfig.ollamaModel || this.getModel(); + const summarizerAlias = { + extends: 'base', + modelConfig: { + model: ollamaModel, + generateContentConfig: { + maxOutputTokens: 2000, + }, + }, + } as const; + this.modelConfigService.registerRuntimeModelConfig( + 'summarizer-default', + summarizerAlias, + ); + this.modelConfigService.registerRuntimeModelConfig( + 'summarizer-shell', + summarizerAlias, + ); + } + // Initialize BaseLlmClient now that the ContentGenerator is available this.baseLlmClient = new BaseLlmClient(this.contentGenerator, this); diff --git a/packages/core/src/core/ollamaContentGenerator.ts b/packages/core/src/core/ollamaContentGenerator.ts index 7dbb5077a20..01e68755fc6 100644 --- a/packages/core/src/core/ollamaContentGenerator.ts +++ b/packages/core/src/core/ollamaContentGenerator.ts @@ -103,8 +103,22 @@ export class OllamaContentGenerator implements ContentGenerator { }); if (!response.ok) { + const responseText = await response.text(); + const requestedModel = String(body.model || this.model); + if (response.status === 404) { + const modelNotFound = + /model ['"]([^'"]+)['"] not found/i.exec(responseText)?.[1] || + /model ['"]([^'"]+)['"]/i.exec(responseText)?.[1]; + const missingModel = modelNotFound || requestedModel; + throw new Error( + `Ollama model "${missingModel}" not found. ` + + `Set OLLAMA_MODEL or pass --model . ` + + `Check installed models with "ollama list" and install with "ollama pull ${missingModel}". ` + + `Original response (${response.status}): ${responseText}`, + ); + } throw new Error( - `Ollama request failed (${response.status}): ${await response.text()}`, + `Ollama request failed (${response.status}): ${responseText}`, ); } diff --git a/packages/core/src/services/modelConfigService.ts b/packages/core/src/services/modelConfigService.ts index 0a642b7e61a..2ac684064bf 100644 --- a/packages/core/src/services/modelConfigService.ts +++ b/packages/core/src/services/modelConfigService.ts @@ -73,6 +73,10 @@ export class ModelConfigService { this.runtimeAliases[aliasName] = alias; } + unregisterRuntimeModelConfig(aliasName: string): void { + delete this.runtimeAliases[aliasName]; + } + private resolveAlias( aliasName: string, aliases: Record, diff --git a/scripts/copy_bundle_assets.js b/scripts/copy_bundle_assets.js index 1dbca40296b..1c4aa8504e5 100644 --- a/scripts/copy_bundle_assets.js +++ b/scripts/copy_bundle_assets.js @@ -24,33 +24,43 @@ import { glob } from 'glob'; const __dirname = dirname(fileURLToPath(import.meta.url)); const root = join(__dirname, '..'); -const bundleDir = join(root, 'bundle'); -// Create the bundle directory if it doesn't exist -if (!existsSync(bundleDir)) { - mkdirSync(bundleDir); +function ensureDir(dir) { + if (!existsSync(dir)) { + mkdirSync(dir, { recursive: true }); + } } -// 1. Copy Sandbox definitions (.sb) -const sbFiles = glob.sync('packages/**/*.sb', { cwd: root }); -for (const file of sbFiles) { - copyFileSync(join(root, file), join(bundleDir, basename(file))); -} +function copyBundleAssetsTo(bundleDir) { + ensureDir(bundleDir); -// 2. Copy Policy definitions (.toml) -const policyDir = join(bundleDir, 'policies'); -if (!existsSync(policyDir)) { - mkdirSync(policyDir); -} + // 1. Copy Sandbox definitions (.sb) + const sbFiles = glob.sync('packages/**/*.sb', { cwd: root }); + for (const file of sbFiles) { + copyFileSync(join(root, file), join(bundleDir, basename(file))); + } + + // 2. Copy Policy definitions (.toml) + const policyDir = join(bundleDir, 'policies'); + ensureDir(policyDir); -// Locate policy files specifically in the core package -const policyFiles = glob.sync('packages/core/src/policy/policies/*.toml', { - cwd: root, -}); + // Locate policy files specifically in the core package + const policyFiles = glob.sync('packages/core/src/policy/policies/*.toml', { + cwd: root, + }); -for (const file of policyFiles) { - copyFileSync(join(root, file), join(policyDir, basename(file))); + for (const file of policyFiles) { + copyFileSync(join(root, file), join(policyDir, basename(file))); + } + + return { sbFiles, policyFiles }; } +const rootBundleDir = join(root, 'bundle'); +const cliBundleDir = join(root, 'packages', 'cli', 'bundle'); + +const { policyFiles } = copyBundleAssetsTo(rootBundleDir); +copyBundleAssetsTo(cliBundleDir); + console.log(`Copied ${policyFiles.length} policy files to bundle/policies/`); -console.log('Assets copied to bundle/'); +console.log('Assets copied to bundle/ and packages/cli/bundle/');