Repository: dmitriz/issue-labeler
Pull request #10: Select next issue
Author: login: dmitriz
Labels: name: Review effort 2/5
name: size:M
description: This PR changes 30-99 lines, ignoring generated files
name: refactor
Created: 2025-05-07T07:37:20Z | Merged: 2025-05-08T08:57:48Z

## **User description**
### **User description**
This pull request introduces a new feature for prioritizing GitHub issues with an "urgent" label and includes several structural improvements to the codebase. The main changes involve adding a new script to select the next issue based on priority, implementing a utility to fetch labeled issues, and enhancing the maintainability and security of the GitHub API integration.

### New Feature: Issue Prioritization
* Added a new script, `scripts/select-next.js`, that calculates priority scores for GitHub issues and selects the next issue to work on based on the "urgent" label. Lower scores indicate higher priority.

### GitHub API Integration Enhancements
* Introduced a new function, `getLabeledIssues`, in `lib/github.js` to fetch open issues from a GitHub repository and filter them by the "urgent" label. This function handles API errors gracefully and logs detailed error messages.
* Improved security and maintainability by adding a `validatePathSegment` function in `lib/github-wrapper.js` to prevent path traversal attacks when constructing GitHub API paths.

### Configuration and Dependency Updates
* Updated `package.json` to include a new script, `"select"`, which runs the `scripts/select-next.js` file.


___

### **PR Type**
Enhancement


___

### **Description**
- Introduced script to select next GitHub issue by urgency
  - Implements priority scoring, prioritizing "urgent" labeled issues
  - Outputs the top-priority issue to the console

- Added utility to fetch open issues with "urgent" label
  - Handles API errors and logs detailed messages

- Improved GitHub API wrapper security with path validation

- Updated package.json with new "select" script entry


___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
      <summary><strong>select-next.js</strong><dd><code>New script for selecting next urgent GitHub issue</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

scripts/select-next.js

<li>Added new script to select the next GitHub issue based on priority<br> <li> Implements scoring function prioritizing "urgent" labeled issues<br> <li> Outputs the top-priority issue to the console<br> <li> Handles errors gracefully


</details>


  </td>
  <td><a href="https://github.com/dmitriz/issue-labeler/pull/10/files#diff-f1848d6ec1a22eeafe8d95739dee863f1f3a490cbcc5370aebe49aeeb9c8ef73">+33/-0</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
      <summary><strong>github.js</strong><dd><code>Utility to fetch urgent-labeled open issues</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

lib/github.js

<li>Added function to fetch open issues with "urgent" label<br> <li> Filters out pull requests from results<br> <li> Handles and logs API errors<br> <li> Exports utility for use in selection script


</details>


  </td>
  <td><a href="https://github.com/dmitriz/issue-labeler/pull/10/files#diff-9714c3844d6c03aa922329305244a541c47d9573ad42aeb91e158f3e9a995009">+32/-0</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
      <summary><strong>github-wrapper.js</strong><dd><code>Add path validation and refactor API config</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

lib/github-wrapper.js

<li>Added validatePathSegment function to prevent path traversal<br> <li> Improved security for GitHub API path construction<br> <li> Refactored API constants and timeout configuration


</details>


  </td>
  <td><a href="https://github.com/dmitriz/issue-labeler/pull/10/files#diff-09e8ff86ffa0113dcc5c155273affbacfa7bcfe337ade3371e36b55983abf61c">+10/-10</a>&nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Configuration changes</strong></td><td><table>
<tr>
  <td>
    <details>
      <summary><strong>package.json</strong><dd><code>Add select script entry to package.json</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

package.json

- Added "select" script to run the new selection script


</details>


  </td>
  <td><a href="https://github.com/dmitriz/issue-labeler/pull/10/files#diff-7ae45ad102eab3b6d7e7896acd08c427a9b25b346470d7bc6507b6481575d519">+1/-0</a>&nbsp; &nbsp; &nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

___

> <details> <summary>  Need help?</summary><li>Type <code>/help how to ...</code> in the comments thread for any questions about Qodo Merge usage.</li><li>Check out the <a href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a> for more information.</li></details>


___

## **CodeAnt-AI Description**
- Introduced a new script (`scripts/select-next.js`) that selects the next GitHub issue to work on by prioritizing those labeled as "urgent", using a simple scoring system.
- Added a utility function (`getLabeledIssues`) in `lib/github.js` to fetch open issues with the "urgent" label, filtering out pull requests and handling API errors robustly.
- Enhanced security in `lib/github-wrapper.js` by adding a `validatePathSegment` function to prevent path traversal attacks and refactored API configuration constants.
- Updated `package.json` to include a new script (`"select"`) for running the issue selection tool.

This PR adds a feature to automate the selection of the next high-priority GitHub issue, improving workflow efficiency for teams handling urgent tasks. It also strengthens the codebase's security and maintainability by improving API integration and error handling.


___

## **Changes walkthrough**
<table><thead><tr><th></th><th align="left">Relevant files</th></tr></thead><tbody><tr><td><strong>Enhancement
</strong></td><td><table>
<tr>
  <td>
    <details>
      <summary><strong>select-next.js</strong><dd><code>Introduce script for selecting next high-priority GitHub issue</code>&nbsp; </dd></summary>
<hr>

scripts/select-next.js
<li>Added a new script to select the next GitHub issue to work on based on <br>priority.<br> <li> Implements a scoring function prioritizing issues labeled as "urgent".<br> <li> Outputs the top-priority issue to the console.<br> <li> Handles cases where no labeled issues are found.<br>


</details>
    

  </td>
  <td><a href="https://github.com/dmitriz/issue-labeler/pull/10/files#diff-f1848d6ec1a22eeafe8d95739dee863f1f3a490cbcc5370aebe49aeeb9c8ef73">+33/-0</a>&nbsp; &nbsp; </td>
</tr>                    

<tr>
  <td>
    <details>
      <summary><strong>github.js</strong><dd><code>Add utility to fetch open urgent-labeled GitHub issues</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

lib/github.js
<li>Added a utility function to fetch open issues with the "urgent" label <br>from GitHub.<br> <li> Filters out pull requests and only returns issues.<br> <li> Handles and logs API errors with detailed messages.<br> <li> Exports the new <code>getLabeledIssues</code> function for use in other modules.<br>


</details>
    

  </td>
  <td><a href="https://github.com/dmitriz/issue-labeler/pull/10/files#diff-9714c3844d6c03aa922329305244a541c47d9573ad42aeb91e158f3e9a995009">+32/-0</a>&nbsp; &nbsp; </td>
</tr>                    

<tr>
  <td>
    <details>
      <summary><strong>package.json</strong><dd><code>Add npm script for selecting next issue by priority</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

package.json
<li>Added a new npm script <code>"select"</code> to run the issue selection script.<br>


</details>
    

  </td>
  <td><a href="https://github.com/dmitriz/issue-labeler/pull/10/files#diff-7ae45ad102eab3b6d7e7896acd08c427a9b25b346470d7bc6507b6481575d519">+1/-0</a>&nbsp; &nbsp; &nbsp; </td>
</tr>                    

<tr>
  <td>
    <details>
      <summary><strong>github-wrapper.js</strong><dd><code>Add path segment validation and refactor API constants</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

lib/github-wrapper.js
<li>Added a <code>validatePathSegment</code> function to prevent path traversal in <br>GitHub API paths.<br> <li> Updated API timeout and refactored constants for clarity.<br>


</details>
    

  </td>
  <td><a href="https://github.com/dmitriz/issue-labeler/pull/10/files#diff-09e8ff86ffa0113dcc5c155273affbacfa7bcfe337ade3371e36b55983abf61c">+10/-10</a>&nbsp; </td>
</tr>                    
</table></td></tr></tr></tbody></table><details>
<summary><strong>💡 Usage Guide</strong></summary>

### Checking Your Pull Request
Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

### Talking to CodeAnt AI
Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:
<pre>
<code>@codeant-ai ask: Your question here</code>
</pre>
This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

### Retrigger review
Ask CodeAnt AI to review the PR again, by typing:
<pre>
<code>@codeant-ai: review</code>
</pre>

### Check Your Repository Health
To analyze the health of your code repository, visit our dashboard at [https://app.codeant.ai](https://app.codeant.ai). This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

</details>

<!-- Korbit AI PR Description Start -->
## Description by Korbit AI

### What change is being made?

Reorganize and enhance the GitHub issue labeler project by refactoring the directory structure, improving environment management, and expanding documentation.

### Why are these changes being made?

These changes improve code maintainability and usability by consolidating configuration management, introducing environment switching capabilities, and providing comprehensive technical documentation. Refactoring enhances clarity and aligns the project structure with best practices, facilitating easier navigation and understanding for current and future developers.

> Is this description stale? Ask me to generate a new description by commenting `/korbit-generate-pr-description`
<!-- Korbit AI PR Description End -->


Diff:
diff --git a/README.md b/README.md
index 2f1d7f2..7833248 100644
--- a/README.md
+++ b/README.md
@@ -4,16 +4,55 @@ This tool automatically suggests urgency and importance labels for GitHub issues
 
 ## Usage
 
-## Usage
-
 First, install the dependencies:
 
 ```bash
 npm install
+```
+
+Run the labeler:
 
 ```bash
 # Directly with Node.js
 node labeler.js
 
 # Using NPM script
-npm start
\ No newline at end of file
+npm start
+```
+
+## Environment Management
+
+The application supports both testing and production environments:
+
+```bash
+# Check current environment status
+npm run env:status
+
+# Switch between testing and production environments
+npm run toggle-env
+```
+
+## Documentation
+
+For detailed information about the repository organization, architecture, and how to extend the application:
+
+- [**Technical Documentation**](TECHNICAL.md) - Comprehensive guide to the codebase structure and functionality
+- [GitHub API Client](github-api-client.js) - Core API client for GitHub interactions
+- [Configuration System](config.js) - Central configuration file for all settings
+
+## Configuration
+
+All configuration, including repository targeting, is managed through the `config.js` file. To target a different repository, simply modify the appropriate environment section in this file:
+
+```javascript
+environments: {
+  production: {
+    active: false,
+    repository: {
+      owner: 'your-org-name',
+      repo: 'your-repo-name',
+      useLocalIssues: false
+    }
+  }
+}
+```
diff --git a/TECHNICAL.md b/TECHNICAL.md
new file mode 100644
index 0000000..c5beaa3
--- /dev/null
+++ b/TECHNICAL.md
@@ -0,0 +1,122 @@
+# GitHub Issue Labeler - Technical Documentation
+
+This document provides high-level technical documentation for the GitHub Issue Labeler project, explaining its architecture, key components, and how to work with the codebase.
+
+## Table of Contents
+
+1. [Project Overview](#project-overview)
+2. [Architecture](#architecture)
+3. [Key Components](#key-components)
+4. [Configuration System](#configuration-system)
+5. [Environment Management](#environment-management)
+6. [Secrets Management](#secrets-management)
+7. [Main Workflows](#main-workflows)
+8. [Testing](#testing)
+9. [How to Extend](#how-to-extend)
+10. [Documentation Maintenance](#documentation-maintenance)
+
+## Project Overview
+
+GitHub Issue Labeler is a tool that automatically assigns labels (urgency and importance) to GitHub issues using AI. It connects to the GitHub API, fetches issue content, processes it with a large language model (LLM), and applies appropriate labels based on the content.
+
+## Architecture
+
+The project follows a modular architecture with clear separation of concerns between GitHub API integration, LLM processing, and configuration management. The application core orchestrates these components to analyze and label GitHub issues.
+
+The configuration system supports different environments, but environments only affect the target repository - all other configuration settings are environment-independent and defined directly in the config.js file.
+
+## Key Components
+
+### 1. API Clients
+
+- **GitHub API Clients**: Handle interactions with GitHub's REST API
+- **Issue Fetcher**: Specialized client for fetching GitHub issue content
+- **LLM Client**: Manages communication with the LLM for issue analysis
+
+### 2. Configuration
+
+- **Central Configuration**: A single source of truth for application settings
+- **Configuration Loader**: Utilities for accessing and modifying configuration
+- **Environment Management**: Tools for switching between target repositories
+
+### 3. Application Logic
+
+- **Core Labeling Logic**: Orchestrates the issue labeling workflow
+- **Prompt Management**: Templates for communicating with the LLM
+- **Response Processing**: Handling and interpreting LLM responses
+
+## Configuration System
+
+The application uses a unified configuration system with JavaScript modules for better readability and maintainability.
+
+### Configuration Structure
+
+The configuration is organized into logical sections:
+- **Environments**: Defines different repository targets (testing vs. production)
+- **GitHub API Settings**: Connection parameters for GitHub
+- **LLM Settings**: Model selection and parameters
+
+### Configuration Management
+
+The system provides utilities for:
+- Loading the appropriate configuration
+- Accessing specific configuration sections
+- Switching between environments (repository targets)
+
+## Environment Management
+
+The system supports multiple environments with different repository targets. **Note that environments ONLY affect which repository is targeted - all other settings remain the same across environments.**
+
+Key features:
+- Environment toggling (switch between test and production repositories)
+- Environment status display (see which repository is currently active)
+- Persistence of environment selection
+
+## Secrets Management
+
+Authentication tokens and other sensitive information are stored separately from the main configuration:
+
+- **Secrets Storage**: Sensitive values are kept in a separate `.secrets` directory
+- **Authentication**: API tokens for GitHub and the LLM service
+- **Separation of Concerns**: Repository information is managed through configuration, not secrets
+
+## Main Workflows
+
+### Issue Labeling Workflow
+
+1. Fetch issue content from the targeted GitHub repository
+2. Process the issue with the configured LLM
+3. Extract label recommendations from the LLM response
+4. Apply recommended labels to the issue
+
+### Environment Switching
+
+Developers can easily switch the target repository without changing code by toggling between environments with a simple command.
+
+## Testing
+
+The project includes various test scripts for different components and integration points.
+
+## How to Extend
+
+### Adding New Environments
+
+New repository targets can be added to the environments section of the configuration.
+
+### Modifying Label Categories
+
+The labeling system can be extended with new categories by updating prompt templates and response processing.
+
+## Documentation Maintenance
+
+**IMPORTANT: This technical documentation must be kept in sync with code changes.**
+
+### Guidelines for Maintaining Documentation:
+
+1. **Update This Document**: Any significant structural changes to the codebase must be reflected here.
+
+2. **Keep It High-Level**: This document focuses on architecture and concepts, not implementation details.
+
+3. **Documentation First**: Update documentation as part of the planning phase for significant changes.
+
+4. **Verification**: Periodically verify that documentation matches the current codebase structure.
diff --git a/config.js b/config.js
new file mode 100644
index 0000000..8f8c7f8
--- /dev/null
+++ b/config.js
@@ -0,0 +1,69 @@
+/**
+ * Central configuration for GitHub Issue Labeler
+ * Using JS format to allow comments and better readability
+ */
+
+module.exports = {
+  /**
+   * Environment configurations
+   * ONLY the repository target changes between environments
+   * All other settings remain constant across environments
+   */
+  environments: {
+    testing: {
+      active: true,
+      repository: {
+        owner: 'dmitriz',
+        repo: 'issue-labeler',
+        // Repository to use during development and testing
+      }
+    },
+    production: {
+      active: false, 
+      repository: {
+        owner: 'dmitriz',
+        repo: 'issue-hub',
+        // The actual production repository where real issues will be labeled
+      }
+    }
+  },
+  
+  /**
+   * GitHub API configuration
+   * Settings that control how the application connects to GitHub's API
+   */
+  github: {
+    // Base URL for GitHub's REST API
+    baseUrl: 'https://api.github.com',
+    
+    // Request timeout in milliseconds
+    // Prevents hanging requests by failing after this duration
+    timeoutMs: 10000,
+    
+    // Maximum number of concurrent sockets to use for API calls
+    // Controls how many parallel connections can be made to GitHub
+    // Higher values allow more concurrent requests but may hit rate limits
+    maxSockets: 100
+  },
+  
+  /**
+   * Model configuration
+   * Settings for the AI model used to analyze and label issues
+   */
+  model: {
+    // The model name/identifier to use for issue analysis
+    // Format: 'provider/model-name'
+    name: 'openai/gpt-4o',
+    
+    // Controls randomness: 0 = deterministic, 1 = maximum randomness
+    // Lower values produce more consistent/predictable responses
+    temperature: 0.3,
+    
+    // Maximum number of tokens (words) in the response
+    // Controls the length of the model's output
+    maxTokens: 1000,
+    
+    // URL endpoint for the model API
+    apiEndpoint: 'https://models.github.ai/inference/chat/completions'
+  }
+};
\ No newline at end of file
diff --git a/config.json b/config.json
deleted file mode 100644
index 9436b91..0000000
--- a/config.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
-  "model": "gpt-4",
-  "temperature": 0.3
-}
\ No newline at end of file
diff --git a/config/github-model.js b/config/github-model.js
deleted file mode 100644
index 46e6b67..0000000
--- a/config/github-model.js
+++ /dev/null
@@ -1,6 +0,0 @@
-// Basic configuration for GitHub model call
-module.exports = {
-  model: "openai/gpt-4o",
-  temperature: 0.3,
-  max_tokens: 1000
-};
\ No newline at end of file
diff --git a/example-github-issues.js b/example-github-issues.js
index 9e55303..4b7e458 100644
--- a/example-github-issues.js
+++ b/example-github-issues.js
@@ -2,10 +2,9 @@
  * Example usage of the GitHub Issues API wrapper
  */
 
-const githubIssues = require('./utils/github-issues');
+const githubIssues = require('./src/github-issues');
 
 async function main() {
-  // Configuration for the target repository
   // Configuration for the target repository
   const owner = process.env.GITHUB_OWNER || 'dmitriz';
   const repo = process.env.GITHUB_REPO || 'issue-labeler';
diff --git a/lib/github-model.js b/lib/github-model.js
deleted file mode 100644
index 2253781..0000000
--- a/lib/github-model.js
+++ /dev/null
@@ -1,79 +0,0 @@
-/**
- * GitHub Model API wrapper for the issue labeling workflow
- * Provides a function for model inference to determine issue labels
- */
-const axios = require('axios');
-const token = process.env.GITHUB_MODEL_TOKEN || require('../.secrets/gh-model').token;
-
-if (!token) {
-  console.error('GITHUB_MODEL_TOKEN environment variable is not set');
-  process.exit(1);
-}
-const config = require('../config/github-model');
-
-// Create a reusable axios instance with keepAlive enabled
-const https = require('https');
-const axiosInstance = axios.create({
-  httpsAgent: new https.Agent({ keepAlive: true }),
-  timeout: 10000
-});
-
-/**
- * Calls the GitHub model API for inference.
- * @param {string} prompt - The input prompt for the model.
- * @returns {Object} - Object containing urgency and importance values.
- */
-async function callModel(prompt) {
-  try {
-    const response = await axiosInstance.post(config.apiEndpoint, {
-      messages: [
-      { role: "user", content: prompt }
-      ],
-      model: config.model,
-      temperature: config.temperature,
-      max_tokens: config.max_tokens
-    }, {
-      headers: {
-      "Authorization": `Bearer ${token}`,
-      "Content-Type": "application/json"
-      }
-    });
-
-    // Extract content from response
-    if (!response.data.choices || !response.data.choices[0]?.message?.content) {
-      throw new Error('Invalid response format from GitHub model API');
-    }
-
-    const rawContent = response.data.choices[0].message.content.trim();
-    
-    // Extract JSON object from the response (handling possible markdown code blocks)
-    let jsonContent = rawContent;
-    const jsonMatch = rawContent.match(/```(?:json|js)?\s*([\s\S]*?)```/);
-    if (jsonMatch && jsonMatch[1]) {
-      jsonContent = jsonMatch[1].trim();
-    }
-
-    try {
-      const parsedJson = JSON.parse(jsonContent);
-      return {
-        urgency: parsedJson.urgency && parsedJson.urgency.trim() !== '' ? parsedJson.urgency.trim() : null,
-        importance: parsedJson.importance && parsedJson.importance.trim() !== '' ? parsedJson.importance.trim() : null
-      };
-    } catch (parseError) {
-      console.error('Failed to parse JSON response:', parseError.message);
-      console.error('Raw response:', rawContent);
-      throw new Error('Failed to parse model response');
-    }
-  } catch (error) {
-    console.error('Error calling GitHub model:', error.message);
-    if (error.response) {
-      console.error('API Error:', error.response.status, error.response.statusText);
-      console.error('Error details:', error.response.data);
-    } else {
-      console.error('Error stack:', error.stack);
-    }
-    throw error;
-  }
-}
-
-module.exports = { callModel };
\ No newline at end of file
diff --git a/lib/githubModel.js b/lib/githubModel.js
deleted file mode 100644
index ce96e7b..0000000
--- a/lib/githubModel.js
+++ /dev/null
@@ -1,70 +0,0 @@
-const axios = require("axios");
-const { token } = require("../.secrets/github");
-const config = require("../config/github-model");
-
-// Regex for cleaning markdown code blocks
-const MARKDOWN_CODE_BLOCK_REGEX = /```(?:\w+)?\s*([\s\S]*?)```/;
-
-async function callGithubModel({ prompt }) {
-  try {
-    const https = require("https");
-    // Create a persistent axios instance with keep-alive and a timeout of 5000ms
-    const axiosInstance = axios.create({
-      httpsAgent: new https.Agent({ keepAlive: true }),
-      timeout: 5000
-    });
-
-    const response = await axiosInstance.post("https://models.github.ai/inference/chat/completions", {
-      messages: [
-        { role: "user", content: prompt }
-      ],
-      model: config.model,
-      temperature: config.temperature,
-      max_tokens: config.max_tokens
-    }, {
-      headers: {
-        "Authorization": `Bearer ${token}`,
-        "Content-Type": "application/json"
-      }
-    });
-
-    // Validate that the API response has the expected structure
-    if (!response.data.choices || !response.data.choices[0]?.message?.content) {
-      throw new Error('Invalid response format from GitHub model API');
-    }
-
-    const rawContent = response.data.choices[0].message.content.trim();
-
-    // Clean the response if it's wrapped in markdown code blocks
-    let cleanedContent = rawContent;
-    if (rawContent.startsWith('```') && rawContent.endsWith('```')) {
-      const match = rawContent.match(MARKDOWN_CODE_BLOCK_REGEX);
-      if (match && match[1]) {
-        cleanedContent = match[1].trim();
-      }
-    }
-    
-    return cleanedContent;
-  } catch (error) {
-    // Check for rate limiting
-    if (error.response?.status === 429) {
-      console.warn("Rate limit exceeded. Implementing exponential backoff retry.");
-      // Get retry-after header if available or default to 1 second
-      const retryAfter = parseInt(error.response.headers?.['retry-after']) || 1;
-      console.log(`Retrying after ${retryAfter} seconds`);
-      throw new Error(`Rate limit exceeded. Retry after ${retryAfter} seconds.`);
-    }
-
-    // Create detailed error message
-    const errorDetails = error.response ? {
-      status: error.response.status,
-      statusText: error.response.statusText,
-      data: error.response.data
-    } : { message: error.message };
-
-    console.error("Full error object:", error); // Log the full error object for debugging
-    throw new Error(`GitHub Models API request failed: ${error.message}. Full error: ${JSON.stringify(errorDetails)}`);
-  }
-}
-
-module.exports = { callGithubModel };
\ No newline at end of file
diff --git a/package.json b/package.json
index 25b5c01..29f6cf7 100644
--- a/package.json
+++ b/package.json
@@ -2,19 +2,22 @@
   "name": "issue-labeler",
   "version": "1.0.0",
   "description": "This tool automatically suggests urgency and importance labels for GitHub issues using a lightweight prompt-based script. It will be extended to use GitHub Models API.",
-  "main": "labeler.js",
+  "main": "src/labeler.js",
   "scripts": {
     "test": "echo \"Running all tests...\" && npm run test:config && npm run test:github-model && npm run test:gh-api && npm run test:minimal",
-    "start": "node labeler.js",
-    "test:github-model": "node test-github-model.js",
-    "test:gh-api": "node test-gh-api-wrapper.js",
-    "test:config": "node test-config.js",
-    "test:label": "node label-test.js",
-    "test:comment": "node comment-test.js",
-    "test:fetch": "node fetch-only-test.js",
-    "test:minimal": "node minimal-test.js",
-    "test:direct-api": "node direct-api-test.js",
-    "test:simple": "node simple-gh-api-test.js",
+    "start": "node src/labeler.js",
+    "select": "node scripts/select-next.js",
+    "toggle-env": "node scripts/toggle-env.js",
+    "env:status": "node scripts/test-config.js",
+    "test:github-model": "node scripts/test-github-model.js",
+    "test:gh-api": "node scripts/test-gh-api-wrapper.js",
+    "test:config": "node scripts/test-config.js",
+    "test:label": "node scripts/label-test.js",
+    "test:comment": "node scripts/comment-test.js",
+    "test:fetch": "node scripts/fetch-only-test.js",
+    "test:minimal": "node scripts/minimal-test.js",
+    "test:direct-api": "node scripts/direct-api-test.js",
+    "test:simple": "node scripts/simple-gh-api-test.js",
     "example:github-issues": "node example-github-issues.js"
   },
   "repository": {
diff --git a/comment-test.js b/scripts/comment-test.js
similarity index 90%
rename from comment-test.js
rename to scripts/comment-test.js
index 5b7ae83..dd8501a 100644
--- a/comment-test.js
+++ b/scripts/comment-test.js
@@ -1,4 +1,4 @@
-const { commentOnIssue } = require('./scripts/github-api');
+const { commentOnIssue } = require('../src/github-api');
 
 console.log('==== COMMENT TEST STARTED ====');
 
@@ -6,7 +6,7 @@ console.log('==== COMMENT TEST STARTED ====');
 (async function() {
   try {
     // Get the first open issue from the fetchIssues function
-    const { fetchIssues } = require('./scripts/github-api');
+    const { fetchIssues } = require('../src/github-api');
     const issues = await fetchIssues({ state: 'open' });
     
     if (issues.length === 0) {
diff --git a/direct-api-test.js b/scripts/direct-api-test.js
similarity index 96%
rename from direct-api-test.js
rename to scripts/direct-api-test.js
index b1bb1dd..98439df 100644
--- a/direct-api-test.js
+++ b/scripts/direct-api-test.js
@@ -1,7 +1,7 @@
 const axios = require('axios');
 let token, owner, repo;
 try {
-  ({ token, owner, repo } = require('./.secrets/github'));
+  ({ token, owner, repo } = require('../.secrets/github'));
   if (!token || !owner || !repo) {
     throw new Error('Missing required GitHub credentials');
   }
diff --git a/fetch-only-test.js b/scripts/fetch-only-test.js
similarity index 93%
rename from fetch-only-test.js
rename to scripts/fetch-only-test.js
index 1174b5e..4dbcba5 100644
--- a/fetch-only-test.js
+++ b/scripts/fetch-only-test.js
@@ -1,4 +1,4 @@
-const { fetchIssues } = require('./scripts/github-api');
+const { fetchIssues } = require('../src/github-api');
 
 console.log('==== FETCH ONLY TEST STARTED ====');
 
diff --git a/label-issue.js b/scripts/label-issue.js
similarity index 76%
rename from label-issue.js
rename to scripts/label-issue.js
index 65031b3..765c424 100644
--- a/label-issue.js
+++ b/scripts/label-issue.js
@@ -4,34 +4,33 @@
  * It fetches issue content, uses a model to determine urgency and importance,
  * and then applies those labels to the issue.
  */
-const { getIssueContent, applyLabels } = require('./lib/github-wrapper');
-const { callModel } = require('./lib/github-model');
+const { getIssueContent, applyLabels } = require('../src/github-api-client');
+const { callModel } = require('../src/github-model');
 const fs = require('fs');
 const path = require('path');
 const { Command } = require('commander');
 
-// Read the prompt template
-// Remove the synchronous file read at the top of the file.
-// Inside the async main function (at its start), add:
-const promptTemplate = await fs.promises.readFile(path.join(__dirname, 'prompt-template.txt'), 'utf8');
-let promptTemplate;
-try {
-  promptTemplate = fs.readFileSync(promptTemplatePath, 'utf8');
-} catch (error) {
-  console.error(`Error reading prompt template: ${error.message}`);
-  process.exit(1);
-}
-// Inside the async main function (at its start), add:
-promptTemplate = await fs.promises.readFile(path.join(__dirname, 'prompt-template.txt'), 'utf8');
+// Path to the prompt template
+const promptTemplatePath = path.join(__dirname, '../prompt-template.txt');
 
 /**
  * Main function to process and label an issue
  * @param {Object} options - Options object
  * @param {number} options.issueNumber - The issue number to process
  * @param {string} options.owner - Repository owner
- const prompt = promptTemplate
-   .replace(/\{\{title\}\}/g, issue.title)
-   .replace(/\{\{body\}\}/g, issue.body || '');
+ * @param {string} options.repo - Repository name
+ * @returns {Promise<void>}
+ */
+async function main({ issueNumber, owner, repo }) {
+  // Read the prompt template - moved inside function for proper async handling
+  let promptTemplate;
+  try {
+    promptTemplate = await fs.promises.readFile(promptTemplatePath, 'utf8');
+  } catch (error) {
+    console.error(`Error reading prompt template: ${error.message}`);
+    throw error;
+  }
+
   console.log(`Processing issue #${issueNumber} from ${owner}/${repo}...`);
   
   try {
@@ -47,7 +46,7 @@ promptTemplate = await fs.promises.readFile(path.join(__dirname, 'prompt-templat
     
     // Step 3: Call the GitHub Model to determine urgency and importance
     console.log('Analyzing issue content with GitHub Model...');
-    const { urgency, importance } = await callModel({ prompt });
+    const { urgency, importance } = await callModel(prompt);
     console.log(`Model inference complete: urgency=${urgency}, importance=${importance}`);
 
     // Step 4: Create and apply labels
@@ -68,7 +67,7 @@ promptTemplate = await fs.promises.readFile(path.join(__dirname, 'prompt-templat
     }
   } catch (error) {
     console.error(`Error processing issue #${issueNumber}:`, error);
-    process.exit(1);
+    throw error;
   }
 }
 
diff --git a/label-test.js b/scripts/label-test.js
similarity index 93%
rename from label-test.js
rename to scripts/label-test.js
index cade40a..7f5685b 100644
--- a/label-test.js
+++ b/scripts/label-test.js
@@ -1,4 +1,4 @@
-const { addLabelsToIssue } = require('./scripts/github-api');
+const { addLabelsToIssue } = require('../src/github-api');
 
 console.log('==== LABEL TEST STARTED ====');
 
diff --git a/minimal-test.js b/scripts/minimal-test.js
similarity index 88%
rename from minimal-test.js
rename to scripts/minimal-test.js
index 6ff925d..37677db 100644
--- a/minimal-test.js
+++ b/scripts/minimal-test.js
@@ -1,5 +1,5 @@
 // A minimal test that just imports the module and logs
-const githubApi = require('./scripts/github-api');
+const githubApi = require('../src/github-api');
 
 console.log('Script started');
 console.log('GitHub API module loaded:', Object.keys(githubApi));
diff --git a/scripts/select-next.js b/scripts/select-next.js
new file mode 100644
index 0000000..053e355
--- /dev/null
+++ b/scripts/select-next.js
@@ -0,0 +1,52 @@
+const { getAllOpenIssues, getIssuesWithLabel } = require('../src/github-issue-fetcher');
+
+/**
+ * Select the next issue to work on based on priority rules:
+ * 1. Filter by urgent label if available
+ * 2. From resulting list, filter by important label if available
+ * 3. Pick most recently updated issue from final list
+ * 4. If no open issues, show appropriate message
+ */
+async function selectNext() {
+  // Get all open issues
+  let issues = await getAllOpenIssues();
+  const URGENT_PRIORITY_SCORE = 0;
+  const NORMAL_PRIORITY_SCORE = 10;
+
+  // Example usage of these constants (if needed in the future logic)
+  const getPriorityScore = (hasUrgentLabel) => 
+    hasUrgentLabel ? URGENT_PRIORITY_SCORE : NORMAL_PRIORITY_SCORE;
+  if (!issues.length) {
+    return console.log('No open issues available for processing.');
+  }
+
+  // Filter step 1 (Urgency):
+  const urgentIssues = getIssuesWithLabel(issues, 'urgent');
+  if (urgentIssues.length) {
+    console.log(`Found ${urgentIssues.length} issues with 'urgent' label. Prioritizing these.`);
+    issues = urgentIssues;
+  }
+  
+  // Filter step 2 (Importance):
+  const importantIssues = getIssuesWithLabel(issues, 'important');
+  if (importantIssues.length) {
+    console.log(`Found ${importantIssues.length} issues with 'important' label from current filtered set. Prioritizing these.`);
+    issues = importantIssues;
+  }
+  
+  // Final selection - sort by updated_at (most recent first)
+  const sortedIssues = issues.sort((a, b) => 
+    new Date(b.updated_at) - new Date(a.updated_at)
+  );
+  
+  const top = sortedIssues[0];
+  console.log(`\nSelected issue (most recently updated):`);
+  console.log(`#${top.number}: ${top.title}`);
+  console.log(`URL: ${top.html_url}`);
+  console.log(`Last updated: ${new Date(top.updated_at).toLocaleString()}`);
+  console.log(`Labels: ${top.labels.map(l => l.name).join(', ') || 'none'}`);
+}
+
+selectNext().catch(err => {
+  console.error('Error selecting next issue:', err.message);
+});
\ No newline at end of file
diff --git a/simple-gh-api-test.js b/scripts/simple-gh-api-test.js
similarity index 95%
rename from simple-gh-api-test.js
rename to scripts/simple-gh-api-test.js
index fc6c010..a15bd06 100644
--- a/simple-gh-api-test.js
+++ b/scripts/simple-gh-api-test.js
@@ -1,4 +1,4 @@
-const { fetchIssues } = require('./scripts/github-api');
+const { fetchIssues } = require('../src/github-api');
 
 // Simple promise-based timeout to detect if the API call is hanging
 const timeout = (ms) => new Promise((_, reject) =>
diff --git a/scripts/switch-env.js b/scripts/switch-env.js
new file mode 100644
index 0000000..639bf44
--- /dev/null
+++ b/scripts/switch-env.js
@@ -0,0 +1,55 @@
+#!/usr/bin/env node
+
+/**
+ * Command-line utility to switch between environments
+ * Usage: node switch-env.js [environment-name]
+ */
+const { switchEnvironment, getActiveEnvironment, getConfig } = require('../src/config-loader');
+
+// Define available environments from config
+let config;
+try {
+  config = getConfig();
+} catch (error) {
+  console.error(`Failed to load configuration: ${error.message}`);
+  process.exit(1);
+}
+
+const availableEnvironments = Object.keys(config.environments);
+const activeEnvironment = getActiveEnvironment(config);
+
+// Display current environment if no arguments
+if (process.argv.length < 3) {
+  console.log(`Current active environment: ${activeEnvironment.name}`);
+  console.log(`Available environments: ${availableEnvironments.join(', ')}`);
+  console.log('Usage: node switch-env.js [environment-name]');
+  process.exit(0);
+}
+
+// Get the requested environment name
+const requestedEnv = process.argv[2];
+
+// Validate the requested environment
+if (!availableEnvironments.includes(requestedEnv)) {
+  console.error(`Error: Environment "${requestedEnv}" not found.`);
+  console.error(`Available environments: ${availableEnvironments.join(', ')}`);
+  process.exit(1);
+}
+
+// Switch to the requested environment
+try {
+  if (requestedEnv === activeEnvironment.name) {
+    console.log(`Environment "${requestedEnv}" is already active.`);
+  } else {
+    switchEnvironment(requestedEnv);
+    console.log(`Switched to environment: ${requestedEnv}`);
+    
+    // Display the repository that will be used
+    const newEnv = getActiveEnvironment();
+    console.log(`Repository: ${newEnv.repository.owner}/${newEnv.repository.repo}`);
+    console.log(`Using ${newEnv.repository.useLocalIssues ? 'local test issues' : 'live GitHub API'}`);
+  }
+} catch (error) {
+  console.error(`Failed to switch environment: ${error.message}`);
+  process.exit(1);
+}
\ No newline at end of file
diff --git a/scripts/test-config.js b/scripts/test-config.js
new file mode 100644
index 0000000..4d13467
--- /dev/null
+++ b/scripts/test-config.js
@@ -0,0 +1,64 @@
+/**
+ * Test script to verify environment configuration system
+ */
+const configLoader = require('../src/config-loader');
+const issuesFetcher = require('../src/github-issue-fetcher');
+const fs = require('fs');
+const path = require('path');
+
+console.log('\n=== ENVIRONMENT CONFIGURATION STATUS ===');
+
+// Display active environment
+const activeEnv = configLoader.getActiveEnvironment();
+console.log('\n1. CURRENT ENVIRONMENT');
+console.log(`   Name: ${activeEnv.name}`);
+console.log(`   Mode: ${activeEnv.repository.useLocalIssues ? 'Testing with local mock issues' : 'Production with live GitHub API'}`);
+
+// Display the repository configured in the environment
+console.log('\n2. REPOSITORY CONFIGURED IN ENVIRONMENT');
+console.log(`   Owner: ${activeEnv.repository.owner}`);
+console.log(`   Repo: ${activeEnv.repository.repo}`);
+
+// Display information from secrets file if it exists
+try {
+  const secretsConfig = require('./.secrets/github');
+  console.log('\n3. SECRETS FOUND');
+  console.log(`   GitHub token: ${secretsConfig.token ? '✓ Present' : '✗ Missing'}`);
+  
+  // Check if repository info exists in secrets
+  const hasRepoInSecrets = secretsConfig.owner && secretsConfig.repo;
+  console.log(`   Repository info: ${hasRepoInSecrets ? '✓ Present' : '✗ Missing'}`);
+  
+  // Display actual repository that will be used (may come from secrets)
+  const repoInfo = issuesFetcher.getCurrentRepositoryInfo();
+  console.log('\n4. REPOSITORY THAT WILL ACTUALLY BE USED');
+  console.log(`   Owner: ${repoInfo.owner}`);
+  console.log(`   Repo: ${repoInfo.repo}`);
+  
+  // Add a clear explanation about source of repository info
+  if (activeEnv.repository.useLocalIssues) {
+    console.log(`   Source: Using test repository from environment config (local testing mode)`);
+  } else if (secretsConfig.owner && secretsConfig.repo && 
+            (secretsConfig.owner === repoInfo.owner && secretsConfig.repo === repoInfo.repo)) {
+    console.log(`   Source: Using repository from .secrets/github.js file`);
+  } else {
+    console.log(`   Source: Using repository from environment config`);
+  }
+} catch (error) {
+  console.log('\n3. SECRETS STATUS');
+  console.log(`   ✗ No GitHub secrets found (.secrets/github.js is missing)`);
+  
+  // Display actual repository that will be used (from environment)
+  const repoInfo = issuesFetcher.getCurrentRepositoryInfo();
+  console.log('\n4. REPOSITORY THAT WILL ACTUALLY BE USED');
+  console.log(`   Owner: ${repoInfo.owner}`);
+  console.log(`   Repo: ${repoInfo.repo}`);
+  console.log(`   Source: Using repository from environment config (no secrets found)`);
+}
+
+// Display available commands
+console.log('\n5. AVAILABLE COMMANDS');
+console.log('   • npm run toggle-env - Switch between testing and production');
+console.log('   • npm run env:status - Show this environment status');
+
+console.log('\nEnvironment setup complete!');
\ No newline at end of file
diff --git a/test-gh-api-wrapper.js b/scripts/test-gh-api-wrapper.js
similarity index 95%
rename from test-gh-api-wrapper.js
rename to scripts/test-gh-api-wrapper.js
index 9b91ca2..61331ff 100644
--- a/test-gh-api-wrapper.js
+++ b/scripts/test-gh-api-wrapper.js
@@ -1,5 +1,5 @@
-const { fetchIssues, addLabelsToIssue, commentOnIssue } = require('./scripts/github-api');
-const { token, owner, repo } = require('./.secrets/github');
+const { fetchIssues, addLabelsToIssue, commentOnIssue } = require('../src/github-api');
+const { token, owner, repo } = require('../.secrets/github');
 
 // Set up process error handling
 process.on('uncaughtException', (error) => {
diff --git a/test-github-model.js b/scripts/test-github-model.js
similarity index 97%
rename from test-github-model.js
rename to scripts/test-github-model.js
index 99b217b..34b3b81 100644
--- a/test-github-model.js
+++ b/scripts/test-github-model.js
@@ -2,7 +2,7 @@
 
 const fs = require('fs');
 const path = require('path');
-const { callGithubModel } = require('./lib/githubModel');
+const { callGithubModel } = require('../src/github-model');
 
 function validateIssue(issue) {
   if (!issue.title || typeof issue.title !== 'string') {
diff --git a/scripts/toggle-env.js b/scripts/toggle-env.js
new file mode 100644
index 0000000..bd4e367
--- /dev/null
+++ b/scripts/toggle-env.js
@@ -0,0 +1,28 @@
+#!/usr/bin/env node
+
+/**
+ * Environment toggle script
+ * Automatically switches between testing and production environments
+ * regardless of which one is currently active.
+ */
+const configLoader = require('../src/config-loader');
+
+// Get currently active environment
+const activeEnv = configLoader.getActiveEnvironment();
+const currentEnv = activeEnv.name;
+
+// Toggle to the other environment
+const targetEnv = currentEnv === 'testing' ? 'production' : 'testing';
+
+try {
+  configLoader.switchEnvironment(targetEnv);
+  console.log(`✓ Switched from "${currentEnv}" to "${targetEnv}"`);
+  
+  // Display the repository that will be used
+  const newEnv = configLoader.getActiveEnvironment();
+  console.log(`\nRepository: ${newEnv.repository.owner}/${newEnv.repository.repo}`);
+  console.log(`Using ${newEnv.repository.useLocalIssues ? 'local test issues' : 'live GitHub API'}`);
+} catch (error) {
+  console.error(`Failed to switch environment: ${error.message}`);
+  process.exit(1);
+}
\ No newline at end of file
diff --git a/src/config-loader.js b/src/config-loader.js
new file mode 100644
index 0000000..07f3c86
--- /dev/null
+++ b/src/config-loader.js
@@ -0,0 +1,141 @@
+/**
+ * Configuration loader for issue-labeler
+ * Handles loading environment-specific configurations from config.js
+ */
+const path = require('path');
+const fs = require('fs');
+
+// Config file path - config.js is in the root directory
+const CONFIG_PATH = path.join(__dirname, '..', 'config.js');
+
+// Import the config directly from JS file
+const config = require('../config');
+
+/**
+ * Validates the configuration structure
+ * @throws {Error} If the configuration is invalid
+ */
+function validateConfig() {
+  if (!config.environments) {
+    throw new Error('Missing "environments" section in configuration');
+  }
+  
+  // Check if at least one environment is defined
+  const envKeys = Object.keys(config.environments);
+  if (envKeys.length === 0) {
+    throw new Error('No environments defined in configuration');
+  }
+  
+  // Check if exactly one environment is active
+  const activeEnvs = envKeys.filter(key => config.environments[key].active);
+  if (activeEnvs.length === 0) {
+    throw new Error('No active environment found in configuration');
+  }
+  if (activeEnvs.length > 1) {
+    throw new Error(`Multiple active environments found: ${activeEnvs.join(', ')}. Only one environment can be active.`);
+  }
+
+  return true;
+}
+
+// Run validation on load
+validateConfig();
+
+/**
+ * Gets the currently active environment configuration
+ * @returns {Object} - The active environment configuration
+ */
+function getActiveEnvironment() {
+  const activeEnvKey = Object.keys(config.environments)
+    .find(key => config.environments[key].active);
+  
+  return {
+    name: activeEnvKey,
+    ...config.environments[activeEnvKey]
+  };
+}
+
+/**
+ * Gets the repository configuration for the active environment
+ * @returns {Object} - Repository configuration object
+ */
+function getRepositoryConfig() {
+  return getActiveEnvironment().repository;
+}
+
+/**
+ * Gets the model configuration (which is the same across environments)
+ * @returns {Object} - Model configuration object
+ */
+function getModelConfig() {
+  return config.model;
+}
+
+/**
+ * Gets the GitHub API configuration
+ * @returns {Object} - API configuration object
+ */
+function getApiConfig() {
+  return config.github;
+}
+
+/**
+ * Gets the complete configuration
+ * @returns {Object} - Complete configuration object
+ */
+function getConfig() {
+  return config;
+}
+
+/**
+ * Helper to update config.js file content
+ * @param {Object} updatedConfig - The updated config to write
+ */
+function updateConfigFile(updatedConfig) {
+  // Read the current file content
+  let fileContent = fs.readFileSync(CONFIG_PATH, 'utf8');
+  
+  // For each environment, update its active state in the file
+  Object.keys(updatedConfig.environments).forEach(env => {
+    const active = updatedConfig.environments[env].active;
+    // Use regex to update the active property in the file
+    const pattern = new RegExp(`(${env}:\\s*{[^}]*active:\\s*)\\w+`, 'g');
+    fileContent = fileContent.replace(pattern, `$1${active}`);
+  });
+  
+  // Write the updated content back to the file
+  fs.writeFileSync(CONFIG_PATH, fileContent, 'utf8');
+}
+
+/**
+ * Switches the active environment
+ * @param {string} environmentName - The name of the environment to activate
+ * @returns {Object} - The updated configuration object
+ */
+function switchEnvironment(environmentName) {
+  if (!config.environments[environmentName]) {
+    throw new Error(`Environment "${environmentName}" not found in configuration`);
+  }
+  
+  // Update in-memory config
+  Object.keys(config.environments).forEach(key => {
+    config.environments[key].active = (key === environmentName);
+  });
+  
+  // Persist changes to the config file
+  updateConfigFile(config);
+  
+  // Clear the require cache for config.js to reload on next require
+  delete require.cache[require.resolve('../config')];
+  
+  return config;
+}
+
+module.exports = {
+  getConfig,
+  getActiveEnvironment,
+  getRepositoryConfig,
+  getModelConfig,
+  getApiConfig,
+  switchEnvironment
+};
\ No newline at end of file
diff --git a/lib/github-wrapper.js b/src/github-api-client.js
similarity index 81%
rename from lib/github-wrapper.js
rename to src/github-api-client.js
index e927540..05ff924 100644
--- a/lib/github-wrapper.js
+++ b/src/github-api-client.js
@@ -1,41 +1,38 @@
 /**
- * GitHub API wrapper for the issue labeling workflow
+ * GitHub API client for the issue labeling workflow
  * Centralizes error handling and API configuration for reliable GitHub interactions.
- * Use this wrapper instead of direct API calls to ensure consistent error handling and rate limit management.
+ * Use this client instead of direct API calls to ensure consistent error handling and rate limit management.
  */
 const axios = require('axios');
 const https = require('https');
+const config = require('./config');
 
 // Use environment variables for sensitive credentials
 const token = process.env.GITHUB_TOKEN;
 
-const github = axios.create({
-  baseURL: BASE_URL,
-  headers: {
-    Authorization: `Bearer ${token}`,
-    Accept: 'application/vnd.github.v3+json',
-    'User-Agent': 'issue-labeler-app'
-  },
-  timeout: 5000,
-  httpsAgent: new https.Agent({ keepAlive: true })
-});
+/**
+ * Validates GitHub path segments to prevent path traversal attacks
+ * @param {string} segment - Path segment to validate
+ * @returns {string} - The validated segment
+ */
+function validatePathSegment(segment) {
   if (!/^[\w.-]+$/.test(segment)) {
-    throw new Error('Invalid path segment');
+    throw new Error(`Invalid path segment: "${segment}". Path segments must only contain alphanumeric characters, dashes, underscores, or periods.`);
   }
   return segment;
 }
 
 const github = axios.create({
-  baseURL: BASE_URL,
+  baseURL: config.github.baseUrl,
   headers: {
     Authorization: `token ${token}`,
     Accept: 'application/vnd.github.v3+json',
     'User-Agent': 'issue-labeler-app'
   },
-  timeout: GITHUB_API_TIMEOUT_MS,
+  timeout: config.github.timeoutMs,
   httpsAgent: new https.Agent({
     keepAlive: true,
-    maxSockets: 100 // Limit the maximum concurrent connections
+    maxSockets: config.github.maxSockets
   })
 });
 
diff --git a/scripts/github-api.js b/src/github-api.js
similarity index 90%
rename from scripts/github-api.js
rename to src/github-api.js
index 884e96b..d3a489e 100644
--- a/scripts/github-api.js
+++ b/src/github-api.js
@@ -3,7 +3,22 @@
  * Provides functions for fetching issues, adding labels, and commenting on issues
  */
 const axios = require('axios');
-const { token, owner, repo } = require('../.secrets/github');
+const configLoader = require('./config-loader');
+
+// Get repository info from config
+const repoConfig = configLoader.getRepositoryConfig();
+const owner = repoConfig.owner;
+const repo = repoConfig.repo;
+
+// Get token from secrets
+let token;
+try {
+  const secrets = require('../.secrets/github');
+  token = secrets.token;
+} catch (error) {
+  console.warn('Failed to load GitHub token from secrets:', error.message);
+}
+
 if (!token || !owner || !repo) {
   throw new Error('Missing GitHub configuration: token, owner, and repo are required');
 }
diff --git a/src/github-issue-fetcher.js b/src/github-issue-fetcher.js
new file mode 100644
index 0000000..e6e8677
--- /dev/null
+++ b/src/github-issue-fetcher.js
@@ -0,0 +1,193 @@
+const axios = require('axios');
+const configLoader = require('./config-loader');
+
+/**
+ * @fileoverview Provides utilities for fetching GitHub issues.
+ * 
+ * @typedef {Object} GitHubIssue
+ * @property {number} id - The unique identifier of the issue.
+ * @property {string} title - The title of the issue.
+ * @property {string} body - The body content of the issue.
+ * @property {Array<{name: string}>} labels - The labels associated with the issue.
+ * @property {boolean} pull_request - Indicates if the issue is a pull request.
+ */
+
+// Load configuration
+const repoConfig = configLoader.getRepositoryConfig();
+const apiConfig = configLoader.getApiConfig();
+
+// Get credentials from .secrets, but repository info from config
+let token;
+const owner = repoConfig.owner;
+const repo = repoConfig.repo;
+
+try {
+  const secretsConfig = require('../.secrets/github');
+  token = secretsConfig.token;
+} catch (error) {
+  console.error('Failed to load secrets:', error.message);
+  process.exit(1);
+}
+
+// Initialize GitHub API client with configuration
+const githubClient = axios.create({
+  baseURL: apiConfig.baseUrl,
+  headers: {
+    Authorization: `token ${token}`,
+    Accept: 'application/vnd.github.v3+json',
+    'User-Agent': 'issue-labeler-app'
+  },
+  timeout: apiConfig.timeoutMs
+});
+
+/**
+ * Validates GitHub path segments to prevent path traversal attacks
+ * @param {string} segment - Path segment to validate
+ * @returns {string} - The validated segment
+ */
+function validatePathSegment(segment) {
+  if (!/^[\w.-]+$/.test(segment)) {
+    throw new Error(`Invalid path segment: "${segment}". Path segments must only contain alphanumeric characters, dashes, underscores, or periods.`);
+  }
+  return segment;
+}
+
+/**
+ * Centralized error handling function
+ * @param {Error} error - The error object
+ * @param {string} context - Description of the operation that failed
+ */
+function handleGitHubError(error, context) {
+  console.error(`Error ${context}: ${error.message}`);
+  if (error.response) {
+    console.error('API Error:', error.response.status, error.response.statusText);
+    console.error('Error details:', error.response.data);
+  }
+  throw error;
+}
+
+/**
+ * Fetches issues from either remote GitHub repository or local test data
+ * based on the active environment configuration
+ * @param {string} endpoint - API endpoint to call
+ * @returns {Promise<Array<GitHubIssue>>} - Array of issues
+ */
+async function fetchIssues(endpoint) {
+  // Check if we should use local test data instead of real API
+  if (repoConfig.useLocalIssues) {
+    console.log('Using local test issues data');
+    try {
+      return getLocalIssues();
+    } catch (error) {
+      console.error('Failed to load local test issues:', error);
+      throw error;
+    }
+  }
+  
+  // Otherwise, fetch from the real GitHub API
+  try {
+    const response = await githubClient.get(endpoint);
+    const issues = response.data.filter(issue => !issue.pull_request);
+    
+    issues.forEach(issue => {
+      issue.labels = issue.labels.map(label => ({
+        ...label,
+        name: label.name.toLowerCase()
+      }));
+    });
+    
+    return issues;
+  } catch (error) {
+    handleGitHubError(error, `fetching issues from ${endpoint}`);
+    throw error;
+  }
+}
+
+/**
+ * Mock function to return local test issues
+ * @returns {Array<GitHubIssue>} - Array of mock issues for testing
+ */
+function getLocalIssues() {
+  return [
+    {
+      id: 1,
+      number: 1,
+      title: "Test Issue: Add automated labeling",
+      body: "We need to implement automated issue labeling for better triage",
+      labels: [
+        { name: "enhancement" }
+      ],
+      created_at: new Date().toISOString(),
+      updated_at: new Date().toISOString(),
+      html_url: "https://github.com/test/test/issues/1"
+    },
+    {
+      id: 2,
+      number: 2,
+      title: "Test Issue: Fix security vulnerability",
+      body: "Critical security vulnerability found in authentication module",
+      labels: [
+        { name: "bug" }
+      ],
+      created_at: new Date().toISOString(),
+      updated_at: new Date().toISOString(),
+      html_url: "https://github.com/test/test/issues/2"
+    }
+  ];
+}
+
+/**
+ * Fetches all open issues from the configured GitHub repository
+ * @returns {Promise<Array<GitHubIssue>>} Array of GitHub issues
+ */
+async function getAllOpenIssues() {
+  if (!owner || typeof owner !== 'string' || !owner.trim()) {
+    throw new Error('GitHub owner must be a non-empty string');
+  }
+  if (!repo || typeof repo !== 'string' || !repo.trim()) {
+    throw new Error('GitHub repo must be a non-empty string');
+  }
+  
+  const safeOwner = validatePathSegment(owner.trim());
+  const safeRepo = validatePathSegment(repo.trim());
+  
+  const endpoint = `/repos/${safeOwner}/${safeRepo}/issues?state=open`;
+  return fetchIssues(endpoint);
+}
+
+/**
+ * Fetches all issues with a specific label from the configured GitHub repository
+ * @param {string} label - The label to filter issues by
+ * @returns {Promise<Array<GitHubIssue>>} Array of GitHub issues with the specified label
+ */
+async function getIssuesWithLabel(label) {
+  if (!label || typeof label !== 'string' || !label.trim()) {
+    throw new Error('Label must be a non-empty string');
+  }
+  
+  const safeOwner = validatePathSegment(owner.trim());
+  const safeRepo = validatePathSegment(repo.trim());
+  const safeLabel = encodeURIComponent(label.trim());
+  
+  const endpoint = `/repos/${safeOwner}/${safeRepo}/issues?state=open&labels=${safeLabel}`;
+  return fetchIssues(endpoint);
+}
+
+/**
+ * Gets the current repository configuration
+ * @returns {Object} Current repository configuration
+ */
+function getCurrentRepositoryInfo() {
+  return {
+    owner,
+    repo,
+    isLocal: repoConfig.useLocalIssues,
+    environment: configLoader.getActiveEnvironment().name
+  };
+}
+
+module.exports = {
+  getAllOpenIssues,
+  getIssuesWithLabel,
+  getCurrentRepositoryInfo
+};
\ No newline at end of file
diff --git a/utils/github-issues.js b/src/github-issues.js
similarity index 100%
rename from utils/github-issues.js
rename to src/github-issues.js
diff --git a/src/github-model.js b/src/github-model.js
new file mode 100644
index 0000000..fa02532
--- /dev/null
+++ b/src/github-model.js
@@ -0,0 +1,177 @@
+/**
+ * GitHub Model API client for issue labeling workflow
+ * Provides functions for model inference to determine issue labels
+ */
+const axios = require('axios');
+const https = require('https');
+const configLoader = require('./config-loader');
+const fs = require('fs');
+const path = require('path');
+
+// Load configuration - use central config instead of separate files
+const modelConfig = configLoader.getModelConfig();
+const apiConfig = configLoader.getApiConfig();
+
+// Get token from .secrets first
+let token;
+try {
+  // Check if we're using gh-model.js or github.js in the secrets folder
+  // Path to secrets is in the root directory, not src
+  const secretsDir = path.join(__dirname, '..', '.secrets');
+  if (fs.existsSync(path.join(secretsDir, 'gh-model.js'))) {
+    token = require('../.secrets/gh-model').token;
+  } else {
+    token = require('../.secrets/github').token;
+  }
+} catch (error) {
+  console.error('Failed to load secrets:', error.message);
+  process.exit(1);
+}
+
+// Create a reusable axios instance with keepAlive enabled
+const axiosInstance = axios.create({
+  httpsAgent: new https.Agent({ keepAlive: true }),
+  timeout: apiConfig.timeoutMs || 10000
+});
+
+// Regex for cleaning markdown code blocks
+const MARKDOWN_CODE_BLOCK_REGEX = /```(?:\w+)?\s*([\s\S]*?)```/;
+
+/**
+ * Parse and extract JSON data from model response
+ * @param {string} rawContent - Raw text from model response
+ * @returns {Object} - Parsed JSON object
+ */
+function parseModelResponse(rawContent) {
+  // Extract JSON object from the response (handling possible markdown code blocks)
+  let jsonContent = rawContent;
+  const jsonMatch = rawContent.match(/```(?:json|js)?\s*([\s\S]*?)```/);
+  if (jsonMatch && jsonMatch[1]) {
+    jsonContent = jsonMatch[1].trim();
+  }
+
+  try {
+    return JSON.parse(jsonContent);
+  } catch (parseError) {
+    console.error('Failed to parse JSON response:', parseError.message);
+    console.error('Raw response:', rawContent);
+    throw new Error('Failed to parse model response');
+  }
+}
+
+/**
+ * Clean model response by removing markdown code blocks if present
+ * @param {string} rawContent - Raw text from model response
+ * @returns {string} - Cleaned content
+ */
+function cleanModelResponse(rawContent) {
+  if (rawContent.startsWith('```') && rawContent.endsWith('```')) {
+    const match = rawContent.match(MARKDOWN_CODE_BLOCK_REGEX);
+    if (match && match[1]) {
+      return match[1].trim();
+    }
+  }
+  return rawContent;
+}
+
+/**
+ * Calls the GitHub model API for inference using structured prompt
+ * @param {string} prompt - The input prompt for the model
+ * @returns {Object} - Object containing urgency and importance values
+ */
+async function callModel(prompt) {
+  try {
+    const response = await axiosInstance.post(modelConfig.apiEndpoint, {
+      messages: [
+        { role: "user", content: prompt }
+      ],
+      model: modelConfig.name,
+      temperature: modelConfig.temperature,
+      max_tokens: modelConfig.maxTokens
+    }, {
+      headers: {
+        "Authorization": `Bearer ${token}`,
+        "Content-Type": "application/json"
+      }
+    });
+
+    // Extract content from response
+    if (!response.data.choices || !response.data.choices[0]?.message?.content) {
+      throw new Error('Invalid response format from GitHub model API');
+    }
+
+    const rawContent = response.data.choices[0].message.content.trim();
+    const parsedJson = parseModelResponse(rawContent);
+    
+    return {
+      urgency: parsedJson.urgency && parsedJson.urgency.trim() !== '' ? parsedJson.urgency.trim() : null,
+      importance: parsedJson.importance && parsedJson.importance.trim() !== '' ? parsedJson.importance.trim() : null
+    };
+  } catch (error) {
+    handleModelError(error);
+    throw error;
+  }
+}
+
+/**
+ * Calls the GitHub model API with raw prompt and returns the raw response
+ * @param {Object} options - Options object
+ * @param {string} options.prompt - The input prompt for the model
+ * @returns {string} - Raw model response
+ */
+async function callGithubModel({ prompt }) {
+  try {
+    const response = await axiosInstance.post(modelConfig.apiEndpoint, {
+      messages: [
+        { role: "user", content: prompt }
+      ],
+      model: modelConfig.name,
+      temperature: modelConfig.temperature,
+      max_tokens: modelConfig.maxTokens
+    }, {
+      headers: {
+        "Authorization": `Bearer ${token}`,
+        "Content-Type": "application/json"
+      }
+    });
+
+    // Validate that the API response has the expected structure
+    if (!response.data.choices || !response.data.choices[0]?.message?.content) {
+      throw new Error('Invalid response format from GitHub model API');
+    }
+
+    const rawContent = response.data.choices[0].message.content.trim();
+    return cleanModelResponse(rawContent);
+  } catch (error) {
+    handleModelError(error);
+    throw error;
+  }
+}
+
+/**
+ * Handle model API errors with proper logging and retry information
+ * @param {Error} error - The error object
+ */
+function handleModelError(error) {
+  // Check for rate limiting
+  if (error.response?.status === 429) {
+    console.warn("Rate limit exceeded. Implementing exponential backoff retry.");
+    // Get retry-after header if available or default to 1 second
+    const retryAfter = parseInt(error.response.headers?.['retry-after']) || 1;
+    console.log(`Retrying after ${retryAfter} seconds`);
+    throw new Error(`Rate limit exceeded. Retry after ${retryAfter} seconds.`);
+  }
+
+  console.error('Error calling GitHub model:', error.message);
+  if (error.response) {
+    console.error('API Error:', error.response.status, error.response.statusText);
+    console.error('Error details:', error.response.data);
+  } else {
+    console.error('Error stack:', error.stack);
+  }
+}
+
+module.exports = {
+  callModel,
+  callGithubModel
+};
\ No newline at end of file
diff --git a/labeler.js b/src/labeler.js
similarity index 97%
rename from labeler.js
rename to src/labeler.js
index 1dc0dc3..d38c650 100644
--- a/labeler.js
+++ b/src/labeler.js
@@ -2,7 +2,7 @@
 
 const fs = require('fs');
 const path = require('path');
-const { callGithubModel } = require('./lib/githubModel');
+const { callGithubModel } = require('./github-model');
 
 // Load the prompt template and config
 const promptTemplate = fs.readFileSync('prompts/label-template.txt', 'utf-8');
diff --git a/test-config.js b/test-config.js
deleted file mode 100644
index 67a0eae..0000000
--- a/test-config.js
+++ /dev/null
@@ -1,5 +0,0 @@
-// Simple configuration check
-const config = require('./.secrets/github');
-
-console.log('GitHub Configuration loaded successfully');
-console.log('Token present:', !!config.token);
\ No newline at end of file


Reviews:
user: login: gemini-code-assist[bot]
body: Hello @dmitriz, I'm Gemini Code Assist[^1]! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

## Summary of Changes
This pull request introduces a new script, `scripts/select-next.js`, to prioritize GitHub issues with an "urgent" label. It also includes structural improvements to the codebase, such as a new function `getLabeledIssues` in `lib/github.js` to fetch labeled issues and a `validatePathSegment` function in `lib/github-wrapper.js` to prevent path traversal attacks. The `package.json` file is updated to include a new script, `"select"`, to run the `scripts/select-next.js` file.

### Highlights
* **Issue Prioritization**: A new script, `scripts/select-next.js`, calculates priority scores for GitHub issues and selects the next issue to work on based on the "urgent" label. Lower scores indicate higher priority.
* **GitHub API Integration Enhancements**: A new function, `getLabeledIssues`, in `lib/github.js` fetches open issues from a GitHub repository and filters them by the "urgent" label, handling API errors gracefully. A `validatePathSegment` function in `lib/github-wrapper.js` prevents path traversal attacks when constructing GitHub API paths.
* **Configuration Update**: The `package.json` file is updated to include a new script, `"select"`, which runs the `scripts/select-next.js` file.

### Changelog
<details>
<summary>Click here to see the changelog</summary>

* **lib/github-wrapper.js**
  * Introduced `validatePathSegment` function to prevent path traversal attacks when constructing GitHub API paths (lines 16-24).
  * Moved API constants to be defined before the validatePathSegment function (lines 12-14).
* **lib/github.js**
  * Added `getLabeledIssues` function to fetch open issues with the 'urgent' label from a GitHub repository (lines 10-27).
  * The function handles API errors and logs detailed error messages (lines 22-26).
* **package.json**
  * Added a new script, `"select"`, to run the `scripts/select-next.js` file (line 9).
* **scripts/select-next.js**
  * Created a new script to calculate priority scores for GitHub issues based on the 'urgent' label (lines 9-14).
  * Implemented logic to select the next issue to work on based on priority scoring (lines 19-29).
  * Added error handling for selecting the next issue (lines 31-33).

</details>


<details>

<summary><b>Using Gemini Code Assist</b></summary>
<br>

The full guide for Gemini Code Assist can be found on our [documentation page](https://developers.google.com/gemini-code-assist/docs/review-github-code), here are some quick tips.

<b>Invoking Gemini</b>

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either `/gemini <command>` or `@gemini-code-assist <command>`. Below is a summary of the supported commands.

Feature | Command | Description
--- | --- | ---
Code Review | `/gemini review` | Performs a code review for the current pull request in its current state.
Pull Request Summary | `/gemini summary` | Provides a summary of the current pull request in its current state.
Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in issue comments and review comments.
Help | `/gemini help` | Displays a list of available commands.

<b>Customization</b>

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a `.gemini/` folder in the base of the repository. Detailed instructions can be found [here](https://developers.google.com/gemini-code-assist/docs/customize-gemini-behavior-github).

<b>Limitations & Feedback</b>

Gemini Code Assist is currently in preview and may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with :thumbsup: and :thumbsdown: on @gemini-code-assist comments to provide feedback.

</details>

<b>You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the [Gemini Code Assist IDE Extension](https://cloud.google.com/products/gemini/code-assist).</b>


[^1]: Review the [Privacy Notices](https://policies.google.com/privacy), [Generative AI Prohibited Use Policy](https://policies.google.com/terms/generative-ai/use-policy), [Terms of Service](https://policies.google.com/terms), and learn how to configure Gemini Code Assist in GitHub [here](https://developers.google.com/gemini-code-assist/docs/review-github-code). Gemini can make mistakes, so double check it and [use code with caution](https://support.google.com/legal/answer/13505487).

---

> A label 'urgent' does appear,
> To quell the developers' fear,
> Priorities align,
> The work will shine,
> Next issue's chosen, crystal clear.


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T07:37:30Z
user: login: gemini-code-assist[bot]
body: ## Code Review
This pull request introduces a new feature for prioritizing GitHub issues with an "urgent" label and includes several structural improvements to the codebase. The changes include adding a new script to select the next issue based on priority, implementing a utility to fetch labeled issues, and enhancing the maintainability and security of the GitHub API integration. Overall, the changes look good, but there are a few areas that could be improved.

### Summary of Findings
* **Error Handling in `getLabeledIssues`**: The `getLabeledIssues` function in `lib/github.js` includes error logging, but it re-throws the error after logging. Consider whether re-throwing is always the desired behavior, or if there are cases where the calling function could handle the error more gracefully.
* **API Key Security**: The API key is being read from `.secrets/github`. While this file is likely gitignored, it's still not best practice to store secrets in files. Consider using environment variables instead, and ensure that the environment variables are not exposed in the client-side code.
* **Path Validation**: The `validatePathSegment` function in `lib/github-wrapper.js` throws an error if the path segment is invalid. This is good, but consider adding more context to the error message to help with debugging.

### Merge Readiness
The pull request is almost ready for merging. Before merging, I recommend addressing the comments related to error handling and API key security. I am unable to directly approve the pull request, and other reviewers should review and approve this code before merging.
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T07:37:40Z
user: login: korbit-ai[bot]
body: ### Review by Korbit AI

###### Korbit automatically attempts to detect when you fix issues in new commits.

| Category | Issue | Status |
|----------|-------|----------|
| <sub>![Readability](https://img.shields.io/badge/Readability-0284c7)</sub> | Hardcoded magic numbers ▹ [view]( https://github.com/dmitriz/issue-labeler/pull/10#discussion_r2077000941 ) | ✅ Fix detected |
| <sub>![Design](https://img.shields.io/badge/Design-0d9488)</sub> | Tight Coupling to Configuration ▹ [view]( https://github.com/dmitriz/issue-labeler/pull/10#discussion_r2077000947 ) | ✅ Fix detected |
| <sub>![Functionality](https://img.shields.io/badge/Functionality-0284c7)</sub> | Case-Sensitive Label Matching ▹ [view]( https://github.com/dmitriz/issue-labeler/pull/10#discussion_r2077000955 ) | ✅ Fix detected |
| <sub>![Security](https://img.shields.io/badge/Security-e11d48)</sub> | Hardcoded Secrets in Codebase ▹ [view]( https://github.com/dmitriz/issue-labeler/pull/10#discussion_r2077000944 ) | ✅ Fix detected |
| <sub>![Functionality](https://img.shields.io/badge/Functionality-0284c7)</sub> | Missing Required Parameter Validation ▹ [view]( https://github.com/dmitriz/issue-labeler/pull/10#discussion_r2077000951 ) | ✅ Fix detected |
| <sub>![Readability](https://img.shields.io/badge/Readability-0284c7)</sub> | Imprecise type annotation ▹ [view]( https://github.com/dmitriz/issue-labeler/pull/10#discussion_r2077000960 ) | ✅ Fix detected |






<details>
  <summary>Files scanned</summary>
&#8203;



| File Path | Reviewed |
| ------------------ | -------------- |
| lib/github.js | ✅ |
| scripts/select-next.js | ✅ |
| lib/github-wrapper.js | ✅ |


> Explore our [documentation](https://docs.korbit.ai) to understand the languages and file types [we support](https://docs.korbit.ai/configuration/supported-languages) and the [files we ignore](https://docs.korbit.ai/configuration/ignoring-files).

</details>





> Check out our [docs](https://docs.korbit.ai) on how you can make Korbit work best for you and your team.
>
> Loving Korbit!? Share us on [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https://www.korbit.ai&text=Korbit%20AI-powered%20Code%20Reviews%20are%20awesome...) [Reddit](https://www.reddit.com/submit?url=https://www.korbit.ai&title=Korbit%20AI-powered%20Code%20Reviews%20are%20awesome...) and [X](https://x.com/intent/tweet?text=Korbit%20AI-powered%20Code%20Reviews%20are%20awesome...%0A%0Ahttps%3A%2F%2Fwww.korbit.ai%0A%0A@KorbitAi)


state: COMMENTED
author_association: NONE
submitted_at: 2025-05-07T07:39:40Z
user: login: codeant-ai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T07:41:22Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>🔭 Outside diff range comments (1)</summary><blockquote>

<details>
<summary>lib/github-wrapper.js (1)</summary><blockquote>

`53-55`: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Verify consistent use of validatePathSegment across all API calls.**

While the `constructIssueUrl` function correctly uses `validatePathSegment`, the direct API calls in `getIssueContent` (line 67) and `applyLabels` (line 95) do not utilize this validation function.

---

🏁 Script executed:

```shell
#!/bin/bash
# Search for direct API calls that don't use validatePathSegment
rg "github\.(get|post|put|delete)" --files-with-matches | xargs rg -l "\/repos\/[\$\w]+" | xargs rg -v "validatePathSegment"
```

Length of output: 75097

---

🏁 Script executed:

```shell
#!/bin/bash
set -e

echo "=== getIssueContent Definition ==="
grep -R -n -C5 "getIssueContent" lib/github-wrapper.js || true

echo
echo "=== applyLabels Definition ==="
grep -R -n -C5 "function applyLabels" lib/github-wrapper.js || true

echo
echo "=== validatePathSegment Usage ==="
grep -R -n "validatePathSegment" lib/github-wrapper.js || true
```

Length of output: 1703

---



</details>

**Ensure validatePathSegment is applied to all GitHub wrapper URLs**

The helper `constructIssueUrl` (which runs owner/repo through `validatePathSegment`) is used in one place, but both `getIssueContent` and `applyLabels` still interpolate raw segments. Unvalidated input can lead to malformed URLs or injection risks.

Please update these two methods in `lib/github-wrapper.js`:

• getIssueContent (around line 67)  
• applyLabels (around line 95)

Suggested diff:

```diff
--- a/lib/github-wrapper.js
+++ b/lib/github-wrapper.js
@@ async function getIssueContent({ issueNumber, owner, repo }) {
-   const response = await github.get(`/repos/${owner}/${repo}/issues/${issueNumber}`);
+   const response = await github.get(
+     constructIssueUrl({ owner, repo, issueNumber })
+   );
@@ async function applyLabels({ issueNumber, owner, repo, labels }) {
-    const response = await github.post(
-      `/repos/${owner}/${repo}/issues/${issueNumber}/labels`,
-      { labels }
-    );
+    const url = `${constructIssueUrl({ owner, repo, issueNumber })}/labels`;
+    const response = await github.post(url, { labels });
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>scripts/select-next.js (2)</summary><blockquote>

`9-14`: **Redundant urgent label check.**

The `score` function checks for the "urgent" label even though `getLabeledIssues` already filters for issues with this label. This redundancy is inefficient and may cause confusion.

```diff
 function score(issue) {
-  // If issue has urgent label, give it highest priority (score 0)
-  const hasUrgentLabel = issue.labels.some(l => l.name === 'urgent');
-  
-  return hasUrgentLabel ? 0 : 10; // 0 for urgent issues, 10 for others
+  // All issues from getLabeledIssues already have the urgent label
+  // Add additional scoring logic here if needed
+  return 0;
 }
```

---

`16-29`: **Consider adding more scoring criteria.**

Currently, the scoring system is binary (urgent or not), but could be expanded to handle multiple labels with different priorities or consider issue age, comments, or activity.

```diff
 function score(issue) {
-  // If issue has urgent label, give it highest priority (score 0)
-  const hasUrgentLabel = issue.labels.some(l => l.name === 'urgent');
-  
-  return hasUrgentLabel ? 0 : 10; // 0 for urgent issues, 10 for others
+  let score = 0;
+  
+  // Base score from labels
+  if (issue.labels.some(l => l.name === 'urgent')) {
+    score -= 10; // Higher priority (lower score)
+  }
+  
+  // Consider issue age - older issues get higher priority
+  const ageInDays = (new Date() - new Date(issue.created_at)) / (1000 * 60 * 60 * 24);
+  score -= Math.min(ageInDays / 7, 5); // Up to 5 points for age (max 5 weeks old)
+  
+  return score;
 }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 7d30e6c5abec5f2174f1f43022e04ec791d7d950 and e7619ba1d5884d55c05ea28a1099b83700df2f1d.

</details>

<details>
<summary>📒 Files selected for processing (4)</summary>

* `lib/github-wrapper.js` (1 hunks)
* `lib/github.js` (1 hunks)
* `package.json` (1 hunks)
* `scripts/select-next.js` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (2)</summary>

<details>
<summary>lib/github.js (2)</summary><blockquote>

<details>
<summary>scripts/select-next.js (2)</summary>

* `require` (1-1)
* `issues` (20-20)

</details>
<details>
<summary>example-github-issues.js (2)</summary>

* `owner` (10-10)
* `repo` (11-11)

</details>

</blockquote></details>
<details>
<summary>scripts/select-next.js (1)</summary><blockquote>

<details>
<summary>lib/github.js (2)</summary>

* `require` (2-2)
* `issues` (15-19)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: pixeebot[bot]

</details>

<details>
<summary>🔇 Additional comments (6)</summary><blockquote>

<details>
<summary>lib/github-wrapper.js (3)</summary>

`12-14`: **Good use of constants for API configuration.**

Moving hard-coded values to named constants improves maintainability and readability.

---

`16-26`: **Solid implementation of path segment validation.**

The regex pattern properly restricts allowed characters to prevent path traversal attacks. The function is well-documented with JSDoc comments.

---

`28-40`: **Improved client configuration with proper rate limiting.**

The `maxSockets` limit helps prevent API rate limit issues by controlling concurrent connections.

</details>
<details>
<summary>lib/github.js (1)</summary>

`1-5`: <details>
<summary>❌ Incorrect review comment</summary>

**Authorization format inconsistency with github-wrapper.js.**

`lib/github.js` uses `Bearer ${token}` while `github-wrapper.js` uses `token ${token}`. According to GitHub's documentation, the correct format is `token ${token}`.

```diff
-const headers = { Authorization: `Bearer ${token}` };
+const headers = { Authorization: `token ${token}` };
```

---

🌐 Web query:

```web
GitHub API authentication token format
```

💡 Result:

GitHub uses several different token formats for API authentication, each with distinct prefixes to indicate the token type.

## GitHub Token Formats

GitHub authentication tokens begin with specific prefixes that identify their purpose and type. The current token formats include:

**Personal Access Tokens:**
- **Fine-grained personal access tokens**: Begin with `github_pat_`
- **Personal access tokens (classic)**: Begin with `ghp_`[5]

**App-based Tokens:**
- **OAuth access tokens**: Begin with `gho_`
- **User access tokens for GitHub Apps**: Begin with `ghu_`
- **Installation access tokens for GitHub Apps**: Begin with `ghs_`
- **Refresh tokens for GitHub Apps**: Begin with `ghr_`[5]

## Token Structure

The newer GitHub token formats follow a specific pattern:
- A token prefix (case-sensitive) like 'ghp_', 'gho_', 'ghu_', 'ghs_', or 'ghr_'
- Followed by any combination of 36 characters that can include a-z (not case-sensitive) or 0-9[4]

For example: `ghp_A1bC2dE3fH4iJ5kL6mN7oP8qR9sT0u`[4]

The older personal access token (classic) format is a combination of 40 hexadecimal characters (a-f, A-F, 0-9).[4]

## Design Features

GitHub intentionally designed these token formats with:

1. **Identifiable prefixes**: The 3-letter prefixes make tokens easily recognizable, starting with `gh` (GitHub) and a third letter indicating the token type (p for personal, o for OAuth, etc.)[2]

2. **Underscore separator**: The underscore separating the prefix from the rest of the token improves readability and makes it easier to select the entire token with a double-click[2]

3. **Security enhancements**: These formatting changes have significantly reduced false positives in GitHub's secret scanning feature, bringing the false positive rate down to approximately 0.5%[2]

## Using Tokens in Authentication

When authenticating to the GitHub API, you typically use these tokens in one of these ways:

1. **Bearer Authentication**: Include the token in the Authorization header:
```bash
curl --request GET \
  --url "https://api.github.com/PATH" \
  --header "Authorization: Bearer YOUR_TOKEN"
```[7]

2. **GitHub CLI**: Use the token as an environment variable:
```bash
GH_TOKEN=YOUR_TOKEN gh api /PATH
```[8]

For GitHub Actions workflows, GitHub recommends using the built-in `GITHUB_TOKEN` secret rather than creating a custom token. This token is automatically provided and can be referenced as `${{ secrets.GITHUB_TOKEN }}`.[1][7]

Citations:

- 1: https://docs.github.com/en/rest/authentication/authenticating-to-the-rest-api
- 2: https://github.blog/engineering/platform-security/behind-githubs-new-authentication-token-formats/
- 3: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens
- 4: https://learn.microsoft.com/en-us/purview/sit-defn-github-personal-access-token
- 5: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/about-authentication-to-github
- 6: https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-user-access-token-for-a-github-app
- 7: https://docs.github.com/actions/security-guides/automatic-token-authentication
- 8: https://docs.github.com/en/enterprise-server@3.16/rest/authentication/authenticating-to-the-rest-api
- 9: https://docs.github.com/en/rest/orgs/personal-access-tokens
- 10: https://github.blog/changelog/2021-03-31-authentication-token-format-updates-are-generally-available/

---



</details>

**Ignore the `token ${token}` suggestion – using `Bearer ${token}` is valid per GitHub’s own docs.**  
No change is necessary in lib/github.js; the current `Authorization: Bearer YOUR_TOKEN` format is supported.  


> Likely an incorrect or invalid review comment.

</details>
<details>
<summary>package.json (1)</summary>

`9-9`: **Good addition of npm script for new functionality.**

The "select" script provides an intuitive way to run the new issue prioritization feature.

</details>
<details>
<summary>scripts/select-next.js (1)</summary>

`19-29`: _⚠️ Potential issue_

**Add null check before accessing sorted[0].**

Line 27 assumes there's at least one issue in the sorted array, which could lead to a runtime error if the array is empty. Although line 21 checks for empty issues, a future code change might bypass this check.

```diff
 async function selectNext() {
   const issues = await getLabeledIssues();
   if (!issues.length) return console.log('No labeled issues found.');

   const sorted = issues
     .map(issue => ({ ...issue, score: score(issue) }))
     .sort((a, b) => a.score - b.score);

-  const top = sorted[0];
-  console.log(`Next best issue:\n#${top.number}: ${top.title}\n${top.html_url}`);
+  if (sorted.length > 0) {
+    const top = sorted[0];
+    console.log(`Next best issue:\n#${top.number}: ${top.title}\n${top.html_url}`);
+  } else {
+    console.log('No issues found with the required criteria.');
+  }
 }
```



> Likely an incorrect or invalid review comment.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T07:41:33Z
user: login: llamapreview[bot]
body: # Auto Pull Request Review from LlamaPReview

## 1. Overview
### 1.1 Core Changes
- **Primary purpose and scope**: Introduces a new feature to select the next GitHub issue based on an "urgent" label, aiming to improve workflow efficiency. It also includes structural changes to the GitHub API integration.
- **Key components modified**:
    - New script `scripts/select-next.js` for issue prioritization.
    - New utility `lib/github.js` for fetching labeled issues.
    - Modifications to `lib/github-wrapper.js` for API configuration and security.
    - Update to `package.json` to add a new `select` script.
- **Cross-component impacts**: The new `lib/github.js` bypasses the existing `lib/github-wrapper.js`, leading to inconsistencies and loss of shared configurations (e.g., timeout, keep-alive, centralized error handling).
- **Business value alignment**: The feature aims to streamline task prioritization, which is valuable. However, critical security and architectural issues in the current implementation negate this value until resolved.

### 1.2 Technical Architecture 
- **System design modifications**:
    - Introduces a new module `lib/github.js` for fetching issues, which unfortunately operates independently of the existing `lib/github-wrapper.js`.
    - Modifies `lib/github-wrapper.js` by removing timeout and keep-alive configurations and changing the authorization header format. A path validation function was added but is not leveraged by the new issue fetching logic.
- **Component interaction changes**: The new `getLabeledIssues` function in `lib/github.js` makes direct API calls using `axios` instead of utilizing the established `github` instance from `lib/github-wrapper.js`. This breaks the intended centralized API management.
- **Integration points impact**: The direct API calls in `lib/github.js` do not benefit from the wrapper's configurations or potential future enhancements like rate limit handling.
- **Dependency changes and implications**: `axios` is used directly in `lib/github.js`, duplicating its usage from the wrapper. Secrets are unsafely managed via a local file.

## 2. Critical Findings
### 2.1 Must Fix (P0🔴)
**Issue**: Critical Security Vulnerability - Reading Secrets from File
- **Analysis Confidence**: High
- **Impact**: `lib/github.js` reads GitHub token, owner, and repo from a local file (`../.secrets/github`). Committing secrets or relying on local uncommitted secret files is a severe security risk, potentially exposing credentials.
- **Resolution**: Remove the `.secrets/github` file and its usage. Transition to using environment variables (e.g., `process.env.GITHUB_TOKEN`, `process.env.GITHUB_OWNER`, `process.env.GITHUB_REPO`) for all sensitive data, consistent with the existing `github-wrapper.js` practice for `GITHUB_TOKEN`.

**Issue**: Architectural Violation & Loss of Robustness - Bypassing `github-wrapper.js`
- **Analysis Confidence**: High
- **Impact**: The new `getLabeledIssues` function in `lib/github.js` uses `axios` directly, bypassing the centralized `github-wrapper.js`. This leads to loss of consistent headers, centralized error handling, timeout configuration, keep-alive settings, and any future wrapper enhancements (e.g., rate limiting). It also means the new `validatePathSegment` function in the wrapper is not utilized for these calls.
- **Resolution**: Refactor `lib/github.js` to use the `github` instance from `lib/github-wrapper.js` for all API calls. Ensure owner and repo are passed to or configured within the wrapper.

**Issue**: Loss of Robustness - Removal of API Timeout in Wrapper
- **Analysis Confidence**: High
- **Impact**: The `timeout: 5000` configuration was removed from `lib/github-wrapper.js`, and the newly defined `GITHUB_API_TIMEOUT_MS` constant is not used. API calls made through the wrapper (and the direct calls in `lib/github.js`) will lack an explicit timeout, potentially causing the application to hang indefinitely on network issues.
- **Resolution**: Restore the timeout configuration in `lib/github-wrapper.js` using the `GITHUB_API_TIMEOUT_MS` constant.

**Issue**: Inefficient Data Fetching
- **Analysis Confidence**: High
- **Impact**: `getLabeledIssues` in `lib/github.js` fetches *all* open issues and then filters them client-side for the "urgent" label. This is highly inefficient for repositories with many issues, consuming unnecessary bandwidth, API quota, and processing time.
- **Resolution**: Modify `getLabeledIssues` to use the GitHub API's `labels` query parameter (e.g., `issues?labels=urgent&state=open`) to fetch only the relevant issues directly.

### 2.2 Should Fix (P1🟡)
**Issue**: Lack of Tests for New Functionality
- **Analysis Confidence**: High
- **Impact**: No unit or integration tests have been added for the new `scripts/select-next.js` or `lib/github.js`. This makes it difficult to verify correctness, ensure robustness against edge cases (e.g., no issues found, API errors), and prevent regressions.
- **Suggested Solution**: Implement comprehensive tests covering the core logic, error handling, and different scenarios for the new issue selection feature.

**Issue**: Potential Performance Regression - Removal of `keepAlive` in Wrapper
- **Analysis Confidence**: High
- **Impact**: The `httpsAgent: new https.Agent({ keepAlive: true })` was removed from `lib/github-wrapper.js`. For applications making multiple sequential API calls, `keepAlive` can improve performance by reusing TCP connections. Its removal is a minor regression.
- **Suggested Solution**: Re-add the `httpsAgent` configuration with `keepAlive: true` to `lib/github-wrapper.js` unless there's a specific documented reason for its removal.

**Issue**: Inconsistent Authorization Header Format in Wrapper
- **Analysis Confidence**: High
- **Impact**: The Authorization header in `lib/github-wrapper.js` was changed from `Bearer ${token}` to `token ${token}`. While GitHub accepts both, `Bearer` is the more standard OAuth 2.0 format. This change introduces inconsistency if other parts of the system or common practices expect `Bearer`.
- **Suggested Solution**: Revert the Authorization header format to `Authorization: Bearer ${token}` in `lib/github-wrapper.js` for consistency and adherence to standards.

### 2.3 Consider (P2🟢)
**Area**: Scoring Logic Simplicity
- **Analysis Confidence**: Medium
- **Improvement Opportunity**: The current scoring (`0` for urgent, `10` for others) is very basic. All urgent issues have the same priority. Consider a more nuanced scoring system (e.g., based on age, activity, or other labels) if finer-grained prioritization among urgent issues is needed.

**Area**: Hardcoded "urgent" Label
- **Analysis Confidence**: High
- **Improvement Opportunity**: The label "urgent" is hardcoded in `lib/github.js` and `scripts/select-next.js`. Making this configurable (e.g., via environment variable or command-line argument) would increase the script's flexibility.

**Area**: Path Validation Regex Usage
- **Analysis Confidence**: Medium
- **Improvement Opportunity**: The `validatePathSegment` function in `lib/github-wrapper.js` is a good security addition. However, its regex `^[\w.-]+$` might be too restrictive for some valid GitHub names if used broadly. More importantly, it's not currently used by the new `lib/github.js` due to the wrapper bypass. Ensure it's applied appropriately when the wrapper is correctly used.

**Area**: Documentation
- **Analysis Confidence**: High
- **Improvement Opportunity**: Add documentation to the project's README explaining the new `npm run select` command, its purpose, and any required environment variables (once secrets handling is fixed, e.g., `GITHUB_TOKEN`, `GITHUB_OWNER`, `GITHUB_REPO`).

### 2.4 Summary of Action Items
- **P0 (Immediate)**:
    1.  Address security vulnerability by removing file-based secret loading and using environment variables.
    2.  Refactor `lib/github.js` to use `lib/github-wrapper.js` for all API calls.
    3.  Reinstate and use API timeout in `lib/github-wrapper.js`.
    4.  Optimize `getLabeledIssues` to fetch only "urgent" issues via API parameters.
- **P1 (Recommended before merge)**:
    1.  Add tests for the new functionality.
    2.  Restore `keepAlive` in `lib/github-wrapper.js`.
    3.  Standardize Authorization header to `Bearer` token in `lib/github-wrapper.js`.
- **P2 (Consider for future improvement)**:
    1.  Enhance scoring logic.
    2.  Make the target label configurable.
    3.  Review and ensure correct application of path validation.
    4.  Update README with new feature documentation.

## 3. Technical Analysis
### 3.1 Code Logic Analysis
📁 **lib/github.js** - **getLabeledIssues**
  - Submitted PR Code:
```javascript
    + const axios = require('axios');
    + const { token, owner, repo } = require('../.secrets/github'); // P0: Security Vulnerability
    + 
    + const headers = { Authorization: `Bearer ${token}` };
    + 
    + async function getLabeledIssues() {
    +   const url = `https://api.github.com/repos/${owner}/${repo}/issues?state=open`; // P0: Inefficient Fetching
    +   try {
    +     const res = await axios.get(url, { headers }); // P0: Bypasses wrapper, uses insecure token
    +     // Filter issues to only include those with "urgent" label
    +     const issues = res.data.filter(issue => {
    +       if (issue.pull_request) return false;
    +       const labels = issue.labels.map(l => l.name);
    +       return labels.includes('urgent');
    +     });
    +     return issues;
    +   } catch (error) { // ... error handling ... }
    + }
```
  - Analysis:
    *   **Current logic and potential issues**:
        *   Reads sensitive credentials (`token`, `owner`, `repo`) directly from a local file `../.secrets/github`, which is a critical security vulnerability. Secrets should be managed via environment variables.
        *   Uses `axios` directly, bypassing the centralized `github-wrapper.js`. This leads to duplicated effort, inconsistency, and loss of features like standardized headers, timeouts (which were also removed from the wrapper), and `keepAlive` configurations.
        *   Fetches all open issues (`issues?state=open`) and then filters them client-side for the "urgent" label. This is highly inefficient for repositories with many issues.
    *   **Edge cases and error handling**: Basic error logging is present, but the primary issue is the architectural bypass and security flaw.
    *   **Cross-component impact**: Negatively impacts maintainability and security by not adhering to the established pattern of using `github-wrapper.js`.
  - LlamaPReview Suggested Improvements:
```javascript
    // P0: Import the configured github instance from the wrapper
    const github = require('./github-wrapper'); 
    // P0: Ensure GITHUB_OWNER and GITHUB_REPO are also sourced from environment variables,
    // similar to GITHUB_TOKEN in the wrapper.
    const owner = process.env.GITHUB_OWNER;
    const repo = process.env.GITHUB_REPO;

    if (!owner || !repo) {
        // Or handle this more gracefully, perhaps in the wrapper's setup
        throw new Error('GITHUB_OWNER and GITHUB_REPO environment variables must be set.');
    }

    async function getLabeledIssues() {
      // P0: Construct path for the wrapper. Owner/repo could also be part of wrapper's base config.
      const path = `/repos/${owner}/${repo}/issues`;
      try {
        // P0: Use the wrapper instance and API filtering
        const res = await github.get(path, {
          params: {
            state: 'open',
            labels: 'urgent', // P0: Filter by label directly via API
          },
        });
        // Filter out pull requests, as the API might return them if they have the label
        const issues = res.data.filter(issue => !issue.pull_request);
        console.log(`Found ${issues.length} open issues with the 'urgent' label.`);
        return issues;
      } catch (error) {
        console.error(`Error fetching labeled issues: ${error.message}`);
        if (error.response) {
          console.error('API Error:', error.response.status, error.response.statusText);
        }
        throw error; // Re-throw for the caller to handle
      }
    }
```
  - Improvement rationale:
    *   **Technical benefits**:
        *   Resolves critical security vulnerability by using environment variables for all secrets.
        *   Adheres to the established architectural pattern by using the `github-wrapper.js`.
        *   Leverages centralized configurations (timeout, headers, keep-alive, error handling) from the wrapper.
        *   Significantly improves performance and reduces API load by fetching only necessary data.
    *   **Business value**: Enhances security, maintainability, and reliability of the application. Reduces operational risk.

📁 **lib/github-wrapper.js** - **axios.create configuration**
  - Submitted PR Code:
```javascript
    // ...
    const token = process.env.GITHUB_TOKEN;
    
    // const github = axios.create({ // Old config
    //   baseURL: BASE_URL,
    //   headers: {
    //     Authorization: `Bearer ${token}`,
    //     Accept: 'application/vnd.github.v3+json',
    //     'User-Agent': 'issue-labeler-app'
    //   },
    //   timeout: 5000, // P0: Timeout removed
    //   httpsAgent: new https.Agent({ keepAlive: true }) // P1: keepAlive removed
    // });
    
    const BASE_URL = 'https://api.github.com';
    const GITHUB_API_TIMEOUT_MS = 10000; // Defined but not used
    
    // ... validatePathSegment function ...

    const github = axios.create({
      baseURL: BASE_URL,
      headers: {
        Authorization: `token ${token}`, // P1: Changed from Bearer
        Accept: 'application/vnd.github.v3+json',
        'User-Agent': 'issue-labeler-app'
      },
      // P0: Missing timeout configuration
      // P1: Missing httpsAgent with keepAlive
    });
```
  - Analysis:
    *   **Current logic and potential issues**:
        *   The `timeout: 5000` configuration was removed. A new constant `GITHUB_API_TIMEOUT_MS` was defined but not applied to the `axios` instance. This means API calls made via the wrapper will have no explicit timeout.
        *   The `httpsAgent: new https.Agent({ keepAlive: true })` configuration was removed, which can be a minor performance regression for multiple API calls.
        *   The `Authorization` header format was changed from `Bearer ${token}` to `token ${token}`. While functional, `Bearer` is more standard.
    *   **Cross-component impact**: Any part of the application relying on the wrapper for API calls will be affected by the missing timeout and `keepAlive` settings.
  - LlamaPReview Suggested Improvements:
```javascript
    const axios = require('axios');
    const https = require('https');
    
    const token = process.env.GITHUB_TOKEN;
    
    const BASE_URL = 'https://api.github.com';
    const GITHUB_API_TIMEOUT_MS = 10000; // Use this constant
    
    // function validatePathSegment(segment) { ... } // Keep this function
    
    const github = axios.create({
      baseURL: BASE_URL,
      headers: {
        // P1: Revert to standard Bearer token format for consistency
        Authorization: `Bearer ${token}`, 
        Accept: 'application/vnd.github.v3+json',
        'User-Agent': 'issue-labeler-app'
      },
      // P0: Restore timeout configuration using the defined constant
      timeout: GITHUB_API_TIMEOUT_MS,
      // P1: Restore httpsAgent with keepAlive for potential performance benefits
      httpsAgent: new https.Agent({ keepAlive: true })
    });
    
    module.exports = github; // Ensure the instance is exported
```
  - Improvement rationale:
    *   **Technical benefits**:
        *   Restores critical timeout functionality, preventing indefinite hangs on network issues.
        *   Reinstates `keepAlive` for potential performance improvements in scenarios with multiple API calls.
        *   Uses the standard `Bearer` token format for consistency.
    *   **Business value**: Improves application robustness, reliability, and potentially performance.

### 3.2 Key Quality Aspects 
- **System scalability considerations**: The current inefficient data fetching in `lib/github.js` (fetching all issues) does not scale well with repositories having a large number of open issues. This needs to be addressed as per P0.
- **Performance bottlenecks and optimizations**:
    - Inefficient data fetching is a major performance bottleneck.
    - Removal of `keepAlive` from `lib/github-wrapper.js` is a minor performance regression.
- **Testing strategy and coverage**: Currently, there are no tests for the new functionality. Adding unit and integration tests is crucial (P1).
- **Documentation needs**: The README should be updated to include information about the new `select` script and its configuration (P2).

## 4. Overall Evaluation
- **Technical assessment**: The PR introduces a potentially useful feature for issue prioritization. However, it currently suffers from critical security vulnerabilities (secrets handling), significant architectural flaws (bypassing the API wrapper), loss of robustness (removed timeouts), and severe inefficiencies (data fetching). The added path validation is a good idea but is undermined by the wrapper bypass.
- **Business impact**: If merged as-is, the PR poses a security risk and could lead to performance issues and instability. Once the critical (P0) and important (P1) issues are addressed, the feature can provide value by improving workflow efficiency.
- **Risk evaluation**: High. The security vulnerability related to token handling and the architectural issues that bypass established patterns and configurations present significant risks.
- **Notable positive aspects and good practices**:
    - The core idea of a script to select the next priority issue is valuable.
    - The attempt to add path validation (`validatePathSegment`) in `lib/github-wrapper.js` shows consideration for security, though its current application needs review in context of proper wrapper usage.
- **Implementation quality**: Currently low due to the P0 and P1 issues. Significant refactoring is required.
- **Final recommendation**: **Request Changes**

---
💡 **LlamaPReview Community**
Have feedback on this AI Code review tool? Join our [GitHub Discussions](https://github.com/JetXu-LLM/LlamaPReview-site/discussions) to share your thoughts and help shape the future of LlamaPReview.
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-07T07:45:28Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>♻️ Duplicate comments (2)</summary><blockquote>

<details>
<summary>scripts/select-next.js (2)</summary><blockquote>

`18-18`: **Convert magic string to a named constant**

The "urgent" label string is used directly in the code, which reduces maintainability.

```diff
+// Priority label constants
+const URGENT_LABEL = 'urgent';

 // First try to find urgent issues
-const urgentIssues = getIssuesWithLabel(allIssues, 'urgent');
+const urgentIssues = getIssuesWithLabel(allIssues, URGENT_LABEL);
```

---

`42-44`: _🛠️ Refactor suggestion_

**Add exit code for error cases**

The script should exit with a non-zero status code on failure, which is important for CI/CD pipelines or automated workflows.

```diff
 selectNext().catch(err => {
   console.error('Error selecting next issue:', err.message);
+  process.exit(1);
 });
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (3)</summary><blockquote>

<details>
<summary>scripts/select-next.js (3)</summary><blockquote>

`22-24`: **Avoid sorting performance issues**

The current sorting approach has two potential issues:
1. Creating Date objects repeatedly in the comparator is inefficient
2. Using `.sort()` mutates the original arrays, which could cause issues if they're used elsewhere


```diff
-  const sortedUrgent = urgentIssues.sort((a, b) => 
-    new Date(b.created_at) - new Date(a.created_at)
-  );

+  // Pre-compute dates and sort on a copy of the array
+  const sortByCreationDate = (issues) => {
+    const issuesWithDates = issues.map(issue => ({
+      issue,
+      date: new Date(issue.created_at)
+    }));
+    return issuesWithDates
+      .sort((a, b) => b.date - a.date)
+      .map(item => item.issue);
+  };
+
+  const sortedUrgent = sortByCreationDate(urgentIssues);
```


Also applies to: 34-36

---

`9-40`: **Consider implementing the suggested algorithm optimization**

The codeant-ai bot suggested replacing the sort operations with a single-pass minimum search, which would be more efficient especially for large datasets.


This approach would eliminate the need for sorting twice and reduce the computational complexity from O(n log n) to O(n).

You could implement the suggestion by finding the most recent urgent issue in one pass, then falling back to the most recent non-urgent issue if needed.

---

`31-31`: **Improve log message consistency**

While informative, this log message differs in style from the other messages in the file.

```diff
-  console.log('No issues labeled "urgent" found. Taking most recent open issue instead.');
+  console.log('No issues labeled "urgent" found. Selecting most recent open issue instead.');
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between e7619ba1d5884d55c05ea28a1099b83700df2f1d and cf834b602f1d357be03aff5021dfce215fb41496.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `lib/github.js` (1 hunks)
* `scripts/select-next.js` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* lib/github.js

</details>

<details>
<summary>🔇 Additional comments (1)</summary><blockquote>

<details>
<summary>scripts/select-next.js (1)</summary>

`1-44`: **Overall implementation aligns well with PR objectives**

This script successfully implements the priority-based issue selection as described in the PR objectives, with clear prioritization of "urgent" issues. The code is well-structured with appropriate error handling and fallback mechanisms.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T07:48:54Z
user: login: dmitriz
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-07T08:06:50Z
user: login: qodo-code-review[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T08:07:35Z
user: login: codiumai-pr-agent-free[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T08:07:37Z
user: login: dmitriz
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-07T08:09:34Z
user: login: gemini-code-assist[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T08:09:41Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 3**

<details>
<summary>♻️ Duplicate comments (3)</summary><blockquote>

<details>
<summary>lib/github.js (3)</summary><blockquote>

`33-33`: **Improve JSDoc return type specification**

The return type should specify the type of array elements being returned.


```diff
- * @returns {Promise<Array>} - Array of issue objects
+ * @returns {Promise<Array<GitHubIssue>>} - Array of GitHub issue objects
```

---

`17-17`: **Security concern with secrets storage**

Storing credentials in the `.secrets` directory rather than environment variables remains a security concern, even if the directory is gitignored. If this is an intentional design choice for your project, please document this decision.

---

`56-57`: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Consider not re-throwing the error**

After handling the error with `handleGitHubError`, you're still re-throwing it. This could make error handling at the caller site duplicative. Consider whether the calling function should handle the error instead.

---

🏁 Script executed:

```shell
#!/bin/bash
# Find where getAllOpenIssues is used and whether error handling is duplicated

# Looking for usages of getAllOpenIssues
rg -n "getAllOpenIssues" --glob "!lib/github.js" -A 5
```

Length of output: 842

---

🏁 Script executed:

```shell
#!/bin/bash
# Display full context of scripts/select-next.js
sed -n '1,200p' scripts/select-next.js
```

Length of output: 1992

---

🏁 Script executed:

```shell
#!/bin/bash
# Show getAllOpenIssues definition and error handling in lib/github.js
rg -n "getAllOpenIssues" -A15 lib/github.js
rg -n "handleGitHubError" -A5 lib/github.js
```

Length of output: 1309

---

🏁 Script executed:

```shell
#!/bin/bash
# Inspect handleGitHubError implementation
rg -n "handleGitHubError" -A20 lib/github-wrapper.js
```

Length of output: 934

---



</details>

**Remove redundant rethrow after logging**  
The `handleGitHubError` helper already throws the error, so the explicit `throw error;` in the `getAllOpenIssues` catch block is unreachable and can be removed.

• lib/github.js – in `getAllOpenIssues` catch (around lines 56–57)  

Suggested change:
```diff
   } catch (error) {
-    handleGitHubError(error, 'fetching open issues');
-    throw error;
+    handleGitHubError(error, 'fetching open issues');
   }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>lib/github.js (1)</summary><blockquote>

`49-52`: **Consider immutability for the labels transformation**

The current implementation mutates the original issue objects by modifying their labels. Consider returning new objects instead to avoid side effects.

```diff
-    issues.forEach(issue => {
-      issue.labels = issue.labels.map(label => ({
-        ...label,
-        name: label.name.toLowerCase()
-      }));
-    });
+    return issues.map(issue => ({
+      ...issue,
+      labels: issue.labels.map(label => ({
+        ...label,
+        name: label.name.toLowerCase()
+      }))
+    }));
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between cf834b602f1d357be03aff5021dfce215fb41496 and 7782b026c287346072cc5511dd13e2043cb7c171.

</details>

<details>
<summary>📒 Files selected for processing (3)</summary>

* `lib/github-wrapper.js` (1 hunks)
* `lib/github.js` (1 hunks)
* `scripts/select-next.js` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (2)</summary>

* scripts/select-next.js
* lib/github-wrapper.js

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

<details>
<summary>lib/github.js</summary>

[error] 75-75: Illegal return statement outside of a function



(parse)

---

[error] 82-83: Expected a statement but instead found '}'.

Expected a statement here.


(parse)

</details>

</details>

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T08:20:04Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 7**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>lib/github.js (1)</summary><blockquote>

`35-59`: **Error handling creates tight coupling**

The error handling code in `getAllOpenIssues` logs errors but also rethrows them, creating tight coupling between the error logging and the calling code. This makes it harder to use this function in different contexts with different error handling strategies.

Consider separating error handling from the function by allowing callers to handle errors:
```diff
 async function getAllOpenIssues() {
   if (!owner || typeof owner !== 'string' || !owner.trim()) {
     throw new Error('GitHub owner must be a non-empty string');
   }
   if (!repo || typeof repo !== 'string' || !repo.trim()) {
     throw new Error('GitHub repo must be a non-empty string');
   }
   const safeOwner = validatePathSegment(owner.trim());
   const safeRepo = validatePathSegment(repo.trim());
-  try {
-    const res = await githubClient.get(`/repos/${safeOwner}/${safeRepo}/issues?state=open`);
-    // Filter out pull requests
-    const issues = res.data.filter(issue => !issue.pull_request);
-    issues.forEach(issue => {
-      issue.labels = issue.labels.map(label => ({
-        ...label,
-        name: label.name.toLowerCase()
-      }));
-    });
-    return issues;
-  } catch (error) {
-    handleGitHubError(error, 'fetching open issues');
-    throw error;
-  }
+  const res = await githubClient.get(`/repos/${safeOwner}/${safeRepo}/issues?state=open`);
+  // Filter out pull requests
+  const issues = res.data.filter(issue => !issue.pull_request);
+  issues.forEach(issue => {
+    issue.labels = issue.labels.map(label => ({
+      ...label,
+      name: label.name.toLowerCase()
+    }));
+  });
+  return issues;
 }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 7782b026c287346072cc5511dd13e2043cb7c171 and ae250db0d5ec121be24e9f577b392f1e7230634d.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `lib/github.js` (1 hunks)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T01:54:56Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 5**

<details>
<summary>🧹 Nitpick comments (11)</summary><blockquote>

<details>
<summary>scripts/toggle-env.js (1)</summary><blockquote>

`9-9`: **Fix typo in comment line**

There appears to be an incomplete or malformed comment: "// Define available environmenCan you insure there is an N Pts from config". This looks like an unfinished thought or copied instruction that was not properly edited.

```diff
-// Define available environmenCan you insure there is an N Pts from config
+// Define available environments from config
```

</blockquote></details>
<details>
<summary>label-issue.js (1)</summary><blockquote>

`43-45`: **Consider adding error handling for missing placeholders**

The template replacement assumes that {{title}} and {{body}} placeholders exist in the template. Consider adding validation or error handling if these placeholders are missing from the template file.

```diff
 // Step 2: Prepare the prompt by replacing the placeholders with actual issue content
+const requiredPlaceholders = ['{{title}}', '{{body}}'];
+for (const placeholder of requiredPlaceholders) {
+  if (!promptTemplate.includes(placeholder)) {
+    console.warn(`Warning: Template missing required placeholder ${placeholder}`);
+  }
+}
 const prompt = promptTemplate
   .replace('{{title}}', issue.title)
   .replace('{{body}}', issue.body || '');
```

</blockquote></details>
<details>
<summary>scripts/switch-env.js (1)</summary><blockquote>

`9-9`: **Fix typo in comment line**

There appears to be an incomplete or malformed comment: "// Define available environmenCan you insure there is an N Pts from config". This looks like an unfinished thought or copied instruction.

```diff
-// Define available environmenCan you insure there is an N Pts from config
+// Define available environments from config
```

</blockquote></details>
<details>
<summary>test-config.js (1)</summary><blockquote>

`41-46`: **Simplify repository source logic condition**

The condition for determining the repository source can be simplified to improve readability.

```diff
  if (activeEnv.repository.useLocalIssues) {
    console.log(`   Source: Using test repository from environment config (local testing mode)`);
-  } else if (secretsConfig.owner && secretsConfig.repo && 
-            (secretsConfig.owner === repoInfo.owner && secretsConfig.repo === repoInfo.repo)) {
+  } else if (hasRepoInSecrets && secretsConfig.owner === repoInfo.owner && secretsConfig.repo === repoInfo.repo) {
    console.log(`   Source: Using repository from .secrets/github.js file`);
  } else {
    console.log(`   Source: Using repository from environment config`);
  }
```

</blockquote></details>
<details>
<summary>TECHNICAL.md (1)</summary><blockquote>

`109-113`: **Minor wording nit-pick**

“`2. Access specific configuration sections`” → should be “Access-specific” (hyphenated compound adjective).

```diff
-2. Access specific configuration sections (repository, model, API)
+2. Access-specific configuration sections (repository, model, API)
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 LanguageTool</summary>

[uncategorized] ~112-~112: When ‘Access-specific’ is used as a modifier, it is usually spelled with a hyphen.
Context: ...the active environment configuration 2. Access specific configuration sections (repository, mod...

(SPECIFIC_HYPHEN)

</details>

</details>

</blockquote></details>
<details>
<summary>github-api-client.js (2)</summary><blockquote>

`40-47`: **Unused `handleGitHubError()` duplicates try/catch logic**

The helper centralises logging but both public functions re-implement identical error handling instead of calling it. Prefer:

```diff
} catch (error) {
-  console.error(`Error fetching issue #${issueNumber}: ${error.message}`);
-  if (error.response) { ... }
-  throw error;
+  handleGitHubError(error, `fetching issue #${issueNumber}`);
}
```

Reduces duplication and keeps behaviour consistent.

---

`10-12`: **Fail fast if `GITHUB_TOKEN` missing**

If `process.env.GITHUB_TOKEN` is undefined the client will still issue requests, yielding 401s that look like configuration bugs. Recommend an explicit check:

```diff
-const token = process.env.GITHUB_TOKEN;
+const token = process.env.GITHUB_TOKEN;
+if (!token) {
+  throw new Error('GITHUB_TOKEN environment variable not set');
+}
```

</blockquote></details>
<details>
<summary>github-model.js (1)</summary><blockquote>

`155-161`: **Double‐throw path for 429 responses**

`handleModelError()` re-throws on 429, *and* the caller re-throws immediately afterwards. This yields two identical stack traces in logs.

Simplest fix: let `handleModelError()` return a wrapped error and throw exactly once:

```diff
} catch (error) {
-  handleModelError(error);
-  throw error;
+  throw handleModelError(error);
}
```

…and have `handleModelError` return the processed `Error` object.

</blockquote></details>
<details>
<summary>github-issue-fetcher.js (3)</summary><blockquote>

`24-30`: **Consider using an absolute path for loading secrets.**

The relative path `./.secrets/github` might cause issues depending on the working directory when the module is executed.

```diff
- const secretsConfig = require('./.secrets/github');
+ const path = require('path');
+ const secretsConfig = require(path.join(__dirname, '.secrets/github'));
```

---

`60-67`: **Error handler might lead to unhandled promise rejections.**

The function logs errors and then throws them, which could lead to unhandled promise rejections if not caught by callers.

Consider letting callers determine whether to throw or handle errors:

```diff
function handleGitHubError(error, context) {
  console.error(`Error ${context}: ${error.message}`);
  if (error.response) {
    console.error('API Error:', error.response.status, error.response.statusText);
    console.error('Error details:', error.response.data);
  }
- throw error;
  return error;
}
```

---

`110-137`: **Consider using fixed timestamps for test data.**

Using current timestamps for test data might cause issues in time-dependent tests. Fixed timestamps would provide more predictable test results.

```diff
function getLocalIssues() {
  return [
    {
      id: 1,
      number: 1,
      title: "Test Issue: Add automated labeling",
      body: "We need to implement automated issue labeling for better triage",
      labels: [
        { name: "enhancement" }
      ],
-     created_at: new Date().toISOString(),
-     updated_at: new Date().toISOString(),
+     created_at: "2023-01-01T00:00:00Z",
+     updated_at: "2023-01-02T00:00:00Z",
      html_url: "https://github.com/test/test/issues/1"
    },
    {
      id: 2,
      number: 2,
      title: "Test Issue: Fix security vulnerability",
      body: "Critical security vulnerability found in authentication module",
      labels: [
        { name: "bug" }
      ],
-     created_at: new Date().toISOString(),
-     updated_at: new Date().toISOString(),
+     created_at: "2023-01-03T00:00:00Z",
+     updated_at: "2023-01-04T00:00:00Z",
      html_url: "https://github.com/test/test/issues/2"
    }
  ];
}
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between ae250db0d5ec121be24e9f577b392f1e7230634d and 175236abab50fd68378fddd9a1cf536ddba7ccd9.

</details>

<details>
<summary>📒 Files selected for processing (16)</summary>

* `README.md` (1 hunks)
* `TECHNICAL.md` (1 hunks)
* `config-loader.js` (1 hunks)
* `config.js` (1 hunks)
* `config.json` (0 hunks)
* `github-api-client.js` (1 hunks)
* `github-issue-fetcher.js` (1 hunks)
* `github-model.js` (1 hunks)
* `label-issue.js` (3 hunks)
* `labeler.js` (1 hunks)
* `package.json` (1 hunks)
* `scripts/select-next.js` (1 hunks)
* `scripts/switch-env.js` (1 hunks)
* `scripts/toggle-env.js` (1 hunks)
* `test-config.js` (1 hunks)
* `test-github-model.js` (1 hunks)

</details>

<details>
<summary>💤 Files with no reviewable changes (1)</summary>

* config.json

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (4)</summary>

* test-github-model.js
* labeler.js
* README.md
* config.js

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (2)</summary>

* package.json
* scripts/select-next.js

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>label-issue.js (2)</summary><blockquote>

<details>
<summary>labeler.js (5)</summary>

* `require` (5-5)
* `fs` (3-3)
* `path` (4-4)
* `promptTemplate` (8-8)
* `prompt` (36-36)

</details>
<details>
<summary>github-model.js (2)</summary>

* `fs` (8-8)
* `path` (9-9)

</details>

</blockquote></details>

</details><details>
<summary>🪛 Biome (1.9.4)</summary>

<details>
<summary>github-model.js</summary>

[error] 47-47: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

---

[error] 68-68: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>
<details>
<summary>🪛 LanguageTool</summary>

<details>
<summary>TECHNICAL.md</summary>

[uncategorized] ~42-~42: Loose punctuation mark.
Context: ...PI Clients  - **`github-api-client.js`**: Core API client for interacting with Gi...

(UNLIKELY_OPENING_PUNCTUATION)

---

[uncategorized] ~48-~48: Loose punctuation mark.
Context: ... ### 2. Configuration  - **`config.js`**: Central configuration file that exports...

(UNLIKELY_OPENING_PUNCTUATION)

---

[uncategorized] ~53-~53: Loose punctuation mark.
Context: ...anagement  - **`scripts/toggle-env.js`**: Script to toggle between testing/produc...

(UNLIKELY_OPENING_PUNCTUATION)

---

[uncategorized] ~58-~58: Loose punctuation mark.
Context: ...pplication Logic  - **`label-issue.js`**: Main script that orchestrates the label...

(UNLIKELY_OPENING_PUNCTUATION)

---

[uncategorized] ~112-~112: When ‘Access-specific’ is used as a modifier, it is usually spelled with a hyphen.
Context: ...the active environment configuration 2. Access specific configuration sections (repository, mod...

(SPECIFIC_HYPHEN)

---

[uncategorized] ~136-~136: Loose punctuation mark.
Context: ...ported directly:  - `.secrets/github.js`: Contains GitHub authentication token - ...

(UNLIKELY_OPENING_PUNCTUATION)

---

[uncategorized] ~145-~145: You might be missing the article “the” here.
Context: ...kflows  ### Issue Labeling Workflow  1. Application fetches issue content from GitHub 2. Is...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)

---

[uncategorized] ~152-~152: You might be missing the article “the” here.
Context: ...w  1. `toggle-env.js` script identifies current active environment 2. Switches active f...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)

---

[uncategorized] ~161-~161: Loose punctuation mark.
Context: ...t scripts:  - **`test-github-model.js`**: Tests the model API client - **`test-co...

(UNLIKELY_OPENING_PUNCTUATION)

---

[uncategorized] ~192-~192: The preposition ‘to’ seems more likely in this position.
Context: ...tegories  1. Update the prompt template in `prompts/label-template.txt` 2. Modify ...

(AI_HYDRA_LEO_REPLACE_IN_TO)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (11)</summary><blockquote>

<details>
<summary>label-issue.js (1)</summary>

`7-8`: **The refactoring improves error handling and module structure**

The changes properly separate concerns by using dedicated modules for API interactions and model calls. The switch from synchronous to asynchronous file reading is a good practice, and throwing errors instead of calling process.exit() improves testability.



Also applies to: 24-32, 49-49, 70-70

</details>
<details>
<summary>scripts/switch-env.js (1)</summary>

`48-49`: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Pass config parameter consistently to getActiveEnvironment()**

The function is called with a config parameter on line 19 but without it on line 48. This inconsistency could lead to errors if the parameter is required.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if getActiveEnvironment requires config parameter
grep -A 3 "function getActiveEnvironment" ../config-loader.js
```

Length of output: 116

---

🏁 Script executed:

```shell
#!/bin/bash
# Locate getActiveEnvironment definition and inspect its signature
echo "=== Definition patterns ==="
rg -n "function getActiveEnvironment" .
rg -n "getActiveEnvironment\s*=\s*\(" .
echo
# Show how it's imported in switch-env.js
echo "=== Calls in scripts/switch-env.js ==="
rg -n "getActiveEnvironment" scripts/switch-env.js
```

Length of output: 550

---

🏁 Script executed:

```shell
#!/bin/bash
# Display the first 100 lines of config-loader.js to inspect getActiveEnvironment implementation
sed -n '1,100p' config-loader.js
```

Length of output: 2719

---



</details>

**Remove extraneous argument from getActiveEnvironment call**

The `getActiveEnvironment` function is defined without any parameters (see config-loader.js:48), so passing `config` to it has no effect. Update the call on line 19 in `scripts/switch-env.js`:

```diff
--- a/scripts/switch-env.js
+++ b/scripts/switch-env.js
@@ -16,7 +16,7 @@
-const activeEnvironment = getActiveEnvironment(config);
+const activeEnvironment = getActiveEnvironment();
```

No changes are needed at line 48—the call there is already correct.  



> Likely an incorrect or invalid review comment.

</details>
<details>
<summary>test-config.js (1)</summary>

`1-64`: **Well-structured environment status report with clear error handling**

The rewritten script provides a comprehensive, structured status report with numbered sections and detailed information about the configuration. The error handling for missing secrets is robust, and the script clearly explains which repository configuration will be used.

</details>
<details>
<summary>config-loader.js (1)</summary>

`129-131`: **`require` cache cleared but module not re-loaded**

`delete require.cache[require.resolve('./config')]` invalidates the cache, yet `config` still references the old object.  
If another module does `require('./config')` immediately after the switch it will get a *fresh* copy, while `config-loader` continues to hold the stale reference. Exporting that stale object may surprise callers.

Quick fix:

```diff
-delete require.cache[require.resolve('./config')];
-return config;
+delete require.cache[require.resolve('./config')];
+return require('./config');   // reloads and returns fresh instance
```

Long-term: expose `getConfig()` and let callers retrieve a fresh copy when needed.

</details>
<details>
<summary>github-issue-fetcher.js (7)</summary>

`1-13`: **Well-structured module documentation and type definitions.**

The JSDoc documentation is comprehensive and includes a clear typedef for the GitHubIssue structure, which will help consumers of this module understand the expected data format.

---

`32-41`: **GitHub API client setup follows security best practices.**

The client configuration properly sets authentication headers, uses a configured timeout, and specifies the required User-Agent header as recommended by GitHub API documentation.

---

`48-53`: **Good security implementation to prevent path traversal attacks.**

The validatePathSegment function uses an appropriate regex pattern to restrict allowed characters in path segments, preventing potential path traversal vulnerabilities.

---

`143-156`: **Thorough input validation with proper security measures.**

The function properly validates input parameters and uses the validatePathSegment function to prevent path traversal attacks.

---

`163-174`: **Properly handles label encoding for API requests.**

The function correctly encodes the label parameter for URL inclusion, which is important for labels containing special characters.

---

`180-187`: **Useful utility function for accessing repository configuration.**

This function returns a clean object with only the necessary configuration properties, including the environment name which is helpful for debugging.

---

`189-193`: **Clean module exports.**

The module follows the principle of information hiding by only exporting the necessary public functions while keeping implementation details private.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T04:18:16Z

Review comments:
diff_hunk: @@ -0,0 +1,32 @@
+const axios = require('axios');
+const { token, owner, repo } = require('../.secrets/github');
path: lib/github.js
user: login: gemini-code-assist[bot]
body: ![high](https://img.shields.io/badge/priority-high-orange)

Storing the token, owner, and repo in a `.secrets` directory is not a secure practice. It's recommended to use environment variables instead. Even though this file is likely gitignored, it's still not best practice to store secrets in files.

```suggestion
const token = process.env.GITHUB_TOKEN;
const owner = process.env.GITHUB_OWNER;
const repo = process.env.GITHUB_REPO;
```
created_at: 2025-05-07T07:37:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -9,16 +9,16 @@ const https = require('https');
 // Use environment variables for sensitive credentials
 const token = process.env.GITHUB_TOKEN;
 
-const github = axios.create({
-  baseURL: BASE_URL,
-  headers: {
-    Authorization: `Bearer ${token}`,
-    Accept: 'application/vnd.github.v3+json',
-    'User-Agent': 'issue-labeler-app'
-  },
-  timeout: 5000,
-  httpsAgent: new https.Agent({ keepAlive: true })
-});
+// Define API constants
+const BASE_URL = 'https://api.github.com';
+const GITHUB_API_TIMEOUT_MS = 10000;
+
+/**
+ * Validates GitHub path segments to prevent path traversal attacks
+ * @param {string} segment - Path segment to validate
+ * @returns {string} - The validated segment
+ */
+function validatePathSegment(segment) {
   if (!/^[\w.-]+$/.test(segment)) {
     throw new Error('Invalid path segment');
   }
path: lib/github-wrapper.js
user: login: gemini-code-assist[bot]
body: ![medium](https://img.shields.io/badge/priority-medium-yellow)

Consider adding more context to the error message to help with debugging. For example, include the invalid segment in the error message.

```javascript
throw new Error(`Invalid path segment: ${segment}`);
```
created_at: 2025-05-07T07:37:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,32 @@
+const axios = require('axios');
+const { token, owner, repo } = require('../.secrets/github');
+
+const headers = { Authorization: `Bearer ${token}` };
+
+/**
+ * Fetches open issues that have urgent labels
+ * @returns {Promise<Array>} - Array of issue objects
+ */
+async function getLabeledIssues() {
+  const url = `https://api.github.com/repos/${owner}/${repo}/issues?state=open`;
+  try {
+    const res = await axios.get(url, { headers });
+    // Filter issues to only include those with "urgent" label
+    const issues = res.data.filter(issue => {
+      if (issue.pull_request) return false;
+      const labels = issue.labels.map(l => l.name);
+      return labels.includes('urgent');
+    });
+    return issues;
+  } catch (error) {
+    console.error(`Error fetching labeled issues: ${error.message}`);
+    if (error.response) {
+      console.error('API Error:', error.response.status, error.response.statusText);
+    }
+    throw error;
+  }
path: lib/github.js
user: login: gemini-code-assist[bot]
body: ![medium](https://img.shields.io/badge/priority-medium-yellow)

After logging the error, you're re-throwing it. Consider whether the calling function should handle the error instead, or if there's a more appropriate way to handle the error in this context.

```suggestion
// throw error;
```
created_at: 2025-05-07T07:37:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,33 @@
+const { getLabeledIssues } = require('../lib/github');
+
+/**
+ * Calculate priority score for an issue based on its urgent label
+ * Lower scores indicate higher priority
+ * @param {Object} issue - GitHub issue object
+ * @returns {number} - Priority score
+ */
+function score(issue) {
+  // If issue has urgent label, give it highest priority (score 0)
+  const hasUrgentLabel = issue.labels.some(l => l.name === 'urgent');
+  
+  return hasUrgentLabel ? 0 : 10; // 0 for urgent issues, 10 for others
path: scripts/select-next.js
user: login: korbit-ai[bot]
body: ### Hardcoded magic numbers <sub>![category Readability](https://img.shields.io/badge/Readability-0284c7)</sub>

<details>
  <summary>Tell me more</summary>

###### What is the issue?
The magic numbers 0 and 10 are hardcoded without being defined as named constants.

###### Why this matters
Magic numbers make it difficult to understand the significance of these values and complicate future modifications if these values need to be adjusted.

###### Suggested change ∙ *Feature Preview*
```javascript
const URGENT_PRIORITY_SCORE = 0;
const NORMAL_PRIORITY_SCORE = 10;

return hasUrgentLabel ? URGENT_PRIORITY_SCORE : NORMAL_PRIORITY_SCORE;
```


###### Provide feedback to improve future suggestions
[![Nice Catch](https://img.shields.io/badge/👍%20Nice%20Catch-71BC78)](https://app.korbit.ai/feedback/9edd23fc-12be-4bab-b20e-f5a30f0430ae/020d1b48-01e3-48ff-9f80-5427b9972eb1/upvote) [![Incorrect](https://img.shields.io/badge/👎%20Incorrect-white)](https://app.korbit.ai/feedback/9edd23fc-12be-4bab-b20e-f5a30f0430ae/020d1b48-01e3-48ff-9f80-5427b9972eb1?what_not_true=true)  [![Not in Scope](https://img.shields.io/badge/👎%20Out%20of%20PR%20scope-white)](https://app.korbit.ai/feedback/9edd23fc-12be-4bab-b20e-f5a30f0430ae/020d1b48-01e3-48ff-9f80-5427b9972eb1?what_out_of_scope=true) [![Not in coding standard](https://img.shields.io/badge/👎%20Not%20in%20our%20standards-white)](https://app.korbit.ai/feedback/9edd23fc-12be-4bab-b20e-f5a30f0430ae/020d1b48-01e3-48ff-9f80-5427b9972eb1?what_not_in_standard=true) [![Other](https://img.shields.io/badge/👎%20Other-white)](https://app.korbit.ai/feedback/9edd23fc-12be-4bab-b20e-f5a30f0430ae/020d1b48-01e3-48ff-9f80-5427b9972eb1)
</details>

<sub>

💬 Looking for more details? Reply to this comment to chat with Korbit.
</sub>

<!--- korbi internal id:83830dd4-d2e7-4517-8bc3-a893afb19ea7 -->


[](83830dd4-d2e7-4517-8bc3-a893afb19ea7)
created_at: 2025-05-07T07:39:39Z
author_association: NONE
diff_hunk: @@ -0,0 +1,32 @@
+const axios = require('axios');
+const { token, owner, repo } = require('../.secrets/github');
path: lib/github.js
user: login: korbit-ai[bot]
body: ### Hardcoded Secrets in Codebase <sub>![category Security](https://img.shields.io/badge/Security-e11d48)</sub>

<details>
  <summary>Tell me more</summary>

###### What is the issue?
The code references secrets directly from a file in the codebase which may be committed to version control.

###### Why this matters
If secrets are stored in the codebase, they could be exposed through version control history, even if the file is later added to .gitignore. This creates a risk of credential exposure.

###### Suggested change ∙ *Feature Preview*
Store sensitive credentials in environment variables and access them through process.env:

```javascript
const token = process.env.GITHUB_TOKEN;
const owner = process.env.GITHUB_OWNER;
const repo = process.env.GITHUB_REPO;
```


###### Provide feedback to improve future suggestions
[![Nice Catch](https://img.shields.io/badge/👍%20Nice%20Catch-71BC78)](https://app.korbit.ai/feedback/9edd23fc-12be-4bab-b20e-f5a30f0430ae/b2017821-1a00-49d1-b10d-d9ae8121f6d2/upvote) [![Incorrect](https://img.shields.io/badge/👎%20Incorrect-white)](https://app.korbit.ai/feedback/9edd23fc-12be-4bab-b20e-f5a30f0430ae/b2017821-1a00-49d1-b10d-d9ae8121f6d2?what_not_true=true)  [![Not in Scope](https://img.shields.io/badge/👎%20Out%20of%20PR%20scope-white)](https://app.korbit.ai/feedback/9edd23fc-12be-4bab-b20e-f5a30f0430ae/b2017821-1a00-49d1-b10d-d9ae8121f6d2?what_out_of_scope=true) [![Not in coding standard](https://img.shields.io/badge/👎%20Not%20in%20our%20standards-white)](https://app.korbit.ai/feedback/9edd23fc-12be-4bab-b20e-f5a30f0430ae/b2017821-1a00-49d1-b10d-d9ae8121f6d2?what_not_in_standard=true) [![Other](https://img.shields.io/badge/👎%20Other-white)](https://app.korbit.ai/feedback/9edd23fc-12be-4bab-b20e-f5a30f0430ae/b2017821-1a00-49d1-b10d-d9ae8121f6d2)
</details>

<sub>

💬 Looking for more details? Reply to this comment to chat with Korbit.
</sub>

<!--- korbi internal id:4e86f3bd-ed26-44a9-87eb-ff3552e2687c -->


[](4e86f3bd-ed26-44a9-87eb-ff3552e2687c)
created_at: 2025-05-07T07:39:39Z
author_association: NONE
diff_hunk: @@ -0,0 +1,32 @@
+const axios = require('axios');
+const { token, owner, repo } = require('../.secrets/github');
+
+const headers = { Authorization: `Bearer ${token}` };
path: lib/github.js
user: login: korbit-ai[bot]
body: ### Tight Coupling to Configuration <sub>![category Design](https://img.shields.io/badge/Design-0d9488)</sub>

<details>
  <summary>Tell me more</summary>

###### What is the issue?
Direct dependency on configuration values makes the module tightly coupled to a specific implementation and hard to reuse.

###### Why this matters
This tight coupling makes it impossible to use this module with different GitHub repositories or authentication methods without modifying the code.

###### Suggested change ∙ *Feature Preview*
Inject dependencies through parameters:
```javascript
class GitHubClient {
  constructor(config) {
    this.token = config.token;
    this.owner = config.owner;
    this.repo = config.repo;
  }
  
  async getLabeledIssues() {
    // implementation using instance properties
  }
}
```


###### Provide feedback to improve future suggestions
[![Nice Catch](https://img.shields.io/badge/👍%20Nice%20Catch-71BC78)](https://app.korbit.ai/feedback/9edd23fc-12be-4bab-b20e-f5a30f0430ae/ffb72392-5411-4412-ac17-268d392f38ca/upvote) [![Incorrect](https://img.shields.io/badge/👎%20Incorrect-white)](https://app.korbit.ai/feedback/9edd23fc-12be-4bab-b20e-f5a30f0430ae/ffb72392-5411-4412-ac17-268d392f38ca?what_not_true=true)  [![Not in Scope](https://img.shields.io/badge/👎%20Out%20of%20PR%20scope-white)](https://app.korbit.ai/feedback/9edd23fc-12be-4bab-b20e-f5a30f0430ae/ffb72392-5411-4412-ac17-268d392f38ca?what_out_of_scope=true) [![Not in coding standard](https://img.shields.io/badge/👎%20Not%20in%20our%20standards-white)](https://app.korbit.ai/feedback/9edd23fc-12be-4bab-b20e-f5a30f0430ae/ffb72392-5411-4412-ac17-268d392f38ca?what_not_in_standard=true) [![Other](https://img.shields.io/badge/👎%20Other-white)](https://app.korbit.ai/feedback/9edd23fc-12be-4bab-b20e-f5a30f0430ae/ffb72392-5411-4412-ac17-268d392f38ca)
</details>

<sub>

💬 Looking for more details? Reply to this comment to chat with Korbit.
</sub>

<!--- korbi internal id:6be2b37c-9356-4702-85d4-3a569ba9e89d -->


[](6be2b37c-9356-4702-85d4-3a569ba9e89d)
created_at: 2025-05-07T07:39:39Z
author_association: NONE
diff_hunk: @@ -0,0 +1,32 @@
+const axios = require('axios');
+const { token, owner, repo } = require('../.secrets/github');
+
+const headers = { Authorization: `Bearer ${token}` };
+
+/**
+ * Fetches open issues that have urgent labels
+ * @returns {Promise<Array>} - Array of issue objects
+ */
+async function getLabeledIssues() {
+  const url = `https://api.github.com/repos/${owner}/${repo}/issues?state=open`;
path: lib/github.js
user: login: korbit-ai[bot]
body: ### Missing Required Parameter Validation <sub>![category Functionality](https://img.shields.io/badge/Functionality-0284c7)</sub>

<details>
  <summary>Tell me more</summary>

###### What is the issue?
No validation for owner and repo parameters before constructing the GitHub API URL, which could lead to runtime errors if these values are undefined or malformed.

###### Why this matters
If the secrets file is missing or malformed, the application will attempt to make an API call with invalid parameters, leading to a 404 error or unexpected behavior.

###### Suggested change ∙ *Feature Preview*
Add validation before constructing the URL:
```javascript
if (!owner || !repo) {
  throw new Error('GitHub owner and repo must be defined');
}
const url = `https://api.github.com/repos/${owner}/${repo}/issues?state=open`;
```


###### Provide feedback to improve future suggestions
[![Nice Catch](https://img.shields.io/badge/👍%20Nice%20Catch-71BC78)](https://app.korbit.ai/feedback/9edd23fc-12be-4bab-b20e-f5a30f0430ae/de83ad58-5e10-4889-8dd6-38390aaa75d0/upvote) [![Incorrect](https://img.shields.io/badge/👎%20Incorrect-white)](https://app.korbit.ai/feedback/9edd23fc-12be-4bab-b20e-f5a30f0430ae/de83ad58-5e10-4889-8dd6-38390aaa75d0?what_not_true=true)  [![Not in Scope](https://img.shields.io/badge/👎%20Out%20of%20PR%20scope-white)](https://app.korbit.ai/feedback/9edd23fc-12be-4bab-b20e-f5a30f0430ae/de83ad58-5e10-4889-8dd6-38390aaa75d0?what_out_of_scope=true) [![Not in coding standard](https://img.shields.io/badge/👎%20Not%20in%20our%20standards-white)](https://app.korbit.ai/feedback/9edd23fc-12be-4bab-b20e-f5a30f0430ae/de83ad58-5e10-4889-8dd6-38390aaa75d0?what_not_in_standard=true) [![Other](https://img.shields.io/badge/👎%20Other-white)](https://app.korbit.ai/feedback/9edd23fc-12be-4bab-b20e-f5a30f0430ae/de83ad58-5e10-4889-8dd6-38390aaa75d0)
</details>

<sub>

💬 Looking for more details? Reply to this comment to chat with Korbit.
</sub>

<!--- korbi internal id:b7883de9-90ed-467b-b6c0-d8df9bd943cd -->


[](b7883de9-90ed-467b-b6c0-d8df9bd943cd)
created_at: 2025-05-07T07:39:39Z
author_association: NONE
diff_hunk: @@ -0,0 +1,32 @@
+const axios = require('axios');
+const { token, owner, repo } = require('../.secrets/github');
+
+const headers = { Authorization: `Bearer ${token}` };
+
+/**
+ * Fetches open issues that have urgent labels
+ * @returns {Promise<Array>} - Array of issue objects
+ */
+async function getLabeledIssues() {
+  const url = `https://api.github.com/repos/${owner}/${repo}/issues?state=open`;
+  try {
+    const res = await axios.get(url, { headers });
+    // Filter issues to only include those with "urgent" label
+    const issues = res.data.filter(issue => {
+      if (issue.pull_request) return false;
+      const labels = issue.labels.map(l => l.name);
+      return labels.includes('urgent');
path: lib/github.js
user: login: korbit-ai[bot]
body: ### Case-Sensitive Label Matching <sub>![category Functionality](https://img.shields.io/badge/Functionality-0284c7)</sub>

<details>
  <summary>Tell me more</summary>

###### What is the issue?
The code only checks for 'urgent' label in lowercase, but GitHub labels are case-sensitive and could be 'Urgent' or 'URGENT'.

###### Why this matters
Issues with different case variations of the 'urgent' label will be missed, leading to incomplete priority scoring.

###### Suggested change ∙ *Feature Preview*
Implement case-insensitive label matching:
```javascript
return labels.some(label => label.toLowerCase() === 'urgent');
```


###### Provide feedback to improve future suggestions
[![Nice Catch](https://img.shields.io/badge/👍%20Nice%20Catch-71BC78)](https://app.korbit.ai/feedback/9edd23fc-12be-4bab-b20e-f5a30f0430ae/b0e2b849-a8ec-44c7-923f-1cfecf1a9010/upvote) [![Incorrect](https://img.shields.io/badge/👎%20Incorrect-white)](https://app.korbit.ai/feedback/9edd23fc-12be-4bab-b20e-f5a30f0430ae/b0e2b849-a8ec-44c7-923f-1cfecf1a9010?what_not_true=true)  [![Not in Scope](https://img.shields.io/badge/👎%20Out%20of%20PR%20scope-white)](https://app.korbit.ai/feedback/9edd23fc-12be-4bab-b20e-f5a30f0430ae/b0e2b849-a8ec-44c7-923f-1cfecf1a9010?what_out_of_scope=true) [![Not in coding standard](https://img.shields.io/badge/👎%20Not%20in%20our%20standards-white)](https://app.korbit.ai/feedback/9edd23fc-12be-4bab-b20e-f5a30f0430ae/b0e2b849-a8ec-44c7-923f-1cfecf1a9010?what_not_in_standard=true) [![Other](https://img.shields.io/badge/👎%20Other-white)](https://app.korbit.ai/feedback/9edd23fc-12be-4bab-b20e-f5a30f0430ae/b0e2b849-a8ec-44c7-923f-1cfecf1a9010)
</details>

<sub>

💬 Looking for more details? Reply to this comment to chat with Korbit.
</sub>

<!--- korbi internal id:bc3062fe-e059-44d5-8944-aff50fc001fd -->


[](bc3062fe-e059-44d5-8944-aff50fc001fd)
created_at: 2025-05-07T07:39:40Z
author_association: NONE
diff_hunk: @@ -0,0 +1,32 @@
+const axios = require('axios');
+const { token, owner, repo } = require('../.secrets/github');
+
+const headers = { Authorization: `Bearer ${token}` };
+
+/**
+ * Fetches open issues that have urgent labels
+ * @returns {Promise<Array>} - Array of issue objects
path: lib/github.js
user: login: korbit-ai[bot]
body: ### Imprecise type annotation <sub>![category Readability](https://img.shields.io/badge/Readability-0284c7)</sub>

<details>
  <summary>Tell me more</summary>

###### What is the issue?
The JSDoc return type is not specific enough about the type of array elements being returned.

###### Why this matters
Vague type annotations make it harder for developers to understand the expected data structure without checking the implementation.

###### Suggested change ∙ *Feature Preview*
```javascript
@returns {Promise<Array<GitHubIssue>>} - Array of GitHub issue objects
```


###### Provide feedback to improve future suggestions
[![Nice Catch](https://img.shields.io/badge/👍%20Nice%20Catch-71BC78)](https://app.korbit.ai/feedback/9edd23fc-12be-4bab-b20e-f5a30f0430ae/23730627-035d-4c2a-a7a2-fd777d8b0ece/upvote) [![Incorrect](https://img.shields.io/badge/👎%20Incorrect-white)](https://app.korbit.ai/feedback/9edd23fc-12be-4bab-b20e-f5a30f0430ae/23730627-035d-4c2a-a7a2-fd777d8b0ece?what_not_true=true)  [![Not in Scope](https://img.shields.io/badge/👎%20Out%20of%20PR%20scope-white)](https://app.korbit.ai/feedback/9edd23fc-12be-4bab-b20e-f5a30f0430ae/23730627-035d-4c2a-a7a2-fd777d8b0ece?what_out_of_scope=true) [![Not in coding standard](https://img.shields.io/badge/👎%20Not%20in%20our%20standards-white)](https://app.korbit.ai/feedback/9edd23fc-12be-4bab-b20e-f5a30f0430ae/23730627-035d-4c2a-a7a2-fd777d8b0ece?what_not_in_standard=true) [![Other](https://img.shields.io/badge/👎%20Other-white)](https://app.korbit.ai/feedback/9edd23fc-12be-4bab-b20e-f5a30f0430ae/23730627-035d-4c2a-a7a2-fd777d8b0ece)
</details>

<sub>

💬 Looking for more details? Reply to this comment to chat with Korbit.
</sub>

<!--- korbi internal id:1af9fd87-8f96-47ed-8e07-5048b7c6f89d -->


[](1af9fd87-8f96-47ed-8e07-5048b7c6f89d)
created_at: 2025-05-07T07:39:40Z
author_association: NONE
diff_hunk: @@ -9,16 +9,16 @@ const https = require('https');
 // Use environment variables for sensitive credentials
 const token = process.env.GITHUB_TOKEN;
 
-const github = axios.create({
-  baseURL: BASE_URL,
-  headers: {
-    Authorization: `Bearer ${token}`,
-    Accept: 'application/vnd.github.v3+json',
-    'User-Agent': 'issue-labeler-app'
-  },
-  timeout: 5000,
-  httpsAgent: new https.Agent({ keepAlive: true })
-});
+// Define API constants
+const BASE_URL = 'https://api.github.com';
+const GITHUB_API_TIMEOUT_MS = 10000;
+
+/**
+ * Validates GitHub path segments to prevent path traversal attacks
+ * @param {string} segment - Path segment to validate
+ * @returns {string} - The validated segment
+ */
+function validatePathSegment(segment) {
   if (!/^[\w.-]+$/.test(segment)) {
     throw new Error('Invalid path segment');
   }
path: lib/github-wrapper.js
user: login: codeant-ai[bot]
body: **Suggestion:** Add a return statement to the validatePathSegment function to fulfill its documented contract. [Business Logic Correction]
```suggestion
/**
 * Validates GitHub path segments to prevent path traversal attacks
 * @param {string} segment - Path segment to validate
 * @returns {string} - The validated segment
 */
function validatePathSegment(segment) {
  if (!/^[\w.-]+$/.test(segment)) {
    throw new Error('Invalid path segment');
  }
  return segment;
}
```
created_at: 2025-05-07T07:41:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,32 @@
+const axios = require('axios');
+const { token, owner, repo } = require('../.secrets/github');
+
+const headers = { Authorization: `Bearer ${token}` };
+
+/**
+ * Fetches open issues that have urgent labels
+ * @returns {Promise<Array>} - Array of issue objects
+ */
+async function getLabeledIssues() {
+  const url = `https://api.github.com/repos/${owner}/${repo}/issues?state=open`;
+  try {
+    const res = await axios.get(url, { headers });
+    // Filter issues to only include those with "urgent" label
+    const issues = res.data.filter(issue => {
+      if (issue.pull_request) return false;
+      const labels = issue.labels.map(l => l.name);
+      return labels.includes('urgent');
+    });
+    return issues;
+  } catch (error) {
+    console.error(`Error fetching labeled issues: ${error.message}`);
+    if (error.response) {
+      console.error('API Error:', error.response.status, error.response.statusText);
+    }
+    throw error;
+  }
+}
path: lib/github.js
user: login: codeant-ai[bot]
body: **Suggestion:** Refactor getLabeledIssues to use a shared axios instance with BASE_URL and timeout, incorporate API-side filtering via the URL, and add pagination support. [API Optimization]
```suggestion
const axiosInstance = axios.create({
  baseURL: BASE_URL,
  timeout: GITHUB_API_TIMEOUT_MS,
  headers: { Authorization: `Bearer ${token}` }
});

/**
 * Fetches open issues that have urgent labels with pagination support
 * @returns {Promise<Array>} - Array of issue objects
 */
async function getLabeledIssues() {
  const url = `/repos/${owner}/${repo}/issues?state=open&labels=urgent`;
  const issues = [];
  let page = 1;
  try {
    let fetched;
    do {
      const res = await axiosInstance.get(`${url}&page=${page}`);
      fetched = res.data;
      issues.push(...fetched);
      page++;
    } while (fetched.length > 0);
    // Exclude pull requests
    return issues.filter(issue => !issue.pull_request);
  } catch (error) {
    console.error(`Error fetching labeled issues: ${error.message}`);
    if (error.response) {
      console.error('API Error:', error.response.status, error.response.statusText);
    }
    throw error;
  }
}
```
created_at: 2025-05-07T07:41:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,33 @@
+const { getLabeledIssues } = require('../lib/github');
+
+/**
+ * Calculate priority score for an issue based on its urgent label
+ * Lower scores indicate higher priority
+ * @param {Object} issue - GitHub issue object
+ * @returns {number} - Priority score
+ */
+function score(issue) {
+  // If issue has urgent label, give it highest priority (score 0)
+  const hasUrgentLabel = issue.labels.some(l => l.name === 'urgent');
+  
+  return hasUrgentLabel ? 0 : 10; // 0 for urgent issues, 10 for others
+}
+
+/**
+ * Select the next issue to work on based on priority scoring
+ */
+async function selectNext() {
+  const issues = await getLabeledIssues();
+  if (!issues.length) return console.log('No labeled issues found.');
+
+  const sorted = issues
+    .map(issue => ({ ...issue, score: score(issue) }))
+    .sort((a, b) => a.score - b.score);
+
+  const top = sorted[0];
+  console.log(`Next best issue:\n#${top.number}: ${top.title}\n${top.html_url}`);
+}
+
+selectNext().catch(err => {
+  console.error('Error selecting next issue:', err.message);
+});
path: scripts/select-next.js
user: login: codeant-ai[bot]
body: **Suggestion:** Replace sorting with a single-pass minimum search and exit with a non-zero status code on failure to streamline issue selection. [Algorithm Optimization]
```suggestion
async function selectNext() {
  const issues = await getLabeledIssues();
  if (!issues.length) return console.log('No labeled issues found.');

  let topIssue = issues[0];
  let topScore = score(topIssue);
  for (let i = 1; i < issues.length; i++) {
    const currentScore = score(issues[i]);
    if (currentScore < topScore) {
      topScore = currentScore;
      topIssue = issues[i];
    }
  }
  console.log(`Next best issue:\n#${topIssue.number}: ${topIssue.title}\n${topIssue.html_url}`);
}

selectNext().catch(err => {
  console.error('Error selecting next issue:', err.message);
  process.exit(1);
});
```
created_at: 2025-05-07T07:41:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,32 @@
+const axios = require('axios');
+const { token, owner, repo } = require('../.secrets/github');
+
+const headers = { Authorization: `Bearer ${token}` };
+
+/**
+ * Fetches open issues that have urgent labels
+ * @returns {Promise<Array>} - Array of issue objects
+ */
+async function getLabeledIssues() {
+  const url = `https://api.github.com/repos/${owner}/${repo}/issues?state=open`;
+  try {
+    const res = await axios.get(url, { headers });
+    // Filter issues to only include those with "urgent" label
+    const issues = res.data.filter(issue => {
+      if (issue.pull_request) return false;
+      const labels = issue.labels.map(l => l.name);
+      return labels.includes('urgent');
+    });
+    return issues;
path: lib/github.js
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Add path segment validation to prevent injection attacks.**

The GitHub API URL is constructed with user-controlled variables without validation, creating a potential security risk. Use the `validatePathSegment` function from `github-wrapper.js`.

```diff
 async function getLabeledIssues() {
-  const url = `https://api.github.com/repos/${owner}/${repo}/issues?state=open`;
+  const url = `https://api.github.com/repos/${validatePathSegment(owner)}/${validatePathSegment(repo)}/issues?state=open`;
   try {
     const res = await axios.get(url, { headers });
```


> Committable suggestion skipped: line range outside the PR's diff.

<details>
<summary>🤖 Prompt for AI Agents (early access)</summary>

```
In lib/github.js around lines 10 to 20, the GitHub API URL is constructed using
user-controlled variables owner and repo without validation, which can lead to
injection attacks. To fix this, import the validatePathSegment function from
github-wrapper.js and apply it to both owner and repo before using them in the
URL. This ensures that only safe path segments are used in the API request URL.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commits f717cc1 to 7782b02
created_at: 2025-05-07T07:41:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,32 @@
+const axios = require('axios');
+const { token, owner, repo } = require('../.secrets/github');
+
+const headers = { Authorization: `Bearer ${token}` };
+
+/**
+ * Fetches open issues that have urgent labels
+ * @returns {Promise<Array>} - Array of issue objects
+ */
+async function getLabeledIssues() {
+  const url = `https://api.github.com/repos/${owner}/${repo}/issues?state=open`;
+  try {
+    const res = await axios.get(url, { headers });
+    // Filter issues to only include those with "urgent" label
+    const issues = res.data.filter(issue => {
+      if (issue.pull_request) return false;
+      const labels = issue.labels.map(l => l.name);
+      return labels.includes('urgent');
+    });
+    return issues;
+  } catch (error) {
+    console.error(`Error fetching labeled issues: ${error.message}`);
+    if (error.response) {
+      console.error('API Error:', error.response.status, error.response.statusText);
+    }
+    throw error;
+  }
+}
path: lib/github.js
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Consider reusing the axios instance from github-wrapper.js.**

This function creates a direct axios request instead of leveraging the centralized GitHub client from `github-wrapper.js` which includes error handling, timeout settings, and security configurations.

```diff
-const axios = require('axios');
-const { token, owner, repo } = require('../.secrets/github');
+const { token, owner, repo } = require('../.secrets/github');
+const githubClient = require('./github-wrapper');

-const headers = { Authorization: `Bearer ${token}` };
-
 /**
  * Fetches open issues that have urgent labels
  * @returns {Promise<Array>} - Array of issue objects
  */
 async function getLabeledIssues() {
-  const url = `https://api.github.com/repos/${owner}/${repo}/issues?state=open`;
   try {
-    const res = await axios.get(url, { headers });
+    const res = await githubClient.get(`/repos/${owner}/${repo}/issues?state=open`);
     // Filter issues to only include those with "urgent" label
     const issues = res.data.filter(issue => {
       if (issue.pull_request) return false;
       const labels = issue.labels.map(l => l.name);
       return labels.includes('urgent');
     });
     return issues;
   } catch (error) {
-    console.error(`Error fetching labeled issues: ${error.message}`);
-    if (error.response) {
-      console.error('API Error:', error.response.status, error.response.statusText);
-    }
+    handleGitHubError(error, 'fetching labeled issues');
     throw error;
   }
 }
```


> Committable suggestion skipped: line range outside the PR's diff.

<details>
<summary>🤖 Prompt for AI Agents (early access)</summary>

```
In lib/github.js around lines 6 to 28, the getLabeledIssues function uses a
direct axios.get call instead of the centralized axios instance from
github-wrapper.js. To fix this, import and use the axios instance or GitHub
client from github-wrapper.js for making the API request. This ensures
consistent error handling, timeout, and security settings across all GitHub API
calls.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-07T07:41:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,44 @@
+const { getAllOpenIssues, getIssuesWithLabel } = require('../lib/github');
+
+/**
+ * Select the next issue to work on based on priority rules:
+ * 1. Pick most recent issue with "urgent" label
+ * 2. If no urgent issues, pick most recent issue
+ * 3. Only show "no issues" message if there are no open issues at all
+ */
+async function selectNext() {
+  // Get all open issues
+  const allIssues = await getAllOpenIssues();
+  
+  if (!allIssues.length) {
+    return console.log('No open issues found in the repository.');
+  }
+
+  // First try to find urgent issues
+  const urgentIssues = getIssuesWithLabel(allIssues, 'urgent');
+  
+  if (urgentIssues.length) {
+    // Sort urgent issues by creation date (most recent first)
+    const sortedUrgent = urgentIssues.sort((a, b) => 
+      new Date(b.created_at) - new Date(a.created_at)
+    );
path: scripts/select-next.js
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Refactor duplicated sorting logic into a reusable function**

The same date-based sorting logic appears in two places. This violates the DRY principle and makes future changes more error-prone.

```diff
-  const sortedUrgent = urgentIssues.sort((a, b) => 
-    new Date(b.created_at) - new Date(a.created_at)
-  );

+  const sortByCreationDate = (issues) => issues.sort((a, b) => 
+    new Date(b.created_at) - new Date(a.created_at)
+  );
+
+  const sortedUrgent = sortByCreationDate(urgentIssues);
```

With a corresponding update to the second sort operation:
```diff
-  const sortedAll = allIssues.sort((a, b) => 
-    new Date(b.created_at) - new Date(a.created_at)
-  );
+  const sortedAll = sortByCreationDate(allIssues);
```


Also applies to: 34-36

<details>
<summary>🤖 Prompt for AI Agents (early access)</summary>

```
In scripts/select-next.js around lines 22 to 24 and also lines 34 to 36, the
date-based sorting logic is duplicated. Refactor by extracting this sorting
logic into a reusable function that takes an array and returns it sorted by
created_at date descending. Replace both sorting instances with calls to this
new function to adhere to the DRY principle and simplify future maintenance.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commits f717cc1 to 7782b02
created_at: 2025-05-07T07:48:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,32 @@
+const axios = require('axios');
+const { token, owner, repo } = require('../.secrets/github');
path: lib/github.js
user: login: dmitriz
body: .secrets will not be committed, your proposed change is breaking, please refrain
created_at: 2025-05-07T08:06:48Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,32 @@
+const axios = require('axios');
+const { token, owner, repo } = require('../.secrets/github');
path: lib/github.js
user: login: qodo-code-review[bot]
body: ### Question: 
.secrets will not be committed, your proposed change is breaking, please refrain

### Answer:
1. The `pr_code_suggestions.extra_instructions` configuration option is used with the `/improve` tool. It allows you to provide specific, free-text instructions to the AI model to guide its code suggestions. This helps in tailoring the suggestions to your project's specific coding standards, patterns, or to explicitly avoid certain types of suggestions. You would add this to your `.pr_agent.toml` configuration file. For example:
   ```toml
   [pr_code_suggestions]
   extra_instructions = """
   - Focus on performance improvements.
   - Do not suggest adding extensive comments for simple logic.
   """
   ```

2. Based on the discussion, the AI suggested moving secrets from a `.secrets` directory to environment variables, and the user indicated this change is undesirable and breaking. To prevent similar suggestions, you can use `extra_instructions` in your `.pr_agent.toml` file like this:
   ```toml
   [pr_code_suggestions]
   extra_instructions = """
   - Avoid suggesting changes to how secrets are managed within the '.secrets' directory.
   - Do not propose moving secrets from files in the '.secrets' directory to environment variables, as this is an intentional design and gitignored.
   """
   ```
   This instructs the AI to respect the current secret management approach in the `.secrets` directory and refrain from proposing such changes.

#### Relevant Sources:

> - https://qodo-merge-docs.qodo.ai//tools/improve#extra-instructions

created_at: 2025-05-07T08:07:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,32 @@
+const axios = require('axios');
+const { token, owner, repo } = require('../.secrets/github');
path: lib/github.js
user: login: codiumai-pr-agent-free[bot]
body: ### Question: 
.secrets will not be committed, your proposed change is breaking, please refrain

### Answer:
1. The `pr_code_suggestions.extra_instructions` option is used with the `/improve` tool. It allows you to add free-text instructions in your configuration file (e.g., `.pr_agent.toml`) to guide the AI in generating code suggestions. This helps make suggestions more relevant to your project or to avoid specific types of unwanted suggestions.
   Example:
   ```toml
   [pr_code_suggestions]
   extra_instructions = """\
   - Avoid suggesting to add try-except blocks.
   - Focus on performance improvements.
   """
   ```

2. Based on the discussion, the user wants to prevent suggestions about moving secrets from a `.secrets` directory to environment variables. You can use `pr_code_suggestions.extra_instructions` to instruct the AI accordingly. Add the following to your configuration file:
   ```toml
   [pr_code_suggestions]
   extra_instructions = """\
   - Do not suggest changing secret management practices for files located in a '.secrets' directory.
   - Assume that storing secrets in '.secrets' files is an intentional and accepted practice for this project and should not be flagged for change to environment variables.
   """
   ```
   This configuration will guide the `/improve` tool to refrain from making similar suggestions about secret management in the future.

#### Relevant Sources:

> - https://qodo-merge-docs.qodo.ai//tools/improve#extra-instructions
> - https://qodo-merge-docs.qodo.ai//faq/index#q:-i-received-an-incorrect-or-irrelevant-suggestion-why

created_at: 2025-05-07T08:07:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,32 @@
+const axios = require('axios');
+const { token, owner, repo } = require('../.secrets/github');
path: lib/github.js
user: login: dmitriz
body: /gemini why not secure practice
created_at: 2025-05-07T08:09:33Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,32 @@
+const axios = require('axios');
+const { token, owner, repo } = require('../.secrets/github');
path: lib/github.js
user: login: gemini-code-assist[bot]
body: You're right that `.secrets` is gitignored, which mitigates some risk. However, it's still not ideal. Even with `.secrets` not being committed, there are scenarios where this approach can lead to exposure:

*   **Accidental Inclusion:** A developer might accidentally add the `.secrets` file to the commit, especially if they're not careful about their git workflow.
*   **IDE/Editor Backups:** Some IDEs or editors create backup files that might include the contents of `.secrets`, and these backups might not be gitignored.
*   **Stolen Development Machine:** If a developer's machine is compromised, the secrets are directly accessible.
*   **Human Error:** Accidental sharing of the `.secrets` file.

Environment variables are generally preferred because they are less likely to be accidentally committed to version control and can be managed more securely in deployment environments. While your current setup might work, it's worth considering the points above for a more robust security posture.
created_at: 2025-05-07T08:09:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,100 @@
+const axios = require('axios');
+/**
+ * @fileoverview Provides GitHub-related utilities and configurations.
+ * 
+ * @requires ../.secrets/github
+ * @property {string} token - The GitHub personal access token for authentication.
+ * @property {string} owner - The owner of the GitHub repository.
+ * @property {string} repo - The name of the GitHub repository.
+ * 
+ * @typedef {Object} GitHubIssue
+ * @property {number} id - The unique identifier of the issue.
+ * @property {string} title - The title of the issue.
+ * @property {string} body - The body content of the issue.
+ * @property {Array<{name: string}>} labels - The labels associated with the issue.
+ * @property {boolean} pull_request - Indicates if the issue is a pull request.
+ */
+const { token, owner, repo } = require('../.secrets/github');
+const githubClient = require('./github-wrapper');
+
+/**
+ * Enhances label matching to be case-insensitive.
+ * @param {Array} labels - List of labels associated with an issue.
+ * @param {String} targetLabel - The label to match.
+ * @returns {Boolean} - True if the target label is found (case-insensitive), otherwise false.
+ */
+function hasLabel(labels, targetLabel) {
+  return labels.some(label => label.name.toLowerCase() === targetLabel.toLowerCase());
+}
+const { validatePathSegment, handleGitHubError } = require('./github-wrapper');
+
+/**
+ * Fetches all open issues from GitHub
+ * @returns {Promise<Array>} - Array of issue objects
+ */
+async function getAllOpenIssues() {
+  if (!owner || typeof owner !== 'string' || !owner.trim()) {
+    throw new Error('GitHub owner must be a non-empty string');
+  }
+  if (!repo || typeof repo !== 'string' || !repo.trim()) {
+    throw new Error('GitHub repo must be a non-empty string');
+  }
+  const safeOwner = validatePathSegment(owner.trim());
+  const safeRepo = validatePathSegment(repo.trim());
+  try {
+    const res = await githubClient.get(`/repos/${safeOwner}/${safeRepo}/issues?state=open`);
+    // Filter out pull requests
+    const issues = res.data.filter(issue => !issue.pull_request);
+    issues.forEach(issue => {
+      issue.labels = issue.labels.map(label => ({
+        ...label,
+        name: label.name.toLowerCase()
+      }));
+    });
+    return issues;
+  } catch (error) {
+    handleGitHubError(error, 'fetching open issues');
+    throw error;
+  }
+}
+  if (!repo || typeof repo !== 'string' || !repo.trim()) {
+    throw new Error('GitHub repo must be a non-empty string');
+  }
+  const safeOwner = validatePathSegment(owner.trim());
+  const safeRepo = validatePathSegment(repo.trim());
+  try {
+    const res = await githubClient.get(`/repos/${safeOwner}/${safeRepo}/issues?state=open`);
+    // Filter out pull requests
+    const issues = res.data.filter(issue => !issue.pull_request);
+    issues.forEach(issue => {
+      issue.labels = issue.labels.map(label => ({
+        ...label,
+        name: label.name.toLowerCase()
+      }));
+    });
+    return issues;
+  } catch (error) {
+    console.error(`Error fetching open issues: ${error.message}`);
+    if (error.response) {
+      console.error('API Error:', error.response.status, error.response.statusText);
+    }
+    throw error;
+  }
+}
path: lib/github.js
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Critical: Remove duplicated function implementation**

The `getAllOpenIssues` function has a duplicated implementation from lines 60-83 that is causing syntax errors. The implementation at lines 36-59 and 60-83 are nearly identical, but the second one has inline error handling instead of using the imported `handleGitHubError`.

```diff
async function getAllOpenIssues() {
  if (!owner || typeof owner !== 'string' || !owner.trim()) {
    throw new Error('GitHub owner must be a non-empty string');
  }
  if (!repo || typeof repo !== 'string' || !repo.trim()) {
    throw new Error('GitHub repo must be a non-empty string');
  }
  const safeOwner = validatePathSegment(owner.trim());
  const safeRepo = validatePathSegment(repo.trim());
  try {
    const res = await githubClient.get(`/repos/${safeOwner}/${safeRepo}/issues?state=open`);
    // Filter out pull requests
    const issues = res.data.filter(issue => !issue.pull_request);
    issues.forEach(issue => {
      issue.labels = issue.labels.map(label => ({
        ...label,
        name: label.name.toLowerCase()
      }));
    });
    return issues;
  } catch (error) {
    handleGitHubError(error, 'fetching open issues');
    throw error;
  }
}
-  if (!repo || typeof repo !== 'string' || !repo.trim()) {
-    throw new Error('GitHub repo must be a non-empty string');
-  }
-  const safeOwner = validatePathSegment(owner.trim());
-  const safeRepo = validatePathSegment(repo.trim());
-  try {
-    const res = await githubClient.get(`/repos/${safeOwner}/${safeRepo}/issues?state=open`);
-    // Filter out pull requests
-    const issues = res.data.filter(issue => !issue.pull_request);
-    issues.forEach(issue => {
-      issue.labels = issue.labels.map(label => ({
-        ...label,
-        name: label.name.toLowerCase()
-      }));
-    });
-    return issues;
-  } catch (error) {
-    console.error(`Error fetching open issues: ${error.message}`);
-    if (error.response) {
-      console.error('API Error:', error.response.status, error.response.statusText);
-    }
-    throw error;
-  }
-}
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
async function getAllOpenIssues() {
  if (!owner || typeof owner !== 'string' || !owner.trim()) {
    throw new Error('GitHub owner must be a non-empty string');
  }
  if (!repo || typeof repo !== 'string' || !repo.trim()) {
    throw new Error('GitHub repo must be a non-empty string');
  }
  const safeOwner = validatePathSegment(owner.trim());
  const safeRepo = validatePathSegment(repo.trim());
  try {
    const res = await githubClient.get(`/repos/${safeOwner}/${safeRepo}/issues?state=open`);
    // Filter out pull requests
    const issues = res.data.filter(issue => !issue.pull_request);
    issues.forEach(issue => {
      issue.labels = issue.labels.map(label => ({
        ...label,
        name: label.name.toLowerCase()
      }));
    });
    return issues;
  } catch (error) {
    handleGitHubError(error, 'fetching open issues');
    throw error;
  }
}
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 75-75: Illegal return statement outside of a function



(parse)

---

[error] 82-83: Expected a statement but instead found '}'.

Expected a statement here.


(parse)

</details>

</details>

<details>
<summary>🤖 Prompt for AI Agents (early access)</summary>

```
In lib/github.js between lines 35 and 83, there are two duplicated
implementations of the getAllOpenIssues function causing syntax errors. Remove
the entire second implementation from lines 60 to 83, keeping only the first
implementation from lines 35 to 59 that uses the imported handleGitHubError for
error handling. This will eliminate the duplication and fix the syntax issues.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-07T08:20:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,100 @@
+const axios = require('axios');
+/**
+ * @fileoverview Provides GitHub-related utilities and configurations.
+ * 
+ * @requires ../.secrets/github
+ * @property {string} token - The GitHub personal access token for authentication.
+ * @property {string} owner - The owner of the GitHub repository.
+ * @property {string} repo - The name of the GitHub repository.
+ * 
+ * @typedef {Object} GitHubIssue
+ * @property {number} id - The unique identifier of the issue.
+ * @property {string} title - The title of the issue.
+ * @property {string} body - The body content of the issue.
+ * @property {Array<{name: string}>} labels - The labels associated with the issue.
+ * @property {boolean} pull_request - Indicates if the issue is a pull request.
+ */
+const { token, owner, repo } = require('../.secrets/github');
+const githubClient = require('./github-wrapper');
+
+/**
+ * Enhances label matching to be case-insensitive.
+ * @param {Array} labels - List of labels associated with an issue.
+ * @param {String} targetLabel - The label to match.
+ * @returns {Boolean} - True if the target label is found (case-insensitive), otherwise false.
+ */
+function hasLabel(labels, targetLabel) {
+  return labels.some(label => label.name.toLowerCase() === targetLabel.toLowerCase());
+}
+const { validatePathSegment, handleGitHubError } = require('./github-wrapper');
+
+/**
+ * Fetches all open issues from GitHub
+ * @returns {Promise<Array>} - Array of issue objects
+ */
+async function getAllOpenIssues() {
+  if (!owner || typeof owner !== 'string' || !owner.trim()) {
+    throw new Error('GitHub owner must be a non-empty string');
+  }
+  if (!repo || typeof repo !== 'string' || !repo.trim()) {
+    throw new Error('GitHub repo must be a non-empty string');
+  }
+  const safeOwner = validatePathSegment(owner.trim());
+  const safeRepo = validatePathSegment(repo.trim());
+  try {
+    const res = await githubClient.get(`/repos/${safeOwner}/${safeRepo}/issues?state=open`);
+    // Filter out pull requests
+    const issues = res.data.filter(issue => !issue.pull_request);
+    issues.forEach(issue => {
+      issue.labels = issue.labels.map(label => ({
+        ...label,
+        name: label.name.toLowerCase()
+      }));
+    });
+    return issues;
+  } catch (error) {
+    handleGitHubError(error, 'fetching open issues');
+    throw error;
+  }
+}
+  if (!repo || typeof repo !== 'string' || !repo.trim()) {
+    throw new Error('GitHub repo must be a non-empty string');
+  }
+  const safeOwner = validatePathSegment(owner.trim());
+  const safeRepo = validatePathSegment(repo.trim());
+  try {
+    const res = await githubClient.get(`/repos/${safeOwner}/${safeRepo}/issues?state=open`);
+    // Filter out pull requests
+    const issues = res.data.filter(issue => !issue.pull_request);
+    issues.forEach(issue => {
+      issue.labels = issue.labels.map(label => ({
+        ...label,
+        name: label.name.toLowerCase()
+      }));
+    });
+    return issues;
+  } catch (error) {
+    console.error(`Error fetching open issues: ${error.message}`);
+    if (error.response) {
+      console.error('API Error:', error.response.status, error.response.statusText);
+    }
+    throw error;
+  }
+}
+
+/**
+ * Get issues with specific labels
+ * @param {Array} issues - List of all issues
+ * @param {String} labelName - Name of the label to filter by
+ * @returns {Array} - Filtered issues
+ */
+function getIssuesWithLabel(issues, labelName) {
+  return issues.filter(issue => 
+    issue.labels.some(label => label.name === labelName)
+  );
+}
path: lib/github.js
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Case-sensitivity inconsistency in label matching**

The `getIssuesWithLabel` function uses case-sensitive matching (`label.name === labelName`) while `getAllOpenIssues` normalizes all labels to lowercase. This could lead to unexpected behavior.

```diff
 function getIssuesWithLabel(issues, labelName) {
   return issues.filter(issue => 
-    issue.labels.some(label => label.name === labelName)
+    issue.labels.some(label => label.name === labelName.toLowerCase())
   );
 }
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
function getIssuesWithLabel(issues, labelName) {
  return issues.filter(issue =>
    issue.labels.some(label => label.name === labelName.toLowerCase())
  );
}
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents (early access)</summary>

```
In lib/github.js around lines 91 to 95, the getIssuesWithLabel function performs
case-sensitive label matching which conflicts with getAllOpenIssues that
normalizes labels to lowercase. To fix this, update the label comparison to be
case-insensitive by converting both label.name and labelName to lowercase before
comparing them.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-07T08:20:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,100 @@
+const axios = require('axios');
+/**
+ * @fileoverview Provides GitHub-related utilities and configurations.
+ * 
+ * @requires ../.secrets/github
+ * @property {string} token - The GitHub personal access token for authentication.
+ * @property {string} owner - The owner of the GitHub repository.
+ * @property {string} repo - The name of the GitHub repository.
+ * 
+ * @typedef {Object} GitHubIssue
+ * @property {number} id - The unique identifier of the issue.
+ * @property {string} title - The title of the issue.
+ * @property {string} body - The body content of the issue.
+ * @property {Array<{name: string}>} labels - The labels associated with the issue.
+ * @property {boolean} pull_request - Indicates if the issue is a pull request.
+ */
+const { token, owner, repo } = require('../.secrets/github');
+const githubClient = require('./github-wrapper');
+
+/**
+ * Enhances label matching to be case-insensitive.
+ * @param {Array} labels - List of labels associated with an issue.
+ * @param {String} targetLabel - The label to match.
+ * @returns {Boolean} - True if the target label is found (case-insensitive), otherwise false.
+ */
+function hasLabel(labels, targetLabel) {
+  return labels.some(label => label.name.toLowerCase() === targetLabel.toLowerCase());
+}
path: lib/github.js
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Unused helper function `hasLabel`**

The `hasLabel` helper function implements case-insensitive label matching but is never used in this file. Either use it in `getIssuesWithLabel` for consistency or remove it to avoid confusion.

```diff
- function hasLabel(labels, targetLabel) {
-   return labels.some(label => label.name.toLowerCase() === targetLabel.toLowerCase());
- }
```

Alternative: Use this function in `getIssuesWithLabel` for consistency:

```diff
 function getIssuesWithLabel(issues, labelName) {
-  return issues.filter(issue => 
-    issue.labels.some(label => label.name === labelName)
-  );
+  return issues.filter(issue => hasLabel(issue.labels, labelName));
 }
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion

`````

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents (early access)</summary>

```
In lib/github.js around lines 26 to 28, the helper function hasLabel is defined
but never used, causing unnecessary code clutter. Refactor the
getIssuesWithLabel function to use hasLabel for checking label presence instead
of duplicating the logic. This will ensure consistent case-insensitive label
matching and remove redundancy.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commits 0cf94d6 to ae250db
created_at: 2025-05-07T08:20:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,64 @@
+const axios = require('axios');
+/**
+ * @fileoverview Provides GitHub-related utilities and configurations.
+ * 
+ * @requires ../.secrets/github
+ * @property {string} token - The GitHub personal access token for authentication.
+ * @property {string} owner - The owner of the GitHub repository.
+ * @property {string} repo - The name of the GitHub repository.
+ * 
+ * @typedef {Object} GitHubIssue
+ * @property {number} id - The unique identifier of the issue.
+ * @property {string} title - The title of the issue.
+ * @property {string} body - The body content of the issue.
+ * @property {Array<{name: string}>} labels - The labels associated with the issue.
+ * @property {boolean} pull_request - Indicates if the issue is a pull request.
+ */
+// Ensure sensitive credentials are only accessed through environment variables
+if (!process.env.GITHUB_TOKEN || !process.env.GITHUB_OWNER || !process.env.GITHUB_REPO) {
+  throw new Error('Missing required environment variables: GITHUB_TOKEN, GITHUB_OWNER, or GITHUB_REPO');
+}
+
+// Flexible configuration: Try environment variables first, then fall back to local config file
+const token = process.env.GITHUB_TOKEN;
+const owner = process.env.GITHUB_OWNER;
+const repo = process.env.GITHUB_REPO;
+
+// Initialize GitHub API client
+const githubClient = axios.create({
+  baseURL: 'https://api.github.com',
+  headers: {
+    Authorization: `token ${token}`
+  }
+});
path: lib/github.js
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**GitHub client creation inconsistency**

A GitHub client is created with axios but then not used on line 45, which makes a direct call to the GitHub API. This creates inconsistency in how API calls are made.

```diff
-    const res = await githubClient.get(`/repos/${safeOwner}/${safeRepo}/issues?state=open`);
+    const res = await githubClient.get(`/repos/${safeOwner}/${safeRepo}/issues`, {
+      params: { state: 'open' }
+    });
```


Also applies to: 45-45

<details>
<summary>🤖 Prompt for AI Agents (early access)</summary>

```
In lib/github.js around lines 28 to 33 and line 45, the GitHub client is created
using axios but the code on line 45 makes a direct API call instead of using
this client, causing inconsistency. Refactor the code on line 45 to use the
previously created githubClient instance for making the API request, ensuring
all GitHub API calls go through the same axios client with consistent
configuration.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-08T01:54:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,64 @@
+const axios = require('axios');
+/**
+ * @fileoverview Provides GitHub-related utilities and configurations.
+ * 
+ * @requires ../.secrets/github
+ * @property {string} token - The GitHub personal access token for authentication.
+ * @property {string} owner - The owner of the GitHub repository.
+ * @property {string} repo - The name of the GitHub repository.
+ * 
+ * @typedef {Object} GitHubIssue
+ * @property {number} id - The unique identifier of the issue.
+ * @property {string} title - The title of the issue.
+ * @property {string} body - The body content of the issue.
+ * @property {Array<{name: string}>} labels - The labels associated with the issue.
+ * @property {boolean} pull_request - Indicates if the issue is a pull request.
+ */
+// Ensure sensitive credentials are only accessed through environment variables
+if (!process.env.GITHUB_TOKEN || !process.env.GITHUB_OWNER || !process.env.GITHUB_REPO) {
+  throw new Error('Missing required environment variables: GITHUB_TOKEN, GITHUB_OWNER, or GITHUB_REPO');
+}
+
+// Flexible configuration: Try environment variables first, then fall back to local config file
+const token = process.env.GITHUB_TOKEN;
+const owner = process.env.GITHUB_OWNER;
+const repo = process.env.GITHUB_REPO;
+
+// Initialize GitHub API client
+const githubClient = axios.create({
+  baseURL: 'https://api.github.com',
+  headers: {
+    Authorization: `token ${token}`
+  }
+});
+
+async function getAllOpenIssues() {
+  if (!owner || typeof owner !== 'string' || !owner.trim()) {
+    throw new Error('GitHub owner must be a non-empty string');
+  }
+  if (!repo || typeof repo !== 'string' || !repo.trim()) {
+    throw new Error('GitHub repo must be a non-empty string');
+  }
+  const safeOwner = validatePathSegment(owner.trim());
+  const safeRepo = validatePathSegment(repo.trim());
+  try {
+    const res = await githubClient.get(`/repos/${safeOwner}/${safeRepo}/issues?state=open`);
+    // Filter out pull requests
+    const issues = res.data.filter(issue => !issue.pull_request);
+    issues.forEach(issue => {
+      issue.labels = issue.labels.map(label => ({
+        ...label,
+        name: label.name.toLowerCase()
+      }));
+    });
+    return issues;
+  } catch (error) {
+    handleGitHubError(error, 'fetching open issues');
+    throw error;
+  }
+}
+
+module.exports = {
+  getAllOpenIssues,
+  getIssuesWithLabel
+};
path: lib/github.js
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>❓ Verification inconclusive</summary>

**Missing getLabeledIssues function mentioned in PR objectives**

The PR objectives mention adding a new function `getLabeledIssues` to fetch GitHub issues filtered by the "urgent" label, but this function is not defined in the file.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if getLabeledIssues is defined elsewhere
rg -A 10 "getLabeledIssues" --type js
```

Length of output: 37

---



</details>

**Implement missing `getLabeledIssues` in lib/github.js**

The PR’s objectives call for a new `getLabeledIssues` function to fetch issues tagged “urgent,” but no such function exists in the code.

Evidence:
- A search for “getLabeledIssues” returned no matches:  
  ```bash
  rg -A 10 getLabeledIssues --type js
  # no output
  ```

Please add (or rename) the function in `lib/github.js`, for example:

```diff
+ async function getLabeledIssues(label = 'urgent') {
+   const safeOwner = validatePathSegment(owner.trim());
+   const safeRepo  = validatePathSegment(repo.trim());
+   try {
+     const res = await githubClient.get(
+       `/repos/${safeOwner}/${safeRepo}/issues`,
+       { params: { state: 'open', labels: label } }
+     );
+     return res.data.filter(i => !i.pull_request);
+   } catch (err) {
+     handleGitHubError(err, `fetching issues with label "${label}"`);
+     throw err;
+   }
+ }
+
  module.exports = {
    getAllOpenIssues,
-   getIssuesWithLabel
+   getIssuesWithLabel,
+   getLabeledIssues
  };
```

<details>
<summary>🤖 Prompt for AI Agents (early access)</summary>

```
In lib/github.js from lines 1 to 64, the PR objectives require a new function
named getLabeledIssues to fetch GitHub issues filtered by the "urgent" label,
but this function is missing. Implement getLabeledIssues as an async function
that uses the existing githubClient to request issues from the repository with a
label query parameter set to "urgent". Filter out pull requests from the
results, normalize label names to lowercase, and return the filtered issues.
Finally, export getLabeledIssues alongside getAllOpenIssues in the
module.exports object.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-08T01:54:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,64 @@
+const axios = require('axios');
+/**
+ * @fileoverview Provides GitHub-related utilities and configurations.
+ * 
+ * @requires ../.secrets/github
path: lib/github.js
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**JSDoc reference to `.secrets/github` contradicts implementation**

The JSDoc claims the file requires `.secrets/github`, but the implementation actually uses environment variables. This inconsistency could confuse developers.

```diff
-* @requires ../.secrets/github
+* @requires process.env
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
- * @requires ../.secrets/github
+ * @requires process.env
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents (early access)</summary>

```
In lib/github.js at line 5, the JSDoc comment incorrectly states that the file
requires '../.secrets/github', but the code uses environment variables instead.
Update or remove the JSDoc reference to '../.secrets/github' to accurately
reflect that environment variables are used for configuration, ensuring
consistency and avoiding confusion.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commits 383a3e4 to e0ddfe6
created_at: 2025-05-08T01:54:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,64 @@
+const axios = require('axios');
+/**
+ * @fileoverview Provides GitHub-related utilities and configurations.
+ * 
+ * @requires ../.secrets/github
+ * @property {string} token - The GitHub personal access token for authentication.
+ * @property {string} owner - The owner of the GitHub repository.
+ * @property {string} repo - The name of the GitHub repository.
+ * 
+ * @typedef {Object} GitHubIssue
+ * @property {number} id - The unique identifier of the issue.
+ * @property {string} title - The title of the issue.
+ * @property {string} body - The body content of the issue.
+ * @property {Array<{name: string}>} labels - The labels associated with the issue.
+ * @property {boolean} pull_request - Indicates if the issue is a pull request.
+ */
+// Ensure sensitive credentials are only accessed through environment variables
+if (!process.env.GITHUB_TOKEN || !process.env.GITHUB_OWNER || !process.env.GITHUB_REPO) {
+  throw new Error('Missing required environment variables: GITHUB_TOKEN, GITHUB_OWNER, or GITHUB_REPO');
+}
+
+// Flexible configuration: Try environment variables first, then fall back to local config file
+const token = process.env.GITHUB_TOKEN;
+const owner = process.env.GITHUB_OWNER;
+const repo = process.env.GITHUB_REPO;
path: lib/github.js
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Inconsistent documentation about configuration source**

Line 17 says "Ensure sensitive credentials are only accessed through environment variables" while line 22 says "Flexible configuration: Try environment variables first, then fall back to local config file". However, the code only uses environment variables with no fallback mechanism.

```diff
-// Ensure sensitive credentials are only accessed through environment variables
+// GitHub credentials are accessed through environment variables

-// Flexible configuration: Try environment variables first, then fall back to local config file
+// Get GitHub credentials from environment variables
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
// GitHub credentials are accessed through environment variables
if (!process.env.GITHUB_TOKEN || !process.env.GITHUB_OWNER || !process.env.GITHUB_REPO) {
  throw new Error('Missing required environment variables: GITHUB_TOKEN, GITHUB_OWNER, or GITHUB_REPO');
}

// Get GitHub credentials from environment variables
const token = process.env.GITHUB_TOKEN;
const owner = process.env.GITHUB_OWNER;
const repo = process.env.GITHUB_REPO;
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents (early access)</summary>

```
In lib/github.js around lines 17 to 25, the comments are inconsistent with the
code because they mention a fallback to a local config file, but the code only
uses environment variables. To fix this, either update the comments to
accurately reflect that only environment variables are used or implement the
fallback logic to read from a local config file if the environment variables are
not set.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-08T01:54:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,64 @@
+const axios = require('axios');
+/**
+ * @fileoverview Provides GitHub-related utilities and configurations.
+ * 
+ * @requires ../.secrets/github
+ * @property {string} token - The GitHub personal access token for authentication.
+ * @property {string} owner - The owner of the GitHub repository.
+ * @property {string} repo - The name of the GitHub repository.
+ * 
+ * @typedef {Object} GitHubIssue
+ * @property {number} id - The unique identifier of the issue.
+ * @property {string} title - The title of the issue.
+ * @property {string} body - The body content of the issue.
+ * @property {Array<{name: string}>} labels - The labels associated with the issue.
+ * @property {boolean} pull_request - Indicates if the issue is a pull request.
+ */
+// Ensure sensitive credentials are only accessed through environment variables
+if (!process.env.GITHUB_TOKEN || !process.env.GITHUB_OWNER || !process.env.GITHUB_REPO) {
+  throw new Error('Missing required environment variables: GITHUB_TOKEN, GITHUB_OWNER, or GITHUB_REPO');
+}
+
+// Flexible configuration: Try environment variables first, then fall back to local config file
+const token = process.env.GITHUB_TOKEN;
+const owner = process.env.GITHUB_OWNER;
+const repo = process.env.GITHUB_REPO;
+
+// Initialize GitHub API client
+const githubClient = axios.create({
+  baseURL: 'https://api.github.com',
+  headers: {
+    Authorization: `token ${token}`
+  }
+});
+
+async function getAllOpenIssues() {
+  if (!owner || typeof owner !== 'string' || !owner.trim()) {
+    throw new Error('GitHub owner must be a non-empty string');
+  }
+  if (!repo || typeof repo !== 'string' || !repo.trim()) {
+    throw new Error('GitHub repo must be a non-empty string');
+  }
+  const safeOwner = validatePathSegment(owner.trim());
+  const safeRepo = validatePathSegment(repo.trim());
path: lib/github.js
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Missing import for validatePathSegment function**

The code uses `validatePathSegment` but this function is not imported anywhere, which will cause a ReferenceError at runtime.

Add this import at the top of the file:
```diff
 const axios = require('axios');
+const { validatePathSegment } = require('./github-wrapper');
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
const axios = require('axios');
const { validatePathSegment } = require('./github-wrapper');
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents (early access)</summary>

```
In lib/github.js around lines 42 to 43, the function validatePathSegment is used
but not imported, causing a runtime ReferenceError. To fix this, add an import
statement for validatePathSegment at the top of the file from its appropriate
module so that the function is properly referenced and available in this file.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-08T01:54:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,64 @@
+const axios = require('axios');
+/**
+ * @fileoverview Provides GitHub-related utilities and configurations.
+ * 
+ * @requires ../.secrets/github
+ * @property {string} token - The GitHub personal access token for authentication.
+ * @property {string} owner - The owner of the GitHub repository.
+ * @property {string} repo - The name of the GitHub repository.
+ * 
+ * @typedef {Object} GitHubIssue
+ * @property {number} id - The unique identifier of the issue.
+ * @property {string} title - The title of the issue.
+ * @property {string} body - The body content of the issue.
+ * @property {Array<{name: string}>} labels - The labels associated with the issue.
+ * @property {boolean} pull_request - Indicates if the issue is a pull request.
+ */
+// Ensure sensitive credentials are only accessed through environment variables
+if (!process.env.GITHUB_TOKEN || !process.env.GITHUB_OWNER || !process.env.GITHUB_REPO) {
+  throw new Error('Missing required environment variables: GITHUB_TOKEN, GITHUB_OWNER, or GITHUB_REPO');
+}
+
+// Flexible configuration: Try environment variables first, then fall back to local config file
+const token = process.env.GITHUB_TOKEN;
+const owner = process.env.GITHUB_OWNER;
+const repo = process.env.GITHUB_REPO;
+
+// Initialize GitHub API client
+const githubClient = axios.create({
+  baseURL: 'https://api.github.com',
+  headers: {
+    Authorization: `token ${token}`
+  }
+});
+
+async function getAllOpenIssues() {
+  if (!owner || typeof owner !== 'string' || !owner.trim()) {
+    throw new Error('GitHub owner must be a non-empty string');
+  }
+  if (!repo || typeof repo !== 'string' || !repo.trim()) {
+    throw new Error('GitHub repo must be a non-empty string');
+  }
+  const safeOwner = validatePathSegment(owner.trim());
+  const safeRepo = validatePathSegment(repo.trim());
+  try {
+    const res = await githubClient.get(`/repos/${safeOwner}/${safeRepo}/issues?state=open`);
+    // Filter out pull requests
+    const issues = res.data.filter(issue => !issue.pull_request);
+    issues.forEach(issue => {
+      issue.labels = issue.labels.map(label => ({
+        ...label,
+        name: label.name.toLowerCase()
+      }));
+    });
+    return issues;
+  } catch (error) {
+    handleGitHubError(error, 'fetching open issues');
path: lib/github.js
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Undefined handleGitHubError function**

The code calls `handleGitHubError` but this function is not defined or imported anywhere in the file.

Either define this function within the file or add an import:
```diff
 const axios = require('axios');
 const { validatePathSegment } = require('./github-wrapper');
+
+/**
+ * Handles GitHub API errors with consistent logging
+ * @param {Error} error - The error object
+ * @param {string} context - Description of the operation that failed
+ */
+function handleGitHubError(error, context) {
+  console.error(`Error ${context}: ${error.message}`);
+  if (error.response) {
+    console.error('API Error:', error.response.status, error.response.statusText);
+  }
+}
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
const axios = require('axios');
const { validatePathSegment } = require('./github-wrapper');

/**
 * Handles GitHub API errors with consistent logging
 * @param {Error} error - The error object
 * @param {string} context - Description of the operation that failed
 */
function handleGitHubError(error, context) {
  console.error(`Error ${context}: ${error.message}`);
  if (error.response) {
    console.error('API Error:', error.response.status, error.response.statusText);
  }
}

// ... rest of your existing code ...

async function getAllOpenIssues() {
  // ... earlier code ...
  try {
    const res = await githubClient.get(`/repos/${safeOwner}/${safeRepo}/issues?state=open`);
    // ...
  } catch (error) {
    handleGitHubError(error, 'fetching open issues');
    throw error;
  }
}
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents (early access)</summary>

```
In lib/github.js at line 56, the function handleGitHubError is called but not
defined or imported. To fix this, either define the handleGitHubError function
within this file with appropriate error handling logic or import it from the
module where it is implemented. Ensure the function is available in the scope
before it is called.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-08T01:54:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,64 @@
+const axios = require('axios');
+/**
+ * @fileoverview Provides GitHub-related utilities and configurations.
+ * 
+ * @requires ../.secrets/github
+ * @property {string} token - The GitHub personal access token for authentication.
+ * @property {string} owner - The owner of the GitHub repository.
+ * @property {string} repo - The name of the GitHub repository.
+ * 
+ * @typedef {Object} GitHubIssue
+ * @property {number} id - The unique identifier of the issue.
+ * @property {string} title - The title of the issue.
+ * @property {string} body - The body content of the issue.
+ * @property {Array<{name: string}>} labels - The labels associated with the issue.
+ * @property {boolean} pull_request - Indicates if the issue is a pull request.
+ */
+// Ensure sensitive credentials are only accessed through environment variables
+if (!process.env.GITHUB_TOKEN || !process.env.GITHUB_OWNER || !process.env.GITHUB_REPO) {
+  throw new Error('Missing required environment variables: GITHUB_TOKEN, GITHUB_OWNER, or GITHUB_REPO');
+}
+
+// Flexible configuration: Try environment variables first, then fall back to local config file
+const token = process.env.GITHUB_TOKEN;
+const owner = process.env.GITHUB_OWNER;
+const repo = process.env.GITHUB_REPO;
+
+// Initialize GitHub API client
+const githubClient = axios.create({
+  baseURL: 'https://api.github.com',
+  headers: {
+    Authorization: `token ${token}`
+  }
+});
+
+async function getAllOpenIssues() {
+  if (!owner || typeof owner !== 'string' || !owner.trim()) {
+    throw new Error('GitHub owner must be a non-empty string');
+  }
+  if (!repo || typeof repo !== 'string' || !repo.trim()) {
+    throw new Error('GitHub repo must be a non-empty string');
+  }
+  const safeOwner = validatePathSegment(owner.trim());
+  const safeRepo = validatePathSegment(repo.trim());
+  try {
+    const res = await githubClient.get(`/repos/${safeOwner}/${safeRepo}/issues?state=open`);
+    // Filter out pull requests
+    const issues = res.data.filter(issue => !issue.pull_request);
+    issues.forEach(issue => {
+      issue.labels = issue.labels.map(label => ({
+        ...label,
+        name: label.name.toLowerCase()
+      }));
+    });
+    return issues;
+  } catch (error) {
+    handleGitHubError(error, 'fetching open issues');
+    throw error;
+  }
+}
+
+module.exports = {
+  getAllOpenIssues,
+  getIssuesWithLabel
+};
path: lib/github.js
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Exporting undefined getIssuesWithLabel function**

The module exports `getIssuesWithLabel` but this function is not defined in the provided code.

Add the missing function implementation:
```diff
+/**
+ * Filter issues by label name
+ * @param {Array<GitHubIssue>} issues - Array of issue objects
+ * @param {string} labelName - The label name to filter by
+ * @returns {Array<GitHubIssue>} - Array of issues with the specified label
+ */
+function getIssuesWithLabel(issues, labelName) {
+  return issues.filter(issue => 
+    issue.labels.some(label => label.name === labelName.toLowerCase())
+  );
+}

 module.exports = {
   getAllOpenIssues,
   getIssuesWithLabel
 };
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
/**
 * Filter issues by label name
 * @param {Array<GitHubIssue>} issues - Array of issue objects
 * @param {string} labelName - The label name to filter by
 * @returns {Array<GitHubIssue>} - Array of issues with the specified label
 */
function getIssuesWithLabel(issues, labelName) {
  return issues.filter(issue =>
    issue.labels.some(label => label.name === labelName.toLowerCase())
  );
}

module.exports = {
  getAllOpenIssues,
  getIssuesWithLabel
};
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents (early access)</summary>

```
In lib/github.js around lines 61 to 64, the module exports a function named
getIssuesWithLabel which is not defined in the code. To fix this, define the
getIssuesWithLabel function in the file. This function should accept parameters
necessary to fetch issues filtered by a specific label from GitHub, implement
the logic to retrieve those issues, and then export it as currently done.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-08T01:54:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,28 @@
+#!/usr/bin/env node
+
+/**
+ * Environment toggle script
+ * Automatically switches between testing and production environments
+ * regardless of which one is currently active.
+ */
+const configLoader = require('../config-loader');
+
+// Get currently active environment
+const activeEnv = configLoader.getActiveEnvironment();
+const currentEnv = activeEnv.name;
+
+// Toggle to the other environment
+const targetEnv = currentEnv === 'testing' ? 'production' : 'testing';
+
+try {
+  configLoader.switchEnvironment(targetEnv);
+  console.log(`✓ Switched from "${currentEnv}" to "${targetEnv}"`);
+  
+  // Display the repository that will be used
+  const newEnv = configLoader.getActiveEnvironment();
+  console.log(`\nRepository: ${newEnv.repository.owner}/${newEnv.repository.repo}`);
+  console.log(`Using ${newEnv.repository.useLocalIssues ? 'local test issues' : 'live GitHub API'}`);
+} catch (error) {
+  console.error(`Failed to switch environment: ${error.message}`);
+  process.exit(1);
+}
path: scripts/toggle-env.js
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Validate environment names before toggling**

The script assumes both "testing" and "production" environments exist in the config without validating. This could cause errors if either environment is missing from the configuration.

```diff
 const targetEnv = currentEnv === 'testing' ? 'production' : 'testing';
 
+// Validate target environment exists
+const availableEnvironments = Object.keys(configLoader.loadConfig().environments);
+if (!availableEnvironments.includes(targetEnv)) {
+  console.error(`Error: Target environment "${targetEnv}" not found in config.`);
+  process.exit(1);
+}
+
 try {
```


> Committable suggestion skipped: line range outside the PR's diff.

<details>
<summary>🤖 Prompt for AI Agents (early access)</summary>

```
In scripts/toggle-env.js lines 1 to 28, the script toggles between "testing" and
"production" environments without checking if these environment names exist in
the configuration. Add validation to confirm that both "testing" and
"production" environments are present before attempting to switch. If either
environment is missing, log an appropriate error message and exit the process to
prevent runtime errors.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-08T04:18:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,141 @@
+/**
+ * Configuration loader for issue-labeler
+ * Handles loading environment-specific configurations from config.js
+ */
+const path = require('path');
+const fs = require('fs');
+
+// Config file path
+const CONFIG_PATH = path.join(__dirname, 'config.js');
+
+// Import the config directly from JS file
+const config = require('./config');
+
+/**
+ * Validates the configuration structure
+ * @throws {Error} If the configuration is invalid
+ */
+function validateConfig() {
+  if (!config.environments) {
+    throw new Error('Missing "environments" section in configuration');
+  }
+  
+  // Check if at least one environment is defined
+  const envKeys = Object.keys(config.environments);
+  if (envKeys.length === 0) {
+    throw new Error('No environments defined in configuration');
+  }
+  
+  // Check if exactly one environment is active
+  const activeEnvs = envKeys.filter(key => config.environments[key].active);
+  if (activeEnvs.length === 0) {
+    throw new Error('No active environment found in configuration');
+  }
+  if (activeEnvs.length > 1) {
+    throw new Error(`Multiple active environments found: ${activeEnvs.join(', ')}. Only one environment can be active.`);
+  }
+
+  return true;
+}
+
+// Run validation on load
+validateConfig();
+
+/**
+ * Gets the currently active environment configuration
+ * @returns {Object} - The active environment configuration
+ */
+function getActiveEnvironment() {
+  const activeEnvKey = Object.keys(config.environments)
+    .find(key => config.environments[key].active);
+  
+  return {
+    name: activeEnvKey,
+    ...config.environments[activeEnvKey]
+  };
+}
+
+/**
+ * Gets the repository configuration for the active environment
+ * @returns {Object} - Repository configuration object
+ */
+function getRepositoryConfig() {
+  return getActiveEnvironment().repository;
+}
+
+/**
+ * Gets the model configuration (which is the same across environments)
+ * @returns {Object} - Model configuration object
+ */
+function getModelConfig() {
+  return config.model;
+}
+
+/**
+ * Gets the GitHub API configuration
+ * @returns {Object} - API configuration object
+ */
+function getApiConfig() {
+  return config.github;
+}
+
+/**
+ * Gets the complete configuration
+ * @returns {Object} - Complete configuration object
+ */
+function getConfig() {
+  return config;
+}
+
+/**
+ * Helper to update config.js file content
+ * @param {Object} updatedConfig - The updated config to write
+ */
+function updateConfigFile(updatedConfig) {
+  // Read the current file content
+  let fileContent = fs.readFileSync(CONFIG_PATH, 'utf8');
+  
+  // For each environment, update its active state in the file
+  Object.keys(updatedConfig.environments).forEach(env => {
+    const active = updatedConfig.environments[env].active;
+    // Use regex to update the active property in the file
+    const pattern = new RegExp(`(${env}:\\s*{[^}]*active:\\s*)\\w+`, 'g');
+    fileContent = fileContent.replace(pattern, `$1${active}`);
+  });
+  
+  // Write the updated content back to the file
path: config-loader.js
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Regex replacement is brittle — risk of corrupting `config.js`.**

`updateConfigFile()` relies on a single-line regex (`(${env}:\\s*{[^}]*active:\\s*)\\w+`) which  
1. Stops at the first `}` → fails when nested objects exist inside an environment block.  
2. Replaces only word characters (`true`/`false`) → breaks if someone formats the boolean as `true,` or adds inline comments.  
3. Performs a global replace, so any substring matching `<env>:` elsewhere in the file will be modified.

Consider parsing the file as real JS instead of doing a text replace, e.g.:

```diff
-// Use regex to update the active property in the file
-const pattern = new RegExp(`(${env}:\\s*{[^}]*active:\\s*)\\w+`, 'g');
-fileContent = fileContent.replace(pattern, `$1${active}`);
+// Safer: load the JS, mutate, then serialise
+const vm = require('vm');
+const loaded = vm.runInNewContext(fileContent, {}, { filename: CONFIG_PATH });
+loaded.environments[env].active = active;
+fileContent = 'module.exports = ' + JSON.stringify(loaded, null, 2) + ';\n';
```

This avoids regex-fragility and preserves formatting.



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    const active = updatedConfig.environments[env].active;
-   // Use regex to update the active property in the file
-   const pattern = new RegExp(`(${env}:\\s*{[^}]*active:\\s*)\\w+`, 'g');
-   fileContent = fileContent.replace(pattern, `$1${active}`);
+   // Safer: load the JS, mutate, then serialise
+   const vm = require('vm');
+   const loaded = vm.runInNewContext(fileContent, {}, { filename: CONFIG_PATH });
+   loaded.environments[env].active = active;
+   fileContent = 'module.exports = ' + JSON.stringify(loaded, null, 2) + ';\n';
  });
  
  // Write the updated content back to the file
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents (early access)</summary>

```
In config-loader.js around lines 100 to 106, the current regex-based approach to
update the active property is fragile and can corrupt config.js due to nested
objects, formatting variations, and unintended global replacements. Replace the
regex text manipulation with a proper JavaScript parser to load the config file
as an AST, modify the active property in the relevant environment object
programmatically, and then serialize the AST back to code. This ensures accurate
updates without breaking formatting or structure.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-08T04:18:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,109 @@
+/**
+ * GitHub API client for the issue labeling workflow
+ * Centralizes error handling and API configuration for reliable GitHub interactions.
+ * Use this client instead of direct API calls to ensure consistent error handling and rate limit management.
+ */
+const axios = require('axios');
+const https = require('https');
+const config = require('./config');
+
+// Use environment variables for sensitive credentials
+const token = process.env.GITHUB_TOKEN;
+
+/**
+ * Validates GitHub path segments to prevent path traversal attacks
+ * @param {string} segment - Path segment to validate
+ * @returns {string} - The validated segment
+ */
+function validatePathSegment(segment) {
+  if (!/^[\w.-]+$/.test(segment)) {
+    throw new Error(`Invalid path segment: "${segment}". Path segments must only contain alphanumeric characters, dashes, underscores, or periods.`);
+  }
+  return segment;
+}
+
+const github = axios.create({
+  baseURL: config.github.baseUrl,
+  headers: {
+    Authorization: `token ${token}`,
+    Accept: 'application/vnd.github.v3+json',
+    'User-Agent': 'issue-labeler-app'
+  },
+  timeout: config.github.timeoutMs,
+  httpsAgent: new https.Agent({
+    keepAlive: true,
+    maxSockets: config.github.maxSockets
+  })
+});
+
+// Centralized error handling function
+function handleGitHubError(error, context) {
+  console.error(`Error ${context}: ${error.message}`);
+  if (error.response) {
+    console.error('API Error:', error.response.status, error.response.statusText);
+    console.error('Error details:', error.response.data);
+  }
+  throw error;
+}
+
+// Wrap the URL construction with validation
+function constructIssueUrl({ owner, repo, issueNumber }) {
+  return `/repos/${validatePathSegment(owner)}/${validatePathSegment(repo)}/issues/${Number(issueNumber)}`;
+}
+
+/**
+ * Fetch issue content from GitHub
+ * @param {Object} options - Options object
+ * @param {number} options.issueNumber - The issue number to fetch
+ * @param {string} options.owner - Repository owner
+ * @param {string} options.repo - Repository name
+ * @returns {Promise<Object>} - Issue object with title and body
+ */
+async function getIssueContent({ issueNumber, owner, repo }) {
+  try {
+    const response = await github.get(`/repos/${owner}/${repo}/issues/${issueNumber}`);
+    return {
+      title: response.data.title || '',
+      body: response.data.body || '',
+      number: response.data.number
+    };
+  } catch (error) {
path: github-api-client.js
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Validation function is defined but silently bypassed — potential injection bug**

`getIssueContent()` (and similarly `applyLabels()`) build the URL with raw `owner` / `repo`:

```js
github.get(`/repos/${owner}/${repo}/issues/${issueNumber}`)
```

`validatePathSegment()` is never called, so a malicious caller could inject `../../evil` and hit arbitrary GitHub API paths.

Patch:

```diff
-const response = await github.get(`/repos/${owner}/${repo}/issues/${issueNumber}`);
+const response = await github.get(
+  constructIssueUrl({ owner, repo, issueNumber })
+);
```

Do the same replacement in `applyLabels()`.

<details>
<summary>🤖 Prompt for AI Agents (early access)</summary>

```
In github-api-client.js around lines 62 to 70, the function getIssueContent
constructs the GitHub API URL using raw owner and repo parameters without
validation, which risks path injection. Fix this by calling the existing
validatePathSegment() function on both owner and repo before using them in the
URL. Apply the same validation to owner and repo parameters in the applyLabels()
function to prevent similar injection vulnerabilities.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-08T04:18:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,175 @@
+/**
+ * GitHub Model API client for issue labeling workflow
+ * Provides functions for model inference to determine issue labels
+ */
+const axios = require('axios');
+const https = require('https');
+const configLoader = require('./config-loader');
+const fs = require('fs');
+const path = require('path');
+
+// Load configuration 
+const modelConfig = configLoader.getModelConfig();
+const apiConfig = configLoader.getApiConfig();
+
+// Get token from .secrets first
+let token;
+try {
+  // Check if we're using gh-model.js or github.js in the secrets folder
+  if (fs.existsSync(path.join(__dirname, '.secrets', 'gh-model.js'))) {
+    token = require('./.secrets/gh-model').token;
+  } else {
+    token = require('./.secrets/github').token;
+  }
+} catch (error) {
+  console.error('Failed to load secrets:', error.message);
+  process.exit(1);
+}
path: github-model.js
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Exiting the process from a library hampers re-use & testing**

`process.exit(1)` in the secrets-loading block kills the whole Node process, which is unexpected when this file is required by other code or unit tests.

Prefer throwing an error and letting the caller decide:

```diff
-} catch (error) {
-  console.error('Failed to load secrets:', error.message);
-  process.exit(1);
-}
+} catch (error) {
+  throw new Error(`Failed to load secrets: ${error.message}`);
+}
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
// Get token from .secrets first
let token;
try {
  // Check if we're using gh-model.js or github.js in the secrets folder
  if (fs.existsSync(path.join(__dirname, '.secrets', 'gh-model.js'))) {
    token = require('./.secrets/gh-model').token;
  } else {
    token = require('./.secrets/github').token;
  }
} catch (error) {
  throw new Error(`Failed to load secrets: ${error.message}`);
}
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents (early access)</summary>

```
In github-model.js around lines 15 to 27, replace the process.exit(1) call in
the catch block with throwing the caught error instead. This change prevents the
module from terminating the entire Node process when secrets loading fails,
allowing the caller to handle the error appropriately for better reusability and
testability.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-08T04:18:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,193 @@
+const axios = require('axios');
+const configLoader = require('./config-loader');
+
+/**
+ * @fileoverview Provides utilities for fetching GitHub issues.
+ * 
+ * @typedef {Object} GitHubIssue
+ * @property {number} id - The unique identifier of the issue.
+ * @property {string} title - The title of the issue.
+ * @property {string} body - The body content of the issue.
+ * @property {Array<{name: string}>} labels - The labels associated with the issue.
+ * @property {boolean} pull_request - Indicates if the issue is a pull request.
+ */
+
+// Load configuration
+const repoConfig = configLoader.getRepositoryConfig();
+const apiConfig = configLoader.getApiConfig();
+
+// Get credentials from .secrets, but repository info from config
+let token;
+const owner = repoConfig.owner;
+const repo = repoConfig.repo;
+
+try {
+  const secretsConfig = require('./.secrets/github');
+  token = secretsConfig.token;
+} catch (error) {
+  console.error('Failed to load secrets:', error.message);
+  process.exit(1);
+}
+
+// Initialize GitHub API client with configuration
+const githubClient = axios.create({
+  baseURL: apiConfig.baseUrl,
+  headers: {
+    Authorization: `token ${token}`,
+    Accept: 'application/vnd.github.v3+json',
+    'User-Agent': 'issue-labeler-app'
+  },
+  timeout: apiConfig.timeoutMs
+});
+
+/**
+ * Validates GitHub path segments to prevent path traversal attacks
+ * @param {string} segment - Path segment to validate
+ * @returns {string} - The validated segment
+ */
+function validatePathSegment(segment) {
+  if (!/^[\w.-]+$/.test(segment)) {
+    throw new Error(`Invalid path segment: "${segment}". Path segments must only contain alphanumeric characters, dashes, underscores, or periods.`);
+  }
+  return segment;
+}
+
+/**
+ * Centralized error handling function
+ * @param {Error} error - The error object
+ * @param {string} context - Description of the operation that failed
+ */
+function handleGitHubError(error, context) {
+  console.error(`Error ${context}: ${error.message}`);
+  if (error.response) {
+    console.error('API Error:', error.response.status, error.response.statusText);
+    console.error('Error details:', error.response.data);
+  }
+  throw error;
+}
+
+/**
+ * Fetches issues from either remote GitHub repository or local test data
+ * based on the active environment configuration
+ * @param {string} endpoint - API endpoint to call
+ * @returns {Promise<Array<GitHubIssue>>} - Array of issues
+ */
+async function fetchIssues(endpoint) {
+  // Check if we should use local test data instead of real API
+  if (repoConfig.useLocalIssues) {
+    console.log('Using local test issues data');
+    try {
+      return getLocalIssues();
+    } catch (error) {
+      console.error('Failed to load local test issues:', error);
+      throw error;
+    }
+  }
+  
+  // Otherwise, fetch from the real GitHub API
+  try {
+    const response = await githubClient.get(endpoint);
+    const issues = response.data.filter(issue => !issue.pull_request);
+    
+    issues.forEach(issue => {
+      issue.labels = issue.labels.map(label => ({
+        ...label,
+        name: label.name.toLowerCase()
+      }));
+    });
+    
+    return issues;
+  } catch (error) {
+    handleGitHubError(error, `fetching issues from ${endpoint}`);
+    throw error;
+  }
+}
path: github-issue-fetcher.js
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Missing pagination handling for GitHub API responses.**

GitHub's API paginates results when there are many items, but the code doesn't handle pagination, potentially missing issues.

```diff
async function fetchIssues(endpoint) {
  // Check if we should use local test data instead of real API
  if (repoConfig.useLocalIssues) {
    console.log('Using local test issues data');
    try {
      return getLocalIssues();
    } catch (error) {
      console.error('Failed to load local test issues:', error);
      throw error;
    }
  }
  
  // Otherwise, fetch from the real GitHub API
  try {
-   const response = await githubClient.get(endpoint);
-   const issues = response.data.filter(issue => !issue.pull_request);
+   let allIssues = [];
+   let page = 1;
+   let hasMorePages = true;
+   
+   while (hasMorePages) {
+     const pageUrl = `${endpoint}${endpoint.includes('?') ? '&' : '?'}per_page=100&page=${page}`;
+     const response = await githubClient.get(pageUrl);
+     const pageIssues = response.data.filter(issue => !issue.pull_request);
+     
+     if (pageIssues.length > 0) {
+       allIssues = allIssues.concat(pageIssues);
+       page += 1;
+     } else {
+       hasMorePages = false;
+     }
+     
+     // Check for Link header to determine if there are more pages
+     const linkHeader = response.headers.link;
+     if (!linkHeader || !linkHeader.includes('rel="next"')) {
+       hasMorePages = false;
+     }
+   }
    
-   issues.forEach(issue => {
+   allIssues.forEach(issue => {
      issue.labels = issue.labels.map(label => ({
        ...label,
        name: label.name.toLowerCase()
      }));
    });
    
-   return issues;
+   return allIssues;
  } catch (error) {
    handleGitHubError(error, `fetching issues from ${endpoint}`);
    throw error;
  }
}
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
async function fetchIssues(endpoint) {
  // Check if we should use local test data instead of real API
  if (repoConfig.useLocalIssues) {
    console.log('Using local test issues data');
    try {
      return getLocalIssues();
    } catch (error) {
      console.error('Failed to load local test issues:', error);
      throw error;
    }
  }
  
  // Otherwise, fetch from the real GitHub API
  try {
    let allIssues = [];
    let page = 1;
    let hasMorePages = true;
    
    while (hasMorePages) {
      const pageUrl = `${endpoint}${endpoint.includes('?') ? '&' : '?'}per_page=100&page=${page}`;
      const response = await githubClient.get(pageUrl);
      const pageIssues = response.data.filter(issue => !issue.pull_request);
      
      if (pageIssues.length > 0) {
        allIssues = allIssues.concat(pageIssues);
        page += 1;
      } else {
        hasMorePages = false;
      }
      
      // Check for Link header to determine if there are more pages
      const linkHeader = response.headers.link;
      if (!linkHeader || !linkHeader.includes('rel="next"')) {
        hasMorePages = false;
      }
    }

    allIssues.forEach(issue => {
      issue.labels = issue.labels.map(label => ({
        ...label,
        name: label.name.toLowerCase()
      }));
    });
    
    return allIssues;
  } catch (error) {
    handleGitHubError(error, `fetching issues from ${endpoint}`);
    throw error;
  }
}
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents (early access)</summary>

```
In github-issue-fetcher.js around lines 75 to 104, the fetchIssues function does
not handle GitHub API pagination, which can cause missing issues when there are
multiple pages of results. Modify the function to repeatedly fetch all pages by
checking the response headers for pagination links or using page parameters,
accumulating issues from each page until all are retrieved, then process and
return the complete list.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-08T04:18:16Z
author_association: CONTRIBUTOR

PR comments:
user: login: codeant-ai[bot]
created_at: 2025-05-07T07:37:24Z
body: CodeAnt AI is reviewing your PR.
author_association: CONTRIBUTOR
user: login: coderabbitai[bot]
created_at: 2025-05-07T07:37:28Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- This is an auto-generated comment: failure by coderabbit.ai -->

> [!CAUTION]
> ## Review failed
> 
> The pull request is closed.

<!-- end of auto-generated comment: failure by coderabbit.ai -->
<!-- walkthrough_start -->

<details>
<summary>📝 Walkthrough</summary>

## Summary by CodeRabbit

- **New Features**
  - Added scripts and configuration to support switching between testing and production environments.
  - Introduced a command-line tool to select the next GitHub issue to work on based on priority labels.
  - Added comprehensive technical documentation and improved user instructions in the README.
  - Centralized configuration management for environments, GitHub API, and model settings.
  - Added new utilities for fetching GitHub issues and interacting with a model API for issue labeling.

- **Refactor**
  - Reorganized scripts and updated import paths to reflect a new project structure.
  - Replaced multiple configuration and model files with a single, unified configuration system.

- **Bug Fixes**
  - Improved error handling and validation for configuration and secrets loading.

- **Chores**
  - Updated npm scripts to match the new structure and added scripts for environment management.

- **Documentation**
  - Expanded and clarified README with usage, configuration, and environment management instructions.
  - Added a detailed technical documentation file.

- **Revert**
  - Removed obsolete configuration and model API files, consolidating logic into new modules.
## Walkthrough

This update restructures configuration and script management for the GitHub Issue Labeler project. It introduces a centralized JavaScript-based configuration system, new scripts for environment toggling and issue selection, and rewrites the model and API client modules. Several old configuration and model wrapper files are removed, and documentation is expanded with detailed technical and usage information.

## Changes

| File(s)                                                                                  | Change Summary                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
|------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `package.json`                                                                           | Main entry updated to `src/labeler.js`; script paths changed to `scripts/`; new npm scripts for environment toggling and issue selection added.                                                                                                                                                                                                                                                                                                                                                                |
| `README.md`, `TECHNICAL.md`                                                              | README expanded with usage, environment, and configuration sections; new TECHNICAL.md details architecture, workflows, configuration, and extension guidelines.                                                                                                                                                                                                                                                                                                                                                |
| `config.js`, `src/config-loader.js`                                                      | New centralized config file (`config.js`) added; new loader module (`src/config-loader.js`) provides API for environment, model, and repository configuration and switching environments.                                                                                                                                                                                                                                                                                                                      |
| `scripts/toggle-env.js`, `scripts/switch-env.js`, `scripts/test-config.js`               | New scripts for toggling and switching environments, and for reporting environment/config status.                                                                                                                                                                                                                                                                                                                                                                                                               |
| `test-config.js`                                                                         | Rewritten to provide detailed environment/config status, replacing minimal config check.                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `config.json`, `config/github-model.js`                                                  | Deleted old static model configuration files.                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `lib/github-model.js`, `lib/githubModel.js`                                              | Deleted old GitHub model API wrapper modules.                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `src/github-model.js`                                                                    | New GitHub Model API client module with robust error handling and support for both structured and raw prompt calls.                                                                                                                                                                                                                                                                                                                                                                                            |
| `src/github-api-client.js`                                                               | API client updated to use centralized config, stricter path validation, and improved documentation.                                                                                                                                                                                                                                                                                                                                                                                                            |
| `src/github-api.js`                                                                     | Now loads repository info from config-loader and token from secrets, with error handling for missing secrets.                                                                                                                                                                                                                                                                                                                                                                                                   |
| `src/github-issue-fetcher.js`                                                            | New module to fetch issues from GitHub or local test data, with input validation and robust error handling.                                                                                                                                                                                                                                                                                                                                                                                                    |
| `scripts/select-next.js`                                                                 | New script to select the next GitHub issue to work on, prioritizing by labels and recency.                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `scripts/label-issue.js`                                                                 | Imports updated to new config/model modules; prompt loading moved inside main function; function signature updated to include `repo`; error handling improved.                                                                                                                                                                                                                                                                                                                                                 |
| `example-github-issues.js`, `scripts/comment-test.js`, `scripts/fetch-only-test.js`,<br> `scripts/label-test.js`, `scripts/minimal-test.js`, `scripts/simple-gh-api-test.js`, `scripts/test-gh-api-wrapper.js`, `scripts/test-github-model.js`, `scripts/direct-api-test.js` | All import paths updated to reference new script or source directories, reflecting project reorganization. No logic changes.                                                                                                                                                                                                                                                                                                                                             |
| `src/labeler.js`                                                                         | Model import path updated to new module location.                                                                                                                                                                                                                                                                                                                                                                                                                                                              |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant User
    participant NPM Script
    participant ConfigLoader
    participant GitHubAPIClient
    participant GitHubModel
    participant SecretsFile

    User->>NPM Script: Run environment toggle/status/select script
    NPM Script->>ConfigLoader: Load config, get/set environment
    NPM Script->>SecretsFile: (If needed) Load token
    NPM Script->>GitHubAPIClient: Fetch issues (if applicable)
    GitHubAPIClient->>ConfigLoader: Get API/repo config
    GitHubAPIClient->>SecretsFile: Get token
    GitHubAPIClient-->>NPM Script: Return issues/data
    NPM Script-->>User: Output status/result
```

```mermaid
sequenceDiagram
    participant LabelerScript
    participant ConfigLoader
    participant GitHubAPIClient
    participant GitHubModel
    participant SecretsFile

    LabelerScript->>ConfigLoader: Load repo/model config
    LabelerScript->>SecretsFile: Load token
    LabelerScript->>GitHubAPIClient: Fetch issues
    GitHubAPIClient->>ConfigLoader: Get repo/API config
    GitHubAPIClient->>SecretsFile: Get token
    GitHubAPIClient-->>LabelerScript: Return issues
    LabelerScript->>GitHubModel: Send prompt for labeling
    GitHubModel->>ConfigLoader: Get model config
    GitHubModel->>SecretsFile: Get token
    GitHubModel-->>LabelerScript: Return label suggestion
```

## Possibly related PRs

- [dmitriz/issue-labeler#9](https://github.com/dmitriz/issue-labeler/pull/9): Introduces the initial implementation of `label-issue.js` and related API/model modules, which are directly refactored and extended in this PR.
- [dmitriz/issue-labeler#3](https://github.com/dmitriz/issue-labeler/pull/3): Adds the first GitHub model API integration and prompt template, which this PR reorganizes and replaces with a new model client and config system.

## Poem

> In configs anew, our settings reside,  
> With scripts to switch and secrets to hide.  
> The labeler’s mind, now sharper and keen,  
> Selects the next issue—efficient, unseen.  
> With toggles and fetchers, the workflow’s a breeze,  
> This codebase now dances with elegant ease!  
> 🚦🔧✨

</details>

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- announcements_start -->

> [!TIP]
> <details>
> <summary>⚡️ Faster reviews with caching</summary>
> 
> - CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure `Review - Disable Cache` at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the `Data Retention` setting under your Organization Settings.
> 
> Enjoy the performance boost—your workflow just got faster.
> 
> </details>

<!-- announcements_end -->

---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 175236abab50fd68378fddd9a1cf536ddba7ccd9 and e0ddfe60d97c398b8b051e820d940c6ff852bada.

</details>

<details>
<summary>📒 Files selected for processing (26)</summary>

* `TECHNICAL.md` (1 hunks)
* `config.js` (1 hunks)
* `config/github-model.js` (0 hunks)
* `example-github-issues.js` (1 hunks)
* `lib/github-model.js` (0 hunks)
* `lib/githubModel.js` (0 hunks)
* `package.json` (1 hunks)
* `scripts/comment-test.js` (1 hunks)
* `scripts/direct-api-test.js` (1 hunks)
* `scripts/fetch-only-test.js` (1 hunks)
* `scripts/label-issue.js` (3 hunks)
* `scripts/label-test.js` (1 hunks)
* `scripts/minimal-test.js` (1 hunks)
* `scripts/select-next.js` (1 hunks)
* `scripts/simple-gh-api-test.js` (1 hunks)
* `scripts/switch-env.js` (1 hunks)
* `scripts/test-config.js` (1 hunks)
* `scripts/test-gh-api-wrapper.js` (1 hunks)
* `scripts/test-github-model.js` (1 hunks)
* `scripts/toggle-env.js` (1 hunks)
* `src/config-loader.js` (1 hunks)
* `src/github-api-client.js` (1 hunks)
* `src/github-api.js` (1 hunks)
* `src/github-issue-fetcher.js` (1 hunks)
* `src/github-model.js` (1 hunks)
* `src/labeler.js` (1 hunks)

</details>

</details>
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=dmitriz/issue-labeler&utm_content=10):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: qodo-code-review[bot]
created_at: 2025-05-07T07:37:45Z
body: ## PR Reviewer Guide 🔍

#### (Review updated until commit https://github.com/dmitriz/issue-labeler/commit/e7619ba1d5884d55c05ea28a1099b83700df2f1d)


Here are some key observations to aid the review process:

<table>
<tr><td>⏱️&nbsp;<strong>Estimated effort to review</strong>: 2 🔵🔵⚪⚪⚪</td></tr>
<tr><td>🧪&nbsp;<strong>No relevant tests</strong></td></tr>
<tr><td>🔒&nbsp;<strong>Security concerns</strong><br><br>

<strong>Sensitive information exposure:</strong><br> The code in lib/github.js imports secrets directly from a file path '../.secrets/github' which could lead to accidental exposure of credentials if the secrets file is not properly protected or accidentally committed to version control. It's better to use environment variables consistently throughout the application as already done in github-wrapper.js.</td></tr>
<tr><td>⚡&nbsp;<strong>Recommended focus areas for review</strong><br><br>

<details><summary><a href='https://github.com/dmitriz/issue-labeler/pull/10/files#diff-9714c3844d6c03aa922329305244a541c47d9573ad42aeb91e158f3e9a995009R2-R2'><strong>Hardcoded Secrets</strong></a>

The code imports secrets directly from a file. Consider using environment variables instead of importing from a secrets file for better security practices.
</summary>

```javascript
const { token, owner, repo } = require('../.secrets/github');

```

</details>

<details><summary><a href='https://github.com/dmitriz/issue-labeler/pull/10/files#diff-09e8ff86ffa0113dcc5c155273affbacfa7bcfe337ade3371e36b55983abf61cR12-R14'><strong>Incomplete Refactoring</strong></a>

The code adds API constants but doesn't use them in the implementation. The axios.create configuration was removed but not replaced with equivalent functionality.
</summary>

```javascript
// Define API constants
const BASE_URL = 'https://api.github.com';
const GITHUB_API_TIMEOUT_MS = 10000;
```

</details>

<details><summary><a href='https://github.com/dmitriz/issue-labeler/pull/10/files#diff-f1848d6ec1a22eeafe8d95739dee863f1f3a490cbcc5370aebe49aeeb9c8ef73R10-R13'><strong>Logic Inconsistency</strong></a>

The getLabeledIssues function already filters for 'urgent' labeled issues, but the score function checks for urgent labels again, which is redundant.
</summary>

```javascript
// If issue has urgent label, give it highest priority (score 0)
const hasUrgentLabel = issue.labels.some(l => l.name === 'urgent');

return hasUrgentLabel ? 0 : 10; // 0 for urgent issues, 10 for others
```

</details>

</td></tr>
</table>

author_association: CONTRIBUTOR
user: login: qodo-code-review[bot]
created_at: 2025-05-07T07:38:20Z
body: ## PR Code Suggestions ✨
<!-- e7619ba -->

Latest suggestions up to e7619ba
Explore these optional code suggestions:

<table><thead><tr><td><strong>Category</strong></td><td align=left><strong>Suggestion&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </strong></td><td align=center><strong>Impact</strong></td></tr><tbody><tr><td rowspan=2>Security</td>
<td>



<details><summary>✅ 
Use environment variables<!-- not_implemented -->
</summary>

___

<details><summary><b>Suggestion Impact:</b></summary>The commit implemented the suggestion by replacing the direct import of secrets with environment variables. Lines 19-28 show the implementation of environment variables for GitHub token, owner, and repo, including validation to ensure they are provided.

code diff:

```diff
+// Ensure sensitive credentials are only accessed through environment variables
+if (!process.env.GITHUB_TOKEN || !process.env.GITHUB_OWNER || !process.env.GITHUB_REPO) {
+  throw new Error('Missing required environment variables: GITHUB_TOKEN, GITHUB_OWNER, or GITHUB_REPO');
+}
 
-const headers = { Authorization: `Bearer ${token}` };
+// Flexible configuration: Try environment variables first, then fall back to local config file
+const token = process.env.GITHUB_TOKEN;
+const owner = process.env.GITHUB_OWNER;
+const repo = process.env.GITHUB_REPO;
```

</details>


___


**Avoid hardcoding paths to sensitive files. Use environment variables instead of <br>importing secrets directly from a file, which is a security risk and makes <br>deployment more difficult.**

[lib/github.js [2]](https://github.com/dmitriz/issue-labeler/pull/10/files#diff-9714c3844d6c03aa922329305244a541c47d9573ad42aeb91e158f3e9a995009R2-R2)

```diff
-const { token, owner, repo } = require('../.secrets/github');
+const token = process.env.GITHUB_TOKEN;
+const owner = process.env.GITHUB_OWNER;
+const repo = process.env.GITHUB_REPO;
```

`[Suggestion processed]`

<details><summary>Suggestion importance[1-10]: 9</summary>

__

Why: The suggestion correctly advises using environment variables for sensitive data like `token`, `owner`, and `repo` names, which is a crucial security best practice over hardcoding paths to secret files.


</details></details></td><td align=center>High

</td></tr><tr><td>



<details><summary>Add input validation<!-- not_implemented --></summary>

___


**Add input validation to check if <code>segment</code> is defined and is a string before <br>testing the regex pattern. This prevents potential errors when undefined or <br>non-string values are passed to the function.**

[lib/github-wrapper.js [21-25]](https://github.com/dmitriz/issue-labeler/pull/10/files#diff-09e8ff86ffa0113dcc5c155273affbacfa7bcfe337ade3371e36b55983abf61cR21-R25)

```diff
 function validatePathSegment(segment) {
-  if (!/^[\w.-]+$/.test(segment)) {
+  if (!segment || typeof segment !== 'string' || !/^[\w.-]+$/.test(segment)) {
     throw new Error('Invalid path segment');
   }
   return segment;
```

- [ ] **Apply / Chat** <!-- /improve --apply_suggestion=1 -->

<details><summary>Suggestion importance[1-10]: 7</summary>

__

Why: The suggestion enhances robustness by adding checks for `segment` being undefined or not a string, preventing potential runtime errors before regex testing. This is a good defensive programming practice.


</details></details></td><td align=center>Medium

</td></tr>
<tr><td align="center" colspan="2">

- [ ] Update <!-- /improve --more_suggestions=true -->

</td><td></td></tr></tbody></table>

___

#### Previous suggestions
<details><summary>✅ Suggestions up to commit e7619ba</summary>
<br><table><thead><tr><td><strong>Category</strong></td><td align=left><strong>Suggestion&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </strong></td><td align=center><strong>Impact</strong></td></tr><tbody><tr><td rowspan=1>Security</td>
<td>



<details><summary>✅ 
Encode URL parameters<!-- not_implemented -->
</summary>

___

<details><summary><b>Suggestion Impact:</b></summary>The commit implemented a different but equivalent approach to the suggestion. Instead of using encodeURIComponent() directly, it created a validatePathSegment function (imported from github-wrapper) that is used to sanitize the owner and repo variables before constructing the URL.

code diff:

```diff
+  const safeOwner = validatePathSegment(owner.trim());
+  const safeRepo = validatePathSegment(repo.trim());
   try {
-    const res = await axios.get(url, { headers });
-    // Filter issues to only include those with "urgent" label
-    const issues = res.data.filter(issue => {
-      if (issue.pull_request) return false;
-      const labels = issue.labels.map(l => l.name);
-      return labels.includes('urgent');
+    const res = await githubClient.get(`/repos/${safeOwner}/${safeRepo}/issues?state=open`);
```

</details>


___


**Use <code>encodeURIComponent()</code> for the <code>owner</code> and <code>repo</code> variables to prevent potential <br>URL injection vulnerabilities when constructing the API URL.**

[lib/github.js [11]](https://github.com/dmitriz/issue-labeler/pull/10/files#diff-9714c3844d6c03aa922329305244a541c47d9573ad42aeb91e158f3e9a995009R11-R11)

```diff
-const url = `https://api.github.com/repos/${owner}/${repo}/issues?state=open`;
+const url = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues?state=open`;
```

 <!-- /improve --apply_suggestion=0 -->

<details><summary>Suggestion importance[1-10]: 9</summary>

__

Why: The suggestion correctly advises using `encodeURIComponent` for `owner` and `repo` when constructing the API URL. This is crucial for preventing potential URL injection vulnerabilities and ensuring the URL is correctly formed, especially if `owner` or `repo` names could contain special characters.


</details></details></td><td align=center>High

</td></tr><tr><td rowspan=2>Possible issue</td>
<td>



<details><summary>✅ 
Validate required credentials
</summary>

___

<details><summary><b>Suggestion Impact:</b></summary>The commit implemented credential validation but in a more comprehensive way. Instead of a single check for all three credentials, it added separate validation for owner and repo with more specific error messages and type checking in the getAllOpenIssues function.

code diff:

```diff
+  if (!owner || typeof owner !== 'string' || !owner.trim()) {
+    throw new Error('GitHub owner must be a non-empty string');
+  }
+  if (!repo || typeof repo !== 'string' || !repo.trim()) {
+    throw new Error('GitHub repo must be a non-empty string');
+  }
```

</details>


___


**Validate that required GitHub credentials exist before proceeding. This prevents <br>runtime errors when making API calls with undefined credentials and provides <br>clearer error messages.**

[lib/github.js [2]](https://github.com/dmitriz/issue-labeler/pull/10/files#diff-9714c3844d6c03aa922329305244a541c47d9573ad42aeb91e158f3e9a995009R2-R2)

```diff
 const { token, owner, repo } = require('../.secrets/github');
+if (!token || !owner || !repo) {
+  throw new Error('Missing required GitHub credentials');
+}
```

 <!-- /improve --apply_suggestion=1 -->

<details><summary>Suggestion importance[1-10]: 8</summary>

__

Why: This suggestion is important as it ensures essential credentials (`token`, `owner`, `repo`) are present after being required. This prevents runtime errors during subsequent API calls and provides a clear, early error message if they are missing.


</details></details></td><td align=center>Medium

</td></tr><tr><td>



<details><summary>Add input type validation<!-- not_implemented --></summary>

___


**Add input validation to check if <code>segment</code> is defined and is a string before <br>testing the regex pattern. This prevents potential errors when undefined or <br>non-string values are passed to the function.**

[lib/github-wrapper.js [16-25]](https://github.com/dmitriz/issue-labeler/pull/10/files#diff-09e8ff86ffa0113dcc5c155273affbacfa7bcfe337ade3371e36b55983abf61cR16-R25)

```diff
 /**
  * Validates GitHub path segments to prevent path traversal attacks
  * @param {string} segment - Path segment to validate
  * @returns {string} - The validated segment
  */
 function validatePathSegment(segment) {
-  if (!/^[\w.-]+$/.test(segment)) {
+  if (!segment || typeof segment !== 'string' || !/^[\w.-]+$/.test(segment)) {
     throw new Error('Invalid path segment');
   }
   return segment;
```

 <!-- /improve --apply_suggestion=2 -->

<details><summary>Suggestion importance[1-10]: 7</summary>

__

Why: The suggestion enhances robustness by adding explicit checks for `segment` being defined and a string. This prevents potential runtime errors if the `validatePathSegment` function is called with undefined or non-string input.


</details></details></td><td align=center>Medium

</td></tr>
<tr><td align="center" colspan="2">

 <!-- /improve --more_suggestions=true -->

</td><td></td></tr></tbody></table>

</details>
author_association: CONTRIBUTOR
user: login: codeant-ai[bot]
created_at: 2025-05-07T07:38:58Z
body: ## Pull Request Feedback 🔍

<table>
<tr><td>🔒&nbsp;<strong>Security concerns</strong><br><br>

<strong>Sensitive information exposure:</strong><br> Using a .secrets file for GitHub credentials may result in the unintentional exposure of sensitive data if not properly managed. Ensure that the secrets file is excluded from version control, and consider switching to environment variables for handling credentials.</td></tr>
<tr><td>⚡&nbsp;<strong>Recommended areas for review</strong><br><br>

<a href='https://github.com/dmitriz/issue-labeler/pull/10/files#diff-09e8ff86ffa0113dcc5c155273affbacfa7bcfe337ade3371e36b55983abf61cR12-R21'><strong>Maintainability</strong></a><br>The new API constants (BASE_URL and GITHUB_API_TIMEOUT_MS) and the validatePathSegment function are introduced to improve security. However, it is not clear how these elements integrate with the existing API request logic. Consider incorporating these constants into network configurations and ensuring that the validatePathSegment function is consistently applied wherever GitHub API paths are constructed.

<a href='https://github.com/dmitriz/issue-labeler/pull/10/files#diff-9714c3844d6c03aa922329305244a541c47d9573ad42aeb91e158f3e9a995009R1-R32'><strong>Secret Management</strong></a><br>The code imports sensitive credentials from a .secrets file using require('../.secrets/github'). This approach can lead to accidental exposure of secrets if the file is not properly secured or gitignored. Consider using environment variables to handle credentials and ensure that the secrets file does not get committed to version control.

<a href='https://github.com/dmitriz/issue-labeler/pull/10/files#diff-f1848d6ec1a22eeafe8d95739dee863f1f3a490cbcc5370aebe49aeeb9c8ef73R1-R33'><strong>Functionality Improvement</strong></a><br>The scoring logic in the selectNext script currently distinguishes only based on the presence of an 'urgent' label, assigning a score of 0 to such issues and 10 otherwise. There is no tie-breaking mechanism if multiple issues have the same score. Consider extending the logic to handle ties more deterministically.

</td></tr>
</table>

author_association: CONTRIBUTOR
user: login: codeant-ai[bot]
created_at: 2025-05-07T07:41:39Z
body: CodeAnt AI finished reviewing your PR.
author_association: CONTRIBUTOR
user: login: coderabbitai[bot]
created_at: 2025-05-08T02:03:19Z
body: > [!NOTE]
> Generated docstrings for this pull request at https://github.com/dmitriz/issue-labeler/pull/12
author_association: CONTRIBUTOR
user: login: dmitriz
created_at: 2025-05-08T08:57:20Z
body: @coderabbitai stop revision here
author_association: OWNER
user: login: coderabbitai[bot]
created_at: 2025-05-08T08:57:39Z
body: <!-- This is an auto-generated reply by CodeRabbit -->
> [!TIP]
> For best results, initiate chat on the files or code changes.

@dmitriz I've received your request and will stop the revision process here. No further automated reviews will be performed on this PR.


<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@dmitriz I've received your request and will stop the revision process here. No further automated reviews will be performed on this PR.</answer></rawResChunk> -->
author_association: CONTRIBUTOR